[automerger skipped] Merge "Import translations. DO NOT MERGE ANYWHERE" into pi-car-dev am: 104b9b93da -s ours am: 0bf49793d3 -s ours am: c0690f52fa -s ours

am skip reason: subject contains skip directive

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

Change-Id: Ib1f291c2cd9c61b5b435518f327b28883a9f7e41
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java b/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java
deleted file mode 100644
index 156abd8..0000000
--- a/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java
+++ /dev/null
@@ -1,45 +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 android.car.encryptionrunner;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * Factory that creates encryption runner.
- */
-public class EncryptionRunnerFactory {
-
-    private EncryptionRunnerFactory() {
-        // prevent instantiation.
-    }
-
-    /**
-     * Creates a new {@link EncryptionRunner}.
-     */
-    public static EncryptionRunner newRunner() {
-        return new Ukey2EncryptionRunner();
-    }
-
-    /**
-     * Creates a new {@link EncryptionRunner} one that doesn't actually do encryption but is useful
-     * for testing.
-     */
-    @VisibleForTesting
-    public static EncryptionRunner newDummyRunner() {
-        return new DummyEncryptionRunner();
-    }
-}
diff --git a/android-car-lib/res/values/values.xml b/android-car-lib/res/values/values.xml
index 0ae2c80..15e70f0 100644
--- a/android-car-lib/res/values/values.xml
+++ b/android-car-lib/res/values/values.xml
@@ -446,6 +446,12 @@
     <style name="Widget.Car.Button.ActionBar">
         <item name="android:scaleType">fitCenter</item>
         <item name="android:padding">@dimen/car_action_button_icon_inset</item>
+        <!-- Added paddingStart/End explicitly to make sure style works the same regardless of -->
+        <!-- whether RTL is enabled or disabled. -->
+        <!-- Note: When RTL is enabled, paddingStart/End overrides value of padding, -->
+        <!-- while padding overrides value of paddingStart/End when RTL is disabled. -->
+        <item name="android:paddingStart">@dimen/car_action_button_icon_inset</item>
+        <item name="android:paddingEnd">@dimen/car_action_button_icon_inset</item>
         <item name="android:background">@drawable/car_action_button_background</item>
         <item name="android:tint">@color/car_tint</item>
     </style>
diff --git a/car-apps-common/Android.bp b/car-apps-common/Android.bp
index 8c52089..dae4a15 100644
--- a/car-apps-common/Android.bp
+++ b/car-apps-common/Android.bp
@@ -24,7 +24,8 @@
         enabled: false,
     },
 
-    libs: ["android.car"],
+    libs: ["android.car-stubs",],
+    min_sdk_version: "29",
 
     static_libs: [
         "androidx.annotation_annotation",
diff --git a/car-apps-common/res/drawable-ldrtl/background_image_scrim.xml b/car-apps-common/res/drawable-ldrtl/background_image_scrim.xml
new file mode 100644
index 0000000..9035a65
--- /dev/null
+++ b/car-apps-common/res/drawable-ldrtl/background_image_scrim.xml
@@ -0,0 +1,53 @@
+<?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">
+    <item>
+        <shape>
+            <gradient
+                android:angle="270"
+                android:startColor="#F5000000"
+                android:centerColor="@android:color/transparent"
+                android:endColor="#F5000000"/>
+        </shape>
+    </item>
+    <item>
+        <shape>
+            <gradient
+                android:angle="180"
+                android:centerX="0.52"
+                android:startColor="@android:color/transparent"
+                android:centerColor="@android:color/black"
+                android:endColor="@android:color/black" />
+        </shape>
+    </item>
+    <item>
+        <shape>
+            <gradient
+                android:angle="180"
+                android:centerX="0.62"
+                android:startColor="@android:color/transparent"
+                android:centerColor="#B3000000"
+                android:endColor="#B3000000" />
+        </shape>
+    </item>
+    <item>
+        <shape>
+            <solid android:color="@color/background_image_30p_black"/>
+        </shape>
+    </item>
+</layer-list>
diff --git a/car-apps-common/res/layout-port/background_image.xml b/car-apps-common/res/layout-port/background_image.xml
index e7c547c..7e4ee8e 100644
--- a/car-apps-common/res/layout-port/background_image.xml
+++ b/car-apps-common/res/layout-port/background_image.xml
@@ -22,11 +22,11 @@
         android:id="@+id/background_image_image"
         android:layout_width="0dp"
         android:layout_height="0dp"
-        app:align_horizontal="left"
+        app:align_horizontal="start"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toRightOf="parent"/>
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
 
     <View
         android:layout_width="0dp"
@@ -34,8 +34,8 @@
         android:background="@drawable/background_image_scrim"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toRightOf="parent"/>
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
 
     <View
         android:id="@+id/background_image_darkening_scrim"
@@ -45,6 +45,6 @@
         android:visibility="gone"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toRightOf="parent"
-        app:layout_constraintRight_toRightOf="parent"/>
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
 </merge>
diff --git a/car-apps-common/res/layout/background_image.xml b/car-apps-common/res/layout/background_image.xml
index f230600..bb889ea 100644
--- a/car-apps-common/res/layout/background_image.xml
+++ b/car-apps-common/res/layout/background_image.xml
@@ -29,11 +29,11 @@
         android:id="@+id/background_image_image"
         android:layout_width="0dp"
         android:layout_height="0dp"
-        app:align_horizontal="right"
+        app:align_horizontal="end"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toLeftOf="@+id/background_image_guideline"/>
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/background_image_guideline"/>
 
     <View
         android:layout_width="0dp"
@@ -41,8 +41,8 @@
         android:background="@drawable/background_image_scrim"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toRightOf="parent"/>
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
 
     <View
         android:id="@+id/background_image_darkening_scrim"
@@ -52,6 +52,6 @@
         android:visibility="gone"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toRightOf="parent"
-        app:layout_constraintRight_toRightOf="parent"/>
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
 </merge>
diff --git a/car-apps-common/res/values/attrs.xml b/car-apps-common/res/values/attrs.xml
index f99da29..c7b3541 100644
--- a/car-apps-common/res/values/attrs.xml
+++ b/car-apps-common/res/values/attrs.xml
@@ -86,8 +86,8 @@
     <declare-styleable name="CrossfadeImageView">
         <attr name="align_horizontal" format="enum">
             <enum name="center" value="0"/>
-            <enum name="left" value="1"/>
-            <enum name="right" value="2"/>
+            <enum name="start" value="1"/>
+            <enum name="end" value="2"/>
         </attr>
     </declare-styleable>
 
diff --git a/car-apps-common/res/values/config.xml b/car-apps-common/res/values/config.xml
index f274eb8..68a0fa1 100644
--- a/car-apps-common/res/values/config.xml
+++ b/car-apps-common/res/values/config.xml
@@ -41,4 +41,10 @@
          1 (show one letter) or 0 (show avatar anonymous icon)
     -->
     <integer name="config_number_of_letters_shown_for_avatar">1</integer>
+
+    <!-- String format used to format a address Uri. -->
+    <string name="config_address_uri_format" translatable="false">geo:0,0?q=%s</string>
+    <!-- String format used to format a navigation Uri. -->
+    <string name="config_navigation_uri_format" translatable="false">google.navigation:q=%s</string>
+
 </resources>
diff --git a/car-apps-common/res/values/styles.xml b/car-apps-common/res/values/styles.xml
index 6983bdc..73457af 100644
--- a/car-apps-common/res/values/styles.xml
+++ b/car-apps-common/res/values/styles.xml
@@ -29,6 +29,12 @@
         <item name="android:layout_height">@dimen/control_bar_button_size</item>
         <!-- This padding is used to force resizing of provided icons. -->
         <item name="android:padding">@dimen/control_bar_button_padding</item>
+        <!-- Added paddingStart/End explicitly to make sure style works regardless of whether -->
+        <!-- RTL is enabled or disabled. -->
+        <!-- Note: When RTL is enabled, paddingStart/End overrides value of padding, -->
+        <!-- while padding overrides value of paddingStart/End when RTL is disabled. -->
+        <item name="android:paddingStart">@dimen/control_bar_button_padding</item>
+        <item name="android:paddingEnd">@dimen/control_bar_button_padding</item>
         <!-- Note: fitCenter makes icons disappear if the view is too small... -->
         <item name="android:scaleType">fitCenter</item>
         <item name="android:layout_gravity">center</item>
@@ -69,6 +75,7 @@
     <style name="TextAppearance">
         <item name="android:fontFamily">roboto-regular</item>
         <item name="android:textColor">@color/primary_text_color</item>
+        <item name="android:textAlignment">viewStart</item>
     </style>
 
     <style name="TextAppearance.Display1" parent="TextAppearance">
@@ -101,12 +108,8 @@
         <item name="android:letterSpacing">@dimen/letter_spacing_body3</item>
     </style>
 
-    <style name="MinimizedControlBarTitle" parent="TextAppearance.Body1">
-        <item name="android:textDirection">locale</item>
-    </style>
-    <style name="MinimizedControlBarSubtitle" parent="TextAppearance.Body3">
-        <item name="android:textDirection">locale</item>
-    </style>
+    <style name="MinimizedControlBarTitle" parent="TextAppearance.Body1"/>
+    <style name="MinimizedControlBarSubtitle" parent="TextAppearance.Body3"/>
 
     <!-- Styles for ControlBar -->
     <style name="ControlBar">
diff --git a/car-apps-common/src/com/android/car/apps/common/CropAlignedImageView.java b/car-apps-common/src/com/android/car/apps/common/CropAlignedImageView.java
index f11f33b..0adbcf6 100644
--- a/car-apps-common/src/com/android/car/apps/common/CropAlignedImageView.java
+++ b/car-apps-common/src/com/android/car/apps/common/CropAlignedImageView.java
@@ -19,9 +19,13 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Matrix;
+import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.ImageView;
 
+import java.util.Locale;
+
 /**
  * A {@link ImageView} that scales in a similar way as {@link ScaleType#CENTER_CROP} but aligning
  * the image to the specified edge of the view.
@@ -29,8 +33,8 @@
 public class CropAlignedImageView extends ImageView {
 
     private static final int ALIGN_HORIZONTAL_CENTER = 0;
-    private static final int ALIGN_HORIZONTAL_LEFT = 1;
-    private static final int ALIGN_HORIZONTAL_RIGHT = 2;
+    private static final int ALIGN_HORIZONTAL_START = 1;
+    private static final int ALIGN_HORIZONTAL_END = 2;
 
     private int mAlignHorizontal;
     private float mAdditionalScale = 1f;
@@ -82,6 +86,8 @@
             float fitHorizontallyScaleFactor = mFrameWidth / originalImageWidth;
             float fitVerticallyScaleFactor = mFrameHeight / originalImageHeight;
             float usedScaleFactor = Math.max(fitHorizontallyScaleFactor, fitVerticallyScaleFactor);
+            int layoutDirection = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
+            boolean isRTL = layoutDirection == View.LAYOUT_DIRECTION_RTL;
 
             // mAdditionalScale isn't factored into the fittedImageWidth
             // because we want to scale from the center of the fitted image, so our translations
@@ -97,11 +103,11 @@
                 case ALIGN_HORIZONTAL_CENTER:
                     dx = mFrameWidth / 2f;
                     break;
-                case ALIGN_HORIZONTAL_LEFT:
-                    dx = fittedImageWidth / 2f;
+                case ALIGN_HORIZONTAL_START:
+                    dx = isRTL ? (mFrameWidth - fittedImageWidth / 2f) : fittedImageWidth / 2f;
                     break;
-                case ALIGN_HORIZONTAL_RIGHT:
-                    dx = (mFrameWidth - fittedImageWidth / 2f);
+                case ALIGN_HORIZONTAL_END:
+                    dx = isRTL ? fittedImageWidth / 2f : (mFrameWidth - fittedImageWidth / 2f);
                     break;
             }
             matrix.postTranslate(dx, mFrameHeight / 2f);
diff --git a/car-apps-common/src/com/android/car/apps/common/NavigationUtils.java b/car-apps-common/src/com/android/car/apps/common/NavigationUtils.java
new file mode 100644
index 0000000..aa0171e
--- /dev/null
+++ b/car-apps-common/src/com/android/car/apps/common/NavigationUtils.java
@@ -0,0 +1,72 @@
+/*
+ * 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.apps.common;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+
+/**
+ * Helper methods for addresses and navigation.
+ */
+public class NavigationUtils {
+
+    /**
+     * Returns the search location intent.
+     *
+     * @param address should be a location as either a place name or address.
+     */
+    public static Intent getViewAddressIntent(Resources res, String address) {
+        String formattedAddress = String.format(res.getString(R.string.config_address_uri_format),
+                Uri.encode(address));
+        Uri addressUri = Uri.parse(formattedAddress);
+        return new Intent(Intent.ACTION_VIEW, addressUri);
+    }
+
+    /**
+     * Returns the search location intent.
+     *
+     * @param address should be a location as either a place name or address.
+     */
+    public static Intent getViewAddressIntent(Context context, String address) {
+        Resources resources = context.getResources();
+        return getViewAddressIntent(resources, address);
+    }
+
+    /**
+     * Returns the navigation intent.
+     *
+     * @param address should be a location as either a place name or address.
+     */
+    public static Intent getNavigationIntent(Resources res, String address) {
+        String formattedAddress = String.format(
+                res.getString(R.string.config_navigation_uri_format), Uri.encode(address));
+        Uri addressUri = Uri.parse(formattedAddress);
+        return new Intent(Intent.ACTION_VIEW, addressUri);
+    }
+
+    /**
+     * Returns the navigation Intent.
+     *
+     * @param address should be a location as either a place name or address.
+     */
+    public static Intent getNavigationIntent(Context context, String address) {
+        Resources resources = context.getResources();
+        return getNavigationIntent(resources, address);
+    }
+}
diff --git a/car-apps-common/src/com/android/car/apps/common/log/L.java b/car-apps-common/src/com/android/car/apps/common/log/L.java
new file mode 100644
index 0000000..ad1c284
--- /dev/null
+++ b/car-apps-common/src/com/android/car/apps/common/log/L.java
@@ -0,0 +1,97 @@
+/*
+ * 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.apps.common.log;
+
+import android.os.Build;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Util class for logging.
+ */
+public class L {
+    private static final List<String> TYPE_LIST = Arrays.asList("eng", "userdebug");
+    /**
+     * Logs verbose level logs if loggable.
+     *
+     * <p>@see String#format(String, Object...) for formatting log string.
+     */
+    public static void v(String tag, @NonNull String msg, Object... args) {
+        if (Log.isLoggable(tag, Log.VERBOSE) || TYPE_LIST.contains(Build.TYPE)) {
+            Log.v(tag, String.format(msg, args));
+        }
+    }
+
+    /**
+     * Logs debug level logs if loggable.
+     *
+     * <p>@see String#format(String, Object...) for formatting log string.
+     */
+    public static void d(String tag, @NonNull String msg, Object... args) {
+        if (Log.isLoggable(tag, Log.DEBUG) || TYPE_LIST.contains(Build.TYPE)) {
+            Log.d(tag, String.format(msg, args));
+        }
+    }
+
+    /**
+     * Logs info level logs if loggable.
+     *
+     * <p>@see String#format(String, Object...) for formatting log string.
+     */
+    public static void i(String tag, @NonNull String msg, Object... args) {
+        if (Log.isLoggable(tag, Log.INFO) || TYPE_LIST.contains(Build.TYPE)) {
+            Log.i(tag, String.format(msg, args));
+        }
+    }
+
+    /**
+     * Logs warning level logs if loggable.
+     *
+     * <p>@see String#format(String, Object...) for formatting log string.
+     */
+    public static void w(String tag, @NonNull String msg, Object... args) {
+        if (Log.isLoggable(tag, Log.WARN) || TYPE_LIST.contains(Build.TYPE)) {
+            Log.w(tag, String.format(msg, args));
+        }
+    }
+
+    /**
+     * Logs error level logs if loggable.
+     *
+     * <p>@see String#format(String, Object...) for formatting log string.
+     */
+    public static void e(String tag, @NonNull String msg, Object... args) {
+        if (Log.isLoggable(tag, Log.ERROR) || TYPE_LIST.contains(Build.TYPE)) {
+            Log.e(tag, String.format(msg, args));
+        }
+    }
+
+    /**
+     * Logs warning level logs if loggable.
+     *
+     * <p>@see String#format(String, Object...) for formatting log string.
+     */
+    public static void e(String tag, Exception e, @NonNull String msg, Object... args) {
+        if (Log.isLoggable(tag, Log.ERROR) || TYPE_LIST.contains(Build.TYPE)) {
+            Log.e(tag, String.format(msg, args), e);
+        }
+    }
+}
diff --git a/car-assist-client-lib/res/values-iw/strings.xml b/car-assist-client-lib/res/values-iw/strings.xml
index 510432d..dc96640 100644
--- a/car-assist-client-lib/res/values-iw/strings.xml
+++ b/car-assist-client-lib/res/values-iw/strings.xml
@@ -16,6 +16,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="assist_action_failed_toast" msgid="3250146468076483714">"לא ניתן לבקש מה-Assistant לבצע פעולה!"</string>
+    <string name="assist_action_failed_toast" msgid="3250146468076483714">"לא ניתן לבקש מ-Assistant לבצע פעולה!"</string>
     <string name="says" msgid="8575666015622916107">"רוצה להודיע כי"</string>
 </resources>
diff --git a/car-assist-client-lib/src/com/android/car/assist/client/CarAssistUtils.java b/car-assist-client-lib/src/com/android/car/assist/client/CarAssistUtils.java
index 1f9d915..6007637 100644
--- a/car-assist-client-lib/src/com/android/car/assist/client/CarAssistUtils.java
+++ b/car-assist-client-lib/src/com/android/car/assist/client/CarAssistUtils.java
@@ -164,8 +164,7 @@
         return hasMessagingStyle(sbn)
                 && hasRequiredAssistantCallbacks(sbn)
                 && ((getReplyAction(sbn.getNotification()) == null)
-                    || replyCallbackHasRemoteInput(sbn))
-                && assistantCallbacksShowNoUi(sbn);
+                    || replyCallbackHasRemoteInput(sbn));
     }
 
     /** Returns true if the semantic action provided can be supported. */
diff --git a/car-media-common/res/layout/playback_fragment.xml b/car-media-common/res/layout/playback_fragment.xml
index 6d751cc..17e10b0 100644
--- a/car-media-common/res/layout/playback_fragment.xml
+++ b/car-media-common/res/layout/playback_fragment.xml
@@ -46,25 +46,26 @@
             android:layout_width="@dimen/app_selector_icon_size"
             android:layout_height="@dimen/app_selector_icon_size"
             android:layout_gravity="center"
-            android:layout_marginLeft="@dimen/playback_fragment_text_margin_x"
+            android:layout_marginStart="@dimen/playback_fragment_text_margin_x"
             android:background="?android:attr/selectableItemBackground"
             android:src="@drawable/ic_app_switch"
             app:layout_constraintTop_toTopOf="@+id/app_name"
             app:layout_constraintBottom_toBottomOf="@+id/app_name"
-            app:layout_constraintLeft_toLeftOf="parent"/>
+            app:layout_constraintStart_toStartOf="parent"/>
 
         <TextView
             android:id="@+id/app_name"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginTop="@dimen/playback_fragment_text_margin_top"
-            android:layout_marginLeft="@dimen/playback_fragment_text_margin_x"
-            android:layout_marginRight="@dimen/playback_fragment_text_margin_x"
+            android:layout_marginStart="@dimen/playback_fragment_text_margin_x"
+            android:layout_marginEnd="@dimen/playback_fragment_text_margin_x"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:singleLine="true"
             android:includeFontPadding="false"
-            app:layout_constraintLeft_toRightOf="@+id/app_icon"
-            app:layout_constraintRight_toLeftOf="@+id/app_selector_container"
+            android:textAlignment="viewStart"
+            app:layout_constraintStart_toEndOf="@+id/app_icon"
+            app:layout_constraintEnd_toStartOf="@+id/app_selector_container"
             app:layout_constraintTop_toTopOf="parent"/>
 
         <TextView
@@ -73,10 +74,10 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginTop="@dimen/playback_fragment_text_margin_top"
-            android:layout_marginLeft="@dimen/playback_fragment_text_margin_x"
-            android:layout_marginRight="@dimen/playback_fragment_text_margin_x"
-            app:layout_constraintLeft_toLeftOf="parent"
-            app:layout_constraintRight_toRightOf="parent"
+            android:layout_marginStart="@dimen/playback_fragment_text_margin_x"
+            android:layout_marginEnd="@dimen/playback_fragment_text_margin_x"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toBottomOf="@+id/app_name"/>
 
         <TextView
@@ -85,10 +86,10 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginTop="@dimen/playback_fragment_text_margin_top"
-            android:layout_marginLeft="@dimen/playback_fragment_text_margin_x"
-            android:layout_marginRight="@dimen/playback_fragment_text_margin_x"
-            app:layout_constraintLeft_toLeftOf="parent"
-            app:layout_constraintRight_toLeftOf="@+id/app_selector_container"
+            android:layout_marginStart="@dimen/playback_fragment_text_margin_x"
+            android:layout_marginEnd="@dimen/playback_fragment_text_margin_x"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/app_selector_container"
             app:layout_constraintTop_toBottomOf="@+id/title"/>
 
         <FrameLayout
@@ -97,10 +98,10 @@
             android:layout_width="@dimen/app_selector_icon_touch_target"
             android:layout_height="@dimen/app_selector_icon_touch_target"
             android:background="?android:attr/selectableItemBackground"
-            android:layout_marginRight="@dimen/app_selector_margin_x"
+            android:layout_marginEnd="@dimen/app_selector_margin_x"
             app:layout_constraintTop_toTopOf="@+id/app_name"
             app:layout_constraintBottom_toBottomOf="@+id/app_name"
-            app:layout_constraintRight_toRightOf="parent">
+            app:layout_constraintEnd_toEndOf="parent">
 
             <ImageView
                 android:id="@+id/app_selector"
@@ -119,8 +120,8 @@
             app:columns="3"
             app:enableOverflow="false"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintLeft_toLeftOf="parent"
-            app:layout_constraintRight_toRightOf="parent"/>
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 
diff --git a/car-media-common/res/values/attrs.xml b/car-media-common/res/values/attrs.xml
index 75ff716..04e2dbb 100644
--- a/car-media-common/res/values/attrs.xml
+++ b/car-media-common/res/values/attrs.xml
@@ -34,8 +34,8 @@
     <declare-styleable name="CrossfadeImageView">
         <attr name="align_horizontal" format="enum">
             <enum name="center" value="0"/>
-            <enum name="left" value="1"/>
-            <enum name="right" value="2"/>
+            <enum name="start" value="1"/>
+            <enum name="end" value="2"/>
         </attr>
     </declare-styleable>
 </resources>
diff --git a/car-media-common/res/values/styles.xml b/car-media-common/res/values/styles.xml
index 5d00241..972837d 100644
--- a/car-media-common/res/values/styles.xml
+++ b/car-media-common/res/values/styles.xml
@@ -18,13 +18,11 @@
     <style name="PlaybackTitleStyle" parent="TextAppearance.Body1">
         <item name="android:singleLine">true</item>
         <item name="android:includeFontPadding">false</item>
-        <item name="android:textDirection">locale</item>
     </style>
 
     <style name="PlaybackSubtitleStyle" parent="TextAppearance.Body3">
         <item name="android:textColor">@color/secondary_text_color</item>
         <item name="android:singleLine">true</item>
         <item name="android:includeFontPadding">false</item>
-        <item name="android:textDirection">locale</item>
     </style>
 </resources>
diff --git a/car-media-common/src/com/android/car/media/common/ControlBarHelper.java b/car-media-common/src/com/android/car/media/common/ControlBarHelper.java
index 0f011fd..4e3b7b9 100644
--- a/car-media-common/src/com/android/car/media/common/ControlBarHelper.java
+++ b/car-media-common/src/com/android/car/media/common/ControlBarHelper.java
@@ -64,8 +64,8 @@
 
         model.getProgress().observe(owner,
                 progress -> {
-                    progressBar.setProgress((int) progress.getProgress());
                     progressBar.setMax((int) progress.getMaxProgress());
+                    progressBar.setProgress((int) progress.getProgress());
                 });
     }
 }
diff --git a/car-media-common/src/com/android/car/media/common/browse/BrowsedMediaItems.java b/car-media-common/src/com/android/car/media/common/browse/BrowsedMediaItems.java
index dea0d6a..5c0a13d 100644
--- a/car-media-common/src/com/android/car/media/common/browse/BrowsedMediaItems.java
+++ b/car-media-common/src/com/android/car/media/common/browse/BrowsedMediaItems.java
@@ -26,6 +26,7 @@
 import com.android.car.media.common.MediaItemMetadata;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 /**
@@ -146,6 +147,7 @@
             mHandler.removeCallbacks(mRetryRunnable);
             mIsDataLoaded = true;
             setValue(children.stream()
+                    .filter(Objects::nonNull)
                     .map(MediaItemMetadata::new)
                     .collect(Collectors.toList()));
         }
diff --git a/car-media-common/src/com/android/car/media/common/browse/SearchedMediaItems.java b/car-media-common/src/com/android/car/media/common/browse/SearchedMediaItems.java
index 51411f0..2f7d0c9 100644
--- a/car-media-common/src/com/android/car/media/common/browse/SearchedMediaItems.java
+++ b/car-media-common/src/com/android/car/media/common/browse/SearchedMediaItems.java
@@ -28,6 +28,7 @@
 import com.android.car.media.common.MediaItemMetadata;
 
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A LiveData that provides access to a MediaBrowser's search results for a given query
@@ -43,7 +44,10 @@
         public void onSearchResult(@NonNull String query, Bundle extras,
                                    @NonNull List<MediaBrowserCompat.MediaItem> items) {
             super.onSearchResult(query, extras, items);
-            setValue(items.stream().map(MediaItemMetadata::new).collect(toList()));
+            setValue(items.stream()
+                    .filter(Objects::nonNull)
+                    .map(MediaItemMetadata::new)
+                    .collect(toList()));
         }
 
         @Override
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 95c0dd5..94803f5 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
@@ -293,7 +293,8 @@
         public void onQueueChanged(@Nullable List<MediaSessionCompat.QueueItem> queue) {
             List<MediaItemMetadata> filtered = queue == null ? Collections.emptyList()
                     : queue.stream()
-                            .filter(item -> item.getDescription() != null
+                            .filter(item -> item != null
+                                    && item.getDescription() != null
                                     && item.getDescription().getTitle() != null)
                             .map(MediaItemMetadata::new)
                             .collect(Collectors.toList());
diff --git a/car-messenger-common/Android.bp b/car-messenger-common/Android.bp
index 19ed50f..da0d075 100644
--- a/car-messenger-common/Android.bp
+++ b/car-messenger-common/Android.bp
@@ -19,19 +19,22 @@
 
     srcs: ["src/**/*.java"],
 
+    manifest: "AndroidManifest.xml",
+
     optimize: {
         enabled: false,
     },
 
-    libs: ["android.car"],
+    min_sdk_version: "29",
+
+    libs: ["android.car-system-stubs",],
 
     resource_dirs: ["res"],
 
     static_libs: [
-        "android.car.userlib",
-        "androidx.legacy_legacy-support-v4",
         "car-apps-common-bp",
         "car-messenger-protos",
         "connected-device-protos",
+        "libphonenumber",
     ],
 }
diff --git a/car-messenger-common/res/values-fr-rCA/strings.xml b/car-messenger-common/res/values-fr-rCA/strings.xml
index 56086e6..9bbe2c7 100644
--- a/car-messenger-common/res/values-fr-rCA/strings.xml
+++ b/car-messenger-common/res/values-fr-rCA/strings.xml
@@ -19,6 +19,7 @@
     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="many">%d new messages</item>
       <item quantity="other">%d nouveaux messages</item>
     </plurals>
     <string name="action_play" msgid="1884580550634079470">"Faire jouer"</string>
diff --git a/car-messenger-common/res/values-fr/strings.xml b/car-messenger-common/res/values-fr/strings.xml
index a96b14f..12cb2f7 100644
--- a/car-messenger-common/res/values-fr/strings.xml
+++ b/car-messenger-common/res/values-fr/strings.xml
@@ -19,6 +19,7 @@
     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="many">%d new messages</item>
       <item quantity="other">%d nouveaux messages</item>
     </plurals>
     <string name="action_play" msgid="1884580550634079470">"Lire"</string>
diff --git a/car-messenger-common/res/values-mn/strings.xml b/car-messenger-common/res/values-mn/strings.xml
index d47453b..0cd0bcc 100644
--- a/car-messenger-common/res/values-mn/strings.xml
+++ b/car-messenger-common/res/values-mn/strings.xml
@@ -18,8 +18,8 @@
 <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>
+      <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>
@@ -30,7 +30,7 @@
     <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="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>
diff --git a/car-messenger-common/res/values/strings.xml b/car-messenger-common/res/values/strings.xml
index ff604e2..b19ffdb 100644
--- a/car-messenger-common/res/values/strings.xml
+++ b/car-messenger-common/res/values/strings.xml
@@ -41,6 +41,6 @@
     <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">%1$s&#160;&#8226;&#160;%2$s</string>
+    <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
index 3045cdc..6397a47 100644
--- a/car-messenger-common/src/com/android/car/messenger/common/BaseNotificationDelegate.java
+++ b/car-messenger-common/src/com/android/car/messenger/common/BaseNotificationDelegate.java
@@ -17,19 +17,17 @@
 package com.android.car.messenger.common;
 
 import android.app.Notification;
+import android.app.Notification.Action;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.Person;
 import android.app.RemoteInput;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.os.Bundle;
 
-import androidx.core.app.NotificationCompat;
-import androidx.core.app.NotificationCompat.Action;
-import androidx.core.app.Person;
-
-import com.android.car.apps.common.LetterTileDrawable;
+import androidx.annotation.Nullable;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -73,7 +71,6 @@
             "com.android.car.messenger.common.REMOTE_INPUT_KEY";
 
     protected final Context mContext;
-    protected final String mClassName;
     protected final NotificationManager mNotificationManager;
     protected final boolean mUseLetterTile;
 
@@ -81,7 +78,7 @@
      * 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)}.
+     * ConversationKey, ConversationNotificationInfo, String, Bitmap)}.
      **/
     protected final Map<ConversationKey, ConversationNotificationInfo> mNotificationInfos =
             new HashMap<>();
@@ -90,40 +87,27 @@
      * 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 =
+    private final Map<ConversationKey, Notification.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)}.
+     * ConversationKey, ConversationNotificationInfo, String, Bitmap)}.
      **/
     protected final Map<MessageKey, Message> mMessages = new HashMap<>();
 
-    /**
-     * Maps a Bitmap of a sender's Large Icon to the sender's unique key.
-     * The extending class should always keep this map updated with the loaded Sender large icons
-     * before calling {@link BaseNotificationDelegate#postNotification(
-     * ConversationKey, ConversationNotificationInfo, String)}. If the large icon is not found for
-     * the {@link SenderKey} when constructing the notification, a {@link LetterTileDrawable} will
-     * be created for the sender, unless {@link BaseNotificationDelegate#mUseLetterTile} is set to
-     * false.
-     **/
-    protected final Map<SenderKey, Bitmap> mSenderLargeIcons = new HashMap<>();
-
     private final int mBitmapSize;
     private final float mCornerRadiusPercent;
 
     /**
      * Constructor for the BaseNotificationDelegate class.
      * @param context of the calling application.
-     * @param className of the calling application.
      * @param useLetterTile whether a letterTile icon should be used if no avatar icon is given.
      **/
-    public BaseNotificationDelegate(Context context, String className, boolean useLetterTile) {
+    public BaseNotificationDelegate(Context context, boolean useLetterTile) {
         mContext = context;
-        mClassName = className;
         mUseLetterTile = useLetterTile;
         mNotificationManager =
                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -141,7 +125,6 @@
         clearNotifications(predicate);
         mNotificationBuilders.entrySet().removeIf(entry -> predicate.test(entry.getKey()));
         mNotificationInfos.entrySet().removeIf(entry -> predicate.test(entry.getKey()));
-        mSenderLargeIcons.entrySet().removeIf(entry -> predicate.test(entry.getKey()));
         mMessages.entrySet().removeIf(
                 messageKeyMapMessageEntry -> predicate.test(messageKeyMapMessageEntry.getKey()));
     }
@@ -159,6 +142,23 @@
         });
     }
 
+    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.
@@ -180,10 +180,11 @@
      * and all of its {@link Message} objects have been linked to it.
      **/
     protected void postNotification(ConversationKey conversationKey,
-            ConversationNotificationInfo notificationInfo, String channelId) {
+            ConversationNotificationInfo notificationInfo, String channelId,
+            @Nullable Bitmap avatarIcon) {
         boolean newNotification = !mNotificationBuilders.containsKey(conversationKey);
 
-        NotificationCompat.Builder builder = newNotification ? new NotificationCompat.Builder(
+        Notification.Builder builder = newNotification ? new Notification.Builder(
                 mContext, channelId) : mNotificationBuilders.get(
                 conversationKey);
         builder.setChannelId(channelId);
@@ -194,9 +195,8 @@
                 R.plurals.notification_new_message, notificationInfo.mMessageKeys.size(),
                 notificationInfo.mMessageKeys.size()));
 
-        if (mSenderLargeIcons.containsKey(getSenderKeyFromConversation(conversationKey))) {
-            builder.setLargeIcon(
-                    mSenderLargeIcons.get(getSenderKeyFromConversation(conversationKey)));
+        if (avatarIcon != null) {
+            builder.setLargeIcon(avatarIcon);
         } else if (mUseLetterTile) {
             builder.setLargeIcon(Utils.createLetterTile(mContext,
                     Utils.getInitials(lastMessage.getSenderName(), ""),
@@ -204,7 +204,7 @@
         }
         // Else, no avatar icon will be shown.
 
-        builder.setWhen(lastMessage.getReceiveTime());
+        builder.setWhen(lastMessage.getReceivedTime());
 
         // Create MessagingStyle
         String userName = (notificationInfo.getUserDisplayName() == null
@@ -213,7 +213,7 @@
         Person user = new Person.Builder()
                 .setName(userName)
                 .build();
-        NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(
+        Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle(
                 user);
         Person sender = new Person.Builder()
                 .setName(lastMessage.getSenderName())
@@ -223,7 +223,7 @@
             if (!message.shouldExcludeFromNotification()) {
                 messagingStyle.addMessage(
                         message.getMessageText(),
-                        message.getReceiveTime(),
+                        message.getReceivedTime(),
                         notificationInfo.isGroupConvo() ? new Person.Builder()
                                 .setName(message.getSenderName())
                                 .setUri(message.getSenderContactUri())
@@ -231,18 +231,18 @@
             }
         });
         if (notificationInfo.isGroupConvo()) {
-            messagingStyle.setConversationTitle(
-                    mContext.getString(R.string.group_conversation_title_separator,
-                            lastMessage.getSenderName(), notificationInfo.getConvoTitle()));
+            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.getAppSmallIconResId() == 0) {
-                builder.setSmallIcon(R.drawable.ic_message);
+            if (notificationInfo.getAppIcon() != null) {
+                builder.setSmallIcon(notificationInfo.getAppIcon());
             } else {
-                builder.setSmallIcon(notificationInfo.getAppSmallIconResId());
+                builder.setSmallIcon(R.drawable.ic_message);
             }
 
             builder.setShowWhen(true);
@@ -281,7 +281,7 @@
             int notificationId) {
         final int icon = android.R.drawable.ic_media_play;
 
-        final List<NotificationCompat.Action> actionList = new ArrayList<>();
+        final List<Notification.Action> actionList = new ArrayList<>();
 
         // Reply action
         if (shouldAddReplyAction(conversationKey.getDeviceId())) {
@@ -289,14 +289,9 @@
             PendingIntent replyIntent = createServiceIntent(conversationKey, notificationId,
                     ACTION_REPLY);
             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()
-                            )
+                    new Notification.Action.Builder(icon, replyString, replyIntent)
+                            .setSemanticAction(Notification.Action.SEMANTIC_ACTION_REPLY)
+                            .addRemoteInput(new RemoteInput.Builder(EXTRA_REMOTE_INPUT_KEY).build())
                             .build()
             );
         }
@@ -306,9 +301,8 @@
         PendingIntent markAsReadIntent = createServiceIntent(conversationKey, notificationId,
                 ACTION_MARK_AS_READ);
         actionList.add(
-                new NotificationCompat.Action.Builder(icon, markAsRead, markAsReadIntent)
-                        .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
-                        .setShowsUserInterface(false)
+                new Notification.Action.Builder(icon, markAsRead, markAsReadIntent)
+                        .setSemanticAction(Notification.Action.SEMANTIC_ACTION_MARK_AS_READ)
                         .build()
         );
 
@@ -319,16 +313,11 @@
             String action) {
         Intent intent = new Intent(mContext, mContext.getClass())
                 .setAction(action)
-                .setClassName(mContext, mClassName)
+                .setClassName(mContext, mContext.getClass().getName())
                 .putExtra(EXTRA_CONVERSATION_KEY, conversationKey);
 
         return PendingIntent.getForegroundService(mContext, notificationId, intent,
                 PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
-    protected SenderKey getSenderKeyFromConversation(ConversationKey conversationKey) {
-        ConversationNotificationInfo info = mNotificationInfos.get(conversationKey);
-        return mMessages.get(info.getLastMessageKey()).getSenderKey();
-    }
-
 }
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
index 5567f50..accebf1 100644
--- a/car-messenger-common/src/com/android/car/messenger/common/ConversationNotificationInfo.java
+++ b/car-messenger-common/src/com/android/car/messenger/common/ConversationNotificationInfo.java
@@ -18,11 +18,12 @@
 
 import static com.android.car.apps.common.util.SafeLog.logw;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Build;
+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;
@@ -36,7 +37,7 @@
  * ConversationNotificationInfo object.
  **/
 public class ConversationNotificationInfo {
-    private static final String TAG = "CMC.ConversationNotificationInfo";
+    private static final String TAG = "CMC.ConvoNotifInfo";
     private static int sNextNotificationId = 0;
     final int mNotificationId = sNextNotificationId++;
 
@@ -51,9 +52,11 @@
     private final String mNotificationKey;
     @Nullable
     private final String mAppDisplayName;
+    private final String mAppPackageName;
     @Nullable
     private final String mUserDisplayName;
-    private final int mAppSmallIconResId;
+    @Nullable
+    private final Icon mAppIcon;
 
     public final LinkedList<MessageKey> mMessageKeys = new LinkedList<>();
 
@@ -68,7 +71,7 @@
         MessagingStyle messagingStyle = conversation.getMessagingStyle();
 
         if (!Utils.isValidConversationNotification(conversation, /* isShallowCheck= */ true)) {
-            if (Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
                 throw new IllegalArgumentException(
                         "ConversationNotificationInfo is missing required fields");
             } else {
@@ -77,18 +80,28 @@
             }
         }
 
+        Icon appIcon = null;
+        if (conversation.getAppIcon() != null) {
+            byte[] iconBytes = conversation.getAppIcon().toByteArray();
+            if (iconBytes != null && iconBytes.length > 0) {
+                appIcon = Icon.createWithData(iconBytes, 0, iconBytes.length);
+            }
+        }
+
         return new ConversationNotificationInfo(deviceName, deviceId,
                 messagingStyle.getConvoTitle(),
                 messagingStyle.getIsGroupConvo(), notificationKey,
                 conversation.getMessagingAppDisplayName(),
-                messagingStyle.getUserDisplayName(), /* appSmallIconResId= */ 0);
+                conversation.getMessagingAppPackageName(),
+                messagingStyle.getUserDisplayName(),
+                appIcon);
 
     }
 
     private ConversationNotificationInfo(@Nullable String deviceName, String deviceId,
             String convoTitle, boolean isGroupConvo, @Nullable String notificationKey,
-            @Nullable String appDisplayName, @Nullable String userDisplayName,
-            int appSmallIconResId) {
+            @Nullable String appDisplayName, String appPackageName,
+            @Nullable String userDisplayName, @Nullable Icon appIcon) {
         boolean missingDeviceId = (deviceId == null);
         boolean missingTitle = (convoTitle == null);
         if (missingDeviceId || missingTitle) {
@@ -107,8 +120,9 @@
         this.mIsGroupConvo = isGroupConvo;
         this.mNotificationKey = notificationKey;
         this.mAppDisplayName = appDisplayName;
+        this.mAppPackageName = appPackageName;
         this.mUserDisplayName = userDisplayName;
-        this.mAppSmallIconResId = appSmallIconResId;
+        this.mAppIcon = appIcon;
     }
 
     /** Returns the id that should be used for this object's {@link android.app.Notification} **/
@@ -158,6 +172,13 @@
     }
 
     /**
+     * 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}.
      */
@@ -167,9 +188,10 @@
     }
 
 
-    /** Returns the icon's resource id of the application that posted this notification. **/
-    public int getAppSmallIconResId() {
-        return mAppSmallIconResId;
+    /** Returns the app's icon of the application that posted this notification. **/
+    @Nullable
+    public Icon getAppIcon() {
+        return mAppIcon;
     }
 
     public MessageKey getLastMessageKey() {
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
index 017d055..462e22d 100644
--- a/car-messenger-common/src/com/android/car/messenger/common/Message.java
+++ b/car-messenger-common/src/com/android/car/messenger/common/Message.java
@@ -18,10 +18,10 @@
 
 import static com.android.car.apps.common.util.SafeLog.logw;
 
-import android.annotation.Nullable;
-import android.os.Build;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.car.messenger.NotificationMsgProto.NotificationMsg;
 import com.android.car.messenger.NotificationMsgProto.NotificationMsg.MessagingStyleMessage;
 
@@ -36,7 +36,7 @@
     private final String mSenderName;
     private final String mDeviceId;
     private final String mMessageText;
-    private final long mReceiveTime;
+    private final long mReceivedTime;
     private final boolean mIsReadOnPhone;
     private boolean mShouldExclude;
     private final String mHandle;
@@ -64,14 +64,15 @@
      *
      * @param deviceId of the phone that received this message.
      * @param updatedMessage containing the information to base this message object off of.
-     * @param appDisplayName of the messaging app this message belongs to.
+     * @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, String appDisplayName) {
+            MessagingStyleMessage updatedMessage, SenderKey senderKey) {
 
         if (!Utils.isValidMessagingStyleMessage(updatedMessage)) {
-            if (Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
                 throw new IllegalArgumentException(
                         "MessagingStyleMessage is missing required fields");
             } else {
@@ -88,12 +89,12 @@
                 Utils.createMessageHandle(updatedMessage),
                 MessageType.NOTIFICATION_MESSAGE,
                 /* senderContactUri */ null,
-                appDisplayName);
+                senderKey);
     }
 
-    private Message(String senderName, String deviceId, String messageText, long receiveTime,
+    private Message(String senderName, String deviceId, String messageText, long receivedTime,
             boolean isReadOnPhone, String handle, MessageType messageType,
-            @Nullable String senderContactUri, String senderKeyMetadata) {
+            @Nullable String senderContactUri, SenderKey senderKey) {
         boolean missingSenderName = (senderName == null);
         boolean missingDeviceId = (deviceId == null);
         boolean missingText = (messageText == null);
@@ -121,13 +122,13 @@
         this.mSenderName = senderName;
         this.mDeviceId = deviceId;
         this.mMessageText = messageText;
-        this.mReceiveTime = receiveTime;
+        this.mReceivedTime = receivedTime;
         this.mIsReadOnPhone = isReadOnPhone;
         this.mShouldExclude = false;
         this.mHandle = handle;
         this.mMessageType = messageType;
         this.mSenderContactUri = senderContactUri;
-        this.mSenderKey = new SenderKey(deviceId, senderName, senderKeyMetadata);
+        this.mSenderKey = senderKey;
     }
 
     /**
@@ -157,8 +158,8 @@
      * Returns the milliseconds since epoch at which this message notification was received on the
      * head-unit.
      */
-    public long getReceiveTime() {
-        return mReceiveTime;
+    public long getReceivedTime() {
+        return mReceivedTime;
     }
 
     /**
@@ -196,7 +197,12 @@
     }
 
     /**
-     * Returns the {@link SenderKey} that is unique for each contact per device.
+     * 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;
@@ -223,7 +229,7 @@
                 + " mSenderName='" + mSenderName + '\''
                 + ", mMessageText='" + mMessageText + '\''
                 + ", mSenderContactUri='" + mSenderContactUri + '\''
-                + ", mReceiveTime=" + mReceiveTime + '\''
+                + ", mReceiveTime=" + mReceivedTime + '\''
                 + ", mIsReadOnPhone= " + mIsReadOnPhone + '\''
                 + ", mShouldExclude= " + mShouldExclude + '\''
                 + ", mHandle='" + mHandle + '\''
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
index 5557830..5432563 100644
--- a/car-messenger-common/src/com/android/car/messenger/common/ProjectionStateListener.java
+++ b/car-messenger-common/src/com/android/car/messenger/common/ProjectionStateListener.java
@@ -42,24 +42,31 @@
     static final String PROJECTION_STATUS_EXTRA_DEVICE_STATE =
             "android.car.projection.DEVICE_STATE";
 
-    private final CarProjectionManager mCarProjectionManager;
+    private CarProjectionManager mCarProjectionManager;
+    private Car mCar;
 
     private int mProjectionState = ProjectionStatus.PROJECTION_STATE_INACTIVE;
     private List<ProjectionStatus> mProjectionDetails = Collections.emptyList();
 
     public ProjectionStateListener(Context context) {
-        mCarProjectionManager = (CarProjectionManager)
-                Car.createCar(context).getCarManager(Car.PROJECTION_SERVICE);
-    }
-
-    /** Registers the listener. Should be called when the caller starts up. **/
-    public void start() {
-        mCarProjectionManager.registerProjectionStatusListener(this);
+        mCar = Car.createCar(context);
+        mCarProjectionManager = (CarProjectionManager) mCar.getCarManager(Car.PROJECTION_SERVICE);
+        if (mCarProjectionManager != null) {
+            mCarProjectionManager.registerProjectionStatusListener(this);
+        }
     }
 
     /** Unregisters the listener. Should be called when the caller's lifecycle is ending. **/
-    public void stop() {
-        mCarProjectionManager.unregisterProjectionStatusListener(this);
+    public void destroy() {
+        if (mCarProjectionManager != null) {
+            mCarProjectionManager.unregisterProjectionStatusListener(this);
+        }
+        if (mCar != null) {
+            mCar.disconnect();
+            mCar = null;
+        }
+        mProjectionState = ProjectionStatus.PROJECTION_STATE_INACTIVE;
+        mProjectionDetails = Collections.emptyList();
     }
 
 
@@ -68,7 +75,6 @@
             List<ProjectionStatus> details) {
         mProjectionState = state;
         mProjectionDetails = details;
-
     }
 
     /**
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
index 2fcd273..bf8222c 100644
--- a/car-messenger-common/src/com/android/car/messenger/common/SenderKey.java
+++ b/car-messenger-common/src/com/android/car/messenger/common/SenderKey.java
@@ -23,8 +23,19 @@
  * unique Key.
  */
 public class SenderKey extends CompositeKey {
+    /**
+     * 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());
+    }
+
     /** Creates a senderkey for SMS, MMS, and {@link NotificationMsg}. **/
-    protected SenderKey(String deviceId, String senderName, String keyMetadata) {
+    private SenderKey(String deviceId, String senderName, String keyMetadata) {
         super(deviceId, senderName + "/" + keyMetadata);
     }
 }
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
index 027189e..99ec52f 100644
--- a/car-messenger-common/src/com/android/car/messenger/common/Utils.java
+++ b/car-messenger-common/src/com/android/car/messenger/common/Utils.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
 import android.text.TextUtils;
 
 import androidx.annotation.Nullable;
@@ -34,6 +36,15 @@
 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.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";
@@ -152,6 +163,26 @@
     }
 
     /**
+     * Ensure the {@link NotificationMsg.AvatarIconSync} object has all the required fields.
+     **/
+    public static boolean isValidAvatarIconSync(NotificationMsg.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;
+    }
+
+    /**
      * Creates a Letter Tile Icon that will display the given initials. If the initials are null,
      * then an avatar anonymous icon will be drawn.
      **/
@@ -226,4 +257,119 @@
         return initials.toString();
     }
 
+    /**
+     * 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);
+        String formattedGroupName = bidiFormatter.unicodeWrap(groupName,
+                TextDirectionHeuristics.LOCALE);
+        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) {
+                        return false;
+                    }
+                }
+
+                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/Android.bp b/car-messenger-common/tests/unit/Android.bp
new file mode 100644
index 0000000..af73ac8
--- /dev/null
+++ b/car-messenger-common/tests/unit/Android.bp
@@ -0,0 +1,39 @@
+//
+// 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.
+//
+
+android_test {
+    name: "car-messenger-common-lib-unit-tests",
+
+    srcs: ["src/**/*.java"],
+
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+
+    static_libs: [
+        "android.car",
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "car-messenger-common",
+        "mockito-target-extended-minus-junit4",
+        "truth-prebuilt",
+    ],
+
+    min_sdk_version: "29",
+}
\ No newline at end of file
diff --git a/car-messenger-common/tests/unit/AndroidManifest.xml b/car-messenger-common/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..eefce32
--- /dev/null
+++ b/car-messenger-common/tests/unit/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<!--
+  ~ 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
new file mode 100644
index 0000000..12bb628
--- /dev/null
+++ b/car-messenger-common/tests/unit/README.md
@@ -0,0 +1,24 @@
+# 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
new file mode 100644
index 0000000..acdffbd
--- /dev/null
+++ b/car-messenger-common/tests/unit/src/com.android.car.messenger.common/UtilsTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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);
+    }
+}
diff --git a/car-telephony-common/res/values-bg/strings.xml b/car-telephony-common/res/values-bg/strings.xml
index 1f38d61..ac7dbf6 100644
--- a/car-telephony-common/res/values-bg/strings.xml
+++ b/car-telephony-common/res/values-bg/strings.xml
@@ -20,7 +20,7 @@
     <string name="voicemail" msgid="2125552157407909509">"Гласова поща"</string>
     <string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g>  ·  <xliff:g id="DURATION">%2$s</xliff:g>"</string>
     <string name="call_state_connecting" msgid="5930724746375294866">"Свързва се…"</string>
-    <string name="call_state_dialing" msgid="1534599871716648114">"Набира се…"</string>
+    <string name="call_state_dialing" msgid="1534599871716648114">"Набиране…"</string>
     <string name="call_state_hold" msgid="6834028102796624100">"Задържано"</string>
     <string name="call_state_call_ended" msgid="4159349597599886429">"Обаждането завърши"</string>
     <string name="call_state_call_active" msgid="2769644783657864202">"Установена е връзка"</string>
diff --git a/car-telephony-common/res/values-da/strings.xml b/car-telephony-common/res/values-da/strings.xml
index dcd52a0..6995f5c 100644
--- a/car-telephony-common/res/values-da/strings.xml
+++ b/car-telephony-common/res/values-da/strings.xml
@@ -21,7 +21,7 @@
     <string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g>  ·  <xliff:g id="DURATION">%2$s</xliff:g>"</string>
     <string name="call_state_connecting" msgid="5930724746375294866">"Tilslutter…"</string>
     <string name="call_state_dialing" msgid="1534599871716648114">"Ringer op…"</string>
-    <string name="call_state_hold" msgid="6834028102796624100">"Afventer"</string>
+    <string name="call_state_hold" msgid="6834028102796624100">"På hold"</string>
     <string name="call_state_call_ended" msgid="4159349597599886429">"Opkaldet er slut"</string>
     <string name="call_state_call_active" msgid="2769644783657864202">"Der er forbindelse"</string>
     <string name="call_state_call_ringing" msgid="4618803402954375017">"Ringer…"</string>
diff --git a/car-telephony-common/res/values-et/strings.xml b/car-telephony-common/res/values-et/strings.xml
index ee65909..e74cb8a 100644
--- a/car-telephony-common/res/values-et/strings.xml
+++ b/car-telephony-common/res/values-et/strings.xml
@@ -25,5 +25,5 @@
     <string name="call_state_call_ended" msgid="4159349597599886429">"Kõne lõpetati"</string>
     <string name="call_state_call_active" msgid="2769644783657864202">"Ühendatud"</string>
     <string name="call_state_call_ringing" msgid="4618803402954375017">"Heliseb …"</string>
-    <string name="call_state_call_ending" msgid="5037498349965472247">"Ühenduse katkest. …"</string>
+    <string name="call_state_call_ending" msgid="5037498349965472247">"Ühenduse katkestamine…"</string>
 </resources>
diff --git a/car-telephony-common/res/values-in/strings.xml b/car-telephony-common/res/values-in/strings.xml
index 53a7831..9ef213c 100644
--- a/car-telephony-common/res/values-in/strings.xml
+++ b/car-telephony-common/res/values-in/strings.xml
@@ -25,5 +25,5 @@
     <string name="call_state_call_ended" msgid="4159349597599886429">"Panggilan Diakhiri"</string>
     <string name="call_state_call_active" msgid="2769644783657864202">"Terhubung"</string>
     <string name="call_state_call_ringing" msgid="4618803402954375017">"Berdering…"</string>
-    <string name="call_state_call_ending" msgid="5037498349965472247">"Memutus hubungan..."</string>
+    <string name="call_state_call_ending" msgid="5037498349965472247">"Memutus sambungan..."</string>
 </resources>
diff --git a/car-telephony-common/res/values-iw/strings.xml b/car-telephony-common/res/values-iw/strings.xml
index 0d1cd68..8e0fe49 100644
--- a/car-telephony-common/res/values-iw/strings.xml
+++ b/car-telephony-common/res/values-iw/strings.xml
@@ -20,7 +20,7 @@
     <string name="voicemail" msgid="2125552157407909509">"דואר קולי"</string>
     <string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g>  ·  <xliff:g id="DURATION">%2$s</xliff:g>"</string>
     <string name="call_state_connecting" msgid="5930724746375294866">"מתחבר…"</string>
-    <string name="call_state_dialing" msgid="1534599871716648114">"מחייג…"</string>
+    <string name="call_state_dialing" msgid="1534599871716648114">"החיוג מתבצע…"</string>
     <string name="call_state_hold" msgid="6834028102796624100">"בהמתנה"</string>
     <string name="call_state_call_ended" msgid="4159349597599886429">"השיחה הסתיימה"</string>
     <string name="call_state_call_active" msgid="2769644783657864202">"מתבצעת שיחה"</string>
diff --git a/car-telephony-common/res/values-ms/strings.xml b/car-telephony-common/res/values-ms/strings.xml
index 1c1d5de..fcde473 100644
--- a/car-telephony-common/res/values-ms/strings.xml
+++ b/car-telephony-common/res/values-ms/strings.xml
@@ -25,5 +25,5 @@
     <string name="call_state_call_ended" msgid="4159349597599886429">"Panggilan Tamat"</string>
     <string name="call_state_call_active" msgid="2769644783657864202">"Disambungkan"</string>
     <string name="call_state_call_ringing" msgid="4618803402954375017">"Berdering…"</string>
-    <string name="call_state_call_ending" msgid="5037498349965472247">"Memutuskan sambungn…"</string>
+    <string name="call_state_call_ending" msgid="5037498349965472247">"Memutuskan sambungan…"</string>
 </resources>
diff --git a/car-telephony-common/res/values-ne/strings.xml b/car-telephony-common/res/values-ne/strings.xml
index b021f11..331c544 100644
--- a/car-telephony-common/res/values-ne/strings.xml
+++ b/car-telephony-common/res/values-ne/strings.xml
@@ -23,7 +23,7 @@
     <string name="call_state_dialing" msgid="1534599871716648114">"डायल गर्दै…"</string>
     <string name="call_state_hold" msgid="6834028102796624100">"होल्डमा छ"</string>
     <string name="call_state_call_ended" msgid="4159349597599886429">"कल समाप्त भयो"</string>
-    <string name="call_state_call_active" msgid="2769644783657864202">"जडान गरिएको छ"</string>
+    <string name="call_state_call_active" msgid="2769644783657864202">"कनेक्ट गरिएको छ"</string>
     <string name="call_state_call_ringing" msgid="4618803402954375017">"घन्टी बज्दै छ…"</string>
     <string name="call_state_call_ending" msgid="5037498349965472247">"विच्छेद गर्दै…"</string>
 </resources>
diff --git a/car-telephony-common/res/values-nl/strings.xml b/car-telephony-common/res/values-nl/strings.xml
index 7d00163..53543e8 100644
--- a/car-telephony-common/res/values-nl/strings.xml
+++ b/car-telephony-common/res/values-nl/strings.xml
@@ -25,5 +25,5 @@
     <string name="call_state_call_ended" msgid="4159349597599886429">"Gesprek beëindigd"</string>
     <string name="call_state_call_active" msgid="2769644783657864202">"Verbonden"</string>
     <string name="call_state_call_ringing" msgid="4618803402954375017">"Gaat over…"</string>
-    <string name="call_state_call_ending" msgid="5037498349965472247">"Verb. verbreken…"</string>
+    <string name="call_state_call_ending" msgid="5037498349965472247">"Verbreken…"</string>
 </resources>
diff --git a/car-telephony-common/res/values-pt/strings.xml b/car-telephony-common/res/values-pt/strings.xml
index 7531adb..1bf5282 100644
--- a/car-telephony-common/res/values-pt/strings.xml
+++ b/car-telephony-common/res/values-pt/strings.xml
@@ -20,7 +20,7 @@
     <string name="voicemail" msgid="2125552157407909509">"Correio de voz"</string>
     <string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g>  ·  <xliff:g id="DURATION">%2$s</xliff:g>"</string>
     <string name="call_state_connecting" msgid="5930724746375294866">"Conectando…"</string>
-    <string name="call_state_dialing" msgid="1534599871716648114">"Discando…"</string>
+    <string name="call_state_dialing" msgid="1534599871716648114">"Chamando...…"</string>
     <string name="call_state_hold" msgid="6834028102796624100">"Em espera"</string>
     <string name="call_state_call_ended" msgid="4159349597599886429">"Chamada encerrada"</string>
     <string name="call_state_call_active" msgid="2769644783657864202">"Conectado"</string>
diff --git a/car-telephony-common/res/values-sw/strings.xml b/car-telephony-common/res/values-sw/strings.xml
index fc82d68..18a33e0 100644
--- a/car-telephony-common/res/values-sw/strings.xml
+++ b/car-telephony-common/res/values-sw/strings.xml
@@ -20,10 +20,10 @@
     <string name="voicemail" msgid="2125552157407909509">"Ujumbe wa sauti"</string>
     <string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g>  ·  <xliff:g id="DURATION">%2$s</xliff:g>"</string>
     <string name="call_state_connecting" msgid="5930724746375294866">"Inaunganisha…"</string>
-    <string name="call_state_dialing" msgid="1534599871716648114">"Inapigia…"</string>
+    <string name="call_state_dialing" msgid="1534599871716648114">"Inapiga…"</string>
     <string name="call_state_hold" msgid="6834028102796624100">"Imesitishwa"</string>
     <string name="call_state_call_ended" msgid="4159349597599886429">"Simu Imekamilika"</string>
     <string name="call_state_call_active" msgid="2769644783657864202">"Imeunganisha"</string>
     <string name="call_state_call_ringing" msgid="4618803402954375017">"Inalia…"</string>
-    <string name="call_state_call_ending" msgid="5037498349965472247">"Inaondoa…"</string>
+    <string name="call_state_call_ending" msgid="5037498349965472247">"Inakata…"</string>
 </resources>
diff --git a/car-telephony-common/res/values-th/strings.xml b/car-telephony-common/res/values-th/strings.xml
index 7af5876..e627b44 100644
--- a/car-telephony-common/res/values-th/strings.xml
+++ b/car-telephony-common/res/values-th/strings.xml
@@ -25,5 +25,5 @@
     <string name="call_state_call_ended" msgid="4159349597599886429">"วางสายแล้ว"</string>
     <string name="call_state_call_active" msgid="2769644783657864202">"เชื่อมต่อแล้ว"</string>
     <string name="call_state_call_ringing" msgid="4618803402954375017">"กำลังส่งเสียง…"</string>
-    <string name="call_state_call_ending" msgid="5037498349965472247">"ยกเลิกการเชื่อมต่อ…"</string>
+    <string name="call_state_call_ending" msgid="5037498349965472247">"ยกเลิกการโทรออก…"</string>
 </resources>
diff --git a/car-telephony-common/res/values/strings.xml b/car-telephony-common/res/values/strings.xml
index 24a3300..9bb8811 100644
--- a/car-telephony-common/res/values/strings.xml
+++ b/car-telephony-common/res/values/strings.xml
@@ -41,10 +41,4 @@
     <string name="call_state_call_ringing">Ringing&#8230;</string>
     <!-- Status label for phone state. &#8230; is an ellipsis. [CHAR LIMIT=25] -->
     <string name="call_state_call_ending">Disconnecting&#8230;</string>
-
-    <!-- String format used to format a address Uri. -->
-    <string name="address_uri_format" translatable="false">geo:0,0?q=%s</string>
-    <!-- String format used to format a navigation Uri. -->
-    <string name="navigation_uri_format" translatable="false">https://maps.google.com/maps?daddr=%s&amp;nav=1</string>
-
 </resources>
\ No newline at end of file
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 69f00d5..ecacb51 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
@@ -19,13 +19,14 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
-import android.util.Log;
 
 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;
 
@@ -78,7 +79,7 @@
     protected abstract T convertToEntity(@NonNull Cursor cursor);
 
     private void onCursorLoaded(Cursor cursor) {
-        Log.d(TAG, "onCursorLoaded: " + this);
+        L.d(TAG, "onCursorLoaded: " + this);
         if (mCurrentCursorRunnable != null) {
             mCurrentCursorRunnable.closeCursorIfNecessary();
             mCurrentRunnableFuture.cancel(false);
diff --git a/car-telephony-common/src/com/android/car/telephony/common/Contact.java b/car-telephony-common/src/com/android/car/telephony/common/Contact.java
index 42a1b9a..498e92b 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/Contact.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/Contact.java
@@ -24,11 +24,12 @@
 import android.os.Parcelable;
 import android.provider.ContactsContract;
 import android.text.TextUtils;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.car.apps.common.log.L;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -233,7 +234,7 @@
 
         if (!TextUtils.equals(accountName, contact.mAccountName)
                 || !TextUtils.equals(lookupKey, contact.mLookupKey)) {
-            Log.w(TAG, "A wrong contact is passed in. A new contact will be created.");
+            L.w(TAG, "A wrong contact is passed in. A new contact will be created.");
             contact = new Contact();
             contact.loadBasicInfo(cursor);
         }
@@ -253,8 +254,7 @@
                 contact.addPostalAddress(cursor);
                 break;
             default:
-                Log.d(TAG,
-                        String.format("This mimetype %s will not be loaded right now.", mimeType));
+                L.d(TAG, String.format("This mimetype %s will not be loaded right now.", mimeType));
         }
 
         return contact;
@@ -364,7 +364,7 @@
     @Override
     public boolean equals(Object obj) {
         return obj instanceof Contact && mLookupKey.equals(((Contact) obj).mLookupKey)
-                && mAccountName.equals(((Contact) obj).mAccountName);
+                && TextUtils.equals(((Contact) obj).mAccountName, mAccountName);
     }
 
     @Override
@@ -450,6 +450,17 @@
     }
 
     /**
+     * Returns the initials of the contact's name based on display order.
+     */
+    public String getInitialsBasedOnDisplayOrder(boolean startWithFirstName) {
+        if (startWithFirstName) {
+            return TelecomUtils.getInitials(mDisplayName, mDisplayNameAlt);
+        } else {
+            return TelecomUtils.getInitials(mDisplayNameAlt, mDisplayName);
+        }
+    }
+
+    /**
      * Returns {@link #mPhoneBookLabel}
      */
     public String getPhonebookLabel() {
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 960714a..6ef5f21 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
@@ -20,12 +20,16 @@
 import android.database.Cursor;
 import android.provider.ContactsContract;
 import android.text.TextUtils;
-import android.util.Log;
+
+import android.util.ArrayMap;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.Observer;
+import androidx.lifecycle.Transformations;
+
+import com.android.car.apps.common.log.L;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -54,6 +58,11 @@
      * key to contacts for one account.
      */
     private final Map<String, Map<String, Contact>> mLookupKeyContactMap = new HashMap<>();
+
+    /**
+     * A map which divides contacts by account.
+     */
+    private final Map<String, List<Contact>> mAccountContactsMap = new ArrayMap<>();
     private boolean mIsLoaded = false;
 
     /**
@@ -135,24 +144,39 @@
 
     /**
      * Returns a {@link LiveData} which monitors the contact list changes.
+     *
+     * @deprecated Use {@link #getContactsLiveDataByAccount(String)} instead.
      */
+    @Deprecated
     public LiveData<List<Contact>> getContactsLiveData() {
         return mContactListAsyncQueryLiveData;
     }
 
     /**
+     * Returns a LiveData that represents all contacts within an account.
+     *
+     * @param accountName the name of an account that contains all the contacts. For the contacts
+     *                    from a Bluetooth connected phone, the account name is equal to the
+     *                    Bluetooth address.
+     */
+    public LiveData<List<Contact>> getContactsLiveDataByAccount(String accountName) {
+        return Transformations.map(mContactListAsyncQueryLiveData,
+                contacts -> contacts == null ? null : mAccountContactsMap.get(accountName));
+    }
+
+    /**
      * Looks up a {@link Contact} by the given phone number. Returns null if can't find a Contact or
      * the {@link InMemoryPhoneBook} is still loading.
      */
     @Nullable
     public Contact lookupContactEntry(String phoneNumber) {
-        Log.v(TAG, String.format("lookupContactEntry: %s", phoneNumber));
+        L.v(TAG, String.format("lookupContactEntry: %s", phoneNumber));
         if (!isLoaded()) {
-            Log.w(TAG, "looking up a contact while loading.");
+            L.w(TAG, "looking up a contact while loading.");
         }
 
         if (TextUtils.isEmpty(phoneNumber)) {
-            Log.w(TAG, "looking up an empty phone number.");
+            L.w(TAG, "looking up an empty phone number.");
             return null;
         }
 
@@ -168,10 +192,10 @@
     @Nullable
     public Contact lookupContactByKey(String lookupKey, @Nullable String accountName) {
         if (!isLoaded()) {
-            Log.w(TAG, "looking up a contact while loading.");
+            L.w(TAG, "looking up a contact while loading.");
         }
         if (TextUtils.isEmpty(lookupKey)) {
-            Log.w(TAG, "looking up an empty lookup key.");
+            L.w(TAG, "looking up an empty lookup key.");
             return null;
         }
         if (mLookupKeyContactMap.containsKey(accountName)) {
@@ -189,11 +213,11 @@
     @NonNull
     public List<Contact> lookupContactByKey(String lookupKey) {
         if (!isLoaded()) {
-            Log.w(TAG, "looking up a contact while loading.");
+            L.w(TAG, "looking up a contact while loading.");
         }
 
         if (TextUtils.isEmpty(lookupKey)) {
-            Log.w(TAG, "looking up an empty lookup key.");
+            L.w(TAG, "looking up an empty lookup key.");
             return Collections.emptyList();
         }
         List<Contact> results = new ArrayList<>();
@@ -226,8 +250,13 @@
             subMap.put(lookupKey, Contact.fromCursor(mContext, cursor, subMap.get(lookupKey)));
         }
 
-        for (Map<String, Contact> subMap : contactMap.values()) {
+        mAccountContactsMap.clear();
+        for (String accountName : contactMap.keySet()) {
+            Map<String, Contact> subMap = contactMap.get(accountName);
             contactList.addAll(subMap.values());
+            List<Contact> accountContacts = new ArrayList<>();
+            accountContacts.addAll(subMap.values());
+            mAccountContactsMap.put(accountName, accountContacts);
         }
 
         mLookupKeyContactMap.clear();
@@ -244,7 +273,7 @@
 
     @Override
     public void onChanged(List<Contact> contacts) {
-        Log.d(TAG, "Contacts loaded:" + (contacts == null ? 0 : contacts.size()));
+        L.d(TAG, "Contacts loaded:" + (contacts == null ? 0 : contacts.size()));
         mIsLoaded = true;
     }
 }
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
index de0242b..394b6d4 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java
@@ -20,12 +20,13 @@
 import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.database.Cursor;
-import android.util.Log;
 
 import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+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.
@@ -49,9 +50,9 @@
 
     private AsyncQueryHandler mAsyncQueryHandler;
     private QueryParam.Provider mQueryParamProvider;
-    private Cursor mCurrentCursor;
     private OnQueryFinishedListener mOnQueryFinishedListener;
     private ContentObserver mContentObserver;
+    private ContentResolver mContentResolver;
     private boolean mIsActive = false;
     private int mToken;
 
@@ -64,6 +65,7 @@
             @NonNull ContentResolver cr,
             @NonNull OnQueryFinishedListener listener) {
         mAsyncQueryHandler = new AsyncQueryHandlerImpl(this, cr);
+        mContentResolver = cr;
         mContentObserver = new ContentObserver(mAsyncQueryHandler) {
             @Override
             public void onChange(boolean selfChange) {
@@ -80,8 +82,9 @@
      */
     @MainThread
     public void startQuery() {
-        Log.d(TAG, "startQuery");
+        L.d(TAG, "startQuery");
         mAsyncQueryHandler.cancelOperation(mToken); // Cancel the query task.
+        mContentResolver.unregisterContentObserver(mContentObserver);
 
         mToken++;
         QueryParam queryParam = mQueryParamProvider.getQueryParam();
@@ -94,6 +97,7 @@
                     queryParam.mSelection,
                     queryParam.mSelectionArgs,
                     queryParam.mOrderBy);
+            mContentResolver.registerContentObserver(queryParam.mUri, false, mContentObserver);
         } else {
             mOnQueryFinishedListener.onQueryFinished(null);
         }
@@ -106,9 +110,9 @@
      */
     @MainThread
     public void stopQuery() {
-        Log.d(TAG, "stopQuery");
+        L.d(TAG, "stopQuery");
         mIsActive = false;
-        cleanupCursorIfNecessary();
+        mContentResolver.unregisterContentObserver(mContentObserver);
         mAsyncQueryHandler.cancelOperation(mToken); // Cancel the query task.
     }
 
@@ -116,24 +120,12 @@
         if (!mIsActive) {
             return;
         }
-        Log.d(TAG, "onQueryComplete");
-        cleanupCursorIfNecessary();
-        if (cursor != null) {
-            cursor.registerContentObserver(mContentObserver);
-            mCurrentCursor = cursor;
-        }
+        L.d(TAG, "onQueryComplete");
         if (mOnQueryFinishedListener != null) {
             mOnQueryFinishedListener.onQueryFinished(cursor);
         }
     }
 
-    protected void cleanupCursorIfNecessary() {
-        if (mCurrentCursor != null) {
-            mCurrentCursor.unregisterContentObserver(mContentObserver);
-        }
-        mCurrentCursor = null;
-    }
-
     private static class AsyncQueryHandlerImpl extends AsyncQueryHandler {
         private ObservableAsyncQuery mQuery;
 
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 d90e2ba..02c0808 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
@@ -71,6 +71,7 @@
     private long mId;
     private String mPhoneNumberString;
     private I18nPhoneNumberWrapper mI18nPhoneNumberWrapper;
+    private String mAccountName;
     private List<Record> mCallRecords = new ArrayList<>();
 
     /**
@@ -81,6 +82,7 @@
         int numberColumn = cursor.getColumnIndex(CallLog.Calls.NUMBER);
         int dateColumn = cursor.getColumnIndex(CallLog.Calls.DATE);
         int callTypeColumn = cursor.getColumnIndex(CallLog.Calls.TYPE);
+        int accountNameColumn = cursor.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_ID);
 
         PhoneCallLog phoneCallLog = new PhoneCallLog();
         phoneCallLog.mId = cursor.getLong(idColumn);
@@ -89,6 +91,7 @@
                 phoneCallLog.mPhoneNumberString);
         Record record = new Record(cursor.getLong(dateColumn), cursor.getInt(callTypeColumn));
         phoneCallLog.mCallRecords.add(record);
+        phoneCallLog.mAccountName = cursor.getString(accountNameColumn);
         return phoneCallLog;
     }
 
@@ -97,6 +100,14 @@
         return mPhoneNumberString;
     }
 
+    /**
+     * Returns the account name that this call log belongs to. For call logs from Bluetooth device,
+     * account name is the same as Bluetooth address.
+     */
+    public String getAccountName() {
+        return mAccountName;
+    }
+
     /** Returns the id of this log. */
     public long getPhoneLogId() {
         return mId;
@@ -164,6 +175,8 @@
         sb.append(Log.pii(mPhoneNumberString));
         sb.append(" CallLog: ");
         sb.append(mCallRecords.size());
+        sb.append(" Account: ");
+        sb.append(mAccountName);
         return sb.toString();
     }
 }
diff --git a/car-telephony-common/src/com/android/car/telephony/common/PostalAddress.java b/car-telephony-common/src/com/android/car/telephony/common/PostalAddress.java
index cb7c774..7de3be6 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/PostalAddress.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/PostalAddress.java
@@ -16,15 +16,17 @@
 
 package com.android.car.telephony.common;
 
+import android.content.Intent;
 import android.content.res.Resources;
 import android.database.Cursor;
-import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.ContactsContract;
-import android.util.Log;
 
 import androidx.annotation.Nullable;
+import com.android.car.apps.common.NavigationUtils;
+import com.android.car.apps.common.log.L;
+
 
 /**
  * Encapsulates data about an address entry. Typically loaded from the local Address store.
@@ -104,23 +106,20 @@
     }
 
     /**
-     * Returns the address Uri for {@link #mFormattedAddress}.
+     * Returns the address Intent for {@link #mFormattedAddress}.
      */
-    public Uri getAddressUri(Resources res) {
-        String address = String.format(res.getString(R.string.address_uri_format),
-                Uri.encode(mFormattedAddress));
-        Log.d(TAG, "The address is: " + address);
-        return Uri.parse(address);
+    public Intent getAddressIntent(Resources res) {
+        L.d(TAG, "Get address intent");
+        return NavigationUtils.getViewAddressIntent(res, mFormattedAddress);
     }
 
     /**
-     * Returns the navigation Uri for {@link #mFormattedAddress}.
+     * Returns the navigation Intent for {@link #mFormattedAddress}.
      */
-    public Uri getNavigationUri(Resources res) {
-        String address = String.format(res.getString(R.string.navigation_uri_format),
-                Uri.encode(mFormattedAddress));
-        Log.d(TAG, "The address is: " + address);
-        return Uri.parse(address);
+
+    public Intent getNavigationIntent(Resources res) {
+        L.d(TAG, "Get navigation intent");
+        return NavigationUtils.getNavigationIntent(res, mFormattedAddress);
     }
 
     @Override
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 090f223..1d934ec 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
@@ -38,8 +38,9 @@
 import android.telecom.Call;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
 import android.text.TextUtils;
-import android.util.Log;
 import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
@@ -48,6 +49,7 @@
 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
 
 import com.android.car.apps.common.LetterTileDrawable;
+import com.android.car.apps.common.log.L;
 
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.request.RequestOptions;
@@ -65,6 +67,14 @@
  */
 public class TelecomUtils {
     private static final String TAG = "CD.TelecomUtils";
+    /**
+     * A reference to keep track of the soring method of sorting by the contact's first name.
+     */
+    public static final Integer SORT_BY_FIRST_NAME = 1;
+    /**
+     * A reference to keep track of the soring method of sorting by the contact's last name.
+     */
+    public static final Integer SORT_BY_LAST_NAME = 2;
 
     private static String sVoicemailNumber;
     private static TelephonyManager sTelephonyManager;
@@ -113,24 +123,19 @@
      * Format a number as a phone number.
      */
     public static String getFormattedNumber(Context context, String number) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "getFormattedNumber: " + number);
-        }
+        L.d(TAG, "getFormattedNumber: " + number);
         if (number == null) {
             return "";
         }
 
         String countryIso = getCurrentCountryIso(context);
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "PhoneNumberUtils.formatNumberToE16, number: "
-                    + number + ", country: " + countryIso);
-        }
-        String e164 = PhoneNumberUtils.formatNumberToE164(number, countryIso);
-        String formattedNumber = PhoneNumberUtils.formatNumber(number, e164, countryIso);
+        L.d(TAG, "PhoneNumberUtils.formatNumber, number: " + number
+                    + ", country: " + countryIso);
+
+        String formattedNumber = PhoneNumberUtils.formatNumber(number, countryIso);
         formattedNumber = TextUtils.isEmpty(formattedNumber) ? number : formattedNumber;
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "getFormattedNumber, result: " + formattedNumber);
-        }
+        L.d(TAG, "getFormattedNumber, result: " + formattedNumber);
+
         return formattedNumber;
     }
 
@@ -146,16 +151,16 @@
             if (country != null) {
                 countryIso = country.getCountryIso();
             } else {
-                Log.e(TAG, "CountryDetector.detectCountry() returned null.");
+                L.e(TAG, "CountryDetector.detectCountry() returned null.");
             }
         }
         if (countryIso == null) {
             countryIso = locale.getCountry();
-            Log.w(TAG, "No CountryDetector; falling back to countryIso based on locale: "
+            L.w(TAG, "No CountryDetector; falling back to countryIso based on locale: "
                     + countryIso);
         }
         if (countryIso == null || countryIso.length() != 2) {
-            Log.w(TAG, "Invalid locale, falling back to US");
+            L.w(TAG, "Invalid locale, falling back to US");
             countryIso = "US";
         }
         return countryIso;
@@ -379,26 +384,48 @@
     /**
      * Sets a Contact avatar onto the provided {@code icon}. The first letter or both letters of the
      * contact's initials.
+     *
+     * @param sortMethod can be either {@link #SORT_BY_FIRST_NAME} or {@link #SORT_BY_LAST_NAME}.
      */
     public static void setContactBitmapAsync(
             Context context,
             @Nullable final ImageView icon,
-            @Nullable final Contact contact) {
-        setContactBitmapAsync(context, icon, contact, null);
+            @Nullable final Contact contact,
+            Integer sortMethod) {
+        setContactBitmapAsync(context, icon, contact, null, sortMethod);
     }
 
     /**
      * Sets a Contact avatar onto the provided {@code icon}. The first letter or both letters of the
-     * contact's initials or {@code fallbackDisplayName} will be used as a fallback resource if
-     * avatar loading fails.
+     * contact's initials. Will start with first name by default.
      */
     public static void setContactBitmapAsync(
             Context context,
             @Nullable final ImageView icon,
             @Nullable final Contact contact,
             @Nullable final String fallbackDisplayName) {
+        setContactBitmapAsync(context, icon, contact, fallbackDisplayName, SORT_BY_FIRST_NAME);
+    }
+
+    /**
+     * Sets a Contact avatar onto the provided {@code icon}. The first letter or both letters of the
+     * contact's initials or {@code fallbackDisplayName} will be used as a fallback resource if
+     * avatar loading fails.
+     *
+     * @param sortMethod can be either {@link #SORT_BY_FIRST_NAME} or {@link #SORT_BY_LAST_NAME}. If
+     *                   the value is {@link #SORT_BY_FIRST_NAME}, the name and initials order will
+     *                   be first name first. Otherwise, the order will be last name first.
+     */
+    public static void setContactBitmapAsync(
+            Context context,
+            @Nullable final ImageView icon,
+            @Nullable final Contact contact,
+            @Nullable final String fallbackDisplayName,
+            Integer sortMethod) {
         Uri avatarUri = contact != null ? contact.getAvatarUri() : null;
-        String initials = contact != null ? contact.getInitials()
+        boolean startWithFirstName = isSortByFirstName(sortMethod);
+        String initials = contact != null
+                ? contact.getInitialsBasedOnDisplayOrder(startWithFirstName)
                 : (fallbackDisplayName == null ? null : getInitials(fallbackDisplayName, null));
         String identifier = contact == null ? fallbackDisplayName : contact.getDisplayName();
 
@@ -486,7 +513,7 @@
     public static void markCallLogAsRead(Context context, String phoneNumberString) {
         if (context.checkSelfPermission(Manifest.permission.WRITE_CALL_LOG)
                 != PackageManager.PERMISSION_GRANTED) {
-            Log.w(TAG, "Missing WRITE_CALL_LOG permission; not marking missed calls as read.");
+            L.w(TAG, "Missing WRITE_CALL_LOG permission; not marking missed calls as read.");
             return;
         }
         ContentValues contentValues = new ContentValues();
@@ -516,7 +543,7 @@
                             where.toString(),
                             selectionArgs.toArray(selectionArgsArray));
         } catch (IllegalArgumentException e) {
-            Log.e(TAG, "markCallLogAsRead failed", e);
+            L.e(TAG, "markCallLogAsRead failed", e);
         }
     }
 
@@ -550,7 +577,7 @@
         RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(
                 context.getResources(), letterTileDrawable.toBitmap(avatarSize));
         return createFromRoundedBitmapDrawable(roundedBitmapDrawable, avatarSize,
-            cornerRadiusPercent);
+                cornerRadiusPercent);
     }
 
     /** Creates an Icon based on the given roundedBitmapDrawable. **/
@@ -567,6 +594,13 @@
         return Icon.createWithBitmap(result);
     }
 
+    /**
+     * Sets the direction of a string, used for displaying phone numbers.
+     */
+    public static String getBidiWrappedNumber(String string) {
+        return BidiFormatter.getInstance().unicodeWrap(string, TextDirectionHeuristics.LTR);
+    }
+
     private static Uri makeResourceUri(Context context, int resourceId) {
         return new Uri.Builder()
                 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
@@ -575,4 +609,11 @@
                 .build();
     }
 
+    /**
+     * Returns true if contacts are sorted by their first names. Returns false if they are sorted by
+     * last names.
+     */
+    public static boolean isSortByFirstName(Integer sortMethod) {
+        return SORT_BY_FIRST_NAME.equals(sortMethod);
+    }
 }
diff --git a/car-ui-lib/Android.bp b/car-ui-lib/Android.bp
index f433c4c..ac82b7b 100644
--- a/car-ui-lib/Android.bp
+++ b/car-ui-lib/Android.bp
@@ -31,10 +31,24 @@
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.appcompat_appcompat",
-	"androidx.asynclayoutinflater_asynclayoutinflater",
+        "androidx.asynclayoutinflater_asynclayoutinflater",
         "androidx-constraintlayout_constraintlayout",
         "androidx.preference_preference",
         "androidx.recyclerview_recyclerview",
         "androidx-constraintlayout_constraintlayout-solver",
     ],
 }
+
+// User this if your project includes overlayable.xml
+android_library {
+
+    name: "car-ui-lib-bp-overlayable",
+
+    resource_dirs: [
+        "res-overlayable"
+    ],
+
+    static_libs: [
+        "car-ui-lib-bp",
+    ],
+}
diff --git a/car-ui-lib/build.gradle b/car-ui-lib/build.gradle
index 7775382..edbed36 100644
--- a/car-ui-lib/build.gradle
+++ b/car-ui-lib/build.gradle
@@ -46,11 +46,11 @@
 apply plugin: 'com.android.library'
 
 android {
-    compileSdkVersion 28
+    compileSdkVersion 29
 
     defaultConfig {
         minSdkVersion 28
-        targetSdkVersion 28
+        targetSdkVersion 29
         versionCode 1
         versionName "1.0"
     }
diff --git a/car-ui-lib/findviewbyid-preupload-hook.sh b/car-ui-lib/findviewbyid-preupload-hook.sh
index 4969536..e520eb2 100755
--- a/car-ui-lib/findviewbyid-preupload-hook.sh
+++ b/car-ui-lib/findviewbyid-preupload-hook.sh
@@ -1,11 +1,7 @@
 #!/bin/bash
 
-if grep -rq "findViewById\|requireViewById" car-ui-lib/src/com/android/car/ui/toolbar/; then
-    grep -r "findViewById\|requireViewById" car-ui-lib/src/com/android/car/ui/toolbar/;
+if grep -rq --exclude=CarUiUtils.java "findViewById\|requireViewById" car-ui-lib/src/com/android/car/ui/; then
+    grep -r --exclude=CarUiUtils.java "findViewById\|requireViewById" car-ui-lib/src/com/android/car/ui/;
     echo "Illegal use of findViewById or requireViewById in car-ui-lib. Please consider using CarUiUtils#findViewByRefId or CarUiUtils#requireViewByRefId" && false;
 fi
 
-if grep -rq "findViewById\|requireViewById" car-ui-lib/src/com/android/car/ui/recyclerview/; then
-    grep -r "findViewById\|requireViewById" car-ui-lib/src/com/android/car/ui/recyclerview/;
-    echo "Illegal use of findViewById or requireViewById in car-ui-lib. Please consider using CarUiUtils#findViewByRefId or CarUiUtils#requireViewByRefId" && false;
-fi
diff --git a/car-ui-lib/referencedesign/Android.mk b/car-ui-lib/referencedesign/Android.mk
new file mode 100644
index 0000000..7da04dc
--- /dev/null
+++ b/car-ui-lib/referencedesign/Android.mk
@@ -0,0 +1,33 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+CAR_UI_RRO_SET_NAME := googlecarui
+CAR_UI_RESOURCE_DIR := $(LOCAL_PATH)/res
+CAR_UI_RRO_TARGETS := \
+    com.android.car.ui.paintbooth \
+    com.android.car.rotaryplayground \
+    com.android.car.themeplayground \
+    com.android.car.carlauncher \
+    com.android.car.home \
+    com.android.car.media \
+    com.android.car.radio \
+    com.android.car.calendar \
+    com.android.car.companiondevicesupport \
+    com.android.car.systemupdater \
+    com.android.car.dialer \
+    com.android.car.linkviewer \
+    com.android.car.settings \
+    com.android.car.voicecontrol \
+    com.android.car.faceenroll \
+    com.android.permissioncontroller \
+    com.android.settings.intelligence \
+    com.google.android.apps.automotive.inputmethod \
+    com.google.android.apps.automotive.inputmethod.dev \
+    com.google.android.embedded.projection \
+    com.google.android.gms \
+    com.google.android.packageinstaller \
+    com.google.android.carassistant \
+    com.google.android.tts \
+    com.android.vending \
+
+include packages/apps/Car/libs/car-ui-lib/generate_rros.mk
diff --git a/car-ui-lib/referencedesign/AndroidManifest.xml b/car-ui-lib/referencedesign/AndroidManifest.xml
new file mode 100644
index 0000000..7585b3c
--- /dev/null
+++ b/car-ui-lib/referencedesign/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="{{RRO_PACKAGE_NAME}}">
+    <application android:hasCode="false"/>
+    <!-- IMPORTANT: Setting the category to "BypassIdMapV1" will cause these static RROs not to
+     have an IDMAP V1 generated within the PackageManager which improves boot performance.
+     IDMAP v1 allowed for overlays of values within an AndroidManifest file but that
+     functionality and IDMAP v1 has been removed in the next version of Android and is not used
+      here. -->
+    <overlay android:priority="10"
+        android:targetPackage="{{TARGET_PACKAGE_NAME}}"
+        android:targetName="car-ui-lib"
+        android:isStatic="true"
+        android:category="BypassIdMapV1"
+        android:requiredSystemPropertyName="ro.build.car_ui_rros_enabled"
+        android:requiredSystemPropertyValue="true"/>
+</manifest>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/color/car_ui_text_color_primary.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/color/car_ui_text_color_primary.xml
new file mode 100644
index 0000000..34033e2
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/color/car_ui_text_color_primary.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<!-- Copy of ?android:attr/textColorPrimary (frameworks/base/res/res/color/text_color_primary.xml)
+     but with a ux restricted state. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/colorForeground"/>
+    <item android:color="?android:attr/colorForeground"/>
+</selector>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_icon_arrow_back.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_icon_arrow_back.xml
new file mode 100644
index 0000000..4ad49b2
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_icon_arrow_back.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:autoMirrored="true"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
+</vector>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_recyclerview_button_ripple_background.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_recyclerview_button_ripple_background.xml
new file mode 100644
index 0000000..0acb196
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_recyclerview_button_ripple_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/car_ui_ripple_color" />
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_recyclerview_ic_down.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_recyclerview_ic_down.xml
new file mode 100644
index 0000000..380bf46
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_recyclerview_ic_down.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:pathData="M14.83,16.42L24,25.59l9.17,-9.17L36,19.25l-12,12 -12,-12z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_recyclerview_ic_up.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_recyclerview_ic_up.xml
new file mode 100644
index 0000000..2eff62f
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_recyclerview_ic_up.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:pathData="M14.83,30.83L24,21.66l9.17,9.17L36,28 24,16 12,28z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_recyclerview_scrollbar_thumb.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_recyclerview_scrollbar_thumb.xml
new file mode 100644
index 0000000..ec6318a
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_recyclerview_scrollbar_thumb.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/car_ui_scrollbar_thumb" />
+    <corners android:radius="@dimen/car_ui_scrollbar_thumb_radius"/>
+</shape>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_toolbar_menu_item_divider.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_toolbar_menu_item_divider.xml
new file mode 100644
index 0000000..1e9a5a2
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_toolbar_menu_item_divider.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~
+  ~ Copyright (C) 2019 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.
+  ~
+ -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <size
+        android:width="16dp"/>
+</shape>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
new file mode 100644
index 0000000..03f8d82
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~
+  ~ Copyright (C) 2019 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.
+  ~
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="#27ffffff"
+        android:radius="48dp"/>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl-port/car_ui_base_layout_toolbar.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl-port/car_ui_base_layout_toolbar.xml
new file mode 100644
index 0000000..fb97b92
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl-port/car_ui_base_layout_toolbar.xml
@@ -0,0 +1,141 @@
+<?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.
+    -->
+<!-- This is for the two-row version of the toolbar -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- When the user finishes searching, we call clearFocus() on the editText in the search bar.
+     clearFocus() will actually send the focus to the first focusable thing in the layout.
+     If that focusable thing is still the search bar it will just reselect it, and the user won't
+     be able to deselect. So make a focusable view here to guarantee that we can clear the focus -->
+    <View
+        android:layout_width="1dp"
+        android:layout_height="1dp"
+        android:focusable="true"
+        android:focusableInTouchMode="true" />
+
+    <FrameLayout
+        android:id="@+id/car_ui_base_layout_content_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/car_ui_toolbar_background"
+        android:layout_alignParentTop="true">
+
+        <!-- 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="112dp"
+            android:layout_height="96dp"
+            android:layout_alignParentLeft="true">
+            <!-- missing: icon src -->
+            <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"
+                android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple"
+                android:tint="@color/car_ui_text_color_primary"/>
+            <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="112dp"
+            android:layout_height="96dp"
+            android:layout_alignParentRight="true">
+
+            <ImageView
+                android:id="@+id/car_ui_toolbar_title_logo"
+                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"/>
+        </FrameLayout>
+
+        <LinearLayout
+            android:id="@+id/car_ui_toolbar_title_container"
+            android:layout_height="96dp"
+            android:layout_width="wrap_content"
+            android:orientation="vertical"
+            android:layout_marginRight="112dp"
+            android:layout_alignParentRight="true"
+            android:gravity="center_vertical">
+            <TextView android:id="@+id/car_ui_toolbar_title"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:singleLine="true"
+                      android:textAlignment="viewStart"
+                      android:textAppearance="@style/TextAppearance.CarUi.Widget.Toolbar.Title"/>
+            <TextView android:id="@+id/car_ui_toolbar_subtitle"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:visibility="gone"
+                      android:textAlignment="viewStart"
+                      android:textAppearance="?android:attr/textAppearanceSmall"/>
+        </LinearLayout>
+
+        <com.android.car.ui.toolbar.TabLayout
+            android:id="@+id/car_ui_toolbar_tabs"
+            android:layout_width="match_parent"
+            android:layout_height="96dp"
+            android:gravity="left"
+            android:layout_alignParentTop="true"
+            android:layout_marginTop="96dp"/>
+
+        <LinearLayout
+            android:id="@+id/car_ui_toolbar_menu_items_container"
+            android:layout_width="wrap_content"
+            android:layout_height="96dp"
+            android:orientation="horizontal"
+            android:divider="@drawable/car_ui_toolbar_menu_item_divider"
+            android:showDividers="beginning|middle|end"
+            android:layout_toRightOf="@+id/car_ui_toolbar_nav_icon_container"
+            android:layout_alignWithParentIfMissing="true"/>
+
+        <FrameLayout
+            android:id="@+id/car_ui_toolbar_search_view_container"
+            android:layout_width="match_parent"
+            android:layout_height="96dp"
+            android:layout_toRightOf="@+id/car_ui_toolbar_menu_items_container"
+            android:layout_alignParentRight="true"/>
+
+        <ProgressBar
+            android:id="@+id/car_ui_toolbar_progress_bar"
+            style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentBottom="true"
+            android:indeterminate="true"
+            android:visibility="gone"/>
+
+    </RelativeLayout>
+</RelativeLayout>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_base_layout_toolbar.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_base_layout_toolbar.xml
new file mode 100644
index 0000000..35d7742
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_base_layout_toolbar.xml
@@ -0,0 +1,135 @@
+<?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.
+    -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- When the user finishes searching, we call clearFocus() on the editText in the search bar.
+     clearFocus() will actually send the focus to the first focusable thing in the layout.
+     If that focusable thing is still the search bar it will just reselect it, and the user won't
+     be able to deselect. So make a focusable view here to guarantee that we can clear the focus -->
+    <View
+        android:layout_width="1dp"
+        android:layout_height="1dp"
+        android:focusable="true"
+        android:focusableInTouchMode="true" />
+
+    <FrameLayout
+        android:id="@+id/car_ui_base_layout_content_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="96dp"
+        android:layout_alignParentTop="true"
+        android:id="@+id/car_ui_toolbar_background">
+
+        <FrameLayout
+            android:id="@+id/car_ui_toolbar_nav_icon_container"
+            android:layout_width="112dp"
+            android:layout_height="match_parent"
+            android:layout_alignParentLeft="true">
+            <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"
+                android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple"
+                android:tint="@color/car_ui_text_color_primary"/>
+            <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="112dp"
+            android:layout_height="match_parent"
+            android:layout_alignParentRight="true">
+
+            <ImageView
+                android:id="@+id/car_ui_toolbar_title_logo"
+                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"/>
+        </FrameLayout>
+
+        <LinearLayout android:layout_height="wrap_content"
+                      android:layout_width="match_parent"
+                      android:id="@+id/car_ui_toolbar_title_container"
+                      android:orientation="vertical"
+                      android:layout_marginRight="112dp"
+                      android:layout_alignParentRight="true"
+                      android:layout_centerVertical="true">
+            <TextView android:id="@+id/car_ui_toolbar_title"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:singleLine="true"
+                      android:textAppearance="@style/TextAppearance.CarUi.Widget.Toolbar.Title"/>
+            <TextView android:id="@+id/car_ui_toolbar_subtitle"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:visibility="gone"
+                      android:textAlignment="viewStart"
+                      android:textAppearance="?android:attr/textAppearanceSmall"/>
+        </LinearLayout>
+
+        <com.android.car.ui.toolbar.TabLayout
+            android:id="@+id/car_ui_toolbar_tabs"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="left"
+            android:layout_toRightOf="@+id/car_ui_toolbar_menu_items_container"
+            android:layout_toLeftOf="@+id/car_ui_toolbar_title_container"/>
+
+        <LinearLayout
+            android:id="@+id/car_ui_toolbar_menu_items_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:orientation="horizontal"
+            android:divider="@drawable/car_ui_toolbar_menu_item_divider"
+            android:showDividers="beginning|middle|end"
+            android:layout_toRightOf="@+id/car_ui_toolbar_nav_icon_container"
+            android:layout_alignWithParentIfMissing="true"/>
+
+        <FrameLayout
+            android:id="@+id/car_ui_toolbar_search_view_container"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_toRightOf="@+id/car_ui_toolbar_menu_items_container"
+            android:layout_alignParentRight="true"/>
+
+        <ProgressBar
+            android:id="@+id/car_ui_toolbar_progress_bar"
+            style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentBottom="true"
+            android:indeterminate="true"
+            android:visibility="gone"/>
+
+    </RelativeLayout>
+</RelativeLayout>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_recycler_view.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_recycler_view.xml
new file mode 100644
index 0000000..c1d16b5
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_recycler_view.xml
@@ -0,0 +1,28 @@
+<?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
+  -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <com.android.car.ui.recyclerview.CarUiRecyclerViewContainer
+        android:id="@+id/car_ui_recycler_view"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_marginStart="@dimen/car_ui_scrollbar_margin"
+        android:tag="carUiRecyclerView"
+        android:layout_weight="1"/>
+
+    <include layout="@layout/car_ui_recyclerview_scrollbar"/>
+</merge>
\ No newline at end of file
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_recyclerview_scrollbar.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_recyclerview_scrollbar.xml
new file mode 100644
index 0000000..85d624c
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_recyclerview_scrollbar.xml
@@ -0,0 +1,73 @@
+<?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.
+  -->
+
+<RelativeLayout
+    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_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentRight="true"/>
+
+    <!-- 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_centerHorizontal="true"
+        android:background="@drawable/car_ui_recyclerview_scrollbar_thumb"
+        android:layout_below="@+id/car_ui_scrollbar_page_up" />
+
+    <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"
+        android:layout_below="@+id/car_ui_scrollbar_page_up"
+        android:layout_above="@+id/car_ui_scrollbar_page_down"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentEnd="true"/>
+
+    <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_alignParentBottom="true"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentRight="true"/>
+</RelativeLayout>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_toolbar.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_toolbar.xml
new file mode 100644
index 0000000..85e34cb
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_toolbar.xml
@@ -0,0 +1,114 @@
+<?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.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="96dp"
+    android:id="@+id/car_ui_toolbar_background">
+
+    <FrameLayout
+        android:id="@+id/car_ui_toolbar_nav_icon_container"
+        android:layout_width="112dp"
+        android:layout_height="match_parent"
+        android:layout_alignParentLeft="true">
+        <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"
+            android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple"
+            android:tint="@color/car_ui_text_color_primary"/>
+        <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="112dp"
+        android:layout_height="match_parent"
+        android:layout_alignParentRight="true">
+
+        <ImageView
+            android:id="@+id/car_ui_toolbar_title_logo"
+            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"/>
+    </FrameLayout>
+
+    <LinearLayout android:layout_height="wrap_content"
+                  android:layout_width="match_parent"
+                  android:id="@+id/car_ui_toolbar_title_container"
+                  android:orientation="vertical"
+                  android:layout_marginRight="112dp"
+                  android:layout_alignParentRight="true"
+                  android:layout_centerVertical="true">
+        <TextView android:id="@+id/car_ui_toolbar_title"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:singleLine="true"
+                  android:textAppearance="@style/TextAppearance.CarUi.Widget.Toolbar.Title"/>
+        <TextView android:id="@+id/car_ui_toolbar_subtitle"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:visibility="gone"
+                  android:textAlignment="viewStart"
+                  android:textAppearance="?android:attr/textAppearanceSmall"/>
+    </LinearLayout>
+
+    <com.android.car.ui.toolbar.TabLayout
+        android:id="@+id/car_ui_toolbar_tabs"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="left"
+        android:layout_toRightOf="@+id/car_ui_toolbar_menu_items_container"
+        android:layout_toLeftOf="@+id/car_ui_toolbar_title_container"/>
+
+    <LinearLayout
+        android:id="@+id/car_ui_toolbar_menu_items_container"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        android:divider="@drawable/car_ui_toolbar_menu_item_divider"
+        android:showDividers="beginning|middle|end"
+        android:layout_toRightOf="@+id/car_ui_toolbar_nav_icon_container"
+        android:layout_alignWithParentIfMissing="true"/>
+
+    <FrameLayout
+        android:id="@+id/car_ui_toolbar_search_view_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_toRightOf="@+id/car_ui_toolbar_menu_items_container"
+        android:layout_alignParentRight="true"/>
+
+    <ProgressBar
+        android:id="@+id/car_ui_toolbar_progress_bar"
+        style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentBottom="true"
+        android:indeterminate="true"
+        android:visibility="gone"/>
+
+</RelativeLayout>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_toolbar_two_row.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_toolbar_two_row.xml
new file mode 100644
index 0000000..e5eff70
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-ldrtl/car_ui_toolbar_two_row.xml
@@ -0,0 +1,119 @@
+<?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.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:id="@+id/car_ui_toolbar_background">
+
+    <!-- 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="112dp"
+        android:layout_height="96dp"
+        android:layout_alignParentLeft="true">
+        <!-- missing: icon src -->
+        <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"
+            android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple"
+            android:tint="@color/car_ui_text_color_primary"/>
+        <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="112dp"
+        android:layout_height="96dp"
+        android:layout_alignParentRight="true">
+
+        <ImageView
+            android:id="@+id/car_ui_toolbar_title_logo"
+            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"/>
+    </FrameLayout>
+
+    <LinearLayout
+        android:id="@+id/car_ui_toolbar_title_container"
+        android:layout_height="96dp"
+        android:layout_width="wrap_content"
+        android:orientation="vertical"
+        android:layout_marginRight="112dp"
+        android:layout_alignParentRight="true"
+        android:gravity="center_vertical">
+        <TextView android:id="@+id/car_ui_toolbar_title"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:singleLine="true"
+                  android:textAlignment="viewStart"
+                  android:textAppearance="@style/TextAppearance.CarUi.Widget.Toolbar.Title"/>
+        <TextView android:id="@+id/car_ui_toolbar_subtitle"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:visibility="gone"
+                  android:textAlignment="viewStart"
+                  android:textAppearance="?android:attr/textAppearanceSmall"/>
+    </LinearLayout>
+
+    <com.android.car.ui.toolbar.TabLayout
+        android:id="@+id/car_ui_toolbar_tabs"
+        android:layout_width="match_parent"
+        android:layout_height="96dp"
+        android:gravity="left"
+        android:layout_alignParentTop="true"
+        android:layout_marginTop="96dp"/>
+
+    <LinearLayout
+        android:id="@+id/car_ui_toolbar_menu_items_container"
+        android:layout_width="wrap_content"
+        android:layout_height="96dp"
+        android:orientation="horizontal"
+        android:divider="@drawable/car_ui_toolbar_menu_item_divider"
+        android:showDividers="beginning|middle|end"
+        android:layout_toRightOf="@+id/car_ui_toolbar_nav_icon_container"
+        android:layout_alignWithParentIfMissing="true"/>
+
+    <FrameLayout
+        android:id="@+id/car_ui_toolbar_search_view_container"
+        android:layout_width="match_parent"
+        android:layout_height="96dp"
+        android:layout_toRightOf="@+id/car_ui_toolbar_menu_items_container"
+        android:layout_alignParentRight="true"/>
+
+    <ProgressBar
+        android:id="@+id/car_ui_toolbar_progress_bar"
+        style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentBottom="true"
+        android:indeterminate="true"
+        android:visibility="gone"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-port/car_ui_base_layout_toolbar.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-port/car_ui_base_layout_toolbar.xml
new file mode 100644
index 0000000..d13f2fd
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/layout-port/car_ui_base_layout_toolbar.xml
@@ -0,0 +1,185 @@
+<?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.
+  -->
+<!-- This is for the two-row version of the toolbar -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- When the user finishes searching, we call clearFocus() on the editText in the search bar.
+     clearFocus() will actually send the focus to the first focusable thing in the layout.
+     If that focusable thing is still the search bar it will just reselect it, and the user won't
+     be able to deselect. So make a focusable view here to guarantee that we can clear the focus -->
+    <View
+        android:layout_width="1dp"
+        android:layout_height="1dp"
+        android:focusable="true"
+        android:focusableInTouchMode="true" />
+
+    <FrameLayout
+        android:id="@+id/car_ui_base_layout_content_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentTop="true" />
+
+    <com.android.car.ui.baselayout.ClickBlockingView
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_alignBottom="@+id/car_ui_toolbar_background"
+        android:layout_alignEnd="@+id/car_ui_toolbar_background"
+        android:layout_alignTop="@+id/car_ui_toolbar_background"
+        android:layout_alignStart="@+id/car_ui_toolbar_background"/>
+
+    <RelativeLayout
+        android:id="@+id/car_ui_toolbar_background"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:tag="car_ui_top_inset"
+        android:layout_alignParentTop="true">
+
+        <View
+            android:id="@+id/car_ui_toolbar_row_separator_guideline2"
+            android:layout_width="match_parent"
+            android:layout_height="96dp" />
+
+        <View
+            android:id="@+id/car_ui_toolbar_row_separator_guideline"
+            android:layout_width="match_parent"
+            android:layout_below="@+id/car_ui_toolbar_row_separator_guideline2"
+            android:layout_height="0dp" />
+
+        <FrameLayout
+            android:id="@+id/car_ui_toolbar_nav_icon_container"
+            android:layout_width="112dp"
+            android:layout_height="0dp"
+            android:layout_above="@+id/car_ui_toolbar_row_separator_guideline"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentTop="true">
+
+            <ImageView
+                android:id="@+id/car_ui_toolbar_nav_icon"
+                android:tint="@color/car_ui_toolbar_nav_icon_color"
+                android:src="@drawable/car_ui_icon_arrow_back"
+                android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple"
+                android:layout_width="44dp"
+                android:layout_height="44dp"
+                android:layout_gravity="center"
+                android:scaleType="fitXY" />
+
+            <ImageView
+                android:id="@+id/car_ui_toolbar_logo"
+                android:layout_width="44dp"
+                android:layout_height="44dp"
+                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"
+            android:layout_above="@+id/car_ui_toolbar_row_separator_guideline"
+            android:layout_toEndOf="@+id/car_ui_toolbar_nav_icon_container"
+            android:layout_alignParentTop="true">
+
+            <ImageView
+                android:id="@+id/car_ui_toolbar_title_logo"
+                android:layout_width="44dp"
+                android:layout_height="44dp"
+                android:layout_gravity="center"
+                android:scaleType="fitXY" />
+        </FrameLayout>
+
+        <FrameLayout
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginStart="16dp"
+            android:layout_above="@+id/car_ui_toolbar_row_separator_guideline"
+            android:layout_toStartOf="@+id/car_ui_toolbar_menu_items_container"
+            android:layout_toEndOf="@+id/car_ui_toolbar_title_logo_container"
+            android:layout_alignParentTop="true" >
+            <LinearLayout
+                android:layout_gravity="center_vertical"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:id="@+id/car_ui_toolbar_title_container"
+                android:orientation="vertical" >
+                <TextView android:id="@+id/car_ui_toolbar_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:textSize="32sp"
+                    android:letterSpacing="0.0"
+                    android:textAlignment="viewStart" />
+                <TextView android:id="@+id/car_ui_toolbar_subtitle"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:visibility="gone"
+                    android:textAppearance="?android:attr/textAppearanceSmall"
+                    android:textAlignment="viewStart" />
+            </LinearLayout>
+        </FrameLayout>
+
+        <FrameLayout
+            android:id="@+id/car_ui_toolbar_search_view_container"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_above="@+id/car_ui_toolbar_row_separator_guideline"
+            android:layout_toStartOf="@+id/car_ui_toolbar_menu_items_container"
+            android:layout_toEndOf="@+id/car_ui_toolbar_nav_icon_container"
+            android:layout_alignWithParentIfMissing="true"
+            android:layout_alignParentTop="true" />
+
+        <LinearLayout
+            android:id="@+id/car_ui_toolbar_menu_items_container"
+            android:divider="@drawable/car_ui_toolbar_menu_item_divider"
+            android:showDividers="beginning|middle|end"
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            android:visibility="gone"
+            android:orientation="horizontal"
+            android:layout_above="@+id/car_ui_toolbar_row_separator_guideline"
+            android:layout_alignParentEnd="true"
+            android:layout_alignParentTop="true" />
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/car_ui_toolbar_row_separator_guideline">
+
+            <com.android.car.ui.toolbar.TabLayout
+                android:id="@+id/car_ui_toolbar_tabs"
+                android:layout_width="match_parent"
+                android:layout_height="96dp" />
+
+            <ProgressBar
+                android:id="@+id/car_ui_toolbar_progress_bar"
+                style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:indeterminate="true"
+                android:visibility="gone"
+                android:layout_alignBottom="@id/car_ui_toolbar_tabs"
+                android:layout_alignParentEnd="true"
+                android:layout_alignParentStart="true" />
+
+        </RelativeLayout>
+
+    </RelativeLayout>
+
+</RelativeLayout>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/values/styles.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/values/styles.xml
new file mode 100644
index 0000000..6847242
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/values/styles.xml
@@ -0,0 +1,13 @@
+<resources>
+
+    <style name="TextAppearance.CarUi.Widget" parent="android:TextAppearance.DeviceDefault.Widget">
+        <item name="android:textAlignment">viewStart</item>
+    </style>
+
+    <style name="TextAppearance.CarUi.Widget.Toolbar"/>
+
+    <style name="TextAppearance.CarUi.Widget.Toolbar.Title">
+        <item name="android:singleLine">true</item>
+        <item name="android:textSize">32sp</item>
+    </style>
+</resources>
diff --git a/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/values/values.xml b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/values/values.xml
new file mode 100644
index 0000000..2b51240
--- /dev/null
+++ b/car-ui-lib/referencedesign/overlay/frameworks/base/core/res/res/values/values.xml
@@ -0,0 +1,19 @@
+<resources>
+    <dimen name="car_ui_toolbar_logo_size">44dp</dimen>
+    <dimen name="car_ui_toolbar_nav_icon_size">44dp</dimen>
+
+    <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>
+
+    <color name="car_ui_toolbar_nav_icon_color">@color/car_ui_text_color_primary</color>
+</resources>
diff --git a/car-ui-lib/referencedesign/product.mk b/car-ui-lib/referencedesign/product.mk
new file mode 100644
index 0000000..f77e94d
--- /dev/null
+++ b/car-ui-lib/referencedesign/product.mk
@@ -0,0 +1,39 @@
+# Inherit from this product to include the "Reference Design" RROs for CarUi
+
+# Include built-time overlays
+PRODUCT_PACKAGE_OVERLAYS += \
+    packages/apps/Car/libs/car-ui-lib/referencedesign/overlay/
+PRODUCT_ENFORCE_RRO_EXCLUDED_OVERLAYS += \
+    packages/apps/Car/libs/car-ui-lib/referencedesign/overlay/
+
+# Include generated RROs
+PRODUCT_PACKAGES += \
+    googlecarui-com-android-car-ui-paintbooth \
+    googlecarui-com-android-car-rotaryplayground \
+    googlecarui-com-android-car-themeplayground \
+    googlecarui-com-android-car-carlauncher \
+    googlecarui-com-android-car-home \
+    googlecarui-com-android-car-media \
+    googlecarui-com-android-car-radio \
+    googlecarui-com-android-car-calendar \
+    googlecarui-com-android-car-companiondevicesupport \
+    googlecarui-com-android-car-systemupdater \
+    googlecarui-com-android-car-dialer \
+    googlecarui-com-android-car-linkviewer \
+    googlecarui-com-android-car-settings \
+    googlecarui-com-android-car-voicecontrol \
+    googlecarui-com-android-car-faceenroll \
+    googlecarui-com-android-permissioncontroller \
+    googlecarui-com-android-settings-intelligence \
+    googlecarui-com-google-android-apps-automotive-inputmethod \
+    googlecarui-com-google-android-apps-automotive-inputmethod-dev \
+    googlecarui-com-google-android-embedded-projection \
+    googlecarui-com-google-android-gms \
+    googlecarui-com-google-android-packageinstaller \
+    googlecarui-com-google-android-carassistant \
+    googlecarui-com-google-android-tts \
+    googlecarui-com-android-vending \
+
+# This system property is used to enable the RROs on startup via
+# the requiredSystemPropertyName/Value attributes in the manifest
+PRODUCT_PRODUCT_PROPERTIES += ro.build.car_ui_rros_enabled=true
diff --git a/car-ui-lib/referencedesign/res/values-ldrtl/values.xml b/car-ui-lib/referencedesign/res/values-ldrtl/values.xml
new file mode 100644
index 0000000..c83009d
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/values-ldrtl/values.xml
@@ -0,0 +1,30 @@
+<resources>
+    <item type="layout" name="car_ui_base_layout_toolbar">@*android:layout/car_ui_base_layout_toolbar</item>
+    <item type="layout" name="car_ui_toolbar">@*android:layout/car_ui_toolbar</item>
+    <item type="layout" name="car_ui_toolbar_two_row">@*android:layout/car_ui_toolbar_two_row</item>
+    <item type="bool" name="car_ui_toolbar_nav_icon_reserve_space">false</item>
+    <item type="bool" name="car_ui_toolbar_logo_fills_nav_icon_space">false</item>
+
+    <item type="id" name="car_ui_toolbar_background">@*android:id/car_ui_toolbar_background</item>
+    <item type="id" name="car_ui_toolbar_nav_icon_container">@*android:id/car_ui_toolbar_nav_icon_container</item>
+    <item type="id" name="car_ui_toolbar_nav_icon">@*android:id/car_ui_toolbar_nav_icon</item>
+    <item type="id" name="car_ui_toolbar_logo">@*android:id/car_ui_toolbar_logo</item>
+    <item type="id" name="car_ui_toolbar_title_logo">@*android:id/car_ui_toolbar_title_logo</item>
+    <item type="id" name="car_ui_toolbar_title_container">@*android:id/car_ui_toolbar_title_container</item>
+    <item type="id" name="car_ui_toolbar_menu_items_container">@*android:id/car_ui_toolbar_menu_items_container</item>
+    <item type="id" name="car_ui_toolbar_title">@*android:id/car_ui_toolbar_title</item>
+    <item type="id" name="car_ui_toolbar_subtitle">@*android:id/car_ui_toolbar_subtitle</item>
+    <item type="id" name="car_ui_toolbar_tabs">@*android:id/car_ui_toolbar_tabs</item>
+    <item type="id" name="car_ui_toolbar_search_view_container">@*android:id/car_ui_toolbar_search_view_container</item>
+    <item type="id" name="car_ui_toolbar_progress_bar">@*android:id/car_ui_toolbar_progress_bar</item>
+    <item type="id" name="car_ui_toolbar_title_logo_container">@*android:id/car_ui_toolbar_title_logo_container</item>
+    <item type="id" name="car_ui_base_layout_content_container">@*android:id/car_ui_base_layout_content_container</item>
+
+     <item type="layout" name="car_ui_recycler_view">@*android:layout/car_ui_recycler_view</item>
+     <item type="id" name="car_ui_recycler_view">@*android:id/car_ui_recycler_view</item>
+     <item type="id" name="car_ui_scroll_bar">@*android:id/car_ui_scroll_bar</item>
+     <item type="id" name="car_ui_scrollbar_page_up">@*android:id/car_ui_scrollbar_page_up</item>
+     <item type="id" name="car_ui_scrollbar_thumb">@*android:id/car_ui_scrollbar_thumb</item>
+     <item type="id" name="car_ui_scrollbar_track">@*android:id/car_ui_scrollbar_track</item>
+     <item type="id" name="car_ui_scrollbar_page_down">@*android:id/car_ui_scrollbar_page_down</item>
+</resources>
diff --git a/car-ui-lib/referencedesign/res/values-port/bools.xml b/car-ui-lib/referencedesign/res/values-port/bools.xml
new file mode 100644
index 0000000..ac4c389
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/values-port/bools.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+
+<resources>
+    <!-- Toolbar -->
+
+    <!-- Whether tabs should use flex layout or not -->
+    <bool name="car_ui_toolbar_tab_flexible_layout">true</bool>
+    <!-- Whether tabs should be displayed on a second row, or they should be placed in the first
+         row, replacing the title -->
+    <bool name="car_ui_toolbar_tabs_on_second_row">true</bool>
+</resources>
diff --git a/car-ui-lib/referencedesign/res/values-port/values.xml b/car-ui-lib/referencedesign/res/values-port/values.xml
new file mode 100644
index 0000000..c069d81
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/values-port/values.xml
@@ -0,0 +1,18 @@
+<resources>
+     <item type="layout" name="car_ui_base_layout_toolbar">@*android:layout/car_ui_base_layout_toolbar</item>
+     <item type="id" name="car_ui_base_layout_content_container">@*android:id/car_ui_base_layout_content_container</item>
+     <item type="id" name="car_ui_toolbar_background">@*android:id/car_ui_toolbar_background</item>
+     <item type="id" name="car_ui_toolbar_row_separator_guideline">@*android:id/car_ui_toolbar_row_separator_guideline</item>
+     <item type="id" name="car_ui_toolbar_nav_icon_container">@*android:id/car_ui_toolbar_nav_icon_container</item>
+     <item type="id" name="car_ui_toolbar_nav_icon">@*android:id/car_ui_toolbar_nav_icon</item>
+     <item type="id" name="car_ui_toolbar_logo">@*android:id/car_ui_toolbar_logo</item>
+     <item type="id" name="car_ui_toolbar_title_logo_container">@*android:id/car_ui_toolbar_title_logo_container</item>
+     <item type="id" name="car_ui_toolbar_title_logo">@*android:id/car_ui_toolbar_title_logo</item>
+     <item type="id" name="car_ui_toolbar_title_container">@*android:id/car_ui_toolbar_title_container</item>
+     <item type="id" name="car_ui_toolbar_title">@*android:id/car_ui_toolbar_title</item>
+     <item type="id" name="car_ui_toolbar_subtitle">@*android:id/car_ui_toolbar_subtitle</item>
+     <item type="id" name="car_ui_toolbar_search_view_container">@*android:id/car_ui_toolbar_search_view_container</item>
+     <item type="id" name="car_ui_toolbar_menu_items_container">@*android:id/car_ui_toolbar_menu_items_container</item>
+     <item type="id" name="car_ui_toolbar_tabs">@*android:id/car_ui_toolbar_tabs</item>
+     <item type="id" name="car_ui_toolbar_progress_bar">@*android:id/car_ui_toolbar_progress_bar</item>
+</resources>
diff --git a/car-ui-lib/res-overlayable/values/overlayable.xml b/car-ui-lib/res-overlayable/values/overlayable.xml
new file mode 100644
index 0000000..821cc50
--- /dev/null
+++ b/car-ui-lib/res-overlayable/values/overlayable.xml
@@ -0,0 +1,465 @@
+<?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.-->
+<!--THIS FILE IS AUTO GENERATED, DO NOT EDIT MANUALLY.-->
+<resources>
+  <overlayable name="car-ui-lib">
+    <policy type="public">
+      <item type="attr" name="CarUiToolbarStyle"/>
+      <item type="attr" name="barrierDirection"/>
+      <item type="attr" name="carUiPreferenceStyle"/>
+      <item type="attr" name="carUiRecyclerViewStyle"/>
+      <item type="attr" name="chainUseRtl"/>
+      <item type="attr" name="constraintSet"/>
+      <item type="attr" name="constraint_referenced_ids"/>
+      <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_constraintDimensionRatio"/>
+      <item type="attr" name="layout_constraintEnd_toEndOf"/>
+      <item type="attr" name="layout_constraintEnd_toStartOf"/>
+      <item type="attr" name="layout_constraintGuide_begin"/>
+      <item type="attr" name="layout_constraintGuide_end"/>
+      <item type="attr" name="layout_constraintGuide_percent"/>
+      <item type="attr" name="layout_constraintHeight_default"/>
+      <item type="attr" name="layout_constraintHeight_max"/>
+      <item type="attr" name="layout_constraintHeight_min"/>
+      <item type="attr" name="layout_constraintHeight_percent"/>
+      <item type="attr" name="layout_constraintHorizontal_bias"/>
+      <item type="attr" name="layout_constraintHorizontal_chainStyle"/>
+      <item type="attr" name="layout_constraintHorizontal_weight"/>
+      <item type="attr" name="layout_constraintLeft_creator"/>
+      <item type="attr" name="layout_constraintLeft_toLeftOf"/>
+      <item type="attr" name="layout_constraintLeft_toRightOf"/>
+      <item type="attr" name="layout_constraintRight_creator"/>
+      <item type="attr" name="layout_constraintRight_toLeftOf"/>
+      <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_constraintTop_creator"/>
+      <item type="attr" name="layout_constraintTop_toBottomOf"/>
+      <item type="attr" name="layout_constraintTop_toTopOf"/>
+      <item type="attr" name="layout_constraintVertical_bias"/>
+      <item type="attr" name="layout_constraintVertical_chainStyle"/>
+      <item type="attr" name="layout_constraintVertical_weight"/>
+      <item type="attr" name="layout_constraintWidth_default"/>
+      <item type="attr" name="layout_constraintWidth_max"/>
+      <item type="attr" name="layout_constraintWidth_min"/>
+      <item type="attr" name="layout_constraintWidth_percent"/>
+      <item type="attr" name="layout_editor_absoluteX"/>
+      <item type="attr" name="layout_editor_absoluteY"/>
+      <item type="attr" name="layout_goneMarginBottom"/>
+      <item type="attr" name="layout_goneMarginEnd"/>
+      <item type="attr" name="layout_goneMarginLeft"/>
+      <item type="attr" name="layout_goneMarginRight"/>
+      <item type="attr" name="layout_goneMarginStart"/>
+      <item type="attr" name="layout_goneMarginTop"/>
+      <item type="attr" name="layout_optimizationLevel"/>
+      <item type="attr" name="state_ux_restricted"/>
+      <item type="attr" name="title"/>
+      <item type="bool" name="car_ui_list_item_single_line_title"/>
+      <item type="bool" name="car_ui_preference_list_show_full_screen"/>
+      <item type="bool" name="car_ui_preference_show_chevron"/>
+      <item type="bool" name="car_ui_scrollbar_enable"/>
+      <item type="bool" name="car_ui_toolbar_logo_fills_nav_icon_space"/>
+      <item type="bool" name="car_ui_toolbar_nav_icon_reserve_space"/>
+      <item type="bool" name="car_ui_toolbar_show_logo"/>
+      <item type="bool" name="car_ui_toolbar_tab_flexible_layout"/>
+      <item type="bool" name="car_ui_toolbar_tabs_on_second_row"/>
+      <item type="color" name="car_ui_activity_background_color"/>
+      <item type="color" name="car_ui_color_accent"/>
+      <item type="color" name="car_ui_list_item_body_text_color"/>
+      <item type="color" name="car_ui_list_item_divider"/>
+      <item type="color" name="car_ui_list_item_header_text_color"/>
+      <item type="color" name="car_ui_list_item_title_text_color"/>
+      <item type="color" name="car_ui_preference_category_title_text_color"/>
+      <item type="color" name="car_ui_preference_edit_text_dialog_message_text_color"/>
+      <item type="color" name="car_ui_preference_icon_color"/>
+      <item type="color" name="car_ui_preference_summary_text_color"/>
+      <item type="color" name="car_ui_preference_switch_track_text_color"/>
+      <item type="color" name="car_ui_preference_title_text_color"/>
+      <item type="color" name="car_ui_recyclerview_divider_color"/>
+      <item type="color" name="car_ui_ripple_color"/>
+      <item type="color" name="car_ui_scrollbar_thumb"/>
+      <item type="color" name="car_ui_text_color_hint"/>
+      <item type="color" name="car_ui_text_color_primary"/>
+      <item type="color" name="car_ui_text_color_secondary"/>
+      <item type="color" name="car_ui_toolbar_menu_item_icon_background_color"/>
+      <item type="color" name="car_ui_toolbar_menu_item_icon_color"/>
+      <item type="color" name="car_ui_toolbar_nav_icon_color"/>
+      <item type="color" name="car_ui_toolbar_search_hint_text_color"/>
+      <item type="color" name="car_ui_toolbar_tab_item_selector"/>
+      <item type="color" name="car_ui_toolbar_tab_selected_color"/>
+      <item type="color" name="car_ui_toolbar_tab_unselected_color"/>
+      <item type="dimen" name="car_ui_body1_size"/>
+      <item type="dimen" name="car_ui_body2_size"/>
+      <item type="dimen" name="car_ui_body3_size"/>
+      <item type="dimen" name="car_ui_button_disabled_alpha"/>
+      <item type="dimen" name="car_ui_dialog_edittext_height"/>
+      <item type="dimen" name="car_ui_dialog_edittext_margin_bottom"/>
+      <item type="dimen" name="car_ui_dialog_edittext_margin_end"/>
+      <item type="dimen" name="car_ui_dialog_edittext_margin_start"/>
+      <item type="dimen" name="car_ui_dialog_edittext_margin_top"/>
+      <item type="dimen" name="car_ui_dialog_icon_size"/>
+      <item type="dimen" name="car_ui_dialog_title_margin"/>
+      <item type="dimen" name="car_ui_header_list_item_text_start_margin"/>
+      <item type="dimen" name="car_ui_letter_spacing_body1"/>
+      <item type="dimen" name="car_ui_letter_spacing_body3"/>
+      <item type="dimen" name="car_ui_list_item_action_divider_height"/>
+      <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_body_text_size"/>
+      <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"/>
+      <item type="dimen" name="car_ui_list_item_header_height"/>
+      <item type="dimen" name="car_ui_list_item_header_start_inset"/>
+      <item type="dimen" name="car_ui_list_item_header_text_size"/>
+      <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"/>
+      <item type="dimen" name="car_ui_list_item_text_start_margin"/>
+      <item type="dimen" name="car_ui_list_item_title_text_size"/>
+      <item type="dimen" name="car_ui_margin"/>
+      <item type="dimen" name="car_ui_padding_0"/>
+      <item type="dimen" name="car_ui_padding_1"/>
+      <item type="dimen" name="car_ui_padding_2"/>
+      <item type="dimen" name="car_ui_padding_3"/>
+      <item type="dimen" name="car_ui_padding_4"/>
+      <item type="dimen" name="car_ui_padding_5"/>
+      <item type="dimen" name="car_ui_padding_6"/>
+      <item type="dimen" name="car_ui_preference_category_icon_margin_end"/>
+      <item type="dimen" name="car_ui_preference_category_icon_size"/>
+      <item type="dimen" name="car_ui_preference_category_min_height"/>
+      <item type="dimen" name="car_ui_preference_category_text_size"/>
+      <item type="dimen" name="car_ui_preference_content_margin_bottom"/>
+      <item type="dimen" name="car_ui_preference_content_margin_top"/>
+      <item type="dimen" name="car_ui_preference_dropdown_padding_start"/>
+      <item type="dimen" name="car_ui_preference_edit_text_dialog_margin_bottom"/>
+      <item type="dimen" name="car_ui_preference_edit_text_dialog_margin_top"/>
+      <item type="dimen" name="car_ui_preference_edit_text_dialog_message_margin_bottom"/>
+      <item type="dimen" name="car_ui_preference_edit_text_dialog_message_margin_end"/>
+      <item type="dimen" name="car_ui_preference_edit_text_dialog_message_margin_start"/>
+      <item type="dimen" name="car_ui_preference_edit_text_dialog_message_text_size"/>
+      <item type="dimen" name="car_ui_preference_edit_text_dialog_text_margin_end"/>
+      <item type="dimen" name="car_ui_preference_edit_text_dialog_text_margin_start"/>
+      <item type="dimen" name="car_ui_preference_icon_margin_end"/>
+      <item type="dimen" name="car_ui_preference_icon_size"/>
+      <item type="dimen" name="car_ui_preference_summary_text_size"/>
+      <item type="dimen" name="car_ui_preference_switch_height"/>
+      <item type="dimen" name="car_ui_preference_switch_text_size"/>
+      <item type="dimen" name="car_ui_preference_switch_width"/>
+      <item type="dimen" name="car_ui_preference_switch_width_half"/>
+      <item type="dimen" name="car_ui_preference_title_text_size"/>
+      <item type="dimen" name="car_ui_primary_icon_size"/>
+      <item type="dimen" name="car_ui_recyclerview_divider_bottom_margin"/>
+      <item type="dimen" name="car_ui_recyclerview_divider_end_margin"/>
+      <item type="dimen" name="car_ui_recyclerview_divider_height"/>
+      <item type="dimen" name="car_ui_recyclerview_divider_start_margin"/>
+      <item type="dimen" name="car_ui_recyclerview_divider_top_margin"/>
+      <item type="dimen" name="car_ui_scrollbar_button_size"/>
+      <item type="dimen" name="car_ui_scrollbar_container_width"/>
+      <item type="dimen" name="car_ui_scrollbar_decelerate_interpolator_factor"/>
+      <item type="dimen" name="car_ui_scrollbar_deceleration_times_divisor"/>
+      <item type="dimen" name="car_ui_scrollbar_margin"/>
+      <item type="dimen" name="car_ui_scrollbar_milliseconds_per_inch"/>
+      <item type="dimen" name="car_ui_scrollbar_min_thumb_height"/>
+      <item type="dimen" name="car_ui_scrollbar_padding_bottom"/>
+      <item type="dimen" name="car_ui_scrollbar_padding_top"/>
+      <item type="dimen" name="car_ui_scrollbar_separator_margin"/>
+      <item type="dimen" name="car_ui_scrollbar_thumb_radius"/>
+      <item type="dimen" name="car_ui_scrollbar_thumb_width"/>
+      <item type="dimen" name="car_ui_sub1_size"/>
+      <item type="dimen" name="car_ui_sub2_size"/>
+      <item type="dimen" name="car_ui_sub3_size"/>
+      <item type="dimen" name="car_ui_toolbar_bottom_inset"/>
+      <item type="dimen" name="car_ui_toolbar_bottom_view_height"/>
+      <item type="dimen" name="car_ui_toolbar_end_inset"/>
+      <item type="dimen" name="car_ui_toolbar_first_row_height"/>
+      <item type="dimen" name="car_ui_toolbar_logo_size"/>
+      <item type="dimen" name="car_ui_toolbar_margin"/>
+      <item type="dimen" name="car_ui_toolbar_menu_item_icon_background_size"/>
+      <item type="dimen" name="car_ui_toolbar_menu_item_icon_ripple_radius"/>
+      <item type="dimen" name="car_ui_toolbar_menu_item_icon_size"/>
+      <item type="dimen" name="car_ui_toolbar_menu_item_margin"/>
+      <item type="dimen" name="car_ui_toolbar_nav_icon_size"/>
+      <item type="dimen" name="car_ui_toolbar_row_height"/>
+      <item type="dimen" name="car_ui_toolbar_search_close_icon_container_width"/>
+      <item type="dimen" name="car_ui_toolbar_search_close_icon_size"/>
+      <item type="dimen" name="car_ui_toolbar_search_height"/>
+      <item type="dimen" name="car_ui_toolbar_search_search_icon_container_width"/>
+      <item type="dimen" name="car_ui_toolbar_search_search_icon_size"/>
+      <item type="dimen" name="car_ui_toolbar_second_row_height"/>
+      <item type="dimen" name="car_ui_toolbar_separator_height"/>
+      <item type="dimen" name="car_ui_toolbar_start_inset"/>
+      <item type="dimen" name="car_ui_toolbar_tab_icon_height"/>
+      <item type="dimen" name="car_ui_toolbar_tab_icon_width"/>
+      <item type="dimen" name="car_ui_toolbar_tab_padding_x"/>
+      <item type="dimen" name="car_ui_toolbar_tab_text_width"/>
+      <item type="dimen" name="car_ui_toolbar_title_logo_padding"/>
+      <item type="dimen" name="car_ui_toolbar_title_margin_start"/>
+      <item type="dimen" name="car_ui_toolbar_title_no_logo_margin_start"/>
+      <item type="dimen" name="car_ui_toolbar_top_inset"/>
+      <item type="dimen" name="car_ui_touch_target_height"/>
+      <item type="dimen" name="car_ui_touch_target_width"/>
+      <item type="dimen" name="wrap_content"/>
+      <item type="drawable" name="car_ui_activity_background"/>
+      <item type="drawable" name="car_ui_divider"/>
+      <item type="drawable" name="car_ui_icon_arrow_back"/>
+      <item type="drawable" name="car_ui_icon_close"/>
+      <item type="drawable" name="car_ui_icon_down"/>
+      <item type="drawable" name="car_ui_icon_lock"/>
+      <item type="drawable" name="car_ui_icon_overflow_menu"/>
+      <item type="drawable" name="car_ui_icon_search"/>
+      <item type="drawable" name="car_ui_icon_search_nav_icon"/>
+      <item type="drawable" name="car_ui_icon_settings"/>
+      <item type="drawable" name="car_ui_list_header_background"/>
+      <item type="drawable" name="car_ui_list_item_avatar_icon_outline"/>
+      <item type="drawable" name="car_ui_list_item_background"/>
+      <item type="drawable" name="car_ui_list_item_divider"/>
+      <item type="drawable" name="car_ui_list_limiting_message_background"/>
+      <item type="drawable" name="car_ui_preference_icon_chevron"/>
+      <item type="drawable" name="car_ui_preference_icon_chevron_disabled"/>
+      <item type="drawable" name="car_ui_preference_icon_chevron_enabled"/>
+      <item type="drawable" name="car_ui_recyclerview_button_ripple_background"/>
+      <item type="drawable" name="car_ui_recyclerview_divider"/>
+      <item type="drawable" name="car_ui_recyclerview_ic_down"/>
+      <item type="drawable" name="car_ui_recyclerview_ic_up"/>
+      <item type="drawable" name="car_ui_recyclerview_scrollbar_thumb"/>
+      <item type="drawable" name="car_ui_toolbar_background"/>
+      <item type="drawable" name="car_ui_toolbar_menu_item_divider"/>
+      <item type="drawable" name="car_ui_toolbar_menu_item_icon_background"/>
+      <item type="drawable" name="car_ui_toolbar_menu_item_icon_ripple"/>
+      <item type="drawable" name="car_ui_toolbar_search_close_icon"/>
+      <item type="drawable" name="car_ui_toolbar_search_search_icon"/>
+      <item type="id" name="action_container"/>
+      <item type="id" name="action_container_touch_interceptor"/>
+      <item type="id" name="action_divider"/>
+      <item type="id" name="avatar_icon"/>
+      <item type="id" name="body"/>
+      <item type="id" name="car_ui_alert_icon"/>
+      <item type="id" name="car_ui_alert_subtitle"/>
+      <item type="id" name="car_ui_alert_title"/>
+      <item type="id" name="car_ui_base_layout_content_container"/>
+      <item type="id" name="car_ui_check_box_end_guideline"/>
+      <item type="id" name="car_ui_check_box_start_guideline"/>
+      <item type="id" name="car_ui_list_item_end_guideline"/>
+      <item type="id" name="car_ui_list_item_start_guideline"/>
+      <item type="id" name="car_ui_list_limiting_message"/>
+      <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"/>
+      <item type="id" name="car_ui_scrollbar_page_up"/>
+      <item type="id" name="car_ui_scrollbar_thumb"/>
+      <item type="id" name="car_ui_scrollbar_track"/>
+      <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"/>
+      <item type="id" name="car_ui_toolbar_end_guideline"/>
+      <item type="id" name="car_ui_toolbar_logo"/>
+      <item type="id" name="car_ui_toolbar_menu_item_icon"/>
+      <item type="id" name="car_ui_toolbar_menu_item_icon_container"/>
+      <item type="id" name="car_ui_toolbar_menu_item_switch"/>
+      <item type="id" name="car_ui_toolbar_menu_item_text"/>
+      <item type="id" name="car_ui_toolbar_menu_item_text_with_icon"/>
+      <item type="id" name="car_ui_toolbar_menu_items_container"/>
+      <item type="id" name="car_ui_toolbar_nav_icon"/>
+      <item type="id" name="car_ui_toolbar_nav_icon_container"/>
+      <item type="id" name="car_ui_toolbar_progress_bar"/>
+      <item type="id" name="car_ui_toolbar_row_separator"/>
+      <item type="id" name="car_ui_toolbar_row_separator_guideline"/>
+      <item type="id" name="car_ui_toolbar_search_bar"/>
+      <item type="id" name="car_ui_toolbar_search_close"/>
+      <item type="id" name="car_ui_toolbar_search_icon"/>
+      <item type="id" name="car_ui_toolbar_search_view_container"/>
+      <item type="id" name="car_ui_toolbar_start_guideline"/>
+      <item type="id" name="car_ui_toolbar_subtitle"/>
+      <item type="id" name="car_ui_toolbar_tab_item_icon"/>
+      <item type="id" name="car_ui_toolbar_tab_item_text"/>
+      <item type="id" name="car_ui_toolbar_tabs"/>
+      <item type="id" name="car_ui_toolbar_title"/>
+      <item type="id" name="car_ui_toolbar_title_container"/>
+      <item type="id" name="car_ui_toolbar_title_logo"/>
+      <item type="id" name="car_ui_toolbar_title_logo_container"/>
+      <item type="id" name="car_ui_toolbar_top_guideline"/>
+      <item type="id" name="check_box_container"/>
+      <item type="id" name="checkbox"/>
+      <item type="id" name="checkbox_widget"/>
+      <item type="id" name="container"/>
+      <item type="id" name="content_icon"/>
+      <item type="id" name="icon"/>
+      <item type="id" name="icon_container"/>
+      <item type="id" name="list"/>
+      <item type="id" name="nested_recycler_view_layout"/>
+      <item type="id" name="radio_button_widget"/>
+      <item type="id" name="recycler_view"/>
+      <item type="id" name="reduced_touch_interceptor"/>
+      <item type="id" name="search"/>
+      <item type="id" name="seekbar"/>
+      <item type="id" name="seekbar_value"/>
+      <item type="id" name="spinner"/>
+      <item type="id" name="supplemental_icon"/>
+      <item type="id" name="switch_widget"/>
+      <item type="id" name="text"/>
+      <item type="id" name="textbox"/>
+      <item type="id" name="title"/>
+      <item type="id" name="title_template"/>
+      <item type="id" name="toolbar"/>
+      <item type="id" name="touch_interceptor"/>
+      <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"/>
+      <item type="layout" name="car_ui_alert_dialog_edit_text"/>
+      <item type="layout" name="car_ui_alert_dialog_list"/>
+      <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_check_box_list_item"/>
+      <item type="layout" name="car_ui_header_list_item"/>
+      <item type="layout" name="car_ui_list_item"/>
+      <item type="layout" name="car_ui_list_limiting_message"/>
+      <item type="layout" name="car_ui_list_preference"/>
+      <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_widget_checkbox"/>
+      <item type="layout" name="car_ui_preference_widget_seekbar"/>
+      <item type="layout" name="car_ui_preference_widget_switch"/>
+      <item type="layout" name="car_ui_recycler_view"/>
+      <item type="layout" name="car_ui_recycler_view_item"/>
+      <item type="layout" name="car_ui_recyclerview_scrollbar"/>
+      <item type="layout" name="car_ui_toolbar"/>
+      <item type="layout" name="car_ui_toolbar_menu_item"/>
+      <item type="layout" name="car_ui_toolbar_search_view"/>
+      <item type="layout" name="car_ui_toolbar_tab_item"/>
+      <item type="layout" name="car_ui_toolbar_tab_item_flexible"/>
+      <item type="layout" name="car_ui_toolbar_tab_item_layout"/>
+      <item type="layout" name="car_ui_toolbar_tab_item_layout_flexible"/>
+      <item type="layout" name="car_ui_toolbar_two_row"/>
+      <item type="string" name="car_ui_alert_dialog_default_button"/>
+      <item type="string" name="car_ui_dialog_preference_negative"/>
+      <item type="string" name="car_ui_dialog_preference_positive"/>
+      <item type="string" name="car_ui_ellipsis"/>
+      <item type="string" name="car_ui_list_item_header_font_family"/>
+      <item type="string" name="car_ui_preference_category_title_font_family"/>
+      <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"/>
+      <item type="string" name="car_ui_scrollbar_component"/>
+      <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_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"/>
+      <item type="style" name="Preference.CarUi.DialogPreference"/>
+      <item type="style" name="Preference.CarUi.DialogPreference.EditTextPreference"/>
+      <item type="style" name="Preference.CarUi.DropDown"/>
+      <item type="style" name="Preference.CarUi.Icon"/>
+      <item type="style" name="Preference.CarUi.Information"/>
+      <item type="style" name="Preference.CarUi.Preference"/>
+      <item type="style" name="Preference.CarUi.PreferenceScreen"/>
+      <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"/>
+      <item type="style" name="TextAppearance.CarUi.AlertDialog.Title"/>
+      <item type="style" name="TextAppearance.CarUi.Body1"/>
+      <item type="style" name="TextAppearance.CarUi.Body2"/>
+      <item type="style" name="TextAppearance.CarUi.Body3"/>
+      <item type="style" name="TextAppearance.CarUi.ListItem"/>
+      <item type="style" name="TextAppearance.CarUi.ListItem.Body"/>
+      <item type="style" name="TextAppearance.CarUi.ListItem.Header"/>
+      <item type="style" name="TextAppearance.CarUi.PreferenceCategoryTitle"/>
+      <item type="style" name="TextAppearance.CarUi.PreferenceEditTextDialogMessage"/>
+      <item type="style" name="TextAppearance.CarUi.PreferenceSummary"/>
+      <item type="style" name="TextAppearance.CarUi.PreferenceTitle"/>
+      <item type="style" name="TextAppearance.CarUi.Sub1"/>
+      <item type="style" name="TextAppearance.CarUi.Sub2"/>
+      <item type="style" name="TextAppearance.CarUi.Sub3"/>
+      <item type="style" name="TextAppearance.CarUi.Widget"/>
+      <item type="style" name="TextAppearance.CarUi.Widget.Toolbar"/>
+      <item type="style" name="TextAppearance.CarUi.Widget.Toolbar.Tab"/>
+      <item type="style" name="TextAppearance.CarUi.Widget.Toolbar.Tab.Selected"/>
+      <item type="style" name="TextAppearance.CarUi.Widget.Toolbar.Title"/>
+      <item type="style" name="Theme.CarUi"/>
+      <item type="style" name="Theme.CarUi.NoToolbar"/>
+      <item type="style" name="Theme.CarUi.WithToolbar"/>
+      <item type="style" name="Widget.CarUi"/>
+      <item type="style" name="Widget.CarUi.AlertDialog"/>
+      <item type="style" name="Widget.CarUi.AlertDialog.HeaderContainer"/>
+      <item type="style" name="Widget.CarUi.AlertDialog.Icon"/>
+      <item type="style" name="Widget.CarUi.AlertDialog.TitleContainer"/>
+      <item type="style" name="Widget.CarUi.Button"/>
+      <item type="style" name="Widget.CarUi.Button.Borderless.Colored"/>
+      <item type="style" name="Widget.CarUi.CarUiRecyclerView"/>
+      <item type="style" name="Widget.CarUi.SeekbarPreference"/>
+      <item type="style" name="Widget.CarUi.SeekbarPreference.Seekbar"/>
+      <item type="style" name="Widget.CarUi.Toolbar"/>
+      <item type="style" name="Widget.CarUi.Toolbar.BottomView"/>
+      <item type="style" name="Widget.CarUi.Toolbar.Container"/>
+      <item type="style" name="Widget.CarUi.Toolbar.Logo"/>
+      <item type="style" name="Widget.CarUi.Toolbar.LogoContainer"/>
+      <item type="style" name="Widget.CarUi.Toolbar.MenuItem"/>
+      <item type="style" name="Widget.CarUi.Toolbar.MenuItem.Container"/>
+      <item type="style" name="Widget.CarUi.Toolbar.MenuItem.IndividualContainer"/>
+      <item type="style" name="Widget.CarUi.Toolbar.NavIcon"/>
+      <item type="style" name="Widget.CarUi.Toolbar.NavIconContainer"/>
+      <item type="style" name="Widget.CarUi.Toolbar.ProgressBar"/>
+      <item type="style" name="Widget.CarUi.Toolbar.Search.CloseIcon"/>
+      <item type="style" name="Widget.CarUi.Toolbar.Search.EditText"/>
+      <item type="style" name="Widget.CarUi.Toolbar.Search.SearchIcon"/>
+      <item type="style" name="Widget.CarUi.Toolbar.SeparatorView"/>
+      <item type="style" name="Widget.CarUi.Toolbar.Subtitle"/>
+      <item type="style" name="Widget.CarUi.Toolbar.Tab"/>
+      <item type="style" name="Widget.CarUi.Toolbar.Tab.Container"/>
+      <item type="style" name="Widget.CarUi.Toolbar.Tab.Icon"/>
+      <item type="style" name="Widget.CarUi.Toolbar.Tab.Text"/>
+      <item type="style" name="Widget.CarUi.Toolbar.TextButton"/>
+      <item type="style" name="Widget.CarUi.Toolbar.TextButton.WithIcon"/>
+      <item type="style" name="Widget.CarUi.Toolbar.Title"/>
+    </policy>
+  </overlayable>
+</resources>
diff --git a/car-ui-lib/res/drawable/car_ui_icon_lock.xml b/car-ui-lib/res/drawable/car_ui_icon_lock.xml
new file mode 100644
index 0000000..d74b14a
--- /dev/null
+++ b/car-ui-lib/res/drawable/car_ui_icon_lock.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+-->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="44dp"
+    android:height="44dp"
+    android:viewportHeight="48.0"
+    android:viewportWidth="48.0">
+    <path
+        android:fillColor="@color/car_ui_text_color_primary"
+        android:pathData="M36,16h-2v-4c0,-5.52 -4.48,-10 -10,-10S14,6.48 14,12v4h-2c-2.21,0 -4,1.79 -4,4v20c0,2.21 1.79,4 4,4h24c2.21,0 4,-1.79 4,-4L40,20c0,-2.21 -1.79,-4 -4,-4zM24,34c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4zM30.2,16L17.8,16v-4c0,-3.42 2.78,-6.2 6.2,-6.2 3.42,0 6.2,2.78 6.2,6.2v4z"/>
+</vector>
diff --git a/car-ui-lib/res/drawable/car_ui_list_limiting_message_background.xml b/car-ui-lib/res/drawable/car_ui_list_limiting_message_background.xml
new file mode 100644
index 0000000..02a8065
--- /dev/null
+++ b/car-ui-lib/res/drawable/car_ui_list_limiting_message_background.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="#282A2D"/>
+    <corners android:radius="96dp"/>
+</shape>
diff --git a/car-ui-lib/res/layout-port/car_ui_base_layout_toolbar.xml b/car-ui-lib/res/layout-port/car_ui_base_layout_toolbar.xml
deleted file mode 100644
index 57d08ce..0000000
--- a/car-ui-lib/res/layout-port/car_ui_base_layout_toolbar.xml
+++ /dev/null
@@ -1,196 +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.
-  -->
-<!-- This is for the two-row version of the toolbar -->
-<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">
-
-    <!-- When the user finishes searching, we call clearFocus() on the editText in the search bar.
-     clearFocus() will actually send the focus to the first focusable thing in the layout.
-     If that focusable thing is still the search bar it will just reselect it, and the user won't
-     be able to deselect. So make a focusable view here to guarantee that we can clear the focus -->
-    <View
-        android:layout_width="1dp"
-        android:layout_height="1dp"
-        android:focusable="true"
-        android:focusableInTouchMode="true" />
-
-    <FrameLayout
-        android:id="@+id/content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <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="wrap_content"
-        android:tag="car_ui_top_inset"
-        app:layout_constraintTop_toTopOf="parent">
-
-        <androidx.constraintlayout.widget.Guideline
-            android:id="@+id/car_ui_toolbar_start_guideline"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            app:layout_constraintGuide_begin="@dimen/car_ui_toolbar_start_inset" />
-
-        <androidx.constraintlayout.widget.Guideline
-            android:id="@+id/car_ui_toolbar_top_guideline"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            app:layout_constraintGuide_begin="@dimen/car_ui_toolbar_top_inset" />
-
-        <androidx.constraintlayout.widget.Guideline
-            android:id="@+id/car_ui_toolbar_end_guideline"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            app:layout_constraintGuide_end="@dimen/car_ui_toolbar_end_inset" />
-
-        <androidx.constraintlayout.widget.Guideline
-            android:id="@+id/car_ui_toolbar_bottom_guideline"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            app:layout_constraintGuide_end="@dimen/car_ui_toolbar_bottom_inset" />
-
-        <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"
-            style="@style/Widget.CarUi.Toolbar.SeparatorView"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/car_ui_toolbar_separator_height"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/car_ui_toolbar_row_separator_guideline" />
-
-        <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_toTopOf="@id/car_ui_toolbar_row_separator"
-            app:layout_constraintStart_toStartOf="@id/car_ui_toolbar_start_guideline"
-            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline">
-
-            <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_toTopOf="@id/car_ui_toolbar_row_separator"
-            app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_nav_icon_container"
-            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline">
-
-            <ImageView
-                android:id="@+id/car_ui_toolbar_title_logo"
-                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>
-
-        <TextView
-            android:id="@+id/car_ui_toolbar_title"
-            style="@style/Widget.CarUi.Toolbar.Title"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:singleLine="true"
-            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
-            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="@id/car_ui_toolbar_top_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_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
-            app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_menu_items_container"
-            app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_nav_icon_container"
-            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline" />
-
-        <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_toTopOf="@id/car_ui_toolbar_row_separator"
-            app:layout_constraintEnd_toStartOf="@id/car_ui_toolbar_end_guideline"
-            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_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_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
-            app:layout_constraintTop_toBottomOf="@id/car_ui_toolbar_row_separator" />
-
-        <View
-            android:id="@+id/car_ui_toolbar_bottom_styleable"
-            style="@style/Widget.CarUi.Toolbar.BottomView"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/car_ui_toolbar_bottom_view_height"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="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_toTopOf="@id/car_ui_toolbar_bottom_styleable"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent" />
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-
-</androidx.constraintlayout.widget.ConstraintLayout>
-
diff --git a/car-ui-lib/res/layout/car_ui_alert_dialog_title_with_subtitle.xml b/car-ui-lib/res/layout/car_ui_alert_dialog_title_with_subtitle.xml
index 271280d..f8d73c3 100644
--- a/car-ui-lib/res/layout/car_ui_alert_dialog_title_with_subtitle.xml
+++ b/car-ui-lib/res/layout/car_ui_alert_dialog_title_with_subtitle.xml
@@ -23,7 +23,7 @@
     style="@style/Widget.CarUi.AlertDialog.HeaderContainer">
 
     <ImageView
-        android:id="@+id/icon"
+        android:id="@+id/car_ui_alert_icon"
         android:layout_width="@dimen/car_ui_dialog_icon_size"
         android:layout_height="@dimen/car_ui_dialog_icon_size"
         style="@style/Widget.CarUi.AlertDialog.Icon"/>
@@ -33,17 +33,15 @@
         android:layout_height="wrap_content"
         style="@style/Widget.CarUi.AlertDialog.TitleContainer">
         <TextView
-            android:id="@+id/alertTitle"
-            android:singleLine="true"
+            android:id="@+id/car_ui_alert_title"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:textAlignment="viewStart"
-            style="?android:attr/windowTitleStyle" />
+            android:textAppearance="@style/TextAppearance.CarUi.AlertDialog.Title" />
 
         <TextView
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:id="@+id/alertSubtitle"
+            android:id="@+id/car_ui_alert_subtitle"
             android:textAppearance="@style/TextAppearance.CarUi.AlertDialog.Subtitle"/>
     </LinearLayout>
 
diff --git a/car-ui-lib/res/layout/car_ui_base_layout.xml b/car-ui-lib/res/layout/car_ui_base_layout.xml
index 4cf7e8d..fdc8b53 100644
--- a/car-ui-lib/res/layout/car_ui_base_layout.xml
+++ b/car-ui-lib/res/layout/car_ui_base_layout.xml
@@ -16,7 +16,8 @@
   -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/car_ui_base_layout_content_container"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:id="@+id/content">
-</FrameLayout>
\ No newline at end of file
+    android:layout_height="match_parent">
+
+</FrameLayout>
diff --git a/car-ui-lib/res/layout/car_ui_base_layout_toolbar.xml b/car-ui-lib/res/layout/car_ui_base_layout_toolbar.xml
index 90b083a..374dc4b 100644
--- a/car-ui-lib/res/layout/car_ui_base_layout_toolbar.xml
+++ b/car-ui-lib/res/layout/car_ui_base_layout_toolbar.xml
@@ -31,7 +31,7 @@
         android:focusableInTouchMode="true" />
 
     <FrameLayout
-        android:id="@+id/content"
+        android:id="@+id/car_ui_base_layout_content_container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         app:layout_constraintBottom_toBottomOf="parent"
@@ -44,34 +44,13 @@
         android:layout_height="@dimen/car_ui_toolbar_first_row_height"
         android:tag="car_ui_top_inset"
         app:layout_constraintTop_toTopOf="parent">
-
-        <androidx.constraintlayout.widget.Guideline
-            android:id="@+id/car_ui_toolbar_start_guideline"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            app:layout_constraintGuide_begin="@dimen/car_ui_toolbar_start_inset" />
-
-        <androidx.constraintlayout.widget.Guideline
-            android:id="@+id/car_ui_toolbar_top_guideline"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            app:layout_constraintGuide_begin="@dimen/car_ui_toolbar_top_inset" />
-
-        <androidx.constraintlayout.widget.Guideline
-            android:id="@+id/car_ui_toolbar_end_guideline"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            app:layout_constraintGuide_end="@dimen/car_ui_toolbar_end_inset" />
-
-        <androidx.constraintlayout.widget.Guideline
-            android:id="@+id/car_ui_toolbar_bottom_guideline"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            app:layout_constraintGuide_end="@dimen/car_ui_toolbar_bottom_inset" />
+        <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 -->
@@ -80,10 +59,10 @@
             style="@style/Widget.CarUi.Toolbar.NavIconContainer"
             android:layout_width="@dimen/car_ui_toolbar_margin"
             android:layout_height="0dp"
-            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
+            app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintHorizontal_bias="0.0"
-            app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_start_guideline"
-            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline">
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent">
 
             <ImageView
                 android:id="@+id/car_ui_toolbar_nav_icon"
@@ -106,9 +85,9 @@
             style="@style/Widget.CarUi.Toolbar.LogoContainer"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
+            app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_nav_icon_container"
-            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline">
+            app:layout_constraintTop_toTopOf="parent">
 
             <ImageView
                 android:id="@+id/car_ui_toolbar_title_logo"
@@ -119,26 +98,37 @@
                 android:scaleType="fitXY" />
         </FrameLayout>
 
-        <TextView
-            android:id="@+id/car_ui_toolbar_title"
-            style="@style/Widget.CarUi.Toolbar.Title"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:singleLine="true"
-            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
-            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="@id/car_ui_toolbar_top_guideline" />
+        <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_toTopOf="@id/car_ui_toolbar_bottom_guideline"
+            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="@id/car_ui_toolbar_top_guideline" />
+            app:layout_constraintTop_toTopOf="parent" />
 
         <LinearLayout
             android:id="@+id/car_ui_toolbar_menu_items_container"
@@ -146,27 +136,18 @@
             android:layout_width="wrap_content"
             android:layout_height="0dp"
             android:orientation="horizontal"
-            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
-            app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_end_guideline"
-            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline" />
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
 
         <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_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
+            app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_menu_items_container"
             app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_nav_icon_container"
-            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline" />
-
-        <View
-            android:id="@+id/car_ui_toolbar_row_separator"
-            style="@style/Widget.CarUi.Toolbar.SeparatorView"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/car_ui_toolbar_separator_height"
-            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent" />
+            app:layout_constraintTop_toTopOf="parent" />
 
         <ProgressBar
             android:id="@+id/car_ui_toolbar_progress_bar"
@@ -175,16 +156,7 @@
             android:layout_height="wrap_content"
             android:indeterminate="true"
             android:visibility="gone"
-            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent" />
-
-        <View
-            android:id="@+id/car_ui_toolbar_bottom_styleable"
-            style="@style/Widget.CarUi.Toolbar.BottomView"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/car_ui_toolbar_bottom_view_height"
-            app:layout_constraintBottom_toTopOf="@+id/car_ui_toolbar_progress_bar"
+            app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent" />
 
diff --git a/car-ui-lib/res/layout/car_ui_base_layout_toolbar_legacy.xml b/car-ui-lib/res/layout/car_ui_base_layout_toolbar_legacy.xml
new file mode 100644
index 0000000..263cc8a
--- /dev/null
+++ b/car-ui-lib/res/layout/car_ui_base_layout_toolbar_legacy.xml
@@ -0,0 +1,49 @@
+<?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.
+  -->
+<!-- This layout is used on Q or earlier, to support OEMs that shipped
+     on Q and only customized the non-baselayout version of the toolbar -->
+<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">
+
+    <!-- When the user finishes searching, we call clearFocus() on the editText in the search bar.
+         clearFocus() will actually send the focus to the first focusable thing in the layout.
+         If that focusable thing is still the search bar it will just reselect it, and the user won't
+         be able to deselect. So make a focusable view here to guarantee that we can clear the focus -->
+    <View
+        android:layout_width="1dp"
+        android:layout_height="1dp"
+        android:focusable="true"
+        android:focusableInTouchMode="true"/>
+
+    <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.toolbar.Toolbar
+        android:id="@+id/car_ui_toolbar"
+        android:tag="car_ui_top_inset"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-ui-lib/res/layout/car_ui_header_list_item.xml b/car-ui-lib/res/layout/car_ui_header_list_item.xml
index 8f07636..e1196cf 100644
--- a/car-ui-lib/res/layout/car_ui_header_list_item.xml
+++ b/car-ui-lib/res/layout/car_ui_header_list_item.xml
@@ -33,7 +33,7 @@
         android:id="@+id/title"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/car_ui_list_item_text_no_icon_start_margin"
+        android:layout_marginStart="@dimen/car_ui_header_list_item_text_start_margin"
         android:textAppearance="@style/TextAppearance.CarUi.ListItem.Header"
         app:layout_constraintBottom_toTopOf="@+id/body"
         app:layout_constraintEnd_toEndOf="parent"
diff --git a/car-ui-lib/res/layout/car_ui_list_limiting_message.xml b/car-ui-lib/res/layout/car_ui_list_limiting_message.xml
new file mode 100644
index 0000000..0b06575
--- /dev/null
+++ b/car-ui-lib/res/layout/car_ui_list_limiting_message.xml
@@ -0,0 +1,39 @@
+<!--
+  ~ 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.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="144dp"
+    android:gravity="center"
+    android:paddingTop="16dp"
+    android:paddingBottom="32dp"
+>
+    <TextView
+        android:id="@+id/car_ui_list_limiting_message"
+        android:layout_width="wrap_content"
+        android:layout_height="96dp"
+        android:layout_gravity="center_horizontal"
+        android:gravity="center"
+        android:paddingStart="48dp"
+        android:paddingEnd="48dp"
+        android:drawableStart="@drawable/car_ui_icon_lock"
+        android:drawablePadding="24dp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textAllCaps="false"
+        android:singleLine="true"
+        android:background="@drawable/car_ui_list_limiting_message_background"
+    />
+</FrameLayout>
\ No newline at end of file
diff --git a/car-ui-lib/res/layout/car_ui_recycler_view.xml b/car-ui-lib/res/layout/car_ui_recycler_view.xml
index 29150d7..50600c1 100644
--- a/car-ui-lib/res/layout/car_ui_recycler_view.xml
+++ b/car-ui-lib/res/layout/car_ui_recycler_view.xml
@@ -16,12 +16,13 @@
   -->
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
-  <include layout="@layout/car_ui_recyclerview_scrollbar"/>
+    <include layout="@layout/car_ui_recyclerview_scrollbar"/>
 
-  <FrameLayout
-      android:id="@+id/car_ui_recycler_view"
-      android:layout_width="0dp"
-      android:layout_height="match_parent"
-      android:layout_marginEnd="@dimen/car_ui_scrollbar_margin"
-      android:layout_weight="1"/>
+    <com.android.car.ui.recyclerview.CarUiRecyclerViewContainer
+        android:id="@+id/car_ui_recycler_view"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="@dimen/car_ui_scrollbar_margin"
+        android:tag="carUiRecyclerView"
+        android:layout_weight="1"/>
 </merge>
\ No newline at end of file
diff --git a/car-ui-lib/res/layout/car_ui_recyclerview_scrollbar.xml b/car-ui-lib/res/layout/car_ui_recyclerview_scrollbar.xml
index ceadfaf..1c7097a 100644
--- a/car-ui-lib/res/layout/car_ui_recyclerview_scrollbar.xml
+++ b/car-ui-lib/res/layout/car_ui_recyclerview_scrollbar.xml
@@ -15,16 +15,16 @@
   ~ limitations under the License.
   -->
 
-<LinearLayout
+<androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
+    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">
 
-    <ImageButton
-        android:id="@+id/page_up"
+    <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"
@@ -32,18 +32,35 @@
         android:focusable="false"
         android:hapticFeedbackEnabled="false"
         android:src="@drawable/car_ui_recyclerview_ic_up"
-        android:scaleType="centerInside" />
+        android:scaleType="centerInside"
+        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: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" />
+        android:background="@drawable/car_ui_recyclerview_scrollbar_thumb"
+        app:layout_constraintTop_toBottomOf="@+id/car_ui_scrollbar_page_up"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"/>
 
-    <ImageButton
-        android:id="@+id/page_down"
+    <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"
@@ -51,5 +68,8 @@
         android:focusable="false"
         android:hapticFeedbackEnabled="false"
         android:src="@drawable/car_ui_recyclerview_ic_down"
-        android:scaleType="centerInside" />
-</LinearLayout>
+        android:scaleType="centerInside"
+        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/res/layout/car_ui_toolbar.xml b/car-ui-lib/res/layout/car_ui_toolbar.xml
index f6ba021..b0716a7 100644
--- a/car-ui-lib/res/layout/car_ui_toolbar.xml
+++ b/car-ui-lib/res/layout/car_ui_toolbar.xml
@@ -104,16 +104,27 @@
             style="@style/Widget.CarUi.Toolbar.Logo"/>
     </FrameLayout>
 
-    <TextView
-        android:id="@+id/car_ui_toolbar_title"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:singleLine="true"
-        style="@style/Widget.CarUi.Toolbar.Title"
-        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"/>
+    <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"
diff --git a/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml b/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml
index 428a2b6..ea93323 100644
--- a/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml
+++ b/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml
@@ -117,16 +117,27 @@
             style="@style/Widget.CarUi.Toolbar.Logo"/>
     </FrameLayout>
 
-    <TextView
-        android:id="@+id/car_ui_toolbar_title"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:singleLine="true"
-        style="@style/Widget.CarUi.Toolbar.Title"
-        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"/>
+    <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"
diff --git a/car-ui-lib/res/values-af/strings.xml b/car-ui-lib/res/values-af/strings.xml
new file mode 100644
index 0000000..8404aca
--- /dev/null
+++ b/car-ui-lib/res/values-af/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Soek …"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Rollees af"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Rollees op"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Terug"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Soek"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Instellings"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Oorloop"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Kanselleer"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Aan"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Af"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Maak toe"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Rollees word beperk terwyl jy bestuur"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Kenmerk is nie beskikbaar terwyl jy bestuur nie"</string>
+</resources>
diff --git a/car-ui-lib/res/values-am/strings.xml b/car-ui-lib/res/values-am/strings.xml
new file mode 100644
index 0000000..55c7fc8
--- /dev/null
+++ b/car-ui-lib/res/values-am/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"ይፈልጉ…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"ወደ ታች ይሸብልሉ"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"ወደ ላይ ይሸብልሉ"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"ተመለስ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"ፈልግ"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"ቅንብሮች"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ትርፍ ፍሰት"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"እሺ"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"ይቅር"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"አብራ"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ቅናሽ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"ዝጋ"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"እየነዱ ማሸብለል ተገድቧል"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"እየነዱ ሳለ ባህሪው አይገኝም"</string>
+</resources>
diff --git a/car-ui-lib/res/values-ar/strings.xml b/car-ui-lib/res/values-ar/strings.xml
new file mode 100644
index 0000000..9bfe85f
--- /dev/null
+++ b/car-ui-lib/res/values-ar/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"بحث…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"التمرير للأسفل"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"التمرير للأعلى"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"رجوع"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"بحث"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"إعدادات"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"القائمة الكاملة"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"حسنًا"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"إلغاء"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"مفعّل"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"غير مفعَّل"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"إغلاق"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"هناك حد أقصى للتمرير على الشاشة أثناء القيادة."</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"لا تتوفَّر هذه الميزة أثناء القيادة."</string>
+</resources>
diff --git a/car-ui-lib/res/values-as/strings.xml b/car-ui-lib/res/values-as/strings.xml
new file mode 100644
index 0000000..2fa5db9
--- /dev/null
+++ b/car-ui-lib/res/values-as/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"সন্ধান কৰক…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"তললৈ স্ক্ৰ’ল কৰক"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"ওপৰলৈ স্ক্ৰ’ল কৰক"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"উভতি যাওক"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"ছেটিংসমূহ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"অভাৰফ্ল’"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ঠিক আছে"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"বাতিল কৰক"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"অন আছে"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"অফ আছে"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"বন্ধ কৰক"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"গাড়ী চলাই থকা সময়ত স্ক্ৰ’ল কৰাটো সীমিত কৰা হৈছে"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"গাড়ী চলাই থকা সময়ত এই সুবিধাটো উপলব্ধ নহয়"</string>
+</resources>
diff --git a/car-ui-lib/res/values-az/strings.xml b/car-ui-lib/res/values-az/strings.xml
new file mode 100644
index 0000000..63cb2bc
--- /dev/null
+++ b/car-ui-lib/res/values-az/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Axtarış…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Aşağı sürüşdürün"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Yuxarı sürüşdürün"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Geri"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Axtarış"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Ayarlar"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Kənara çıxma"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Ok"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Ləğv edin"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Aktiv"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Deaktiv"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Qapadın"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Sürüşdürmə avtomobil idarə edərkən məhdudlaşdırılır"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funksiya avtomobil idarə edərkən əlçatan deyil"</string>
+</resources>
diff --git a/car-ui-lib/res/values-b+sr+Latn/strings.xml b/car-ui-lib/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..104095f
--- /dev/null
+++ b/car-ui-lib/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Pretražite…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Pomerite nadole"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Pomerite nagore"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Nazad"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Pretraži"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Podešavanja"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Preklopni meni"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Potvrdi"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Otkaži"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Uključeno"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Isključeno"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Zatvori"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Pomeranje je ograničeno tokom vožnje"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funkcija nije dostupna tokom vožnje"</string>
+</resources>
diff --git a/car-ui-lib/res/values-be/strings.xml b/car-ui-lib/res/values-be/strings.xml
new file mode 100644
index 0000000..643faa7
--- /dev/null
+++ b/car-ui-lib/res/values-be/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Пошук…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Прагартаць уніз"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Прагартаць уверх"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Назад"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Пошук"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Налады"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Дадатковае меню"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ОК"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Скасаваць"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Уключана"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Выключана"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Закрыць"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Функцыя прагортвання абмежаваная, калі аўтамабіль рухаецца"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Функцыя недаступная, калі аўтамабіль рухаецца"</string>
+</resources>
diff --git a/car-ui-lib/res/values-bg/strings.xml b/car-ui-lib/res/values-bg/strings.xml
new file mode 100644
index 0000000..714b410
--- /dev/null
+++ b/car-ui-lib/res/values-bg/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Търсете…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Превъртане надолу"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Превъртане нагоре"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Назад"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Търсене"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Настройки"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Препълване"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Отказ"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Вкл."</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Изкл."</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Затваряне"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Превъртането е ограничено при шофиране"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Функцията не е налице по време на шофиране"</string>
+</resources>
diff --git a/car-ui-lib/res/values-bn/strings.xml b/car-ui-lib/res/values-bn/strings.xml
new file mode 100644
index 0000000..333887b
--- /dev/null
+++ b/car-ui-lib/res/values-bn/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"সার্চ করুন…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"নিচের দিকে স্ক্রল করুন"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"উপরের দিকে স্ক্রল করুন"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"ফিরুন"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"সেটিংস"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ওভারফ্লো মেনু"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"আচ্ছা"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"বাতিল করুন"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"চালু"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"বন্ধ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"বন্ধ করুন"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"গাড়ি চালানোর সময় স্ক্রলিং ফিচার সীমিতভাবে ব্যবহার করা যাবে"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"গাড়ি চালানোর সময় এই ফিচার কাজ করবে না"</string>
+</resources>
diff --git a/car-ui-lib/res/values-bs/strings.xml b/car-ui-lib/res/values-bs/strings.xml
new file mode 100644
index 0000000..c08deb5
--- /dev/null
+++ b/car-ui-lib/res/values-bs/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Pretražite…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Klizanje prema dolje"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Klizanje prema gore"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Nazad"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Pretraživanje"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Postavke"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Preklopni meni"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Uredu"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Otkaži"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Uključeno"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Isključeno"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Zatvori"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Klizanje je ograničeno tokom vožnje"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funkcija nije dostupna tokom vožnje"</string>
+</resources>
diff --git a/car-ui-lib/res/values-ca/strings.xml b/car-ui-lib/res/values-ca/strings.xml
new file mode 100644
index 0000000..db00c39
--- /dev/null
+++ b/car-ui-lib/res/values-ca/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Cerca…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Desplaça cap avall"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Desplaça cap amunt"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Enrere"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Cerca"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Configuració"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Menú addicional"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"D\'acord"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Cancel·la"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Activat"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Desactivat"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Tanca"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"El desplaçament està limitat mentre condueixes"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Aquesta funció no està disponible mentre condueixes"</string>
+</resources>
diff --git a/car-ui-lib/res/values-cs/strings.xml b/car-ui-lib/res/values-cs/strings.xml
new file mode 100644
index 0000000..50e7191
--- /dev/null
+++ b/car-ui-lib/res/values-cs/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Vyhledat…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Posunout dolů"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Posunout nahoru"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Zpět"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Hledat"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Nastavení"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Rozbalovací nabídka"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Zrušit"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Zap"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Vyp"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Zavřít"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Posouvání zobrazení je při řízení omezeno"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funkce při řízení není dostupná"</string>
+</resources>
diff --git a/car-ui-lib/res/values-da/strings.xml b/car-ui-lib/res/values-da/strings.xml
new file mode 100644
index 0000000..2b01661
--- /dev/null
+++ b/car-ui-lib/res/values-da/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Søg…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Rul ned"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Rul op"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Tilbage"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Søg"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Indstillinger"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Overløb"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Luk"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Til"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Fra"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Luk"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Rullefunktionen er begrænset under kørsel"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funktionen er ikke tilgængelig, mens du kører"</string>
+</resources>
diff --git a/car-ui-lib/res/values-de/strings.xml b/car-ui-lib/res/values-de/strings.xml
new file mode 100644
index 0000000..14502c8
--- /dev/null
+++ b/car-ui-lib/res/values-de/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Suchen…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Nach unten scrollen"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Nach oben scrollen"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Zurück"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Suchen"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Einstellungen"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Dreipunkt-Menü"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Abbrechen"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"An"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Aus"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Schließen"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Scrollen während der Fahrt eingeschränkt"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funktion während der Fahrt nicht verfügbar"</string>
+</resources>
diff --git a/car-ui-lib/res/values-el/strings.xml b/car-ui-lib/res/values-el/strings.xml
new file mode 100644
index 0000000..90f9e5c
--- /dev/null
+++ b/car-ui-lib/res/values-el/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Αναζήτηση…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Κύλιση προς τα κάτω"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Κύλιση προς τα επάνω"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Πίσω"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Αναζήτηση"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Ρυθμίσεις"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Υπερχείλιση"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Άκυρο"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Ενεργό"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Ανενεργή"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Κλείσιμο"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Η κύλιση είναι περιορισμένη κατά τη διάρκεια της οδήγησης."</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Η λειτουργία δεν διατίθεται κατά τη διάρκεια της οδήγησης."</string>
+</resources>
diff --git a/car-ui-lib/res/values-en-rAU/strings.xml b/car-ui-lib/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..14455e4
--- /dev/null
+++ b/car-ui-lib/res/values-en-rAU/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Search…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Scroll down"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Scroll up"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Back"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Settings"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Overflow"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Cancel"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"On"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Off"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Close"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Scrolling limited while driving"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Feature not available while driving"</string>
+</resources>
diff --git a/car-ui-lib/res/values-en-rCA/strings.xml b/car-ui-lib/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..14455e4
--- /dev/null
+++ b/car-ui-lib/res/values-en-rCA/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Search…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Scroll down"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Scroll up"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Back"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Settings"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Overflow"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Cancel"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"On"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Off"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Close"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Scrolling limited while driving"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Feature not available while driving"</string>
+</resources>
diff --git a/car-ui-lib/res/values-en-rGB/strings.xml b/car-ui-lib/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..14455e4
--- /dev/null
+++ b/car-ui-lib/res/values-en-rGB/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Search…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Scroll down"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Scroll up"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Back"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Settings"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Overflow"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Cancel"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"On"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Off"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Close"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Scrolling limited while driving"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Feature not available while driving"</string>
+</resources>
diff --git a/car-ui-lib/res/values-en-rIN/strings.xml b/car-ui-lib/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..14455e4
--- /dev/null
+++ b/car-ui-lib/res/values-en-rIN/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Search…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Scroll down"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Scroll up"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Back"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Settings"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Overflow"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Cancel"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"On"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Off"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Close"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Scrolling limited while driving"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Feature not available while driving"</string>
+</resources>
diff --git a/car-ui-lib/res/values-en-rXC/strings.xml b/car-ui-lib/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..cc754fd
--- /dev/null
+++ b/car-ui-lib/res/values-en-rXC/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‏‎‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‎‎‏‎‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‎Search…‎‏‎‎‏‎"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‎‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎Scroll down‎‏‎‎‏‎"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‏‎‎‏‎‏‏‏‏‏‏‎‎‎Scroll up‎‏‎‎‏‎"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‎Back‎‏‎‎‏‎"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‎‏‎‎‎‏‎‎‎‎‎‎‎‏‎‎‏‎‏‏‎‎‎‎‏‎‏‏‎‎‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‎‎Search‎‏‎‎‏‎"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎‎‎‏‎Settings‎‏‎‎‏‎"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‏‏‎‏‏‎‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‏‏‎‎‎‏‏‎‏‎‏‎‎‏‏‎Overflow‎‏‎‎‏‎"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‎‎‎Ok‎‏‎‎‏‎"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‎‎‎‏‎‏‎‎‏‏‏‏‏‏‏‎Cancel‎‏‎‎‏‎"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎On‎‏‎‎‏‎"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‏‏‏‎‎‎‎Off‎‏‎‎‏‎"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‏‏‎‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎‎‎‎‏‎‏‎Close‎‏‎‎‏‎"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‎‏‏‏‏‎‏‏‎Scrolling limited while driving‎‏‎‎‏‎"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‎‏‎‏‎Feature not available while driving‎‏‎‎‏‎"</string>
+</resources>
diff --git a/car-ui-lib/res/values-es-rUS/strings.xml b/car-ui-lib/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..31c5bd1
--- /dev/null
+++ b/car-ui-lib/res/values-es-rUS/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Buscar…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Desplazamiento hacia abajo"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Desplazamiento hacia arriba"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Atrás"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Buscar"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Configuración"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Ampliado"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Aceptar"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Cancelar"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Sí"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"No"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Cerrar"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"La función de desplazamiento se limita al conducir"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Esta función no está disponible mientras conduces"</string>
+</resources>
diff --git a/car-ui-lib/res/values-es/strings.xml b/car-ui-lib/res/values-es/strings.xml
new file mode 100644
index 0000000..145d73e
--- /dev/null
+++ b/car-ui-lib/res/values-es/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Realiza una búsqueda…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Desplazarse hacia abajo"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Desplazarse hacia arriba"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Atrás"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Buscar"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Ajustes"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Menú adicional"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Aceptar"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Cancelar"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Activado"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Desactivado"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Cerrar"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Desplazamiento limitado mientras conduces"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Función no disponible mientras conduces"</string>
+</resources>
diff --git a/car-ui-lib/res/values-et/strings.xml b/car-ui-lib/res/values-et/strings.xml
new file mode 100644
index 0000000..0d494d1
--- /dev/null
+++ b/car-ui-lib/res/values-et/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Otsing …"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Keri alla"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Keri üles"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Tagasi"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Otsi"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Seaded"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Ületäide"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Tühista"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Sees"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Väljas"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Sule"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Kerimine on sõitmise ajal piiratud"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funktsioon pole sõidu ajal saadaval"</string>
+</resources>
diff --git a/car-ui-lib/res/values-eu/strings.xml b/car-ui-lib/res/values-eu/strings.xml
new file mode 100644
index 0000000..3935830
--- /dev/null
+++ b/car-ui-lib/res/values-eu/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Bilatu…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Egin behera"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Egin gora"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Atzera"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Bilaketa"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Ezarpenak"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Luzapena"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Ados"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Utzi"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Aktibatuta"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Desaktibatuta"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Itxi"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Gora edo behera egiteko eginbidea mugatuta dago gidatu bitartean"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Eginbide hau ezin da erabili gidatu bitartean"</string>
+</resources>
diff --git a/car-ui-lib/res/values-fa/strings.xml b/car-ui-lib/res/values-fa/strings.xml
new file mode 100644
index 0000000..9d8b9f6
--- /dev/null
+++ b/car-ui-lib/res/values-fa/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"جستجو…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"پیمایش به پایین"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"پیمایش به بالا"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"برگشت"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"جستجو"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"تنظیمات"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"لبریزشده"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"تأیید"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"لغو"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"روشن"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"خاموش"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"بستن"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"پیمایش درحین رانندگی محدود شده است"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"این ویژگی هنگام رانندگی در دسترس نیست"</string>
+</resources>
diff --git a/car-ui-lib/res/values-fi/strings.xml b/car-ui-lib/res/values-fi/strings.xml
new file mode 100644
index 0000000..78aa846
--- /dev/null
+++ b/car-ui-lib/res/values-fi/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Hae…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Vieritä alas"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Vieritä ylös"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Takaisin"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Haku"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Asetukset"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Ylivuoto"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Peruuta"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Päällä"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Pois"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Sulje"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Vierittämistä rajoitettu ajon aikana"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Ominaisuus ei ole käytettävissä ajon aikana"</string>
+</resources>
diff --git a/car-ui-lib/res/values-fr-rCA/strings.xml b/car-ui-lib/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..e5a7c6f
--- /dev/null
+++ b/car-ui-lib/res/values-fr-rCA/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Rechercher…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Faire défiler vers le bas"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Faire défiler vers le haut"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Retour"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Rechercher"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Paramètres"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Menu déroulant"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Annuler"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Activé"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Désactivé"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Fermer"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Le défilement de l\'écran est limité durant la conduite"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Cette fonctionnalité n\'est pas accessible durant la conduite"</string>
+</resources>
diff --git a/car-ui-lib/res/values-fr/strings.xml b/car-ui-lib/res/values-fr/strings.xml
new file mode 100644
index 0000000..cd58390
--- /dev/null
+++ b/car-ui-lib/res/values-fr/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Rechercher…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Défiler vers le bas"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Défiler vers le haut"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Retour"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Rechercher"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Paramètres"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Menu à développer"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Annuler"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Activé"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Désactivé"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Fermer"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Défilement limité lorsque vous conduisez"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Fonctionnalité non disponible lorsque vous conduisez"</string>
+</resources>
diff --git a/car-ui-lib/res/values-gl/strings.xml b/car-ui-lib/res/values-gl/strings.xml
new file mode 100644
index 0000000..7b7356c
--- /dev/null
+++ b/car-ui-lib/res/values-gl/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Busca…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Desprazarse cara abaixo"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Desprazarse cara arriba"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Atrás"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Buscar"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Configuración"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Elemento do menú adicional"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Aceptar"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Cancelar"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Si"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Non"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Pechar"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"O desprazamento está limitado mentres conduces"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Esta función non está dispoñible mentres conduces"</string>
+</resources>
diff --git a/car-ui-lib/res/values-gu/strings.xml b/car-ui-lib/res/values-gu/strings.xml
new file mode 100644
index 0000000..081510c
--- /dev/null
+++ b/car-ui-lib/res/values-gu/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"શોધો…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"નીચે સ્ક્રોલ કરો"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"ઉપર સ્ક્રોલ કરો"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"પાછળ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"સેટિંગ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ઓવરફ્લો"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ઓકે"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"રદ કરો"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"ચાલુ"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"બંધ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"બંધ કરો"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"ડ્રાઇવિંગ કરતી વખતે સ્ક્રોલ કરવું મર્યાદિત છે"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"ડ્રાઇવિંગ કરતી વખતે આ સુવિધા ઉપલબ્ધ રહેશે નહીં"</string>
+</resources>
diff --git a/car-ui-lib/res/values-hi/strings.xml b/car-ui-lib/res/values-hi/strings.xml
new file mode 100644
index 0000000..4900774
--- /dev/null
+++ b/car-ui-lib/res/values-hi/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"खोजें…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"नीचे की ओर स्क्रोल करें"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"ऊपर की ओर स्क्रोल करें"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"वापस जाएं"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"खोजें"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"सेटिंग"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ओवरफ़्लो मेन्यू"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ठीक है"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"अभी नहीं"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"चालू है"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"बंद है"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"बंद करें"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"गाड़ी चलाते समय इससे ज़्यादा स्क्रोल नहीं कर सकते"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"गाड़ी चलाते समय इस सुविधा का इस्तेमाल नहीं किया जा सकता"</string>
+</resources>
diff --git a/car-ui-lib/res/values-hr/strings.xml b/car-ui-lib/res/values-hr/strings.xml
new file mode 100644
index 0000000..2f64956
--- /dev/null
+++ b/car-ui-lib/res/values-hr/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Pretražite…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Pomak prema dolje"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Pomak prema gore"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Natrag"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Pretraži"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Postavke"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Dodatno"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"U redu"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Odustani"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Uključeno"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Isključeno"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Zatvori"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Pomicanje je ograničeno tijekom vožnje"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Značajka nije dostupna tijekom vožnje"</string>
+</resources>
diff --git a/car-ui-lib/res/values-hu/strings.xml b/car-ui-lib/res/values-hu/strings.xml
new file mode 100644
index 0000000..3f8233a
--- /dev/null
+++ b/car-ui-lib/res/values-hu/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Keresés…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Görgetés lefelé"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Görgetés felfelé"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Vissza"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Keresés"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Beállítások"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"További elemeket tartalmazó menü"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Mégse"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Be"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Ki"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Bezárás"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Vezetés közben a görgetés korlátozott"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Vezetés közben nem áll rendelkezésre a funkció"</string>
+</resources>
diff --git a/car-ui-lib/res/values-hy/strings.xml b/car-ui-lib/res/values-hy/strings.xml
new file mode 100644
index 0000000..ff952ea
--- /dev/null
+++ b/car-ui-lib/res/values-hy/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Որոնում…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Ոլորել վար"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Ոլորել վեր"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Հետ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Որոնել"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Կարգավորումներ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Լրացուցիչ ընտրացանկ"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Եղավ"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Չեղարկել"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Միացված է"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Անջատված է"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Փակել"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Վարելու ընթացքում ոլորումը սահմանափակված է"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Վարելու ընթացքում գործառույթը հասանելի չէ"</string>
+</resources>
diff --git a/car-ui-lib/res/values-in/strings.xml b/car-ui-lib/res/values-in/strings.xml
new file mode 100644
index 0000000..daf9e0f
--- /dev/null
+++ b/car-ui-lib/res/values-in/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Telusuri…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Scroll ke bawah"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Scroll ke atas"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Kembali"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Penelusuran"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Setelan"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Tambahan"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Oke"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Batal"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Aktif"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Nonaktif"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Tutup"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Men-scroll dibatasi saat mengemudi"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Fitur tidak tersedia saat mengemudi"</string>
+</resources>
diff --git a/car-ui-lib/res/values-is/strings.xml b/car-ui-lib/res/values-is/strings.xml
new file mode 100644
index 0000000..5cc3463
--- /dev/null
+++ b/car-ui-lib/res/values-is/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Leita…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Fletta niður"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Fletta upp"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Til baka"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Leit"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Stillingar"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Yfirflæði"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Í lagi"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Hætta"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Kveikt"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Slökkt"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Loka"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Fletting er takmörkuð meðan á akstri stendur"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Þessi eiginleiki er ekki í boði meðan á akstri stendur"</string>
+</resources>
diff --git a/car-ui-lib/res/values-it/strings.xml b/car-ui-lib/res/values-it/strings.xml
new file mode 100644
index 0000000..2386303
--- /dev/null
+++ b/car-ui-lib/res/values-it/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Cerca…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Scorri verso il basso"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Scorri verso l\'alto"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Indietro"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Cerca"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Impostazioni"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Extra"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Annulla"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"On"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Off"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Chiudi"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Scorrimento limitato durante la guida"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funzionalità non disponibile durante la guida"</string>
+</resources>
diff --git a/car-ui-lib/res/values-iw/strings.xml b/car-ui-lib/res/values-iw/strings.xml
new file mode 100644
index 0000000..543da53
--- /dev/null
+++ b/car-ui-lib/res/values-iw/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"חיפוש…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"גלילה למטה"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"גלילה למעלה"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"חזרה"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"חיפוש"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"הגדרות"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"אפשרויות נוספות"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"אישור"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"ביטול"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"פועל"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"כבוי"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"סגירה"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"הגלילה מוגבלת בזמן הנהיגה"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"התכונה לא זמינה בזמן הנהיגה"</string>
+</resources>
diff --git a/car-ui-lib/res/values-ja/strings.xml b/car-ui-lib/res/values-ja/strings.xml
new file mode 100644
index 0000000..7db4ad8
--- /dev/null
+++ b/car-ui-lib/res/values-ja/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"検索…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"下にスクロール"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"上にスクロール"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"戻る"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"検索"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"設定"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"オーバーフロー"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"キャンセル"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"ON"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"OFF"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"閉じる"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"運転中のスクロール操作は制限されています"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"この機能は運転中は利用できません"</string>
+</resources>
diff --git a/car-ui-lib/res/values-ka/strings.xml b/car-ui-lib/res/values-ka/strings.xml
new file mode 100644
index 0000000..2e6a0da
--- /dev/null
+++ b/car-ui-lib/res/values-ka/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"ძიება…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"ქვემოთ გადაადგილება"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"ზემოთ გადაადგილება"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"უკან"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"ძიება"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"პარამეტრები"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"გადავსება"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"კარგი"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"გაუქმება"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"ჩართულია"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"გამორთულია"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"დახურვა"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"გადაადგილება შეზღუდულია მანქანის მართვისას"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"ფუნქცია მიუწვდომელია მანქანის მართვისას"</string>
+</resources>
diff --git a/car-ui-lib/res/values-kk/strings.xml b/car-ui-lib/res/values-kk/strings.xml
new file mode 100644
index 0000000..3d32de3
--- /dev/null
+++ b/car-ui-lib/res/values-kk/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Іздеу…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Төмен айналдыру"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Жоғары айналдыру"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Артқа"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Іздеу"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Параметрлер"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Қосымша мәзір"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Жарайды"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Бас тарту"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Қосулы"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Өшірулі"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Жабу"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Көлік жүргізу кезінде айналдыру мүмкіндігі шектеледі."</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Көлік жүргізу кезінде бұл функция жұмыс істемейді."</string>
+</resources>
diff --git a/car-ui-lib/res/values-km/strings.xml b/car-ui-lib/res/values-km/strings.xml
new file mode 100644
index 0000000..65aa214
--- /dev/null
+++ b/car-ui-lib/res/values-km/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"ស្វែងរក…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"រំកិលចុះក្រោម"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"រំកិល​​ឡើង​លើ"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"ថយក្រោយ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"ស្វែងរក"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"ការកំណត់"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ម៉ឺនុយបន្ថែម"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"យល់ព្រម"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"បោះបង់"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"បើក"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"បិទ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"បិទ"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"ការរំកិលបានកំណត់ ខណៈពេលកំពុង​បើកបរ"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"មិនអាច​ប្រើមុខងារ​នេះបានទេ ខណៈពេល​កំពុង​បើកបរ"</string>
+</resources>
diff --git a/car-ui-lib/res/values-kn/strings.xml b/car-ui-lib/res/values-kn/strings.xml
new file mode 100644
index 0000000..a0e7a85
--- /dev/null
+++ b/car-ui-lib/res/values-kn/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"ಹುಡುಕಿ…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"ಕೆಳಗೆ ಸ್ಕ್ರಾಲ್ ಮಾಡಿ"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"ಮೇಲೆ ಸ್ಕ್ರಾಲ್ ಮಾಡಿ"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"ಹಿಂದಕ್ಕೆ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ಓವರ್‌ಫ್ಲೋ"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ಸರಿ"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"ರದ್ದುಮಾಡಿ"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"ಆನ್ ಆಗಿದೆ"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ಆಫ್ ಆಗಿದೆ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"ಮುಚ್ಚಿ"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"ಡ್ರೈವ್ ಮಾಡುವಾಗ ಸ್ಕ್ರಾಲ್ ಮಾಡುವಿಕೆ ವೈಶಿಷ್ಟ್ಯವನ್ನು ಸೀಮಿತಗೊಳಿಸಲಾಗಿದೆ"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"ಡ್ರೈವ್ ಮಾಡುವಾಗ ಈ ವೈಶಿಷ್ಟ್ಯ ಲಭ್ಯವಿಲ್ಲ"</string>
+</resources>
diff --git a/car-ui-lib/res/values-ko/strings.xml b/car-ui-lib/res/values-ko/strings.xml
new file mode 100644
index 0000000..bf84fb9
--- /dev/null
+++ b/car-ui-lib/res/values-ko/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"검색…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"아래로 스크롤"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"위로 스크롤"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"뒤로"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"검색"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"설정"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"더보기"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"확인"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"취소"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"사용 설정됨"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"사용 중지됨"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"닫기"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"운전 중에는 스크롤이 제한됨"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"운전 중에 사용할 수 없는 기능입니다."</string>
+</resources>
diff --git a/car-ui-lib/res/values-ky/strings.xml b/car-ui-lib/res/values-ky/strings.xml
new file mode 100644
index 0000000..5d55581
--- /dev/null
+++ b/car-ui-lib/res/values-ky/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Издөө…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Төмөн сыдыруу"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Жогору сыдыруу"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Артка"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Издөө"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Жөндөөлөр"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Кошумча меню"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Жарайт"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Жок"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Күйүк"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Өчүк"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Жабуу"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Унаа айдаганда сыдыруу чектелет"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Унаа айдаганда бул функция жеткиликтүү эмес"</string>
+</resources>
diff --git a/car-ui-lib/res/values-lo/strings.xml b/car-ui-lib/res/values-lo/strings.xml
new file mode 100644
index 0000000..e17fae2
--- /dev/null
+++ b/car-ui-lib/res/values-lo/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"ຊອກຫາ…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"ເລື່ອນລົງ"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"ເລື່ອນຂຶ້ນ"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"ກັບຄືນ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"ຊອກຫາ"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"ການຕັ້ງຄ່າ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ລົ້ນ"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ຕົກລົງ"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"ຍົກເລີກ"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"ເປີດ"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ປິດ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"ປິດ"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"ຈຳກັດການເລື່ອນໃນຂະນະທີ່ຂັບລົດ"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"ຄຸນສົມບັດບໍ່ສາມາດໃຊ້ໄດ້ໃນເວລາຂັບລົດ"</string>
+</resources>
diff --git a/car-ui-lib/res/values-lt/strings.xml b/car-ui-lib/res/values-lt/strings.xml
new file mode 100644
index 0000000..2946f72
--- /dev/null
+++ b/car-ui-lib/res/values-lt/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Ieškoti…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Slinkti žemyn"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Slinkti aukštyn"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Atgal"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Paieška"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Nustatymai"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Perpildymas"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Gerai"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Atšaukti"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Įjungta"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Išjungta"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Uždaryti"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Slinkimas apribotas vairuojant"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funkcija nepasiekiama vairuojant"</string>
+</resources>
diff --git a/car-ui-lib/res/values-lv/strings.xml b/car-ui-lib/res/values-lv/strings.xml
new file mode 100644
index 0000000..4f658c7
--- /dev/null
+++ b/car-ui-lib/res/values-lv/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Meklēt…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Ritināt uz leju"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Ritināt uz augšu"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Atpakaļ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Meklēšana"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Iestatījumi"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Pārpilde"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Labi"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Atcelt"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Ieslēgta"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Izslēgta"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Aizvērt"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Ritināšana ir ierobežota braukšanas laikā."</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funkcija nav pieejama braukšanas laikā."</string>
+</resources>
diff --git a/car-ui-lib/res/values-mk/strings.xml b/car-ui-lib/res/values-mk/strings.xml
new file mode 100644
index 0000000..1470f8d
--- /dev/null
+++ b/car-ui-lib/res/values-mk/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Пребарувајте…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Оди надолу"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Оди нагоре"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Назад"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Пребарување"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Поставки"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Прелевање"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Во ред"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Откажи"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Вклучено"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Исклучено"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Затвори"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Лизгањето е ограничено при возење"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Функцијата не е достапна при возење"</string>
+</resources>
diff --git a/car-ui-lib/res/values-ml/strings.xml b/car-ui-lib/res/values-ml/strings.xml
new file mode 100644
index 0000000..e324e7c
--- /dev/null
+++ b/car-ui-lib/res/values-ml/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"തിരയുക…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"താഴോട്ട് സ്‌ക്രോൾ ചെയ്യുക"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"മുകളിലോട്ട് സ്‌ക്രോൾ ചെയ്യുക"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"മടങ്ങുക"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"ക്രമീകരണം"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ഓവർഫ്ലോ"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ശരി"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"റദ്ദാക്കുക"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"ഓണാണ്"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ഓഫാണ്"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"അടയ്‌ക്കുക"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"ഡ്രൈവ് ചെയ്യുമ്പോൾ സ്ക്രോൾ ചെയ്യൽ പരിമിതപ്പെടുത്തിയിരിക്കുന്നു"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"ഡ്രൈവ് ചെയ്യുമ്പോൾ ഫീച്ചർ ലഭ്യമല്ല"</string>
+</resources>
diff --git a/car-ui-lib/res/values-mn/strings.xml b/car-ui-lib/res/values-mn/strings.xml
new file mode 100644
index 0000000..7c62426
--- /dev/null
+++ b/car-ui-lib/res/values-mn/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Хайх..."</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Доош гүйлгэх"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Дээш гүйлгэх"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Буцах"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Хайлт"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Тохиргоо"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Халих"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Ок"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Болих"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Асаалттай"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Унтраалттай"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Хаах"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Жолоо барьж байх үед гүйлгэхийг хязгаарласан"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Жолоо барьж байх үед онцлог боломжгүй"</string>
+</resources>
diff --git a/car-ui-lib/res/values-mr/strings.xml b/car-ui-lib/res/values-mr/strings.xml
new file mode 100644
index 0000000..339a363
--- /dev/null
+++ b/car-ui-lib/res/values-mr/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"शोधा…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"खाली स्क्रोल करा"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"वर स्क्रोल करा"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"मागे जा"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"सेटिंग्ज"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ओव्हरफ्लो"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ओके"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"रद्द करा"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"सुरू आहे"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"बंद आहे"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"बंद करा"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"ड्राइव्ह करताना स्क्रोलिंग मर्यादित केली आहे"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"ड्राइव्ह करताना वैशिष्ट्य उपलब्ध नाही"</string>
+</resources>
diff --git a/car-ui-lib/res/values-ms/strings.xml b/car-ui-lib/res/values-ms/strings.xml
new file mode 100644
index 0000000..d31613e
--- /dev/null
+++ b/car-ui-lib/res/values-ms/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Cari…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Tatal ke bawah"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Tatal ke atas"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Kembali"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Cari"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Tetapan"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Limpahan"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Ok"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Batal"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Hidup"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Mati"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Tutup"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Penatalan terhad semasa memandu"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Ciri tidak tersedia semasa anda memandu"</string>
+</resources>
diff --git a/car-ui-lib/res/values-my/strings.xml b/car-ui-lib/res/values-my/strings.xml
new file mode 100644
index 0000000..adada08
--- /dev/null
+++ b/car-ui-lib/res/values-my/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"ရှာဖွေရန်…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"အောက်သို့ လှိမ့်ရန်"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"အပေါ်သို့ လှိမ့်ရန်"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"နောက်သို့"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"ရှာဖွေခြင်း"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"ဆက်တင်များ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"အပို"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"မလုပ်တော့"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"ဖွင့်ထားသည်"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ပိတ်ထားသည်"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"ပိတ်ရန်"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"ယာဉ်မောင်းနေစဉ် အပေါ်အောက်ရွှေ့ခြင်းကို ကန့်သတ်ထားသည်"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"ကားမောင်းနေစဉ် ဝန်ဆောင်မှု မရနိုင်ပါ"</string>
+</resources>
diff --git a/car-ui-lib/res/values-nb/strings.xml b/car-ui-lib/res/values-nb/strings.xml
new file mode 100644
index 0000000..21160c8
--- /dev/null
+++ b/car-ui-lib/res/values-nb/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Søk"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Rull ned"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Rull opp"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Tilbake"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Søk"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Innstillinger"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Overflyt"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Avbryt"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"På"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Av"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Lukk"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Rullefunksjonen er begrenset mens du kjører"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funksjonen er ikke tilgjengelig når du kjører"</string>
+</resources>
diff --git a/car-ui-lib/res/values-ne/strings.xml b/car-ui-lib/res/values-ne/strings.xml
new file mode 100644
index 0000000..f33ac72
--- /dev/null
+++ b/car-ui-lib/res/values-ne/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"खोज्नुहोस्…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"तलतिर स्क्रोल गर्नुहोस्"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"माथितिर स्क्रोल गर्नु…"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"पछाडि"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"खोज्नुहोस्"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"सेटिङ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ओभरफ्लो"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ठिक छ"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"रद्द गर्नुहोस्"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"सुचारू छ"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"बन्द छ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"बन्द गर्नुहोस्"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"सवारी साधन चलाइरहेका बेला योभन्दा बढी स्क्रोल गर्न पाइँदैन"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"सवारी साधन चलाइरहेका बेला यो सुविधा उपलब्ध हुँदैन"</string>
+</resources>
diff --git a/car-ui-lib/res/values-nl/strings.xml b/car-ui-lib/res/values-nl/strings.xml
new file mode 100644
index 0000000..da110d5
--- /dev/null
+++ b/car-ui-lib/res/values-nl/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Zoeken…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Omlaag scrollen"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Omhoog scrollen"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Terug"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Zoeken"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Instellingen"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Overloop"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Annuleren"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Aan"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Uit"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Sluiten"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Scrollen is beperkt tijdens het rijden"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Functie niet beschikbaar tijdens het rijden"</string>
+</resources>
diff --git a/car-ui-lib/res/values-or/strings.xml b/car-ui-lib/res/values-or/strings.xml
new file mode 100644
index 0000000..2533631
--- /dev/null
+++ b/car-ui-lib/res/values-or/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"ସନ୍ଧାନ କରନ୍ତୁ…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"ତଳକୁ ସ୍କ୍ରୋଲ୍ କରନ୍ତୁ"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"ଉପରକୁ ସ୍କ୍ରୋଲ୍ କରନ୍ତୁ"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"ପଛକୁ ଫେରନ୍ତୁ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"ସେଟିଂସ୍"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ଓଭରଫ୍ଲୋ"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ଠିକ୍ ଅଛି"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"ବାତିଲ୍ କରନ୍ତୁ"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"ଚାଲୁ ଅଛି"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ବନ୍ଦ ଅଛି"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"ଗାଡ଼ି ଚଲାଇବା ସମୟରେ ସ୍କ୍ରୋଲିଂ ସୀମିତ ଅଟେ"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"ଗାଡ଼ି ଚଲାଇବା ସମୟରେ ଫିଚର୍ ଉପଲବ୍ଧ ହେବ ନାହିଁ"</string>
+</resources>
diff --git a/car-ui-lib/res/values-pa/strings.xml b/car-ui-lib/res/values-pa/strings.xml
new file mode 100644
index 0000000..9910e95
--- /dev/null
+++ b/car-ui-lib/res/values-pa/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"ਖੋਜ…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"ਹੇਠਾਂ ਵੱਲ ਸਕ੍ਰੋਲ ਕਰੋ"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"ਉੱਪਰ ਵੱਲ ਸਕ੍ਰੋਲ ਕਰੋ"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"ਪਿੱਛੇ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"ਖੋਜ"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"ਸੈਟਿੰਗਾਂ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ਓਵਰਫ਼ਲੋ"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ਠੀਕ ਹੈ"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"ਰੱਦ ਕਰੋ"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"ਚਾਲੂ"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ਬੰਦ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"ਬੰਦ ਕਰੋ"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"ਚਲਦੀ ਗੱਡੀ ਵਿੱਚ ਸਕ੍ਰੋਲ ਕਰਨ ਨੂੰ ਸੀਮਤ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"ਚੱਲਦੀ ਗੱਡੀ ਵਿੱਚ ਇਹ ਵਿਸ਼ੇਸ਼ਤਾ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
+</resources>
diff --git a/car-ui-lib/res/values-pl/strings.xml b/car-ui-lib/res/values-pl/strings.xml
new file mode 100644
index 0000000..8feefdb
--- /dev/null
+++ b/car-ui-lib/res/values-pl/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Szukaj…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Przewiń w dół"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Przewiń w górę"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Wstecz"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Szukaj"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Ustawienia"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Rozszerzone menu"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Anuluj"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Wł."</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Wył."</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Zamknij"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Podczas jazdy można przewijać tylko w ograniczonym zakresie"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funkcja niedostępna podczas jazdy"</string>
+</resources>
diff --git a/car-ui-lib/res/values-port/bools.xml b/car-ui-lib/res/values-port/bools.xml
index 16f3724..ed7d502 100644
--- a/car-ui-lib/res/values-port/bools.xml
+++ b/car-ui-lib/res/values-port/bools.xml
@@ -17,10 +17,9 @@
 
 <resources>
     <!-- Toolbar -->
-
+    <!-- Normally no resources should live in any other resource folder than default configuration. -->
+    <!-- Because OEMs will have to always add them to their RROs. -->
+    <!-- But we can't remove this resource from -port folder, because it was used as part of an earlier release -->
     <!-- Whether tabs should use flex layout or not -->
     <bool name="car_ui_toolbar_tab_flexible_layout">true</bool>
-    <!-- Whether tabs should be displayed on a second row, or they should be placed in the first
-         row, replacing the title -->
-    <bool name="car_ui_toolbar_tabs_on_second_row">true</bool>
-</resources>
+</resources>
\ No newline at end of file
diff --git a/car-ui-lib/res/values-pt-rPT/strings.xml b/car-ui-lib/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..e2c6caa
--- /dev/null
+++ b/car-ui-lib/res/values-pt-rPT/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Pesquisar…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Deslocar para baixo"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Deslocar para cima"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Anterior"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Pesquisar"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Definições"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Menu adicional"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Cancelar"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Ativado"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Desativado"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Fechar"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Deslocamento limitado durante a condução"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funcionalidade não disponível durante a condução."</string>
+</resources>
diff --git a/car-ui-lib/res/values-pt/strings.xml b/car-ui-lib/res/values-pt/strings.xml
new file mode 100644
index 0000000..6b47812
--- /dev/null
+++ b/car-ui-lib/res/values-pt/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Pesquisar…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Rolar para baixo"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Rolar para cima"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Voltar"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Pesquisa"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Configurações"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Menu flutuante"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Ok"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Cancelar"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Ativada"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Desativada"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Fechar"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"O recurso de rolagem fica limitado enquanto você dirige"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Recurso indisponível enquanto você dirige"</string>
+</resources>
diff --git a/car-ui-lib/res/values-ro/strings.xml b/car-ui-lib/res/values-ro/strings.xml
new file mode 100644
index 0000000..8d8e771
--- /dev/null
+++ b/car-ui-lib/res/values-ro/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Căutați…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Derulați în jos"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Derulați în sus"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Înapoi"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Căutați"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Setări"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Suplimentar"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Anulați"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Activat"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Dezactivat"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Închideți"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Derularea este restricționată în timp ce conduceți"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funcția nu este disponibilă când conduceți"</string>
+</resources>
diff --git a/car-ui-lib/res/values-ru/strings.xml b/car-ui-lib/res/values-ru/strings.xml
new file mode 100644
index 0000000..9db229e
--- /dev/null
+++ b/car-ui-lib/res/values-ru/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Поиск…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Прокрутить вниз"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Прокрутить вверх"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Назад"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Поиск"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Настройки"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Дополнительное меню"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ОК"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Отмена"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Включено"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Выключено"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Закрыть"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Функция прокручивания ограничена во время вождения."</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Функция недоступна во время вождения."</string>
+</resources>
diff --git a/car-ui-lib/res/values-si/strings.xml b/car-ui-lib/res/values-si/strings.xml
new file mode 100644
index 0000000..0f18f5e
--- /dev/null
+++ b/car-ui-lib/res/values-si/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"සොයන්න…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"පහළට අනුචලනය කරන්න"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"ඉහළට අනුචලනය කරන්න"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"ආපසු"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"සෙවීම"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"සැකසීම්"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ඉතිරියනය"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"හරි"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"අවලංගු කරන්න"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"ක්‍රියාත්මකයි"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ක්‍රියාවිරහිතයි"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"වසන්න"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"රිය පදවන අතරතුර සීමිත අනුචලනය"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"රිය පදවන අතරේ විශේෂාංගය නොමැත"</string>
+</resources>
diff --git a/car-ui-lib/res/values-sk/strings.xml b/car-ui-lib/res/values-sk/strings.xml
new file mode 100644
index 0000000..34891ea
--- /dev/null
+++ b/car-ui-lib/res/values-sk/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Vyhľadať…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Posunúť nadol"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Posunúť nahor"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Späť"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Hľadať"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Nastavenia"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Rozšírená ponuka"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Ok"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Zrušiť"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Zap."</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Vyp."</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Zavrieť"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Posúvanie zobrazenia je počas jazdy obmedzené"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funkcia nie je k dispozícii počas jazdy"</string>
+</resources>
diff --git a/car-ui-lib/res/values-sl/strings.xml b/car-ui-lib/res/values-sl/strings.xml
new file mode 100644
index 0000000..330494d
--- /dev/null
+++ b/car-ui-lib/res/values-sl/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Iskanje …"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Pomik navzdol"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Pomik navzgor"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Nazaj"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Išči"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Nastavitve"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Element menija z dodatnimi elementi"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"V redu"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Prekliči"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Vklopljeno"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Izklopljeno"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Zapri"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Pomikanje je med vožnjo omejeno"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funkcija med vožnjo ni na voljo"</string>
+</resources>
diff --git a/car-ui-lib/res/values-sq/strings.xml b/car-ui-lib/res/values-sq/strings.xml
new file mode 100644
index 0000000..e7cae6b
--- /dev/null
+++ b/car-ui-lib/res/values-sq/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Kërko…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Lëviz poshtë"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Lëviz lart"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Pas"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Kërko"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Cilësimet"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Tejkalo"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Në rregull"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Anulo"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Aktiv"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Joaktiv"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Mbyll"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Lëvizja është e kufizuar gjatë drejtimit të makinës"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Veçoria nuk ofrohet gjatë drejtimit të makinës"</string>
+</resources>
diff --git a/car-ui-lib/res/values-sr/strings.xml b/car-ui-lib/res/values-sr/strings.xml
new file mode 100644
index 0000000..0b63d3f
--- /dev/null
+++ b/car-ui-lib/res/values-sr/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Претражите…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Померите надоле"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Померите нагоре"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Назад"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Претражи"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Подешавања"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Преклопни мени"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Потврди"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Откажи"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Укључено"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Искључено"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Затвори"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Померање је ограничено током вожње"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Функција није доступна током вожње"</string>
+</resources>
diff --git a/car-ui-lib/res/values-sv/strings.xml b/car-ui-lib/res/values-sv/strings.xml
new file mode 100644
index 0000000..375ac74
--- /dev/null
+++ b/car-ui-lib/res/values-sv/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Sök …"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Scrolla nedåt"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Scrolla uppåt"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Tillbaka"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Sök"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Inställningar"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Fler menyalternativ"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Avbryt"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"På"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Av"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Stäng"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Scrollning begränsas när du kör"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Funktionen är inte tillgänglig när du kör"</string>
+</resources>
diff --git a/car-ui-lib/res/values-sw/strings.xml b/car-ui-lib/res/values-sw/strings.xml
new file mode 100644
index 0000000..a2b549f
--- /dev/null
+++ b/car-ui-lib/res/values-sw/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Tafuta…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Sogeza chini"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Sogeza juu"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Nyuma"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Tafuta"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Mipangilio"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Vipengee vya ziada"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Sawa"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Ghairi"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Imewashwa"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Imezimwa"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Funga"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Umedhibitiwa kusogeza unapoendesha gari"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Kipengele hakipatikani unapoendesha gari"</string>
+</resources>
diff --git a/car-ui-lib/res/values-ta/strings.xml b/car-ui-lib/res/values-ta/strings.xml
new file mode 100644
index 0000000..4ca575a
--- /dev/null
+++ b/car-ui-lib/res/values-ta/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"தேடுக…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"கீழே செல்லும்"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"மேலே செல்லும்"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"பின்செல்வதற்கான பட்டன்"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"அமைப்புகள்"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"ஓவர்ஃப்லோ"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"சரி"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"ரத்துசெய்"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"ஆன்"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ஆஃப்"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"மூடுக"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"வாகனம் ஓட்டிக் கொண்டிருப்பதனால் இதற்குமேல் ஸ்க்ரோல் செய்ய முடியாது"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"வாகனம் ஓட்டும்போது இந்த அம்சத்தைப் பயன்படுத்த இயலாது"</string>
+</resources>
diff --git a/car-ui-lib/res/values-te/strings.xml b/car-ui-lib/res/values-te/strings.xml
new file mode 100644
index 0000000..39e4c3e
--- /dev/null
+++ b/car-ui-lib/res/values-te/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"వెతకండి…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"కిందికి స్క్రోల్ చేయండి"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"పైకి స్క్రోల్ చేయండి"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"వెనుకకు"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"సెట్టింగ్‌లు"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"అతివ్యాప్తి అంశాలు"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"సరే"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"రద్దు చేయండి"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"ఆన్‌లో ఉంది"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ఆఫ్‌లో ఉంది"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"మూసివేయండి"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"డ్రైవింగ్ చేస్తున్నప్పుడు స్క్రోలింగ్ పరిమితంగా ఉంటుంది"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"డ్రైవింగ్ చేస్తున్నప్పుడు ఈ ఫీచర్ అందుబాటులో ఉండదు"</string>
+</resources>
diff --git a/car-ui-lib/res/values-th/strings.xml b/car-ui-lib/res/values-th/strings.xml
new file mode 100644
index 0000000..fa43930
--- /dev/null
+++ b/car-ui-lib/res/values-th/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"ค้นหา…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"เลื่อนลง"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"เลื่อนขึ้น"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"กลับ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"ค้นหา"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"การตั้งค่า"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"รายการเพิ่มเติม"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ตกลง"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"ยกเลิก"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"เปิด"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ปิด"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"ปิด"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"การเลื่อนถูกจำกัดไม่ให้ใช้งานขณะขับรถ"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"ฟีเจอร์ไม่พร้อมใช้งานขณะขับรถ"</string>
+</resources>
diff --git a/car-ui-lib/res/values-tl/strings.xml b/car-ui-lib/res/values-tl/strings.xml
new file mode 100644
index 0000000..7a95e0e
--- /dev/null
+++ b/car-ui-lib/res/values-tl/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Maghanap…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Mag-scroll pababa"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Mag-scroll pataas"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Bumalik"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Paghahanap"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Mga Setting"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Overflow"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Ok"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Kanselahin"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"I-on"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"I-off"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Isara"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Limitado ang pag-scroll habang nagmamaneho"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Hindi available ang feature habang nagmamaneho"</string>
+</resources>
diff --git a/car-ui-lib/res/values-tr/strings.xml b/car-ui-lib/res/values-tr/strings.xml
new file mode 100644
index 0000000..3a56e11
--- /dev/null
+++ b/car-ui-lib/res/values-tr/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Ara…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Aşağı kaydır"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Yukarı kaydır"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Geri"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Ara"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Ayarlar"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Taşma"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Tamam"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"İptal"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Açık"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Kapalı"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Kapat"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Sürüş sırasında ekran kaydırma işlevi sınırlandırılmıştır"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Sürüş sırasında bu özellik kullanılamaz"</string>
+</resources>
diff --git a/car-ui-lib/res/values-uk/strings.xml b/car-ui-lib/res/values-uk/strings.xml
new file mode 100644
index 0000000..4188f47
--- /dev/null
+++ b/car-ui-lib/res/values-uk/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Пошук…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Прокрутити вниз"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Прокрутити вгору"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Назад"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Пошук"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Налаштування"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Додаткове меню"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Скасувати"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Увімкнено"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Вимкнено"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Закрити"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Прокручування обмежено під час водіння"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Функція недоступна під час руху автомобіля"</string>
+</resources>
diff --git a/car-ui-lib/res/values-ur/strings.xml b/car-ui-lib/res/values-ur/strings.xml
new file mode 100644
index 0000000..feecf75
--- /dev/null
+++ b/car-ui-lib/res/values-ur/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"تلاش کریں…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"نیچے اسکرول کریں"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"اوپر اسکرول کریں"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"پیچھے"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"تلاش کریں"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"ترتیبات"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"اوورفلو"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"ٹھیک ہے"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"منسوخ کریں"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"آن ہے"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"آف ہے"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"بند کریں"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"ڈرائیونگ کے دوران اسکرولنگ محدود ہے"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"ڈرائیونگ کے دوران یہ خصوصیت دستیاب نہیں ہے"</string>
+</resources>
diff --git a/car-ui-lib/res/values-uz/strings.xml b/car-ui-lib/res/values-uz/strings.xml
new file mode 100644
index 0000000..2cda4e9
--- /dev/null
+++ b/car-ui-lib/res/values-uz/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Qidirish…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Pastga surish"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Tepaga surish"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Orqaga"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Qidiruv"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Sozlamalar"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Kengaytirilgan"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"OK"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Bekor qilish"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Yoniq"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Yoqilmagan"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Yopish"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Avtomobilda harakatlanayotganda aylantirish funksiyasi cheklangan"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Avtomobilda harakatlanayotganda bu funksiya ishlamaydi"</string>
+</resources>
diff --git a/car-ui-lib/res/values-vi/strings.xml b/car-ui-lib/res/values-vi/strings.xml
new file mode 100644
index 0000000..2950aac
--- /dev/null
+++ b/car-ui-lib/res/values-vi/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Tìm kiếm…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Cuộn xuống"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Cuộn lên"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Quay lại"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Tìm kiếm"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Cài đặt"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Trình đơn mục bổ sung"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Ok"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Hủy"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Đang bật"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Đang tắt"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Đóng"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Tính năng cuộn bị hạn chế khi đang lái xe"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Bạn không sử dụng được tính năng này khi đang lái xe"</string>
+</resources>
diff --git a/car-ui-lib/res/values-w1280dp/dimens.xml b/car-ui-lib/res/values-w1280dp/dimens.xml
deleted file mode 100644
index a06df2b..0000000
--- a/car-ui-lib/res/values-w1280dp/dimens.xml
+++ /dev/null
@@ -1,22 +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">
-    <!-- Keylines -->
-    <dimen name="car_ui_keyline_1">32dp</dimen>
-    <dimen name="car_ui_keyline_2">108dp</dimen>
-    <dimen name="car_ui_keyline_3">128dp</dimen>
-    <dimen name="car_ui_keyline_4">168dp</dimen>
-</resources>
diff --git a/car-ui-lib/res/values-zh-rCN/strings.xml b/car-ui-lib/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..883d637
--- /dev/null
+++ b/car-ui-lib/res/values-zh-rCN/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"搜索…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"向下滚动"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"向上滚动"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"返回"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"搜索"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"设置"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"溢出菜单"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"确定"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"取消"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"开启"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"关闭"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"关闭"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"驾车时滚动操作受限"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"驾车时无法使用此功能"</string>
+</resources>
diff --git a/car-ui-lib/res/values-zh-rHK/strings.xml b/car-ui-lib/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..f215fa5
--- /dev/null
+++ b/car-ui-lib/res/values-zh-rHK/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"搜尋…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"向下捲動"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"向上捲動"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"返回"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"搜尋"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"設定"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"展開式選單"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"確定"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"取消"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"已開啟"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"已關閉"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"關閉"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"捲動功能在駕駛時受限制"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"無法在駕駛時使用此功能"</string>
+</resources>
diff --git a/car-ui-lib/res/values-zh-rTW/strings.xml b/car-ui-lib/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..20fb010
--- /dev/null
+++ b/car-ui-lib/res/values-zh-rTW/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"搜尋…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"向下捲動"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"向上捲動"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"返回"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"搜尋"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"設定"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"溢位"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"確定"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"取消"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"開啟"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"關閉"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"關閉"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"系統會限制開車時的捲動操作"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"開車時無法使用這項功能"</string>
+</resources>
diff --git a/car-ui-lib/res/values-zu/strings.xml b/car-ui-lib/res/values-zu/strings.xml
new file mode 100644
index 0000000..15aefff
--- /dev/null
+++ b/car-ui-lib/res/values-zu/strings.xml
@@ -0,0 +1,33 @@
+<?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">
+    <string name="car_ui_toolbar_default_search_hint" msgid="7807151740020494659">"Sesha…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="2419547312871553905">"Skrolela phansi"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="4152120100101092092">"Skrolela phezulu"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="2689756063478650269">"Emuva"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="8705757227447034030">"Sesha"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="6694060516340354337">"Izilungiselelo"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="771643815366812499">"Ukuphuphuma"</string>
+    <string name="car_ui_dialog_preference_positive" msgid="1918267574135176336">"Kulungile"</string>
+    <string name="car_ui_dialog_preference_negative" msgid="4156315694902086271">"Khansela"</string>
+    <string name="car_ui_preference_switch_on" msgid="5512335363135043642">"Vuliwe"</string>
+    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Valiwe"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="1775326289021319685">"Vala"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="1036509082539078267">"Ukuskrola kukhawulelwe uma ushayela"</string>
+    <string name="car_ui_restricted_while_driving" msgid="8401317496175957541">"Isici asitholakali ngenkathi ushayela"</string>
+</resources>
diff --git a/car-ui-lib/res/values/attrs.xml b/car-ui-lib/res/values/attrs.xml
index 8f7b3e0..5698d25 100644
--- a/car-ui-lib/res/values/attrs.xml
+++ b/car-ui-lib/res/values/attrs.xml
@@ -104,12 +104,14 @@
     <attr name="CarUiToolbarStyle" format="reference"/>
 
     <declare-styleable name="CarUiRecyclerView">
-        <!-- Whether to enable the car_ui_recyclerview_divider for linear layout or not. -->
+        <!-- Whether to enable the dividers or not. Linear and grid layout uses
+        car_ui_recyclerview_divider.xml and car_ui_divider.xml drawables
+        respectively for styling dividers. -->
         <attr name="enableDivider" format="boolean" />
         <!-- Top offset for car ui recycler view. -->
-        <attr name="startOffset" format="integer" />
+        <attr name="topOffset" format="integer" />
         <!-- Bottom offset for car ui recycler view for linear layout. -->
-        <attr name="endOffset" format="integer" />
+        <attr name="bottomOffset" format="integer" />
 
         <!-- Number of columns in a grid layout. -->
         <attr name="numOfColumns" format="integer" />
@@ -126,6 +128,8 @@
     <declare-styleable name="CarUiPreference">
         <!-- Toggle for showing chevron -->
         <attr name="showChevron" format="boolean" />
+        <!-- Show ripple when disabled preference is clicked -->
+        <attr name="showRippleOnDisabledPreference" format="boolean" />
     </declare-styleable>
 
     <!-- Theme attribute to specify a default style for all CarUiPreferences -->
diff --git a/car-ui-lib/res/values/dimens.xml b/car-ui-lib/res/values/dimens.xml
index 3a77194..95a15ff 100644
--- a/car-ui-lib/res/values/dimens.xml
+++ b/car-ui-lib/res/values/dimens.xml
@@ -39,12 +39,9 @@
     <dimen name="car_ui_body1_size">32sp</dimen>
     <dimen name="car_ui_body2_size">28sp</dimen>
     <dimen name="car_ui_body3_size">24sp</dimen>
-
-    <!-- Keylines -->
-    <dimen name="car_ui_keyline_1">24dp</dimen>
-    <dimen name="car_ui_keyline_2">96dp</dimen>
-    <dimen name="car_ui_keyline_3">112dp</dimen>
-    <dimen name="car_ui_keyline_4">148dp</dimen>
+    <dimen name="car_ui_sub1_size">22sp</dimen>
+    <dimen name="car_ui_sub2_size">20sp</dimen>
+    <dimen name="car_ui_sub3_size">18sp</dimen>
 
     <!-- Tabs -->
 
@@ -86,6 +83,8 @@
     <dimen name="car_ui_toolbar_title_logo_padding">0dp</dimen>
     <!-- Margin at the start of the title -->
     <dimen name="car_ui_toolbar_title_margin_start">@dimen/car_ui_padding_2</dimen>
+    <!-- Margin at the start of the title when there is no logo present -->
+    <dimen name="car_ui_toolbar_title_no_logo_margin_start">0dp</dimen>
     <!-- Space at the end and in between menu items -->
     <dimen name="car_ui_toolbar_menu_item_margin">@dimen/car_ui_padding_2</dimen>
     <!-- Ripple effect radius for icon menu items -->
@@ -128,6 +127,7 @@
     <dimen name="car_ui_scrollbar_container_width">@dimen/car_ui_margin</dimen>
     <dimen name="car_ui_scrollbar_button_size">@dimen/car_ui_touch_target_width</dimen>
     <dimen name="car_ui_scrollbar_thumb_width">7dp</dimen>
+    <dimen name="car_ui_scrollbar_min_thumb_height">56dp</dimen>
     <dimen name="car_ui_scrollbar_separator_margin">16dp</dimen>
     <dimen name="car_ui_scrollbar_margin">@dimen/car_ui_margin</dimen>
     <dimen name="car_ui_scrollbar_thumb_radius">100dp</dimen>
@@ -137,8 +137,8 @@
     <item name="car_ui_scrollbar_deceleration_times_divisor" format="float" type="dimen">0.45</item>
     <item name="car_ui_scrollbar_decelerate_interpolator_factor" format="float" type="dimen">1.8</item>
 
-    <dimen name="car_ui_scrollbar_padding_start">0dp</dimen>
-    <dimen name="car_ui_scrollbar_padding_end">0dp</dimen>
+    <dimen name="car_ui_scrollbar_padding_top">0dp</dimen>
+    <dimen name="car_ui_scrollbar_padding_bottom">0dp</dimen>
 
     <!-- Preferences -->
 
@@ -179,7 +179,7 @@
     <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_icon_size">56dp</dimen>
-    <dimen name="car_ui_dialog_title_margin">@dimen/car_ui_keyline_1</dimen>
+    <dimen name="car_ui_dialog_title_margin">24dp</dimen>
 
     <!-- List item  -->
 
@@ -191,6 +191,7 @@
     <dimen name="car_ui_list_item_header_start_inset">0dp</dimen>
     <dimen name="car_ui_list_item_start_inset">0dp</dimen>
     <dimen name="car_ui_list_item_end_inset">0dp</dimen>
+    <dimen name="car_ui_header_list_item_text_start_margin">0dp</dimen>
     <dimen name="car_ui_list_item_text_start_margin">24dp</dimen>
     <dimen name="car_ui_list_item_text_no_icon_start_margin">24dp</dimen>
 
diff --git a/car-ui-lib/res/values/integers.xml b/car-ui-lib/res/values/integers.xml
index 623ef00..1ffe098 100644
--- a/car-ui-lib/res/values/integers.xml
+++ b/car-ui-lib/res/values/integers.xml
@@ -17,4 +17,6 @@
 <resources>
     <!-- Default max string length -->
     <integer name="car_ui_default_max_string_length">120</integer>
-</resources>
\ No newline at end of file
+    <integer name="car_ui_scrollbar_longpress_initial_delay">1000</integer>
+    <integer name="car_ui_scrollbar_longpress_repeat_interval">100</integer>
+</resources>
diff --git a/car-ui-lib/res/values/strings.xml b/car-ui-lib/res/values/strings.xml
index a42068f..627c542 100644
--- a/car-ui-lib/res/values/strings.xml
+++ b/car-ui-lib/res/values/strings.xml
@@ -29,6 +29,8 @@
     <string name="car_ui_scrollbar_page_down_button">Scroll down</string>
     <!-- Content description for car ui recycler view scroll bar up arrow [CHAR LIMIT=30] -->
     <string name="car_ui_scrollbar_page_up_button">Scroll up</string>
+    <!-- The content description on the toolbar back button -->
+    <string name="car_ui_toolbar_nav_icon_content_description">Back</string>
     <!-- Title of the search menu item. Will be displayed if the button is in the overflow menu. [CHAR_LIMIT=50] -->
     <string name="car_ui_toolbar_menu_item_search_title">Search</string>
     <!-- Title of the settings menu item. Will be displayed if the button is in the overflow menu. [CHAR_LIMIT=50] -->
@@ -51,7 +53,10 @@
     <string name="car_ui_list_item_header_font_family" translatable="false">sans-serif-medium</string>
 
     <!-- Text to show when no button is provided and a default button is used. -->
-    <string name="car_ui_alert_dialog_default_button" translatable="false">Close</string>
+    <string name="car_ui_alert_dialog_default_button">Close</string>
+
+    <!-- Shown at the bottom of a content limited list when user has scrolled past the limit while driving -->
+    <string name="car_ui_scrolling_limited_message">Scrolling limited while driving</string>
 
     <!-- Shown in a toast when the user attempts to do something distracting while driving [CHAR_LIMIT=200] -->
     <string name="car_ui_restricted_while_driving">Feature not available while driving</string>
diff --git a/car-ui-lib/res/values/styles.xml b/car-ui-lib/res/values/styles.xml
index 9cbf41e..77dd80c 100644
--- a/car-ui-lib/res/values/styles.xml
+++ b/car-ui-lib/res/values/styles.xml
@@ -49,14 +49,19 @@
     </style>
 
     <style name="Widget.CarUi.Toolbar.Title">
-        <item name="android:layout_marginStart">@dimen/car_ui_toolbar_title_margin_start</item>
         <item name="android:textAppearance">@style/TextAppearance.CarUi.Widget.Toolbar.Title</item>
-        <item name="android:textDirection">locale</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.TextButton" parent="Widget.CarUi.Button.Borderless.Colored">
         <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>
     </style>
 
     <style name="Widget.CarUi.Toolbar.TextButton.WithIcon">
@@ -230,6 +235,7 @@
 
     <style name="TextAppearance.CarUi" parent="android:TextAppearance.DeviceDefault">
         <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textAlignment">viewStart</item>
     </style>
 
     <style name="TextAppearance.CarUi.Body1">
@@ -244,6 +250,18 @@
         <item name="android:textSize">@dimen/car_ui_body3_size</item>
     </style>
 
+    <style name="TextAppearance.CarUi.Sub1">
+        <item name="android:textSize">@dimen/car_ui_sub1_size</item>
+    </style>
+
+    <style name="TextAppearance.CarUi.Sub2">
+        <item name="android:textSize">@dimen/car_ui_sub2_size</item>
+    </style>
+
+    <style name="TextAppearance.CarUi.Sub3">
+        <item name="android:textSize">@dimen/car_ui_sub3_size</item>
+    </style>
+
     <style name="TextAppearance.CarUi.PreferenceCategoryTitle">
         <item name="android:fontFamily">@string/car_ui_preference_category_title_font_family</item>
         <item name="android:textColor">@color/car_ui_preference_category_title_text_color</item>
@@ -265,9 +283,12 @@
         <item name="android:textSize">@dimen/car_ui_preference_edit_text_dialog_message_text_size</item>
     </style>
 
-    <style name="TextAppearance.CarUi.AlertDialog.Subtitle" parent="android:TextAppearance.DeviceDefault"/>
+    <style name="TextAppearance.CarUi.AlertDialog.Title" parent="TextAppearance.CarUi.Body3"/>
+    <style name="TextAppearance.CarUi.AlertDialog.Subtitle" parent="TextAppearance.CarUi.Sub3"/>
 
-    <style name="TextAppearance.CarUi.Widget" parent="android:TextAppearance.DeviceDefault.Widget"/>
+    <style name="TextAppearance.CarUi.Widget" parent="android:TextAppearance.DeviceDefault.Widget">
+        <item name="android:textAlignment">viewStart</item>
+    </style>
 
     <style name="TextAppearance.CarUi.Widget.Toolbar"/>
 
diff --git a/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java b/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java
index 4803b45..bb301e3 100644
--- a/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java
+++ b/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java
@@ -22,6 +22,7 @@
 import android.database.Cursor;
 import android.graphics.drawable.Drawable;
 import android.text.InputFilter;
+import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -41,6 +42,7 @@
 
 import com.android.car.ui.recyclerview.CarUiListItemAdapter;
 import com.android.car.ui.recyclerview.CarUiRadioButtonListItemAdapter;
+import com.android.car.ui.utils.CarUiUtils;
 
 /**
  * Wrapper for AlertDialog.Builder
@@ -354,7 +356,7 @@
     private void setCustomList(@NonNull CarUiListItemAdapter adapter) {
         View customList = LayoutInflater.from(mContext).inflate(
                 R.layout.car_ui_alert_dialog_list, null);
-        RecyclerView mList = customList.requireViewById(R.id.list);
+        RecyclerView mList = CarUiUtils.requireViewByRefId(customList, R.id.list);
         mList.setLayoutManager(new LinearLayoutManager(mContext));
         mList.setAdapter(adapter);
         mBuilder.setView(customList);
@@ -587,7 +589,7 @@
         View contentView = LayoutInflater.from(mContext).inflate(
                 R.layout.car_ui_alert_dialog_edit_text, null);
 
-        EditText editText = contentView.requireViewById(R.id.textbox);
+        EditText editText = CarUiUtils.requireViewByRefId(contentView, R.id.textbox);
         editText.setText(prompt);
 
         if (textChangedListener != null) {
@@ -623,21 +625,23 @@
 
     /** Final steps common to both {@link #create()} and {@link #show()} */
     private void prepareDialog() {
-        if (mSubtitle != null) {
+        View customTitle = LayoutInflater.from(mContext).inflate(
+                R.layout.car_ui_alert_dialog_title_with_subtitle, null);
 
-            View customTitle = LayoutInflater.from(mContext).inflate(
-                    R.layout.car_ui_alert_dialog_title_with_subtitle, null);
+        TextView mTitleView =
+                CarUiUtils.requireViewByRefId(customTitle, R.id.car_ui_alert_title);
+        TextView mSubtitleView =
+                CarUiUtils.requireViewByRefId(customTitle, R.id.car_ui_alert_subtitle);
+        ImageView mIconView =
+                CarUiUtils.requireViewByRefId(customTitle, R.id.car_ui_alert_icon);
 
-            TextView mTitleView = customTitle.requireViewById(R.id.alertTitle);
-            TextView mSubtitleView = customTitle.requireViewById(R.id.alertSubtitle);
-            ImageView mIconView = customTitle.requireViewById(R.id.icon);
-
-            mTitleView.setText(mTitle);
-            mSubtitleView.setText(mSubtitle);
-            mIconView.setImageDrawable(mIcon);
-            mIconView.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
-            mBuilder.setCustomTitle(customTitle);
-        }
+        mTitleView.setText(mTitle);
+        mTitleView.setVisibility(TextUtils.isEmpty(mTitle) ? View.GONE : View.VISIBLE);
+        mSubtitleView.setText(mSubtitle);
+        mSubtitleView.setVisibility(TextUtils.isEmpty(mSubtitle) ? View.GONE : View.VISIBLE);
+        mIconView.setImageDrawable(mIcon);
+        mIconView.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
+        mBuilder.setCustomTitle(customTitle);
 
         if (!mNeutralButtonSet && !mNegativeButtonSet && !mPositiveButtonSet) {
             String mDefaultButtonText = mContext.getString(
diff --git a/car-ui-lib/src/com/android/car/ui/baselayout/ClickBlockingView.java b/car-ui-lib/src/com/android/car/ui/baselayout/ClickBlockingView.java
new file mode 100644
index 0000000..181495c
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/baselayout/ClickBlockingView.java
@@ -0,0 +1,106 @@
+/*
+ * 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.baselayout;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A view that doesn't allow any touches to pass through it to views below.
+ *
+ * <p>Used in baselayouts to prevent clicking through the toolbar.
+ */
+public class ClickBlockingView extends View {
+
+    private boolean mEatingTouch = false;
+    private boolean mEatingHover = false;
+
+    public ClickBlockingView(Context context) {
+        super(context);
+    }
+
+    public ClickBlockingView(Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ClickBlockingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public ClickBlockingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        // Copied from androidx.appcompat.widget.Toolbar
+
+        // We always eat touch events, but should still respect the touch event dispatch
+        // contract. If the normal View implementation doesn't want the events, we'll just silently
+        // eat the rest of the gesture without reporting the events to the default implementation
+        // since that's what it expects.
+
+        final int action = ev.getActionMasked();
+        if (action == MotionEvent.ACTION_DOWN) {
+            mEatingTouch = false;
+        }
+
+        if (!mEatingTouch) {
+            final boolean handled = super.onTouchEvent(ev);
+            if (action == MotionEvent.ACTION_DOWN && !handled) {
+                mEatingTouch = true;
+            }
+        }
+
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            mEatingTouch = false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onHoverEvent(MotionEvent ev) {
+        // Copied from androidx.appcompat.widget.Toolbar
+
+        // Same deal as onTouchEvent() above. Eat all hover events, but still
+        // respect the touch event dispatch contract.
+
+        final int action = ev.getActionMasked();
+        if (action == MotionEvent.ACTION_HOVER_ENTER) {
+            mEatingHover = false;
+        }
+
+        if (!mEatingHover) {
+            final boolean handled = super.onHoverEvent(ev);
+            if (action == MotionEvent.ACTION_HOVER_ENTER && !handled) {
+                mEatingHover = true;
+            }
+        }
+
+        if (action == MotionEvent.ACTION_HOVER_EXIT || action == MotionEvent.ACTION_CANCEL) {
+            mEatingHover = false;
+        }
+
+        return true;
+    }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/baselayout/InsetsChangedListener.java b/car-ui-lib/src/com/android/car/ui/baselayout/InsetsChangedListener.java
index 595ad06..8902e1e 100644
--- a/car-ui-lib/src/com/android/car/ui/baselayout/InsetsChangedListener.java
+++ b/car-ui-lib/src/com/android/car/ui/baselayout/InsetsChangedListener.java
@@ -16,6 +16,8 @@
 
 package com.android.car.ui.baselayout;
 
+import androidx.annotation.NonNull;
+
 /**
  * Interface for receiving changes to {@link Insets}.
  *
@@ -27,5 +29,5 @@
  */
 public interface InsetsChangedListener {
     /** Called when the insets change */
-    void onCarUiInsetsChanged(Insets insets);
+    void onCarUiInsetsChanged(@NonNull Insets insets);
 }
diff --git a/car-ui-lib/src/com/android/car/ui/core/BaseLayoutController.java b/car-ui-lib/src/com/android/car/ui/core/BaseLayoutController.java
index f488e9c..e1209d9 100644
--- a/car-ui-lib/src/com/android/car/ui/core/BaseLayoutController.java
+++ b/car-ui-lib/src/com/android/car/ui/core/BaseLayoutController.java
@@ -15,10 +15,9 @@
  */
 package com.android.car.ui.core;
 
-import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
-
 import android.app.Activity;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -36,9 +35,10 @@
 import com.android.car.ui.baselayout.InsetsChangedListener;
 import com.android.car.ui.toolbar.ToolbarController;
 import com.android.car.ui.toolbar.ToolbarControllerImpl;
+import com.android.car.ui.utils.CarUiUtils;
 
-import java.util.HashMap;
 import java.util.Map;
+import java.util.WeakHashMap;
 
 /**
  * BaseLayoutController accepts an {@link Activity} and sets up the base layout inside of it.
@@ -47,7 +47,7 @@
  */
 class BaseLayoutController {
 
-    private static Map<Activity, BaseLayoutController> sBaseLayoutMap = new HashMap<>();
+    private static final Map<Activity, BaseLayoutController> sBaseLayoutMap = new WeakHashMap<>();
 
     private InsetsUpdater mInsetsUpdater;
 
@@ -72,14 +72,18 @@
      *
      * <p>You can get a reference to it by calling {@link #getBaseLayout(Activity)}.
      */
-    /* package */ static void build(Activity activity) {
-        sBaseLayoutMap.put(activity, new BaseLayoutController(activity));
+    /* package */
+    static void build(Activity activity) {
+        if (getThemeBoolean(activity, R.attr.carUiBaseLayout)) {
+            sBaseLayoutMap.put(activity, new BaseLayoutController(activity));
+        }
     }
 
     /**
      * Destroy the BaseLayoutController for the given {@link Activity}.
      */
-    /* package */ static void destroy(Activity activity) {
+    /* package */
+    static void destroy(Activity activity) {
         sBaseLayoutMap.remove(activity);
     }
 
@@ -96,6 +100,14 @@
         return mInsetsUpdater.getInsets();
     }
 
+    /* package */ void dispatchNewInsets(Insets insets) {
+        mInsetsUpdater.dispatchNewInsets(insets);
+    }
+
+    /* package */ void replaceInsetsChangedListenerWith(InsetsChangedListener listener) {
+        mInsetsUpdater.replaceInsetsChangedListenerWith(listener);
+    }
+
     /**
      * Installs the base layout into an activity, moving its content view under the base layout.
      *
@@ -104,34 +116,42 @@
      * @param activity The {@link Activity} to install a base layout in.
      */
     private void installBaseLayout(Activity activity) {
-        boolean baseLayoutEnabled = getThemeBoolean(activity, R.attr.carUiBaseLayout);
         boolean toolbarEnabled = getThemeBoolean(activity, R.attr.carUiToolbar);
-        if (!baseLayoutEnabled) {
-            return;
-        }
+        boolean legacyToolbar = Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q;
+        @LayoutRes final int baseLayoutRes;
 
-        @LayoutRes final int baseLayoutRes = toolbarEnabled
-                ? R.layout.car_ui_base_layout_toolbar
-                : R.layout.car_ui_base_layout;
+        if (toolbarEnabled) {
+            baseLayoutRes = legacyToolbar
+                    ? R.layout.car_ui_base_layout_toolbar_legacy
+                    : R.layout.car_ui_base_layout_toolbar;
+        } else {
+            baseLayoutRes = R.layout.car_ui_base_layout;
+        }
 
         View baseLayout = LayoutInflater.from(activity)
                 .inflate(baseLayoutRes, null, false);
 
         // Replace windowContentView with baseLayout
-        ViewGroup windowContentView = activity.getWindow().findViewById(android.R.id.content);
+        ViewGroup windowContentView = CarUiUtils.findViewByRefId(
+                activity.getWindow().getDecorView(), android.R.id.content);
         ViewGroup contentViewParent = (ViewGroup) windowContentView.getParent();
         int contentIndex = contentViewParent.indexOfChild(windowContentView);
         contentViewParent.removeView(windowContentView);
         contentViewParent.addView(baseLayout, contentIndex, windowContentView.getLayoutParams());
 
         // Add windowContentView to the baseLayout's content view
-        FrameLayout contentView = requireViewByRefId(baseLayout, R.id.content);
+        FrameLayout contentView = CarUiUtils.requireViewByRefId(baseLayout,
+                R.id.car_ui_base_layout_content_container);
         contentView.addView(windowContentView, new FrameLayout.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT));
 
         if (toolbarEnabled) {
-            mToolbarController = new ToolbarControllerImpl(baseLayout);
+            if (legacyToolbar) {
+                mToolbarController = CarUiUtils.requireViewByRefId(baseLayout, R.id.car_ui_toolbar);
+            } else {
+                mToolbarController = new ToolbarControllerImpl(baseLayout);
+            }
         }
 
         mInsetsUpdater = new InsetsUpdater(activity, baseLayout, windowContentView);
@@ -142,8 +162,8 @@
      * Gets the boolean value of an Attribute from an {@link Activity Activity's}
      * {@link android.content.res.Resources.Theme}.
      */
-    private boolean getThemeBoolean(Activity activity, int attr) {
-        TypedArray a = activity.getTheme().obtainStyledAttributes(new int[] { attr });
+    private static boolean getThemeBoolean(Activity activity, int attr) {
+        TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{attr});
 
         try {
             return a.getBoolean(0, false);
@@ -176,6 +196,7 @@
         private final View mRightInsetView;
         private final View mTopInsetView;
         private final View mBottomInsetView;
+        private InsetsChangedListener mInsetsChangedListenerDelegate;
 
         private boolean mInsetsDirty = true;
         @NonNull
@@ -184,8 +205,8 @@
         /**
          * Constructs an InsetsUpdater that calculates and dispatches insets to an {@link Activity}.
          *
-         * @param activity The activity that is using base layouts
-         * @param baseLayout The root view of the base layout
+         * @param activity    The activity that is using base layouts
+         * @param baseLayout  The root view of the base layout
          * @param contentView The android.R.id.content View
          */
         InsetsUpdater(Activity activity, View baseLayout, View contentView) {
@@ -236,6 +257,10 @@
             return mInsets;
         }
 
+        public void replaceInsetsChangedListenerWith(InsetsChangedListener listener) {
+            mInsetsChangedListenerDelegate = listener;
+        }
+
         /**
          * onGlobalLayout() should recalculate the amount of insets we need, and then dispatch them.
          */
@@ -245,11 +270,14 @@
                 return;
             }
 
-            View content = mActivity.requireViewById(android.R.id.content);
+            View content = CarUiUtils.requireViewByRefId(mActivity.getWindow().getDecorView(),
+                    android.R.id.content);
 
             // Calculate how much each inset view overlays the content view
-            int top, bottom, left, right;
-            top = bottom = left = right = 0;
+            int top = 0;
+            int left = 0;
+            int right = 0;
+            int bottom = 0;
             if (mTopInsetView != null) {
                 top = Math.max(0, getBottomOfView(mTopInsetView) - getTopOfView(content));
             }
@@ -278,26 +306,37 @@
          *
          * @param insets The newly-changed insets.
          */
-        private void dispatchNewInsets(Insets insets) {
-            boolean handled = false;
-            if (mActivity instanceof InsetsChangedListener) {
-                ((InsetsChangedListener) mActivity).onCarUiInsetsChanged(insets);
-                handled = true;
-            }
+        /* package */ void dispatchNewInsets(Insets insets) {
+            mInsets = insets;
 
-            if (mActivity instanceof FragmentActivity) {
-                for (Fragment fragment : ((FragmentActivity) mActivity).getSupportFragmentManager()
-                        .getFragments()) {
-                    if (fragment instanceof InsetsChangedListener) {
-                        ((InsetsChangedListener) fragment).onCarUiInsetsChanged(insets);
-                        handled = true;
+            boolean handled = false;
+
+            if (mInsetsChangedListenerDelegate != null) {
+                mInsetsChangedListenerDelegate.onCarUiInsetsChanged(insets);
+                handled = true;
+            } else {
+                // If an explicit InsetsChangedListener is not provided,
+                // pass the insets to activities and fragments
+                if (mActivity instanceof InsetsChangedListener) {
+                    ((InsetsChangedListener) mActivity).onCarUiInsetsChanged(insets);
+                    handled = true;
+                }
+
+                if (mActivity instanceof FragmentActivity) {
+                    for (Fragment fragment : ((FragmentActivity) mActivity)
+                            .getSupportFragmentManager().getFragments()) {
+                        if (fragment instanceof InsetsChangedListener) {
+                            ((InsetsChangedListener) fragment).onCarUiInsetsChanged(insets);
+                            handled = true;
+                        }
                     }
                 }
             }
 
             if (!handled) {
-                mActivity.requireViewById(android.R.id.content).setPadding(
-                        insets.getLeft(), insets.getTop(), insets.getRight(), insets.getBottom());
+                CarUiUtils.requireViewByRefId(mActivity.getWindow().getDecorView(),
+                        android.R.id.content).setPadding(insets.getLeft(), insets.getTop(),
+                        insets.getRight(), insets.getBottom());
             }
         }
 
diff --git a/car-ui-lib/src/com/android/car/ui/core/CarUi.java b/car-ui-lib/src/com/android/car/ui/core/CarUi.java
index 21050be..ad67121 100644
--- a/car-ui-lib/src/com/android/car/ui/core/CarUi.java
+++ b/car-ui-lib/src/com/android/car/ui/core/CarUi.java
@@ -21,13 +21,19 @@
 import androidx.annotation.Nullable;
 
 import com.android.car.ui.baselayout.Insets;
+import com.android.car.ui.baselayout.InsetsChangedListener;
 import com.android.car.ui.toolbar.ToolbarController;
 
+import java.lang.reflect.Method;
+
 /**
  * Public interface for general CarUi static functions.
  */
 public class CarUi {
 
+    /** Prevent instantiating this class */
+    private CarUi() {}
+
     /**
      * Gets the {@link ToolbarController} for an activity. Requires that the Activity uses
      * Theme.CarUi.WithToolbar, or otherwise sets carUiBaseLayout and carUiToolbar to true.
@@ -36,7 +42,7 @@
      */
     @Nullable
     public static ToolbarController getToolbar(Activity activity) {
-        BaseLayoutController controller = BaseLayoutController.getBaseLayout(activity);
+        BaseLayoutController controller = getBaseLayoutController(activity);
         if (controller != null) {
             return controller.getToolbarController();
         }
@@ -55,14 +61,26 @@
     public static ToolbarController requireToolbar(Activity activity) {
         ToolbarController result = getToolbar(activity);
         if (result == null) {
-            throw new IllegalArgumentException("Activity does not have a CarUi Toolbar! "
-                    + "Are you using Theme.CarUi.WithToolbar?");
+            throw new IllegalArgumentException("Activity " + activity
+                    + " does not have a CarUi Toolbar!"
+                    + " Are you using Theme.CarUi.WithToolbar?");
         }
 
         return result;
     }
 
     /**
+     * Registering a listener to receive the InsetsChanged updates instead of the Activity.
+     */
+    public static void replaceInsetsChangedListenerWith(Activity activity,
+            InsetsChangedListener listener) {
+        BaseLayoutController controller = getBaseLayoutController(activity);
+        if (controller != null) {
+            controller.replaceInsetsChangedListenerWith(listener);
+        }
+    }
+
+    /**
      * Gets the current {@link Insets} of the given {@link Activity}. Only applies to Activities
      * using the base layout, ie have the theme attribute "carUiBaseLayout" set to true.
      *
@@ -72,7 +90,7 @@
      */
     @Nullable
     public static Insets getInsets(Activity activity) {
-        BaseLayoutController controller = BaseLayoutController.getBaseLayout(activity);
+        BaseLayoutController controller = getBaseLayoutController(activity);
         if (controller != null) {
             return controller.getInsets();
         }
@@ -93,10 +111,30 @@
     public static Insets requireInsets(Activity activity) {
         Insets result = getInsets(activity);
         if (result == null) {
-            throw new IllegalArgumentException("Activity does not have a base layout! "
-                    + "Are you using Theme.CarUi.WithToolbar or Theme.CarUi.NoToolbar?");
+            throw new IllegalArgumentException("Activity " + activity
+                    + " does not have a base layout!"
+                    + " Are you using Theme.CarUi.WithToolbar or Theme.CarUi.NoToolbar?");
         }
 
         return result;
     }
+
+    /* package */ static BaseLayoutController getBaseLayoutController(Activity activity) {
+        if (activity.getClassLoader().equals(CarUi.class.getClassLoader())) {
+            return BaseLayoutController.getBaseLayout(activity);
+        } else {
+            // Note: (b/156532465)
+            // The usage of the alternate classloader is to accommodate GMSCore.
+            // Some activities are loaded dynamically from external modules.
+            try {
+                Class baseLayoutControllerClass = activity.getClassLoader()
+                        .loadClass(BaseLayoutController.class.getName());
+                Method method = baseLayoutControllerClass
+                        .getDeclaredMethod("getBaseLayout", Activity.class);
+                return (BaseLayoutController) method.invoke(null, activity);
+            } catch (ReflectiveOperationException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/core/CarUiInstaller.java b/car-ui-lib/src/com/android/car/ui/core/CarUiInstaller.java
index 1aaa375..7e725ae 100644
--- a/car-ui-lib/src/com/android/car/ui/core/CarUiInstaller.java
+++ b/car-ui-lib/src/com/android/car/ui/core/CarUiInstaller.java
@@ -15,17 +15,25 @@
  */
 package com.android.car.ui.core;
 
+import static com.android.car.ui.core.CarUi.getBaseLayoutController;
+
 import android.app.Activity;
 import android.app.Application;
 import android.content.ContentProvider;
 import android.content.ContentValues;
+import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.car.ui.baselayout.Insets;
+
+import java.lang.reflect.Method;
+
 /**
  * {@link ContentProvider ContentProvider's} onCreate() methods are "called for all registered
  * content providers on the application main thread at application launch time." This means we
@@ -34,14 +42,55 @@
  */
 public class CarUiInstaller extends ContentProvider {
 
+    private static final String TAG = "CarUiInstaller";
+    private static final String CAR_UI_INSET_LEFT = "CAR_UI_INSET_LEFT";
+    private static final String CAR_UI_INSET_RIGHT = "CAR_UI_INSET_RIGHT";
+    private static final String CAR_UI_INSET_TOP = "CAR_UI_INSET_TOP";
+    private static final String CAR_UI_INSET_BOTTOM = "CAR_UI_INSET_BOTTOM";
+
     @Override
     public boolean onCreate() {
-        Application application = (Application) getContext().getApplicationContext();
+        Context context = getContext();
+        if (context == null) {
+            Log.e(TAG, "CarUiInstaller had a null context!");
+            return false;
+        }
+        Log.i(TAG, "CarUiInstaller started for " + context.getPackageName());
+
+        Application application = (Application) context.getApplicationContext();
         application.registerActivityLifecycleCallbacks(
                 new Application.ActivityLifecycleCallbacks() {
+                    private Insets mInsets = null;
+                    private boolean mIsActivityStartedForFirstTime = false;
+
                     @Override
                     public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
-                        BaseLayoutController.build(activity);
+                        if (activity.getClassLoader()
+                                .equals(CarUiInstaller.class.getClassLoader())) {
+                            BaseLayoutController.build(activity);
+                        } else {
+                            callBaseLayoutControllerMethod("build", activity);
+                        }
+
+                        if (savedInstanceState != null) {
+                            int inset_left = savedInstanceState.getInt(CAR_UI_INSET_LEFT);
+                            int inset_top = savedInstanceState.getInt(CAR_UI_INSET_TOP);
+                            int inset_right = savedInstanceState.getInt(CAR_UI_INSET_RIGHT);
+                            int inset_bottom = savedInstanceState.getInt(CAR_UI_INSET_BOTTOM);
+                            mInsets = new Insets(inset_left, inset_top, inset_right, inset_bottom);
+                        }
+
+                        mIsActivityStartedForFirstTime = true;
+                    }
+
+                    @Override
+                    public void onActivityPostStarted(Activity activity) {
+                        BaseLayoutController controller = getBaseLayoutController(activity);
+                        if (mInsets != null && controller != null
+                                && mIsActivityStartedForFirstTime) {
+                            controller.dispatchNewInsets(mInsets);
+                            mIsActivityStartedForFirstTime = false;
+                        }
                     }
 
                     @Override
@@ -62,13 +111,27 @@
 
                     @Override
                     public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+                        BaseLayoutController controller = getBaseLayoutController(activity);
+                        if (controller != null) {
+                            Insets insets = controller.getInsets();
+                            outState.putInt(CAR_UI_INSET_LEFT, insets.getLeft());
+                            outState.putInt(CAR_UI_INSET_TOP, insets.getTop());
+                            outState.putInt(CAR_UI_INSET_RIGHT, insets.getRight());
+                            outState.putInt(CAR_UI_INSET_BOTTOM, insets.getBottom());
+                        }
                     }
 
                     @Override
                     public void onActivityDestroyed(Activity activity) {
-                        BaseLayoutController.destroy(activity);
+                        if (activity.getClassLoader()
+                                .equals(CarUiInstaller.class.getClassLoader())) {
+                            BaseLayoutController.destroy(activity);
+                        } else {
+                            callBaseLayoutControllerMethod("destroy", activity);
+                        }
                     }
                 });
+
         return true;
     }
 
@@ -102,4 +165,20 @@
             @Nullable String[] selectionArgs) {
         return 0;
     }
+
+    private static void callBaseLayoutControllerMethod(String methodName, Activity activity) {
+        // Note: (b/156532465)
+        // The usage of the alternate classloader is to accommodate GMSCore.
+        // Some activities are loaded dynamically from external modules.
+        try {
+            Class baseLayoutControllerClass =
+                    activity.getClassLoader()
+                            .loadClass(BaseLayoutController.class.getName());
+            Method method = baseLayoutControllerClass
+                    .getDeclaredMethod(methodName, Activity.class);
+            method.invoke(null, activity);
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiDialogFragment.java b/car-ui-lib/src/com/android/car/ui/preference/CarUiDialogFragment.java
index 6a848b8..4a61b3f 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/CarUiDialogFragment.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/CarUiDialogFragment.java
@@ -36,6 +36,8 @@
 import androidx.fragment.app.DialogFragment;
 import androidx.preference.DialogPreference;
 
+import com.android.car.ui.utils.CarUiUtils;
+
 /**
  * Abstract base class which presents a dialog associated with a {@link
  * androidx.preference.DialogPreference}. Since the preference object may not be available during
@@ -193,7 +195,7 @@
      */
     @CallSuper
     protected void onBindDialogView(View view) {
-        View dialogMessageView = view.findViewById(android.R.id.message);
+        View dialogMessageView = CarUiUtils.findViewByRefId(view, android.R.id.message);
 
         if (dialogMessageView != null) {
             CharSequence message = mDialogMessage;
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java b/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java
index 5db22cf..635cad9 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java
@@ -18,20 +18,30 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Toast;
 
 import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
 
 import com.android.car.ui.R;
+import com.android.car.ui.utils.CarUiUtils;
 
 /**
  * This class extends the base {@link Preference} class. Adds the support to add a drawable icon to
  * the preference if there is one of fragment, intent or onPreferenceClickListener set.
  */
-public class CarUiPreference extends Preference {
+public class CarUiPreference extends Preference implements DisabledPreferenceCallback {
 
     private Context mContext;
     private boolean mShowChevron;
+    private String mMessageToShowWhenDisabledPreferenceClicked;
+
+    private boolean mShouldShowRippleOnDisabledPreference;
+    private Drawable mBackground;
+    private View mPreference;
 
     public CarUiPreference(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
@@ -61,6 +71,18 @@
                 defStyleRes);
 
         mShowChevron = a.getBoolean(R.styleable.CarUiPreference_showChevron, true);
+        mShouldShowRippleOnDisabledPreference = a.getBoolean(
+                R.styleable.CarUiPreference_showRippleOnDisabledPreference, false);
+    }
+
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        boolean viewEnabled = isEnabled();
+        mPreference = holder.itemView;
+        mBackground = CarUiUtils.setPreferenceViewEnabled(viewEnabled, holder.itemView, mBackground,
+                mShouldShowRippleOnDisabledPreference);
     }
 
     @Override
@@ -80,7 +102,37 @@
         }
     }
 
+    /**
+     * This is similar to {@link Preference#performClick()} with the only difference that we do not
+     * return when view is not enabled.
+     */
+    @Override
+    public void performClick() {
+        if (isEnabled()) {
+            super.performClick();
+        } else if (mMessageToShowWhenDisabledPreferenceClicked != null
+                && !mMessageToShowWhenDisabledPreferenceClicked.isEmpty()) {
+            Toast.makeText(mContext, mMessageToShowWhenDisabledPreferenceClicked,
+                    Toast.LENGTH_LONG).show();
+        }
+    }
+
     public void setShowChevron(boolean showChevron) {
         mShowChevron = showChevron;
     }
+
+    /**
+     * Sets the ripple on the disabled preference.
+     */
+    @Override
+    public void setShouldShowRippleOnDisabledPreference(boolean showRipple) {
+        mShouldShowRippleOnDisabledPreference = showRipple;
+        CarUiUtils.updateRippleStateOnDisabledPreference(isEnabled(),
+                mShouldShowRippleOnDisabledPreference, mBackground, mPreference);
+    }
+
+    @Override
+    public void setMessageToShowWhenDisabledPreferenceClicked(String message) {
+        mMessageToShowWhenDisabledPreferenceClicked = message;
+    }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiSwitchPreference.java b/car-ui-lib/src/com/android/car/ui/preference/CarUiSwitchPreference.java
new file mode 100644
index 0000000..1ccadf8
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/preference/CarUiSwitchPreference.java
@@ -0,0 +1,111 @@
+/*
+ * 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 com.android.car.ui.preference;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.ui.R;
+import com.android.car.ui.utils.CarUiUtils;
+
+/**
+ * This class extends the base {@link SwitchPreference} class. Adds the functionality to show
+ * message when preference is disabled.
+ */
+public class CarUiSwitchPreference extends SwitchPreference implements DisabledPreferenceCallback {
+
+    private String mMessageToShowWhenDisabledPreferenceClicked;
+
+    private boolean mShouldShowRippleOnDisabledPreference;
+    private Drawable mBackground;
+    private View mPreference;
+    private Context mContext;
+
+    public CarUiSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(context, attrs);
+    }
+
+    public CarUiSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context, attrs);
+    }
+
+    public CarUiSwitchPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context, attrs);
+    }
+
+    public CarUiSwitchPreference(Context context) {
+        super(context);
+        init(context, null);
+    }
+
+    private void init(Context context, AttributeSet attrs) {
+        mContext = context;
+        TypedArray preferenceAttributes = getContext().obtainStyledAttributes(attrs,
+                R.styleable.CarUiPreference);
+        mShouldShowRippleOnDisabledPreference = preferenceAttributes.getBoolean(
+                R.styleable.CarUiPreference_showRippleOnDisabledPreference, false);
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        mPreference = holder.itemView;
+        mBackground = CarUiUtils.setPreferenceViewEnabled(isEnabled(), holder.itemView, mBackground,
+                mShouldShowRippleOnDisabledPreference);
+    }
+
+    /**
+     * This is similar to {@link Preference#performClick()} with the only difference that we do not
+     * return when view is not enabled.
+     */
+    @Override
+    public void performClick() {
+        if (isEnabled()) {
+            super.performClick();
+        } else if (mMessageToShowWhenDisabledPreferenceClicked != null
+                && !mMessageToShowWhenDisabledPreferenceClicked.isEmpty()) {
+            Toast.makeText(mContext, mMessageToShowWhenDisabledPreferenceClicked,
+                    Toast.LENGTH_LONG).show();
+        }
+    }
+
+    /**
+     * Sets the ripple on the disabled preference.
+     */
+    @Override
+    public void setShouldShowRippleOnDisabledPreference(boolean showRipple) {
+        mShouldShowRippleOnDisabledPreference = showRipple;
+        CarUiUtils.updateRippleStateOnDisabledPreference(isEnabled(),
+                mShouldShowRippleOnDisabledPreference, mBackground, mPreference);
+    }
+
+    @Override
+    public void setMessageToShowWhenDisabledPreferenceClicked(String message) {
+        mMessageToShowWhenDisabledPreferenceClicked = message;
+    }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/preference/DisabledPreferenceCallback.java b/car-ui-lib/src/com/android/car/ui/preference/DisabledPreferenceCallback.java
new file mode 100644
index 0000000..b7a12bc
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/preference/DisabledPreferenceCallback.java
@@ -0,0 +1,33 @@
+/*
+ * 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 com.android.car.ui.preference;
+
+/**
+ * Interface for preferences to handle clicks when its disabled.
+ */
+public interface DisabledPreferenceCallback {
+
+    /**
+     * Sets if the ripple effect should be shown on disabled preference.
+     */
+    default void setShouldShowRippleOnDisabledPreference(boolean showRipple) {}
+
+    /**
+     * Sets the message to be displayed when the disabled preference is clicked.
+     */
+    default void setMessageToShowWhenDisabledPreferenceClicked(String message) {}
+}
diff --git a/car-ui-lib/src/com/android/car/ui/preference/EditTextPreferenceDialogFragment.java b/car-ui-lib/src/com/android/car/ui/preference/EditTextPreferenceDialogFragment.java
index 97a58d2..7df5228 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/EditTextPreferenceDialogFragment.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/EditTextPreferenceDialogFragment.java
@@ -28,6 +28,8 @@
 import androidx.annotation.NonNull;
 import androidx.preference.EditTextPreference;
 
+import com.android.car.ui.utils.CarUiUtils;
+
 /**
  * Presents a dialog with an {@link EditText} associated with an {@link EditTextPreference}.
  *
@@ -78,7 +80,7 @@
     protected void onBindDialogView(View view) {
         super.onBindDialogView(view);
 
-        mEditText = view.findViewById(android.R.id.edit);
+        mEditText = CarUiUtils.findViewByRefId(view, android.R.id.edit);
 
         if (mEditText == null) {
             throw new IllegalStateException(
diff --git a/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceFragment.java b/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceFragment.java
index 2543fb5..0a4ddde 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceFragment.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceFragment.java
@@ -36,6 +36,7 @@
 import com.android.car.ui.recyclerview.CarUiListItemAdapter;
 import com.android.car.ui.recyclerview.CarUiRecyclerView;
 import com.android.car.ui.toolbar.Toolbar;
+import com.android.car.ui.utils.CarUiUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -48,6 +49,8 @@
 
     private ListPreference mPreference;
     private CarUiContentListItem mSelectedItem;
+    private List<CarUiListItem> mListItems;
+    private CharSequence[] mEntryValues;
 
     /**
      * Returns a new instance of {@link ListPreferenceFragment} for the {@link ListPreference} with
@@ -72,8 +75,8 @@
     @Override
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
-        final CarUiRecyclerView carUiRecyclerView = view.requireViewById(R.id.list);
-        final Toolbar toolbar = view.requireViewById(R.id.toolbar);
+        final CarUiRecyclerView carUiRecyclerView = CarUiUtils.requireViewByRefId(view, R.id.list);
+        final Toolbar toolbar = CarUiUtils.requireViewByRefId(view, R.id.toolbar);
 
         carUiRecyclerView.setPadding(0, toolbar.getHeight(), 0, 0);
         toolbar.registerToolbarHeightChangeListener(newHeight -> {
@@ -91,21 +94,21 @@
         toolbar.setTitle(mPreference.getTitle());
 
         CharSequence[] entries = mPreference.getEntries();
-        CharSequence[] entryValues = mPreference.getEntryValues();
+        mEntryValues = mPreference.getEntryValues();
 
-        if (entries == null || entryValues == null) {
+        if (entries == null || mEntryValues == null) {
             throw new IllegalStateException(
                     "ListPreference requires an entries array and an entryValues array.");
         }
 
-        if (entries.length != entryValues.length) {
+        if (entries.length != mEntryValues.length) {
             throw new IllegalStateException(
                     "ListPreference entries array length does not match entryValues array length.");
         }
 
         int selectedEntryIndex = mPreference.findIndexOfValue(mPreference.getValue());
-        List<CarUiListItem> listItems = new ArrayList<>();
-        CarUiListItemAdapter adapter = new CarUiListItemAdapter(listItems);
+        mListItems = new ArrayList<>();
+        CarUiListItemAdapter adapter = new CarUiListItemAdapter(mListItems);
 
         for (int i = 0; i < entries.length; i++) {
             String entry = entries[i].toString();
@@ -121,30 +124,34 @@
             item.setOnCheckedChangeListener((listItem, isChecked) -> {
                 if (mSelectedItem != null) {
                     mSelectedItem.setChecked(false);
-                    adapter.notifyItemChanged(listItems.indexOf(mSelectedItem));
+                    adapter.notifyItemChanged(mListItems.indexOf(mSelectedItem));
                 }
                 mSelectedItem = listItem;
             });
 
-            listItems.add(item);
+            mListItems.add(item);
         }
 
-        toolbar.registerOnBackListener(() -> {
-            if (mSelectedItem != null) {
-                int selectedIndex = listItems.indexOf(mSelectedItem);
-                String entryValue = entryValues[selectedIndex].toString();
-
-                if (mPreference.callChangeListener(entryValue)) {
-                    mPreference.setValue(entryValue);
-                }
-            }
-
-            return false;
-        });
-
         carUiRecyclerView.setAdapter(adapter);
     }
 
+    @Override
+    public void onStop() {
+        super.onStop();
+        updatePreference();
+    }
+
+    private void updatePreference() {
+        if (mSelectedItem != null) {
+            int selectedIndex = mListItems.indexOf(mSelectedItem);
+            String entryValue = mEntryValues[selectedIndex].toString();
+
+            if (mPreference.callChangeListener(entryValue)) {
+                mPreference.setValue(entryValue);
+            }
+        }
+    }
+
     private ListPreference getListPreference() {
         if (getArguments() == null) {
             throw new IllegalStateException("Preference arguments cannot be null");
diff --git a/car-ui-lib/src/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java b/car-ui-lib/src/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
index c445142..8427c5f 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
@@ -35,6 +35,7 @@
 import com.android.car.ui.recyclerview.CarUiListItemAdapter;
 import com.android.car.ui.recyclerview.CarUiRecyclerView;
 import com.android.car.ui.toolbar.Toolbar;
+import com.android.car.ui.utils.CarUiUtils;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -73,8 +74,8 @@
     @Override
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
-        final CarUiRecyclerView recyclerView = view.requireViewById(R.id.list);
-        final Toolbar toolbar = view.requireViewById(R.id.toolbar);
+        final CarUiRecyclerView recyclerView = CarUiUtils.requireViewByRefId(view, R.id.list);
+        final Toolbar toolbar = CarUiUtils.requireViewByRefId(view, R.id.toolbar);
 
         recyclerView.setPadding(0, toolbar.getHeight(), 0, 0);
         toolbar.registerToolbarHeightChangeListener(newHeight -> {
@@ -131,14 +132,18 @@
 
         CarUiListItemAdapter adapter = new CarUiListItemAdapter(listItems);
         recyclerView.setAdapter(adapter);
+    }
 
-        toolbar.registerOnBackListener(() -> {
-            if (mPreference.callChangeListener(mNewValues)) {
-                mPreference.setValues(mNewValues);
-            }
+    @Override
+    public void onStop() {
+        super.onStop();
+        updatePreference();
+    }
 
-            return false;
-        });
+    private void updatePreference() {
+        if (mPreference.callChangeListener(mNewValues)) {
+            mPreference.setValues(mNewValues);
+        }
     }
 
     private CarUiMultiSelectListPreference getPreference() {
diff --git a/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java b/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java
index bff6c13..260cf6f 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java
@@ -36,6 +36,7 @@
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.car.ui.R;
@@ -82,8 +83,8 @@
         }
 
         // TODO(b/150230923) remove the code for the old toolbar height change when apps are ready
-        final RecyclerView recyclerView = view.findViewById(R.id.recycler_view);
-        final Toolbar toolbar = view.findViewById(R.id.toolbar);
+        final RecyclerView recyclerView = CarUiUtils.findViewByRefId(view, R.id.recycler_view);
+        final Toolbar toolbar = CarUiUtils.findViewByRefId(view, R.id.toolbar);
         if (recyclerView == null || toolbar == null) {
             return;
         }
@@ -108,9 +109,9 @@
     @Override
     public void onCarUiInsetsChanged(Insets insets) {
         View view = requireView();
-        view.requireViewById(R.id.recycler_view)
+        CarUiUtils.requireViewByRefId(view, R.id.recycler_view)
                 .setPadding(0, insets.getTop(), 0, insets.getBottom());
-        view.getRootView().requireViewById(android.R.id.content)
+        CarUiUtils.requireViewByRefId(view.getRootView(), android.R.id.content)
                 .setPadding(insets.getLeft(), 0, insets.getRight(), 0);
     }
 
@@ -241,6 +242,7 @@
             new Pair<>(ListPreference.class, CarUiListPreference.class),
             new Pair<>(MultiSelectListPreference.class, CarUiMultiSelectListPreference.class),
             new Pair<>(EditTextPreference.class, CarUiEditTextPreference.class),
+            new Pair<>(SwitchPreference.class, CarUiSwitchPreference.class),
             new Pair<>(Preference.class, CarUiPreference.class)
     );
 
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java
index dec9080..36ed154 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java
@@ -22,12 +22,15 @@
 import android.car.drivingstate.CarUxRestrictions;
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.Rect;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
@@ -48,38 +51,67 @@
 import com.android.car.ui.utils.CarUxRestrictionsUtil;
 
 import java.lang.annotation.Retention;
+import java.util.Objects;
 
 /**
- * View that extends a {@link RecyclerView} and wraps itself into a {@link LinearLayout} which
- * could potentially include a scrollbar that has page up and down arrows. Interaction with this
- * view is similar to a {@code RecyclerView} as it takes the same adapter and the layout manager.
+ * View that extends a {@link RecyclerView} and wraps itself into a {@link LinearLayout} which could
+ * potentially include a scrollbar that has page up and down arrows. Interaction with this view is
+ * similar to a {@code RecyclerView} as it takes the same adapter and the layout manager.
  */
 public final class CarUiRecyclerView extends RecyclerView implements
         Toolbar.OnHeightChangedListener {
 
     private static final String TAG = "CarUiRecyclerView";
 
-    private final UxRestrictionChangedListener mListener = new UxRestrictionChangedListener();
+    private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener =
+            new UxRestrictionChangedListener();
 
     private CarUxRestrictionsUtil mCarUxRestrictionsUtil;
     private boolean mScrollBarEnabled;
     private String mScrollBarClass;
     private boolean mFullyInitialized;
-    private float mScrollBarPaddingStart;
-    private float mScrollBarPaddingEnd;
+    private int mScrollBarPaddingTop;
+    private int mScrollBarPaddingBottom;
+    private boolean mHasScrolledToTop = false;
 
     private ScrollBar mScrollBar;
     private int mInitialTopPadding;
 
-    private GridOffsetItemDecoration mOffsetItemDecoration;
-    private GridDividerItemDecoration mDividerItemDecoration;
     @CarUiRecyclerViewLayout
     private int mCarUiRecyclerViewLayout;
+
+    @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 int mNumOfColumns;
     private boolean mInstallingExtScrollBar = false;
     private int mContainerVisibility = View.VISIBLE;
+    private Rect mContainerPadding;
+    private Rect mContainerPaddingRelative;
     private LinearLayout mContainer;
 
+    // Set to true when when styled attributes are read and initialized.
+    private boolean mIsInitialized;
+    private boolean mEnableDividers;
+    private int mTopOffset;
+    private int mBottomOffset;
+
+    private ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener = () -> {
+        if (mInitialTopPadding == 0) {
+            mInitialTopPadding = getPaddingTop();
+        }
+        mFullyInitialized = true;
+    };
+
     /**
      * The possible values for setScrollBarPosition. The default value is actually {@link
      * CarUiRecyclerViewLayout#LINEAR}.
@@ -91,13 +123,15 @@
     @Retention(SOURCE)
     public @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 default.
          */
         int LINEAR = 0;
 
-        /** Arranges items in a Grid. */
-        int GRID = 2;
+        /**
+         * Arranges items in a Grid.
+         */
+        int GRID = 1;
     }
 
     /**
@@ -124,8 +158,7 @@
 
         /**
          * Sets the maximum number of items available in the adapter. A value less than '0' means
-         * the
-         * list should not be capped.
+         * the list should not be capped.
          */
         void setMaxItems(int maxItems);
     }
@@ -155,83 +188,100 @@
         mScrollBarEnabled = context.getResources().getBoolean(R.bool.car_ui_scrollbar_enable);
         mFullyInitialized = false;
 
-        mScrollBarPaddingStart =
-                context.getResources().getDimension(R.dimen.car_ui_scrollbar_padding_start);
-        mScrollBarPaddingEnd =
-                context.getResources().getDimension(R.dimen.car_ui_scrollbar_padding_end);
+        mScrollBarPaddingTop = context.getResources()
+                .getDimensionPixelSize(R.dimen.car_ui_scrollbar_padding_top);
+        mScrollBarPaddingBottom = context.getResources()
+                .getDimensionPixelSize(R.dimen.car_ui_scrollbar_padding_bottom);
 
         mCarUiRecyclerViewLayout =
                 a.getInt(R.styleable.CarUiRecyclerView_layoutStyle, CarUiRecyclerViewLayout.LINEAR);
         mNumOfColumns = a.getInt(R.styleable.CarUiRecyclerView_numOfColumns, /* defValue= */ 2);
-        boolean enableDivider =
+        mEnableDividers =
                 a.getBoolean(R.styleable.CarUiRecyclerView_enableDivider, /* defValue= */ false);
 
-        if (mCarUiRecyclerViewLayout == CarUiRecyclerViewLayout.LINEAR) {
+        mDividerItemDecorationLinear = new LinearDividerItemDecoration(
+                context.getDrawable(R.drawable.car_ui_recyclerview_divider));
 
-            int linearTopOffset =
-                    a.getInteger(R.styleable.CarUiRecyclerView_startOffset, /* defValue= */ 0);
-            int linearBottomOffset =
-                    a.getInteger(R.styleable.CarUiRecyclerView_endOffset, /* defValue= */ 0);
+        mDividerItemDecorationGrid =
+                new GridDividerItemDecoration(
+                        context.getDrawable(R.drawable.car_ui_divider),
+                        context.getDrawable(R.drawable.car_ui_divider),
+                        mNumOfColumns);
 
-            if (enableDivider) {
-                RecyclerView.ItemDecoration dividerItemDecoration =
-                        new LinearDividerItemDecoration(
-                                context.getDrawable(R.drawable.car_ui_recyclerview_divider));
-                addItemDecoration(dividerItemDecoration);
-            }
-            RecyclerView.ItemDecoration topOffsetItemDecoration =
-                    new LinearOffsetItemDecoration(linearTopOffset, OffsetPosition.START);
+        mTopOffset = a.getInteger(R.styleable.CarUiRecyclerView_topOffset, /* defValue= */0);
+        mBottomOffset = a.getInteger(
+                R.styleable.CarUiRecyclerView_bottomOffset, /* defValue= */0);
 
-            RecyclerView.ItemDecoration bottomOffsetItemDecoration =
-                    new LinearOffsetItemDecoration(linearBottomOffset, OffsetPosition.END);
+        mTopOffsetItemDecorationLinear =
+                new LinearOffsetItemDecoration(mTopOffset, OffsetPosition.START);
+        mBottomOffsetItemDecorationLinear =
+                new LinearOffsetItemDecoration(mBottomOffset, OffsetPosition.END);
+        mTopOffsetItemDecorationGrid =
+                new GridOffsetItemDecoration(mTopOffset, mNumOfColumns,
+                        OffsetPosition.START);
+        mBottomOffsetItemDecorationGrid =
+                new GridOffsetItemDecoration(mBottomOffset, mNumOfColumns,
+                        OffsetPosition.END);
 
-            addItemDecoration(topOffsetItemDecoration);
-            addItemDecoration(bottomOffsetItemDecoration);
+        mIsInitialized = true;
+
+        // Check if a layout manager has already been set via XML
+        boolean isLayoutMangerSet = getLayoutManager() != null;
+        if (!isLayoutMangerSet && mCarUiRecyclerViewLayout == CarUiRecyclerViewLayout.LINEAR) {
             setLayoutManager(new LinearLayoutManager(getContext()));
-        } else {
-            int gridTopOffset =
-                    a.getInteger(R.styleable.CarUiRecyclerView_startOffset, /* defValue= */ 0);
-            int gridBottomOffset =
-                    a.getInteger(R.styleable.CarUiRecyclerView_endOffset, /* defValue= */ 0);
-
-            if (enableDivider) {
-                mDividerItemDecoration =
-                        new GridDividerItemDecoration(
-                                context.getDrawable(R.drawable.car_ui_divider),
-                                context.getDrawable(R.drawable.car_ui_divider),
-                                mNumOfColumns);
-                addItemDecoration(mDividerItemDecoration);
-            }
-
-            mOffsetItemDecoration =
-                    new GridOffsetItemDecoration(gridTopOffset, mNumOfColumns,
-                            OffsetPosition.START);
-
-            GridOffsetItemDecoration bottomOffsetItemDecoration =
-                    new GridOffsetItemDecoration(gridBottomOffset, mNumOfColumns,
-                            OffsetPosition.END);
-
-            addItemDecoration(mOffsetItemDecoration);
-            addItemDecoration(bottomOffsetItemDecoration);
+        } else if (!isLayoutMangerSet && mCarUiRecyclerViewLayout == CarUiRecyclerViewLayout.GRID) {
             setLayoutManager(new GridLayoutManager(getContext(), mNumOfColumns));
-            setNumOfColumns(mNumOfColumns);
         }
 
+        a.recycle();
+
         if (!mScrollBarEnabled) {
-            a.recycle();
             mFullyInitialized = true;
             return;
         }
 
+        mContainer = new LinearLayout(getContext());
+
+        setVerticalScrollBarEnabled(false);
+        setHorizontalScrollBarEnabled(false);
+
         mScrollBarClass = context.getResources().getString(R.string.car_ui_scrollbar_component);
-        a.recycle();
-        this.getViewTreeObserver()
-                .addOnGlobalLayoutListener(() -> {
-                    if (mInitialTopPadding == 0) {
-                        mInitialTopPadding = getPaddingTop();
-                    }
-                    mFullyInitialized = true;
-                });
+    }
+
+    @Override
+    public void setLayoutManager(@Nullable LayoutManager layoutManager) {
+        // Cannot setup item decorations before stylized attributes have been read.
+        if (mIsInitialized) {
+            addItemDecorations(layoutManager);
+        }
+        super.setLayoutManager(layoutManager);
+    }
+
+    // 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));
+
+        if (layoutManager instanceof GridLayoutManager) {
+            if (mEnableDividers) {
+                addItemDecoration(Objects.requireNonNull(mDividerItemDecorationGrid));
+            }
+            addItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationGrid));
+            addItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationGrid));
+            setNumOfColumns(((GridLayoutManager) layoutManager).getSpanCount());
+        } else {
+            if (mEnableDividers) {
+                addItemDecoration(Objects.requireNonNull(mDividerItemDecorationLinear));
+            }
+            addItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationLinear));
+            addItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationLinear));
+        }
     }
 
     @Override
@@ -254,11 +304,11 @@
      */
     public void setNumOfColumns(int numberOfColumns) {
         mNumOfColumns = numberOfColumns;
-        if (mOffsetItemDecoration != null) {
-            mOffsetItemDecoration.setNumOfColumns(mNumOfColumns);
+        if (mTopOffsetItemDecorationGrid != null) {
+            mTopOffsetItemDecorationGrid.setNumOfColumns(mNumOfColumns);
         }
-        if (mDividerItemDecoration != null) {
-            mDividerItemDecoration.setNumOfColumns(mNumOfColumns);
+        if (mDividerItemDecorationGrid != null) {
+            mDividerItemDecorationGrid.setNumOfColumns(mNumOfColumns);
         }
     }
 
@@ -275,6 +325,10 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mCarUxRestrictionsUtil.register(mListener);
+
+        this.getViewTreeObserver()
+                .addOnGlobalLayoutListener(mOnGlobalLayoutListener);
+
         if (mInstallingExtScrollBar || !mScrollBarEnabled) {
             return;
         }
@@ -290,26 +344,37 @@
 
     /**
      * 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.
+     * 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.
      */
     private void installExternalScrollBar() {
-        ViewGroup parent = (ViewGroup) getParent();
-        mContainer = new LinearLayout(getContext());
         LayoutInflater inflater = LayoutInflater.from(getContext());
         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());
-        mContainer.setVisibility(mContainerVisibility);
+        ViewGroup parent = (ViewGroup) getParent();
         int index = parent.indexOfChild(this);
-        parent.removeView(this);
-        ((FrameLayout) requireViewByRefId(mContainer, R.id.car_ui_recycler_view))
-                .addView(this,
-                        new FrameLayout.LayoutParams(
-                                ViewGroup.LayoutParams.MATCH_PARENT,
-                                ViewGroup.LayoutParams.MATCH_PARENT));
-        setVerticalScrollBarEnabled(false);
-        setHorizontalScrollBarEnabled(false);
+
+        parent.removeViewInLayout(this);
+
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.FILL_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));
@@ -332,26 +397,64 @@
 
         mScrollBar.initialize(this, scrollView);
 
-        mScrollBar.setPadding((int) mScrollBarPaddingStart, (int) mScrollBarPaddingEnd);
+        setScrollBarPadding(mScrollBarPaddingTop, mScrollBarPaddingBottom);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mCarUxRestrictionsUtil.unregister(mListener);
+        this.getViewTreeObserver().removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
+    }
+
+    @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
+    public void setPadding(int left, int top, int right, int bottom) {
+        super.setPadding(0, top, 0, bottom);
+        mContainerPaddingRelative = null;
+        mContainerPadding = new Rect(left, 0, right, 0);
+        if (mContainer != null) {
+            mContainer.setPadding(left, 0, right, 0);
+        }
+        setScrollBarPadding(mScrollBarPaddingTop, mScrollBarPaddingBottom);
+    }
+
+    @Override
+    public void setPaddingRelative(int start, int top, int end, int bottom) {
+        super.setPaddingRelative(0, top, 0, bottom);
+        mContainerPadding = null;
+        mContainerPaddingRelative = new Rect(start, 0, end, 0);
+        if (mContainer != null) {
+            mContainer.setPaddingRelative(start, 0, end, 0);
+        }
+        setScrollBarPadding(mScrollBarPaddingTop, mScrollBarPaddingBottom);
     }
 
     /**
-     * Sets the scrollbar's padding start (top) and end (bottom).
-     * This padding is applied in addition to the padding of the inner RecyclerView.
+     * Sets the scrollbar's padding top and bottom. This padding is applied in addition to the
+     * padding of the RecyclerView.
      */
-    public void setScrollBarPadding(int paddingStart, int paddingEnd) {
+    public void setScrollBarPadding(int paddingTop, int paddingBottom) {
         if (mScrollBarEnabled) {
-            mScrollBarPaddingStart = paddingStart;
-            mScrollBarPaddingEnd = paddingEnd;
+            mScrollBarPaddingTop = paddingTop;
+            mScrollBarPaddingBottom = paddingBottom;
 
             if (mScrollBar != null) {
-                mScrollBar.setPadding(paddingStart, paddingEnd);
+                mScrollBar.setPadding(paddingTop + getPaddingTop(),
+                        paddingBottom + getPaddingBottom());
             }
         }
     }
@@ -365,6 +468,28 @@
         return super.getLayoutManager();
     }
 
+    /**
+     * Sets divider item decoration for linear layout.
+     */
+    public void setLinearDividerItemDecoration(boolean enableDividers) {
+        if (enableDividers) {
+            addItemDecoration(mDividerItemDecorationLinear);
+            return;
+        }
+        removeItemDecoration(mDividerItemDecorationLinear);
+    }
+
+    /**
+     * Sets divider item decoration for grid layout.
+     */
+    public void setGridDividerItemDecoration(boolean enableDividers) {
+        if (enableDividers) {
+            addItemDecoration(mDividerItemDecorationGrid);
+            return;
+        }
+        removeItemDecoration(mDividerItemDecorationGrid);
+    }
+
     private static RuntimeException andLog(String msg, Throwable t) {
         Log.e(TAG, msg, t);
         throw new RuntimeException(msg, t);
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerViewContainer.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerViewContainer.java
new file mode 100644
index 0000000..3a0519f
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerViewContainer.java
@@ -0,0 +1,56 @@
+/*
+ * 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.recyclerview;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * Container that contains the scrollbar and RecyclerView when scrollbar is enabled.
+ *
+ * This container is required to expose addViewInLayout such that the scrollbar can be added without
+ * triggering multiple invalidate and relayout calls.
+ */
+public class CarUiRecyclerViewContainer extends FrameLayout {
+
+    public CarUiRecyclerViewContainer(Context context) {
+        super(context);
+    }
+
+    public CarUiRecyclerViewContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    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);
+    }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiSnapHelper.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiSnapHelper.java
index 807d856..a7fff75 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiSnapHelper.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiSnapHelper.java
@@ -16,7 +16,6 @@
 package com.android.car.ui.recyclerview;
 
 import android.content.Context;
-import android.graphics.PointF;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -26,6 +25,8 @@
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView.LayoutManager;
 
+import java.util.Objects;
+
 /**
  * Inspired by {@link androidx.car.widget.PagedSnapHelper}
  *
@@ -67,6 +68,12 @@
     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));
         }
@@ -79,106 +86,6 @@
     }
 
     /**
-     * Smooth scrolls the RecyclerView by a given distance.
-     */
-    public void smoothScrollBy(int scrollDistance) {
-        LayoutManager layoutManager = mRecyclerView.getLayoutManager();
-        if (layoutManager == null) {
-            return;
-        }
-
-        int position = findTargetSnapPosition(layoutManager, scrollDistance);
-        if (position == RecyclerView.NO_POSITION) {
-            mRecyclerView.smoothScrollBy(0, scrollDistance);
-            return;
-        }
-
-        RecyclerView.SmoothScroller scroller = createScroller(layoutManager);
-
-        if (scroller == null) {
-            return;
-        }
-
-        scroller.setTargetPosition(position);
-        layoutManager.startSmoothScroll(scroller);
-    }
-
-    /**
-     * Finds the target position for snapping.
-     *
-     * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
-     *                      {@link RecyclerView}
-     */
-    private int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager,
-            int scrollDistance) {
-
-        if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
-            return RecyclerView.NO_POSITION;
-        }
-
-        final int itemCount = layoutManager.getItemCount();
-        if (itemCount == 0) {
-            return RecyclerView.NO_POSITION;
-        }
-
-        final View currentView = findViewIfScrollable(layoutManager);
-        if (currentView == null) {
-            return RecyclerView.NO_POSITION;
-        }
-
-        final int currentPosition = layoutManager.getPosition(currentView);
-        if (currentPosition == RecyclerView.NO_POSITION) {
-            return RecyclerView.NO_POSITION;
-        }
-
-        RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider =
-                (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager;
-        // deltaJumps sign comes from the velocity which may not match the order of children in
-        // the LayoutManager. To overcome this, we ask for a vector from the LayoutManager to
-        // get the direction.
-        PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1);
-        if (vectorForEnd == null) {
-            // cannot get a vector for the given position.
-            return RecyclerView.NO_POSITION;
-        }
-
-        int vDeltaJump;
-        int hDeltaJump;
-        if (layoutManager.canScrollHorizontally()) {
-            hDeltaJump = estimateNextPositionDiffForFling(layoutManager,
-                    getHorizontalHelper(layoutManager), scrollDistance);
-            if (vectorForEnd.x < 0) {
-                hDeltaJump = -hDeltaJump;
-            }
-        } else {
-            hDeltaJump = 0;
-        }
-        if (layoutManager.canScrollVertically()) {
-            vDeltaJump = estimateNextPositionDiffForFling(layoutManager,
-                    getVerticalHelper(layoutManager), scrollDistance);
-            if (vectorForEnd.y < 0) {
-                vDeltaJump = -vDeltaJump;
-            }
-        } else {
-            vDeltaJump = 0;
-        }
-
-        int deltaJump = layoutManager.canScrollVertically() ? vDeltaJump : hDeltaJump;
-        if (deltaJump == 0) {
-            return RecyclerView.NO_POSITION;
-        }
-
-        int targetPos = currentPosition + deltaJump;
-        if (targetPos < 0) {
-            targetPos = 0;
-        }
-        if (targetPos >= itemCount) {
-            targetPos = itemCount - 1;
-        }
-        return targetPos;
-    }
-
-    /**
      * 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
@@ -226,7 +133,8 @@
             return null;
         }
 
-        View lastVisibleChild = layoutManager.getChildAt(childCount - 1);
+        @NonNull View lastVisibleChild = Objects.requireNonNull(
+                layoutManager.getChildAt(childCount - 1));
 
         // Check if the last child visible is the last item in the list.
         boolean lastItemVisible =
@@ -273,15 +181,6 @@
         return isValidSnapView(childToReturn, orientationHelper) ? childToReturn : null;
     }
 
-    private View findViewIfScrollable(LayoutManager layoutManager) {
-        if (layoutManager.canScrollVertically()) {
-            return findTopView(layoutManager, getVerticalHelper(layoutManager));
-        } else if (layoutManager.canScrollHorizontally()) {
-            return findTopView(layoutManager, getHorizontalHelper(layoutManager));
-        }
-        return null;
-    }
-
     private static int distanceToTopMargin(@NonNull View targetView, OrientationHelper helper) {
         final int childTop = helper.getDecoratedStart(targetView);
         final int containerTop = helper.getStartAfterPadding();
@@ -314,7 +213,7 @@
             }
             int absDistance = Math.abs(distanceToTopMargin(child, helper));
 
-            /** if child top is closer than previous closest, set it as closest */
+            /* if child top is closer than previous closest, set it as closest */
             if (absDistance < absClosest) {
                 absClosest = absDistance;
                 closestChild = child;
@@ -335,7 +234,7 @@
      * @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 boolean isValidSnapView(View view, OrientationHelper helper) {
+    private static boolean isValidSnapView(View view, OrientationHelper helper) {
         return helper.getDecoratedMeasurement(view) <= helper.getTotalSpace();
     }
 
@@ -347,7 +246,7 @@
      * @param helper An {@link OrientationHelper} to aid with calculation.
      * @return A float indicating the percentage of the given view that is visible.
      */
-    private float getPercentageVisible(View view, OrientationHelper helper) {
+    static float getPercentageVisible(View view, OrientationHelper helper) {
         int start = helper.getStartAfterPadding();
         int end = helper.getEndAfterPadding();
 
@@ -419,7 +318,8 @@
         int lastChildPosition = isAtEnd(layoutManager) ? 0 : layoutManager.getChildCount() - 1;
 
         OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
-        View lastChild = layoutManager.getChildAt(lastChildPosition);
+        @NonNull View lastChild = Objects.requireNonNull(
+                layoutManager.getChildAt(lastChildPosition));
         float percentageVisible = getPercentageVisible(lastChild, orientationHelper);
 
         int maxDistance = layoutManager.getHeight();
@@ -440,6 +340,80 @@
     }
 
     /**
+     * Estimates a position to which CarUiSnapHelper 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 (int) 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 RecyclerView.LayoutManager} associated with the attached
+     *                      {@link RecyclerView}.
+     * @param helper        The relevant {@link OrientationHelper} for the attached
+     *                      {@link RecyclerView.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(RecyclerView.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) {
@@ -447,7 +421,7 @@
             return true;
         }
 
-        View firstChild = layoutManager.getChildAt(0);
+        @NonNull View firstChild = Objects.requireNonNull(layoutManager.getChildAt(0));
         OrientationHelper orientationHelper =
                 layoutManager.canScrollVertically() ? getVerticalHelper(layoutManager)
                         : getHorizontalHelper(layoutManager);
@@ -471,7 +445,8 @@
                 layoutManager.canScrollVertically() ? getVerticalHelper(layoutManager)
                         : getHorizontalHelper(layoutManager);
 
-        View lastVisibleChild = layoutManager.getChildAt(childCount - 1);
+        @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.
@@ -521,58 +496,4 @@
     private static int clamp(int value, int min, int max) {
         return Math.max(min, Math.min(max, value));
     }
-
-    private static int estimateNextPositionDiffForFling(RecyclerView.LayoutManager layoutManager,
-            OrientationHelper helper,
-            int scrollDistance) {
-        int[] distances = new int[]{scrollDistance, scrollDistance};
-        float distancePerChild = computeDistancePerChild(layoutManager, helper);
-
-        if (distancePerChild <= 0) {
-            return 0;
-        }
-        int distance =
-                Math.abs(distances[0]) > Math.abs(distances[1]) ? distances[0] : distances[1];
-        return (int) Math.round(distance / distancePerChild);
-    }
-
-    private static float computeDistancePerChild(RecyclerView.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);
-            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 -1;
-        }
-        return 1f * distance / ((maxPos - minPos) + 1);
-    }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/ContentLimiting.java b/car-ui-lib/src/com/android/car/ui/recyclerview/ContentLimiting.java
new file mode 100644
index 0000000..29fb159
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/ContentLimiting.java
@@ -0,0 +1,51 @@
+/*
+ * 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.recyclerview;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.StringRes;
+
+/**
+ * An interface for {@link androidx.recyclerview.widget.RecyclerView.Adapter} objects whose
+ * content can be limited to a provided maximum number of items.
+ */
+public interface ContentLimiting {
+
+    /**
+     * A value that indicates there should be no limit.
+     */
+    int UNLIMITED = -1;
+
+    /**
+     * Sets the maximum number of items available in the adapter. Use {@link #UNLIMITED} if
+     * the list should not be capped.
+     */
+    void setMaxItems(int maxItems);
+
+    /**
+     * Sets the message to show in the UI when the list content length is capped.
+     */
+    void setScrollingLimitedMessageResId(@StringRes int resId);
+
+    /**
+     * Returns the resource ID of a string resource that can uniquely identify the list
+     * displayed via this adapter in the UI for the purposes of mapping UXR restriction
+     * customizations to it.
+     */
+    @IdRes
+    int getConfigurationId();
+}
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/ContentLimitingAdapter.java b/car-ui-lib/src/com/android/car/ui/recyclerview/ContentLimitingAdapter.java
new file mode 100644
index 0000000..a3114ab
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/ContentLimitingAdapter.java
@@ -0,0 +1,322 @@
+/*
+ * 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.recyclerview;
+
+import android.util.Log;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * A {@link RecyclerView.Adapter} that can limit its content based on a given length limit which
+ * can change at run-time.
+ *
+ * @param <T> type of the {@link RecyclerView.ViewHolder} objects used by base classes.
+ */
+public abstract class ContentLimitingAdapter<T extends RecyclerView.ViewHolder>
+        extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements ContentLimiting {
+    private static final String TAG = "ContentLimitingAdapter";
+
+    private static final int SCROLLING_LIMITED_MESSAGE_VIEW_TYPE = Integer.MAX_VALUE;
+
+    private Integer mScrollingLimitedMessageResId;
+    private RangeFilter mRangeFilter = new PassThroughFilter();
+    private RecyclerView mRecyclerView;
+    private boolean mIsLimiting = false;
+
+    /**
+     * Returns the viewType value to use for the scrolling limited message views.
+     *
+     * Override this method to provide your own alternative value if {@link Integer#MAX_VALUE} is
+     * a viewType value already in-use by your adapter.
+     */
+    public int getScrollingLimitedMessageViewType() {
+        return SCROLLING_LIMITED_MESSAGE_VIEW_TYPE;
+    }
+
+    @Override
+    @NonNull
+    public final RecyclerView.ViewHolder onCreateViewHolder(
+            @NonNull ViewGroup parent, int viewType) {
+        if (viewType == getScrollingLimitedMessageViewType()) {
+            return ScrollingLimitedViewHolder.create(parent);
+        }
+
+        return onCreateViewHolderImpl(parent, viewType);
+    }
+
+    /** See {@link RangeFilter#indexToPosition}. */
+    protected int indexToPosition(int index) {
+        return mRangeFilter.indexToPosition(index);
+    }
+
+    /** See {@link RangeFilter#positionToIndex}. */
+    protected int positionToIndex(int position) {
+        return mRangeFilter.positionToIndex(position);
+    }
+
+    /**
+     * Returns a {@link androidx.recyclerview.widget.RecyclerView.ViewHolder} of type {@code T}.
+     *
+     * <p>It is delegated to by {@link #onCreateViewHolder(ViewGroup, int)} to handle any
+     * {@code viewType}s other than the one corresponding to the "scrolling is limited" message.
+     */
+    protected abstract T onCreateViewHolderImpl(
+            @NonNull ViewGroup parent, int viewType);
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public final void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+        if (holder instanceof ScrollingLimitedViewHolder) {
+            ScrollingLimitedViewHolder vh = (ScrollingLimitedViewHolder) holder;
+            vh.bind(mScrollingLimitedMessageResId);
+        } else {
+            int index = mRangeFilter.positionToIndex(position);
+            if (index != RangeFilterImpl.INVALID_INDEX) {
+                int size = getUnrestrictedItemCount();
+                if (0 <= index && index < size) {
+                    onBindViewHolderImpl((T) holder, index);
+                } else {
+                    Log.e(TAG, "onBindViewHolder pos: " + position + " gave index: "
+                            + index + " out of bounds size: " + size
+                            + " " + mRangeFilter.toString());
+                }
+            } else {
+                Log.e(TAG, "onBindViewHolder invalid position " + position
+                        + " " + mRangeFilter.toString());
+            }
+        }
+    }
+
+    /**
+     * Binds {@link androidx.recyclerview.widget.RecyclerView.ViewHolder}s of type {@code T}.
+     *
+     * <p>It is delegated to by {@link #onBindViewHolder(RecyclerView.ViewHolder, int)} to handle
+     * holders that are not of type {@link ScrollingLimitedViewHolder}.
+     */
+    protected abstract void onBindViewHolderImpl(T holder, int position);
+
+    @Override
+    public final int getItemViewType(int position) {
+        if (mRangeFilter.positionToIndex(position) == RangeFilterImpl.INVALID_INDEX) {
+            return getScrollingLimitedMessageViewType();
+        } else {
+            return getItemViewTypeImpl(mRangeFilter.positionToIndex(position));
+        }
+    }
+
+    /**
+     * Returns the view type of the item at {@code position}.
+     *
+     * <p>Defaults to the implementation in {@link RecyclerView.Adapter#getItemViewType(int)}.
+     *
+     * <p>It is delegated to by {@link #getItemViewType(int)} for all positions other than the
+     * {@link #getScrollingLimitedMessagePosition()}.
+     */
+    protected int getItemViewTypeImpl(int position) {
+        return super.getItemViewType(position);
+    }
+
+    /**
+     * Returns the position where the "scrolling is limited" message should be placed.
+     *
+     * <p>The default implementation is to put this item at the very end of the limited list.
+     * Subclasses can override to choose a different position to suit their needs.
+     *
+     * @deprecated limiting message offset is not supported any more.
+     */
+    @Deprecated
+    protected int getScrollingLimitedMessagePosition() {
+        return getItemCount() - 1;
+    }
+
+    @Override
+    public final int getItemCount() {
+        if (mIsLimiting) {
+            return mRangeFilter.getFilteredCount();
+        } else {
+            return getUnrestrictedItemCount();
+        }
+    }
+
+    /**
+     * Returns the number of items in the unrestricted list being displayed via this adapter.
+     */
+    protected abstract int getUnrestrictedItemCount();
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public final void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
+        super.onViewRecycled(holder);
+
+        if (!(holder instanceof ScrollingLimitedViewHolder)) {
+            onViewRecycledImpl((T) holder);
+        }
+    }
+
+    /**
+     * Recycles {@link androidx.recyclerview.widget.RecyclerView.ViewHolder}s of type {@code T}.
+     *
+     * <p>It is delegated to by {@link #onViewRecycled(RecyclerView.ViewHolder)} to handle
+     * holders that are not of type {@link ScrollingLimitedViewHolder}.
+     */
+    @SuppressWarnings("unused")
+    protected void onViewRecycledImpl(@NonNull T holder) {
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public final boolean onFailedToRecycleView(@NonNull RecyclerView.ViewHolder holder) {
+        if (!(holder instanceof ScrollingLimitedViewHolder)) {
+            return onFailedToRecycleViewImpl((T) holder);
+        }
+        return super.onFailedToRecycleView(holder);
+    }
+
+    /**
+     * Handles failed recycle attempts for
+     * {@link androidx.recyclerview.widget.RecyclerView.ViewHolder}s of type {@code T}.
+     *
+     * <p>It is delegated to by {@link #onFailedToRecycleView(RecyclerView.ViewHolder)} for holders
+     * that are not of type {@link ScrollingLimitedViewHolder}.
+     */
+    protected boolean onFailedToRecycleViewImpl(@NonNull T holder) {
+        return super.onFailedToRecycleView(holder);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public final void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
+        super.onViewAttachedToWindow(holder);
+        if (!(holder instanceof ScrollingLimitedViewHolder)) {
+            onViewAttachedToWindowImpl((T) holder);
+        }
+    }
+
+    /**
+     * Handles attaching {@link androidx.recyclerview.widget.RecyclerView.ViewHolder}s of type
+     * {@code T} to the application window.
+     *
+     * <p>It is delegated to by {@link #onViewAttachedToWindow(RecyclerView.ViewHolder)} for
+     * holders that are not of type {@link ScrollingLimitedViewHolder}.
+     */
+    @SuppressWarnings("unused")
+    protected void onViewAttachedToWindowImpl(@NonNull T holder) {
+    }
+
+    @Override
+    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
+        mRecyclerView = recyclerView;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public final void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) {
+        super.onViewDetachedFromWindow(holder);
+        if (!(holder instanceof ScrollingLimitedViewHolder)) {
+            onViewDetachedFromWindowImpl((T) holder);
+        }
+    }
+
+    /**
+     * Handles detaching {@link androidx.recyclerview.widget.RecyclerView.ViewHolder}s of type
+     * {@code T} from the application window.
+     *
+     * <p>It is delegated to by {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder)} for
+     * holders that are not of type {@link ScrollingLimitedViewHolder}.
+     */
+    @SuppressWarnings("unused")
+    protected void onViewDetachedFromWindowImpl(@NonNull T holder) {
+    }
+
+    @Override
+    public void setMaxItems(int maxItems) {
+        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);
+        }
+    }
+
+    /**
+     * Returns the position in the truncated list to scroll to when the list is limited.
+     *
+     * Returns -1 to disable the scrolling.
+     */
+    protected int getScrollToPositionWhenRestricted() {
+        return -1;
+    }
+
+    private void autoScrollWhenRestricted() {
+        int scrollToPosition = getScrollToPositionWhenRestricted();
+        if (scrollToPosition >= 0) {
+            RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+            if (layoutManager != null) {
+                mRecyclerView.getLayoutManager().scrollToPosition(scrollToPosition);
+            }
+        }
+    }
+
+    /**
+     * Computes the anchor point index in the original list when limiting starts.
+     * Returns position 0 by default.
+     *
+     * Override this function to return a different anchor point to control the position of the
+     * limiting window.
+     */
+    protected int computeAnchorIndexWhenRestricting() {
+        return 0;
+    }
+
+    /**
+     * Updates the changes from underlying data along with a new anchor.
+     */
+    public void updateUnderlyingDataChanged(int unrestrictedCount, int newAnchorIndex) {
+        mRangeFilter.recompute(unrestrictedCount, newAnchorIndex);
+    }
+
+    /**
+     * Changes the index where the limiting range surrounds. Items that are added and removed will
+     * be notified.
+     */
+    public void notifyLimitingAnchorChanged(int newPivotIndex) {
+        mRangeFilter.notifyPivotIndexChanged(newPivotIndex);
+    }
+
+    @Override
+    public void setScrollingLimitedMessageResId(@StringRes int resId) {
+        if (mScrollingLimitedMessageResId == null || mScrollingLimitedMessageResId != resId) {
+            mScrollingLimitedMessageResId = resId;
+            mRangeFilter.invalidateMessagePositions();
+        }
+    }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/DefaultScrollBar.java b/car-ui-lib/src/com/android/car/ui/recyclerview/DefaultScrollBar.java
index 7b38504..912dda3 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/DefaultScrollBar.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/DefaultScrollBar.java
@@ -23,10 +23,8 @@
 import android.view.ViewGroup;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.Interpolator;
-import android.widget.ImageView;
 
 import androidx.annotation.IntRange;
-import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.OrientationHelper;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -36,31 +34,23 @@
 /**
  * The default scroll bar widget for the {@link CarUiRecyclerView}.
  *
- * <p>Inspired by {@link androidx.car.widget.PagedListView}. Most pagination and scrolling logic has
- * been ported from the PLV with minor updates.
+ * <p>Inspired by {@link androidx.car.widget.PagedListView}. Most pagination and scrolling logic
+ * has been ported from the PLV with minor updates.
  */
 class DefaultScrollBar implements ScrollBar {
 
-    @VisibleForTesting
-    int mPaddingStart;
-    @VisibleForTesting
-    int mPaddingEnd;
-
     private float mButtonDisabledAlpha;
     private CarUiSnapHelper mSnapHelper;
 
-    private ImageView mUpButton;
     private View mScrollView;
+    private View mScrollTrack;
     private View mScrollThumb;
-    private ImageView mDownButton;
-
-    private int mSeparatingMargin;
+    private View mUpButton;
+    private View mDownButton;
+    private int mScrollbarThumbMinHeight;
 
     private RecyclerView mRecyclerView;
 
-    /** The amount of space that the scroll thumb is allowed to roam over. */
-    private int mScrollThumbTrackHeight;
-
     private final Interpolator mPaginationInterpolator = new AccelerateDecelerateInterpolator();
 
     private final int mRowsPerPage = -1;
@@ -68,6 +58,9 @@
 
     private OrientationHelper mOrientationHelper;
 
+    private OnContinuousScrollListener mPageUpOnContinuousScrollListener;
+    private OnContinuousScrollListener mPageDownOnContinuousScrollListener;
+
     @Override
     public void initialize(RecyclerView rv, View scrollView) {
         mRecyclerView = rv;
@@ -77,28 +70,39 @@
         Resources res = rv.getContext().getResources();
 
         mButtonDisabledAlpha = CarUiUtils.getFloat(res, R.dimen.car_ui_button_disabled_alpha);
+        mScrollbarThumbMinHeight = rv.getContext().getResources()
+                .getDimensionPixelSize(R.dimen.car_ui_scrollbar_min_thumb_height);
 
         getRecyclerView().addOnScrollListener(mRecyclerViewOnScrollListener);
         getRecyclerView().getRecycledViewPool().setMaxRecycledViews(0, 12);
 
-        mSeparatingMargin = res.getDimensionPixelSize(R.dimen.car_ui_scrollbar_separator_margin);
-
-        mUpButton = requireViewByRefId(mScrollView, R.id.page_up);
-        PaginateButtonClickListener upButtonClickListener =
+        mUpButton = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_page_up);
+        PaginateButtonClickListener paginateUpButtonClickListener =
                 new PaginateButtonClickListener(PaginationListener.PAGE_UP);
-        mUpButton.setOnClickListener(upButtonClickListener);
+        mUpButton.setOnClickListener(paginateUpButtonClickListener);
+        mPageUpOnContinuousScrollListener = new OnContinuousScrollListener(rv.getContext(),
+                paginateUpButtonClickListener);
+        mUpButton.setOnTouchListener(mPageUpOnContinuousScrollListener);
 
-        mDownButton = requireViewByRefId(mScrollView, R.id.page_down);
-        PaginateButtonClickListener downButtonClickListener =
+        mDownButton = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_page_down);
+        PaginateButtonClickListener paginateDownButtonClickListener =
                 new PaginateButtonClickListener(PaginationListener.PAGE_DOWN);
-        mDownButton.setOnClickListener(downButtonClickListener);
+        mDownButton.setOnClickListener(paginateDownButtonClickListener);
+        mPageDownOnContinuousScrollListener = new OnContinuousScrollListener(rv.getContext(),
+                paginateDownButtonClickListener);
+        mDownButton.setOnTouchListener(mPageDownOnContinuousScrollListener);
 
-        mScrollThumb = requireViewByRefId(mScrollView, R.id.scrollbar_thumb);
+        mScrollTrack = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_track);
+        mScrollThumb = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_thumb);
 
         mSnapHelper = new CarUiSnapHelper(rv.getContext());
         getRecyclerView().setOnFlingListener(null);
         mSnapHelper.attachToRecyclerView(getRecyclerView());
 
+        // enables fast scrolling.
+        FastScroller fastScroller = new FastScroller(mRecyclerView, mScrollTrack, mScrollView);
+        fastScroller.enable();
+
         mScrollView.addOnLayoutChangeListener(
                 (View v,
                         int left,
@@ -109,27 +113,6 @@
                         int oldTop,
                         int oldRight,
                         int oldBottom) -> {
-                    int width = right - left;
-
-                    OrientationHelper orientationHelper =
-                            getOrientationHelper(getRecyclerView().getLayoutManager());
-
-                    // This value will keep track of the top of the current view being laid out.
-                    int layoutTop = orientationHelper.getStartAfterPadding() + mPaddingStart;
-
-                    // Lay out the up button at the top of the view.
-                    layoutViewCenteredFromTop(mUpButton, layoutTop, width);
-                    layoutTop = mUpButton.getBottom();
-
-                    // Lay out the scroll thumb
-                    layoutTop += mSeparatingMargin;
-                    layoutViewCenteredFromTop(mScrollThumb, layoutTop, width);
-
-                    // Lay out the bottom button at the bottom of the view.
-                    int downBottom = orientationHelper.getEndAfterPadding() - mPaddingEnd;
-                    layoutViewCenteredFromBottom(mDownButton, downBottom, width);
-
-                    mHandler.post(this::calculateScrollThumbTrackHeight);
                     mHandler.post(() -> updatePaginationButtons(/* animate= */ false));
                 });
     }
@@ -145,9 +128,8 @@
 
     @Override
     public void setPadding(int paddingStart, int paddingEnd) {
-        this.mPaddingStart = paddingStart;
-        this.mPaddingEnd = paddingEnd;
-        requestLayout();
+        mScrollView.setPadding(mScrollView.getPaddingLeft(), paddingStart,
+                mScrollView.getPaddingRight(), paddingEnd);
     }
 
     /**
@@ -156,6 +138,13 @@
      * @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);
     }
@@ -166,6 +155,13 @@
      * @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);
     }
@@ -179,74 +175,32 @@
         return mDownButton.isEnabled();
     }
 
-    /** Listener for when the list should paginate. */
+    /**
+     * Listener for when the list should paginate.
+     */
     interface PaginationListener {
         int PAGE_UP = 0;
         int PAGE_DOWN = 1;
 
-        /** Called when the linked view should be paged in the given direction */
+        /**
+         * Called when the linked view should be paged in the given direction
+         */
         void onPaginate(int direction);
     }
 
     /**
-     * Calculate the amount of space that the scroll bar thumb is allowed to roam. The thumb is
-     * allowed to take up the space between the down bottom and the up or alpha jump button,
-     * depending
-     * on if the latter is visible.
-     */
-    private void calculateScrollThumbTrackHeight() {
-        // Subtracting (2 * mSeparatingMargin) for the top/bottom margin above and below the
-        // scroll bar thumb.
-        mScrollThumbTrackHeight = mDownButton.getTop() - (2 * mSeparatingMargin);
-
-        // If there's an alpha jump button, then the thumb is laid out starting from below that.
-        mScrollThumbTrackHeight -= mUpButton.getBottom();
-    }
-
-    /**
-     * Lays out the given View starting from the given {@code top} value downwards and centered
-     * within the given {@code availableWidth}.
-     *
-     * @param view The view to lay out.
-     * @param top The top value to start laying out from. This value will be the resulting top value
-     * of the view.
-     * @param availableWidth The width in which to center the given view.
-     */
-    private static void layoutViewCenteredFromTop(View view, int top, int availableWidth) {
-        int viewWidth = view.getMeasuredWidth();
-        int viewLeft = (availableWidth - viewWidth) / 2;
-        view.layout(viewLeft, top, viewLeft + viewWidth, top + view.getMeasuredHeight());
-    }
-
-    /**
-     * Lays out the given View starting from the given {@code bottom} value upwards and centered
-     * within the given {@code availableSpace}.
-     *
-     * @param view The view to lay out.
-     * @param bottom The bottom value to start laying out from. This value will be the resulting
-     * bottom value of the view.
-     * @param availableWidth The width in which to center the given view.
-     */
-    private static void layoutViewCenteredFromBottom(View view, int bottom, int availableWidth) {
-        int viewWidth = view.getMeasuredWidth();
-        int viewLeft = (availableWidth - viewWidth) / 2;
-        view.layout(viewLeft, bottom - view.getMeasuredHeight(), viewLeft + viewWidth, bottom);
-    }
-
-    /**
      * Sets the range, offset and extent of the scroll bar. The range represents the size of a
      * container for the scrollbar thumb; offset is the distance from the start of the container to
      * where the thumb should be; and finally, extent is the size of the thumb.
      *
      * <p>These values can be expressed in arbitrary units, so long as they share the same units.
-     * The
-     * values should also be positive.
+     * The values should also be positive.
      *
-     * @param range The range of the scrollbar's thumb
-     * @param offset The offset of the scrollbar's thumb
-     * @param extent The extent of the scrollbar's thumb
+     * @param range   The range of the scrollbar's thumb
+     * @param offset  The offset of the scrollbar's thumb
+     * @param extent  The extent of the scrollbar's thumb
      * @param animate Whether or not the thumb should animate from its current position to the
-     * position specified by the given range, offset and extent.
+     *                position specified by the given range, offset and extent.
      */
     private void setParameters(
             @IntRange(from = 0) int range,
@@ -281,23 +235,23 @@
      * 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 range  The total amount of space the scroll bar is allowed to roam over.
      * @param extent The amount of space that the scroll bar takes up relative to the range.
      * @return The height of the scroll bar thumb in pixels.
      */
     private int calculateScrollThumbLength(int range, int extent) {
         // Scale the length by the available space that the thumb can fill.
-        return Math.round(((float) extent / range) * mScrollThumbTrackHeight);
+        return Math.max(Math.round(((float) extent / range) * mScrollTrack.getHeight()),
+                mScrollbarThumbMinHeight);
     }
 
     /**
      * Calculates and returns how much the scroll thumb should be offset from the top of where it
-     * has
-     * been laid out.
+     * 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 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.
      */
@@ -305,14 +259,16 @@
         // Ensure that if the user has reached the bottom of the list, then the scroll bar is
         // aligned to the bottom as well. Otherwise, scale the offset appropriately.
         // This offset will be a value relative to the parent of this scrollbar, so start by where
-        // the top of mScrollThumb is.
-        return mScrollThumb.getTop()
+        // the top of scrollbar track is.
+        return mScrollTrack.getTop()
                 + (isDownEnabled()
-                ? Math.round(((float) offset / range) * mScrollThumbTrackHeight)
-                : mScrollThumbTrackHeight - thumbLength);
+                ? Math.round(((float) offset / range) * (mScrollTrack.getHeight() - thumbLength))
+                : mScrollTrack.getHeight() - thumbLength);
     }
 
-    /** Moves the given view to the specified 'y' position. */
+    /**
+     * Moves the given view to the specified 'y' position.
+     */
     private void moveY(final View view, float newPosition, boolean animate) {
         final int duration = animate ? 200 : 0;
         view.animate()
@@ -351,7 +307,9 @@
                 }
             };
 
-    /** Returns the page the given position is on, starting with page 0. */
+    /**
+     * Returns the page the given position is on, starting with page 0.
+     */
     int getPage(int position) {
         if (mRowsPerPage == -1) {
             return -1;
@@ -375,32 +333,30 @@
      * {@code CarUiRecyclerView}.
      *
      * <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
+     * If this is not possible due to the first item being taller than the containing {@code
      * CarUiRecyclerView}, then the snapping will not occur.
      */
     void pageUp() {
         int currentOffset = getRecyclerView().computeVerticalScrollOffset();
-        if (getRecyclerView().getLayoutManager() == null
-                || getRecyclerView().getChildCount() == 0
-                || currentOffset == 0) {
+        RecyclerView.LayoutManager layoutManager = getRecyclerView().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(getRecyclerView().getLayoutManager());
+        OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
         int screenSize = orientationHelper.getTotalSpace();
         int scrollDistance = screenSize;
+        boolean isPageUpOverLongItem;
         // The iteration order matters. In case where there are 2 items longer than screen size, we
         // want to focus on upcoming view.
-        for (int i = 0; i < getRecyclerView().getChildCount(); i++) {
+        for (int i = 0; i < layoutManager.getChildCount(); i++) {
             /*
              * We treat child View longer than screen size differently:
              * 1) When it enters screen, next pageUp will align its bottom with parent bottom;
              * 2) When it leaves screen, next pageUp will align its top with parent top.
              */
-            View child = getRecyclerView().getChildAt(i);
+            View child = layoutManager.getChildAt(i);
             if (child.getHeight() > screenSize) {
                 if (orientationHelper.getDecoratedEnd(child) < screenSize) {
                     // Child view bottom is entering screen. Align its bottom with parent bottom.
@@ -411,13 +367,32 @@
                     // is less than a full scroll. Align child top with parent top.
                     scrollDistance = Math.abs(orientationHelper.getDecoratedStart(child));
                 }
+
                 // There can be two items that are longer than the screen. We stop at the first one.
                 // This is affected by the iteration order.
-                break;
+                // Distance should always be positive. Negate its value to scroll up.
+                mRecyclerView.smoothScrollBy(0, -scrollDistance);
+                return;
             }
         }
-        // Distance should always be positive. Negate its value to scroll up.
-        mSnapHelper.smoothScrollBy(-scrollDistance);
+
+        int nextPos = mSnapHelper.estimateNextPositionDiffForScrollDistance(orientationHelper,
+                -scrollDistance);
+        View currentPosView = getFirstFullyVisibleChild(orientationHelper);
+        int currentPos = currentPosView != null ? mRecyclerView.getLayoutManager().getPosition(
+                currentPosView) : 0;
+        mRecyclerView.smoothScrollToPosition(Math.max(0, currentPos + nextPos));
+    }
+
+    private View getFirstFullyVisibleChild(OrientationHelper helper) {
+        for (int i = 0; i < getRecyclerView().getChildCount(); i++) {
+            View child = getRecyclerView().getChildAt(i);
+            if (CarUiSnapHelper.getPercentageVisible(child, helper) == 1f) {
+                return getRecyclerView().getChildAt(i);
+            }
+        }
+
+        return null;
     }
 
     /**
@@ -429,21 +404,21 @@
      * scrolled the length of a page, but not snapped to.
      */
     void pageDown() {
-        if (getRecyclerView().getLayoutManager() == null
-                || getRecyclerView().getChildCount() == 0) {
+        RecyclerView.LayoutManager layoutManager = getRecyclerView().getLayoutManager();
+        if (layoutManager == null || layoutManager.getChildCount() == 0) {
             return;
         }
 
-        OrientationHelper orientationHelper =
-                getOrientationHelper(getRecyclerView().getLayoutManager());
+        OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
         int screenSize = orientationHelper.getTotalSpace();
         int scrollDistance = screenSize;
 
         // If the last item is partially visible, page down should bring it to the top.
-        View lastChild = getRecyclerView().getChildAt(getRecyclerView().getChildCount() - 1);
-        if (getRecyclerView().getLayoutManager().isViewPartiallyVisible(lastChild,
+        View lastChild = layoutManager.getChildAt(layoutManager.getChildCount() - 1);
+        if (layoutManager.isViewPartiallyVisible(lastChild,
                 /* completelyVisible= */ false, /* acceptEndPointInclusion= */ false)) {
-            scrollDistance = orientationHelper.getDecoratedStart(lastChild);
+            scrollDistance = orientationHelper.getDecoratedStart(lastChild)
+                    - orientationHelper.getStartAfterPadding();
             if (scrollDistance <= 0) {
                 // - Scroll value is zero if the top of last item is aligned with top of the screen;
                 // - Scroll value can be negative if the child is longer than the screen size and
@@ -455,16 +430,18 @@
 
         // The iteration order matters. In case where there are 2 items longer than screen size, we
         // want to focus on upcoming view (the one at the bottom of screen).
-        for (int i = getRecyclerView().getChildCount() - 1; i >= 0; i--) {
+        for (int i = layoutManager.getChildCount() - 1; i >= 0; i--) {
             /* We treat child View longer than screen size differently:
              * 1) When it enters screen, next pageDown will align its top with parent top;
              * 2) When it leaves screen, next pageDown will align its bottom with parent bottom.
              */
-            View child = getRecyclerView().getChildAt(i);
+            View child = layoutManager.getChildAt(i);
             if (child.getHeight() > screenSize) {
-                if (orientationHelper.getDecoratedStart(child) > 0) {
+                if (orientationHelper.getDecoratedStart(child)
+                        - orientationHelper.getStartAfterPadding() > 0) {
                     // Child view top is entering screen. Align its top with parent top.
-                    scrollDistance = orientationHelper.getDecoratedStart(child);
+                    scrollDistance = orientationHelper.getDecoratedStart(lastChild)
+                            - orientationHelper.getStartAfterPadding();
                 } else if (screenSize < orientationHelper.getDecoratedEnd(child)
                         && orientationHelper.getDecoratedEnd(child) < 2 * screenSize) {
                     // Child view bottom is about to enter screen - its distance to parent bottom
@@ -477,22 +454,18 @@
             }
         }
 
-        mSnapHelper.smoothScrollBy(scrollDistance);
+        mRecyclerView.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.
+     * been calculated because the method of determining scrollbar visibility uses the current
+     * layout. If this is called after an adapter change but before the new layout, the visibility
+     * determination may not be correct.
      *
      * @param animate {@code true} if the scrollbar should animate to its new position. {@code
-     * false}
-     * if no animation is used
+     *                false} if no animation is used
      */
     private void updatePaginationButtons(boolean animate) {
 
@@ -529,12 +502,16 @@
         mScrollView.invalidate();
     }
 
-    /** Returns {@code true} if the RecyclerView is completely displaying the first item. */
+    /**
+     * Returns {@code true} if the RecyclerView is completely displaying the first item.
+     */
     boolean isAtStart() {
         return mSnapHelper.isAtStart(getRecyclerView().getLayoutManager());
     }
 
-    /** Returns {@code true} if the RecyclerView is completely displaying the last item. */
+    /**
+     * Returns {@code true} if the RecyclerView is completely displaying the last item.
+     */
     boolean isAtEnd() {
         return mSnapHelper.isAtEnd(getRecyclerView().getLayoutManager());
     }
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapter.java b/car-ui-lib/src/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapter.java
new file mode 100644
index 0000000..c865b75
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapter.java
@@ -0,0 +1,249 @@
+/*
+ * 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.recyclerview;
+
+import android.view.ViewGroup;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * A delegating implementation of {@link ContentLimiting} interface.
+ *
+ * <p>This class will provide content limiting capability to any {@link RecyclerView.Adapter} that
+ * is wrapped in it.
+ *
+ * @param <T> type of the {@link RecyclerView.ViewHolder} objects used by the delegate.
+ */
+public class DelegatingContentLimitingAdapter<T extends RecyclerView.ViewHolder>
+        extends ContentLimitingAdapter<T> {
+    private static final int SCROLLING_LIMITED_MESSAGE_VIEW_TYPE = Integer.MAX_VALUE;
+    private static final int SCROLLING_LIMITED_MESSAGE_DEFAULT_POSITION_OFFSET = -1;
+
+    private final RecyclerView.Adapter<T> mDelegate;
+    private final int mScrollingLimitedMessageViewType;
+    private final int mScrollingLimitedMessagePositionOffset;
+    @IdRes
+    private final int mConfigId;
+
+    /**
+     * Provides the abilities to delegate {@link ContentLimitingAdapter} callback functions.
+     */
+    public interface ContentLimiting {
+        /**
+         * @see ContentLimitingAdapter#getScrollToPositionWhenRestricted()
+         */
+        int getScrollToPositionWhenRestricted();
+
+        /**
+         * @see ContentLimitingAdapter#computeAnchorIndexWhenRestricting()
+         */
+        int computeAnchorIndexWhenRestricting();
+    }
+
+    /**
+     * Constructs a {@link DelegatingContentLimitingAdapter} that uses {@link Integer#MAX_VALUE}
+     * for the scrolling limited message viewType and positions it at the very bottom of the list
+     * being content limited.
+     *
+     * <p>Use {@link #DelegatingContentLimitingAdapter(RecyclerView.Adapter, int, int, int)} if you
+     * need to customize any of the two default values above.
+     *
+     * @param delegate - the {@link RecyclerView.Adapter} whose content needs to be limited.
+     * @param configId - an Id Resource that can be used to identify said adapter.
+     */
+    public DelegatingContentLimitingAdapter(
+            RecyclerView.Adapter<T> delegate,
+            @IdRes int configId) {
+        this(delegate,
+                configId,
+                SCROLLING_LIMITED_MESSAGE_VIEW_TYPE,
+                SCROLLING_LIMITED_MESSAGE_DEFAULT_POSITION_OFFSET);
+    }
+
+    /**
+     * Constructs a {@link DelegatingContentLimitingAdapter}.
+     *
+     * @param delegate - the {@link RecyclerView.Adapter} whose content needs to be limited.
+     * @param configId - an Id Resource that can be used to identify said adapter.
+     * @param viewType - viewType value for the scrolling limited message
+     * @param offset   - offset of the position of the scrolling limited message. Negative values
+     *                 will be treated as a "bottom offset", i.e. they represent the value to
+     *                 subtract from {@link #getItemCount()} to get to the actual position of the
+     *                 message. For example, by default the offset is -1, meaning the position of
+     *                 the scrolling limited message will be getItemCount() - 1, which in a list
+     *                 indexed at 0 means the very last item. Positive values will be treated as
+     *                 "top offset", so an offset of 0 will put the scrolling limited message at the
+     *                 very top of the list.
+     * @deprecated offset is not supported in the {@link ContentLimitingAdapter} any more.
+     */
+    @Deprecated
+    public DelegatingContentLimitingAdapter(RecyclerView.Adapter<T> delegate,
+            @IdRes int configId,
+            int viewType,
+            int offset) {
+        mDelegate = delegate;
+        mConfigId = configId;
+        mScrollingLimitedMessageViewType = viewType;
+        mScrollingLimitedMessagePositionOffset = offset;
+        mDelegate.registerAdapterDataObserver(new Observer());
+    }
+
+    private class Observer extends RecyclerView.AdapterDataObserver {
+
+        @Override
+        public void onChanged() {
+            DelegatingContentLimitingAdapter.this.notifyDataSetChanged();
+        }
+
+        @Override
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            DelegatingContentLimitingAdapter.this
+                    .notifyItemRangeChanged(positionStart, itemCount);
+        }
+
+        @Override
+        public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
+            DelegatingContentLimitingAdapter.this
+                    .notifyItemRangeChanged(positionStart, itemCount, payload);
+        }
+
+        @Override
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            DelegatingContentLimitingAdapter.this
+                    .notifyItemRangeInserted(positionStart, itemCount);
+        }
+
+        @Override
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            DelegatingContentLimitingAdapter.this
+                    .notifyItemRangeRemoved(positionStart, itemCount);
+        }
+
+        @Override
+        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+            DelegatingContentLimitingAdapter.this.notifyDataSetChanged();
+        }
+    }
+
+    @Override
+    @NonNull
+    public T onCreateViewHolderImpl(@NonNull ViewGroup parent, int viewType) {
+        return mDelegate.onCreateViewHolder(parent, viewType);
+    }
+
+    @Override
+    public void onBindViewHolderImpl(T holder, int position) {
+        mDelegate.onBindViewHolder(holder, position);
+    }
+
+    @Override
+    public int getItemViewTypeImpl(int position) {
+        return mDelegate.getItemViewType(position);
+    }
+
+    @Override
+    protected void onViewRecycledImpl(@NonNull T holder) {
+        mDelegate.onViewRecycled(holder);
+    }
+
+    @Override
+    protected boolean onFailedToRecycleViewImpl(@NonNull T holder) {
+        return mDelegate.onFailedToRecycleView(holder);
+    }
+
+    @Override
+    protected void onViewAttachedToWindowImpl(@NonNull T holder) {
+        mDelegate.onViewAttachedToWindow(holder);
+    }
+
+    @Override
+    protected void onViewDetachedFromWindowImpl(@NonNull T holder) {
+        mDelegate.onViewDetachedFromWindow(holder);
+    }
+
+    @Override
+    public void setHasStableIds(boolean hasStableIds) {
+        mDelegate.setHasStableIds(hasStableIds);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mDelegate.getItemId(position);
+    }
+
+    @Override
+    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
+        super.onAttachedToRecyclerView(recyclerView);
+        mDelegate.onAttachedToRecyclerView(recyclerView);
+    }
+
+    @Override
+    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
+        super.onDetachedFromRecyclerView(recyclerView);
+        mDelegate.onDetachedFromRecyclerView(recyclerView);
+    }
+
+    @Override
+    protected int computeAnchorIndexWhenRestricting() {
+        if (mDelegate instanceof DelegatingContentLimitingAdapter.ContentLimiting) {
+            return ((DelegatingContentLimitingAdapter.ContentLimiting) mDelegate)
+                    .computeAnchorIndexWhenRestricting();
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    protected int getScrollToPositionWhenRestricted() {
+        if (mDelegate instanceof DelegatingContentLimitingAdapter.ContentLimiting) {
+            return ((DelegatingContentLimitingAdapter.ContentLimiting) mDelegate)
+                    .getScrollToPositionWhenRestricted();
+        } else {
+            return -1;
+        }
+    }
+
+    @Override
+    public int getUnrestrictedItemCount() {
+        return mDelegate.getItemCount();
+    }
+
+    @Override
+    @IdRes
+    public int getConfigurationId() {
+        return mConfigId;
+    }
+
+    @Override
+    public int getScrollingLimitedMessageViewType() {
+        return mScrollingLimitedMessageViewType;
+    }
+
+    @Override
+    protected int getScrollingLimitedMessagePosition() {
+        if (mScrollingLimitedMessagePositionOffset < 0) {
+            // For negative values, treat them as a bottom offset.
+            return getItemCount() + mScrollingLimitedMessagePositionOffset;
+        } else {
+            // For positive values, treat them like a top offset.
+            return mScrollingLimitedMessagePositionOffset;
+        }
+    }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/FastScroller.java b/car-ui-lib/src/com/android/car/ui/recyclerview/FastScroller.java
new file mode 100644
index 0000000..15c0ff2
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/FastScroller.java
@@ -0,0 +1,139 @@
+/*
+ * 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.recyclerview;
+
+import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
+
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.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>
+ */
+class FastScroller implements View.OnTouchListener {
+
+    private float mTouchDownY = -1;
+
+    private View mScrollTrackView;
+    private View mScrollThumb;
+    private RecyclerView mRecyclerView;
+    private int mClickActionThreshold;
+
+    FastScroller(@NonNull RecyclerView recyclerView, @NonNull View scrollTrackView,
+            @NonNull View scrollView) {
+        mRecyclerView = recyclerView;
+        mScrollTrackView = scrollTrackView;
+        mScrollThumb = requireViewByRefId(scrollView, R.id.car_ui_scrollbar_thumb);
+        mClickActionThreshold = ViewConfiguration.get(
+                recyclerView.getContext()).getScaledTouchSlop();
+    }
+
+    void enable() {
+        if (mRecyclerView != null) {
+            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/src/com/android/car/ui/recyclerview/OnContinuousScrollListener.java b/car-ui-lib/src/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
new file mode 100644
index 0000000..c7f66bd
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
@@ -0,0 +1,108 @@
+/*
+ * 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.recyclerview;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+
+import androidx.annotation.NonNull;
+
+import com.android.car.ui.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 class OnContinuousScrollListener implements OnTouchListener {
+
+    private final Handler mHandler = new Handler();
+
+    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.car_ui_scrollbar_longpress_initial_delay);
+
+        this.mRepeatInterval = context.getResources().getInteger(
+                R.integer.car_ui_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/src/com/android/car/ui/recyclerview/PassThroughFilter.java b/car-ui-lib/src/com/android/car/ui/recyclerview/PassThroughFilter.java
new file mode 100644
index 0000000..4a02a94
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/PassThroughFilter.java
@@ -0,0 +1,60 @@
+/*
+ * 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.recyclerview;
+
+/**
+ * A trivial implementation that doesn't do any filtering (simplifies the filter's code).
+ */
+public class PassThroughFilter implements RangeFilter {
+    private int mCount;
+
+    @Override
+    public void recompute(int newCount, int pivotIndex) {
+        mCount = newCount;
+    }
+
+    @Override
+    public void notifyPivotIndexChanged(int pivotIndex) {
+    }
+
+    @Override
+    public int getFilteredCount() {
+        return mCount;
+    }
+
+    @Override
+    public int indexToPosition(int index) {
+        return index;
+    }
+
+    @Override
+    public int positionToIndex(int position) {
+        return position;
+    }
+
+    @Override
+    public void invalidateMessagePositions() {
+    }
+
+    @Override
+    public void applyFilter() {
+    }
+
+    @Override
+    public void removeFilter() {
+    }
+};
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/RangeFilter.java b/car-ui-lib/src/com/android/car/ui/recyclerview/RangeFilter.java
new file mode 100644
index 0000000..c9ea953
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/RangeFilter.java
@@ -0,0 +1,72 @@
+/*
+ * 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.recyclerview;
+
+/**
+ * Interface for helper objects that hide elements from lists that are too long. The limiting
+ * happens around a pivot element that can be anywhere in the list. Elements near that pivot will
+ * be visible, while elements at the head and / or tail of the list will be replaced by a message
+ * telling the user about the truncation.
+ */
+public interface RangeFilter {
+
+    int INVALID_INDEX = -1;
+    int INVALID_POSITION = -1;
+
+    /**
+     * Computes new restrictions when the list (and optionally) the pivot have changed.
+     * The implementation doesn't send any notification.
+     */
+    void recompute(int newCount, int pivotIndex);
+
+    /**
+     * Computes new restrictions when only the pivot has changed.
+     * The implementation must send notification changes (ideally incremental ones).
+     */
+    void notifyPivotIndexChanged(int pivotIndex);
+
+    /** Returns the number of elements in the resulting list, including the message(s). */
+    int getFilteredCount();
+
+    /**
+     * Converts an index in the unfiltered data set to a RV position in the filtered UI in the
+     * 0 .. {@link #getFilteredCount} range which includes the limits message(s).
+     * Returns INVALID_POSITION if that element has been filtered out.
+     */
+    int indexToPosition(int index);
+
+    /**
+     * Converts a RV position in the filtered UI to an index in the unfiltered data set.
+     * Returns INVALID_INDEX if a message is shown at that position.
+     */
+    int positionToIndex(int position);
+
+    /** Send notification changes for the restriction message(s) if there are any. */
+    void invalidateMessagePositions();
+
+    /**
+     * Called when the filter will be applied. If needed, notifies the adapter with data
+     * removal signal.
+     */
+    void applyFilter();
+
+    /**
+     * Called when the filter will be removed. If needed, notifies the adapter with data
+     * inserted signal.
+     */
+    void removeFilter();
+}
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/RangeFilterImpl.java b/car-ui-lib/src/com/android/car/ui/recyclerview/RangeFilterImpl.java
new file mode 100644
index 0000000..ecba8da
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/RangeFilterImpl.java
@@ -0,0 +1,264 @@
+/*
+ * 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.recyclerview;
+
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * An implementation of {@link RangeFilter} interface.
+ */
+public class RangeFilterImpl implements RangeFilter {
+
+    private static final String TAG = "RangeFilterImpl";
+
+    private final RecyclerView.Adapter<?> mAdapter;
+    private final int mMaxItems;
+    private final int mMaxItemsFirstHalf;
+    private final int mMaxItemsSecondHalf;
+
+    private int mUnlimitedCount;
+    private int mPivotIndex;
+    private final ListRange mRange = new ListRange();
+
+    /**
+     * Constructor
+     * @param adapter the adapter to notify of changes in {@link #notifyPivotIndexChanged(int)}.
+     * @param maxItems the maximum number of items to show.
+     */
+    public RangeFilterImpl(RecyclerView.Adapter<?> adapter, int maxItems) {
+        mAdapter = adapter;
+        if (maxItems <= 0) {
+            mMaxItemsFirstHalf = 0;
+            mMaxItemsSecondHalf = 0;
+            mMaxItems = 0;
+        } else {
+            mMaxItemsFirstHalf = maxItems / 2;
+            mMaxItemsSecondHalf = maxItems - mMaxItemsFirstHalf;
+            mMaxItems = maxItems;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "RangeFilterImpl{"
+                + "mMaxItemsFirstHalf=" + mMaxItemsFirstHalf
+                + "mMaxItemsSecondHalf=" + mMaxItemsSecondHalf
+                + ", mUnlimitedCount=" + mUnlimitedCount
+                + ", mPivotIndex=" + mPivotIndex
+                + ", mRange=" + mRange.toString()
+                + '}';
+    }
+
+    @Override
+    public int getFilteredCount() {
+        return mRange.mLimitedCount;
+    }
+
+    @Override
+    public void invalidateMessagePositions() {
+        if (mRange.mClampedHead > 0) {
+            mAdapter.notifyItemChanged(0);
+        }
+        if (mRange.mClampedTail > 0) {
+            mAdapter.notifyItemChanged(getFilteredCount() - 1);
+        }
+    }
+
+    @Override
+    public void applyFilter() {
+        if (mRange.isTailClamped()) {
+            mAdapter.notifyItemInserted(mUnlimitedCount);
+            mAdapter.notifyItemRangeRemoved(mRange.mEndIndex, mUnlimitedCount - mRange.mEndIndex);
+        }
+        if (mRange.isHeadClamped()) {
+            mAdapter.notifyItemRangeRemoved(0, mRange.mStartIndex);
+            mAdapter.notifyItemInserted(0);
+        }
+    }
+
+    @Override
+    public void removeFilter() {
+        if (mRange.isTailClamped()) {
+            // Remove the message
+            mAdapter.notifyItemRemoved(mRange.mLimitedCount - 1);
+            // Add the tail items that were dropped
+            mAdapter.notifyItemRangeInserted(mRange.mLimitedCount - 1,
+                    mUnlimitedCount - mRange.mEndIndex);
+        }
+        if (mRange.isHeadClamped()) {
+            // Add the head items that were dropped
+            mAdapter.notifyItemRangeInserted(1, mRange.mStartIndex);
+            // Remove the message
+            mAdapter.notifyItemRemoved(0);
+        }
+    }
+
+    @Override
+    public void recompute(int newCount, int pivotIndex) {
+        if (pivotIndex < 0 || newCount <= pivotIndex) {
+            Log.e(TAG, "Invalid pivotIndex: " + pivotIndex + " newCount: " + newCount);
+            pivotIndex = 0;
+        }
+        mUnlimitedCount = newCount;
+        mPivotIndex = pivotIndex;
+
+        mRange.mClampedHead = 0;
+        mRange.mClampedTail = 0;
+
+        if (mUnlimitedCount <= mMaxItems) {
+            // Under the cap case.
+            mRange.mStartIndex = 0;
+            mRange.mEndIndex = mUnlimitedCount;
+            mRange.mLimitedCount = mUnlimitedCount;
+        } else if (mMaxItems <= 0) {
+            // Zero cap case.
+            mRange.mStartIndex = 0;
+            mRange.mEndIndex = 0;
+            mRange.mLimitedCount = 1; // One limit message
+            mRange.mClampedTail = 1;
+        } else if (mPivotIndex <= mMaxItemsFirstHalf) {
+            // No need to clamp the head case
+            // For example: P = 2, M/2 = 2 => exactly two items before the pivot.
+            // Tail has to be clamped or we'd be in the "under the cap" case.
+            mRange.mStartIndex = 0;
+            mRange.mEndIndex = mMaxItems;
+            mRange.mLimitedCount = mMaxItems + 1; // One limit message at the end
+            mRange.mClampedTail = 1;
+        } else if ((mUnlimitedCount - 1 - mPivotIndex) <= mMaxItemsSecondHalf) {
+            // No need to clamp the tail case
+            // For example: C = 5, P = 2 => exactly 2 items after the pivot (count is exclusive).
+            // Head has to be clamped or we'd be in the "under the cap" case.
+            mRange.mEndIndex = mUnlimitedCount;
+            mRange.mStartIndex = mRange.mEndIndex - mMaxItems;
+            mRange.mLimitedCount = mMaxItems + 1; // One limit message at the start
+            mRange.mClampedHead = 1;
+        } else {
+            // Both head and tail need clamping
+            mRange.mStartIndex = mPivotIndex - mMaxItemsFirstHalf;
+            mRange.mEndIndex = mPivotIndex + mMaxItemsSecondHalf;
+            mRange.mLimitedCount = mMaxItems + 2; // One limit message at each end.
+            mRange.mClampedHead = 1;
+            mRange.mClampedTail = 1;
+        }
+    }
+
+    @Override
+    public void notifyPivotIndexChanged(int pivotIndex) {
+        // TODO: Implement this function.
+    }
+
+    @Override
+    public int indexToPosition(int index) {
+        if ((mRange.mStartIndex <= index) && (index < mRange.mEndIndex)) {
+            return mRange.indexToPosition(index);
+        } else {
+            return INVALID_POSITION;
+        }
+    }
+
+    @Override
+    public int positionToIndex(int position) {
+        return mRange.positionToIndex(position);
+    }
+
+    @VisibleForTesting
+    ListRange getRange() {
+        return mRange;
+    }
+
+    /** Represents a portion of the unfiltered list. */
+    static class ListRange {
+        public static final int INVALID_INDEX = -1;
+
+        @VisibleForTesting
+        /* In original data, inclusive. */
+                int mStartIndex;
+        @VisibleForTesting
+        /* In original data, exclusive. */
+                int mEndIndex;
+
+        @VisibleForTesting
+        /* 1 when clamped, otherwise 0. */
+                int mClampedHead;
+        @VisibleForTesting
+        /* 1 when clamped, otherwise 0. */
+                int mClampedTail;
+
+        @VisibleForTesting
+        /* The count of the resulting elements, including the truncation message(s). */
+                int mLimitedCount;
+
+        /**
+         * Deep copy from a ListRange.
+         */
+        public void copyFrom(ListRange range) {
+            mStartIndex = range.mStartIndex;
+            mEndIndex = range.mEndIndex;
+            mClampedHead = range.mClampedHead;
+            mClampedTail = range.mClampedTail;
+            mLimitedCount = range.mLimitedCount;
+        }
+
+        @Override
+        public String toString() {
+            return "ListRange{"
+                    + "mStartIndex=" + mStartIndex
+                    + ", mEndIndex=" + mEndIndex
+                    + ", mClampedHead=" + mClampedHead
+                    + ", mClampedTail=" + mClampedTail
+                    + ", mLimitedCount=" + mLimitedCount
+                    + '}';
+        }
+
+        /**
+         * Returns true if two ranges intersect.
+         */
+        public boolean intersects(ListRange range) {
+            return ((range.mEndIndex > mStartIndex) && (mEndIndex > range.mStartIndex));
+        }
+
+        /**
+         * Converts an index in the unrestricted list to the position in the restricted one.
+         *
+         * Unchecked index needed by {@link #notifyPivotIndexChanged(int)}.
+         */
+        public int indexToPosition(int index) {
+            return index - mStartIndex + mClampedHead;
+        }
+
+        /** Converts the position in the restricted list to an index in the unrestricted one.*/
+        public int positionToIndex(int position) {
+            int index = position - mClampedHead + mStartIndex;
+            if ((index < mStartIndex) || (mEndIndex <= index)) {
+                return INVALID_INDEX;
+            } else {
+                return index;
+            }
+        }
+
+        public boolean isHeadClamped() {
+            return mClampedHead == 1;
+        }
+
+        public boolean isTailClamped() {
+            return mClampedTail == 1;
+        }
+    }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/ScrollingLimitedViewHolder.java b/car-ui-lib/src/com/android/car/ui/recyclerview/ScrollingLimitedViewHolder.java
new file mode 100644
index 0000000..63b8692
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/ScrollingLimitedViewHolder.java
@@ -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.
+ */
+
+package com.android.car.ui.recyclerview;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.R;
+import com.android.car.ui.utils.CarUiUtils;
+
+/**
+ * {@link RecyclerView.ViewHolder} for the last item in a scrolling limited list.
+ */
+public final class ScrollingLimitedViewHolder extends RecyclerView.ViewHolder {
+
+    private final TextView mMessage;
+
+    /**
+     * Return an instance of {@link ScrollingLimitedViewHolder} with an already inflated root view.
+     * @param parent - the parent {@link ViewGroup} to use during inflation of the root view.
+     */
+    public static ScrollingLimitedViewHolder create(@NonNull ViewGroup parent) {
+        View rootView = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.car_ui_list_limiting_message, parent, false);
+        return new ScrollingLimitedViewHolder(rootView);
+    }
+
+    ScrollingLimitedViewHolder(@NonNull View itemView) {
+        super(itemView);
+        mMessage = CarUiUtils.requireViewByRefId(itemView, R.id.car_ui_list_limiting_message);
+    }
+
+    /**
+     * Update the content of this {@link ScrollingLimitedViewHolder} object using the provided
+     * message String resource id.
+     * @param messageId
+     */
+    public void bind(@StringRes @Nullable Integer messageId) {
+        int resId = (messageId != null) ? messageId : R.string.car_ui_scrolling_limited_message;
+        mMessage.setText(mMessage.getContext().getString(resId));
+    }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java b/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java
index d462b92..d36346e 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java
@@ -24,6 +24,8 @@
 
 import com.android.car.ui.R;
 
+import java.util.Objects;
+
 /** Adds interior dividers to a RecyclerView with a GridLayoutManager. */
 public class GridDividerItemDecoration extends RecyclerView.ItemDecoration {
 
@@ -86,7 +88,9 @@
      * @param parent The RecyclerView onto which dividers are being added
      */
     private void drawHorizontalDividers(Canvas canvas, RecyclerView parent) {
-        int childCount = parent.getChildCount();
+        RecyclerView.LayoutManager layoutManager = Objects.requireNonNull(
+                parent.getLayoutManager());
+        int childCount = layoutManager.getChildCount();
         int rowCount = childCount / mNumColumns;
         int lastRowChildCount = childCount % mNumColumns;
         int lastColumn = Math.min(childCount, mNumColumns);
@@ -99,8 +103,9 @@
                 lastRowChildIndex = i + ((rowCount - 1) * mNumColumns);
             }
 
-            View firstRowChild = parent.getChildAt(i);
-            View lastRowChild = parent.getChildAt(lastRowChildIndex);
+
+            View firstRowChild = layoutManager.getChildAt(i);
+            View lastRowChild = layoutManager.getChildAt(lastRowChildIndex);
 
             int dividerTop =
                     firstRowChild.getTop() + (int) parent.getContext().getResources().getDimension(
@@ -127,7 +132,9 @@
      * @param parent The RecyclerView onto which dividers are being added
      */
     private void drawVerticalDividers(Canvas canvas, RecyclerView parent) {
-        double childCount = parent.getChildCount();
+        RecyclerView.LayoutManager layoutManager = Objects.requireNonNull(
+                parent.getLayoutManager());
+        double childCount = layoutManager.getChildCount();
         double rowCount = Math.ceil(childCount / mNumColumns);
         int rightmostChildIndex;
         for (int i = 1; i <= rowCount; i++) {
@@ -141,8 +148,8 @@
                 rightmostChildIndex = (i * mNumColumns) - 1;
             }
 
-            View leftmostChild = parent.getChildAt(mNumColumns * (i - 1));
-            View rightmostChild = parent.getChildAt(rightmostChildIndex);
+            View leftmostChild = layoutManager.getChildAt(mNumColumns * (i - 1));
+            View rightmostChild = layoutManager.getChildAt(rightmostChildIndex);
 
             // draws on top of each row.
             int dividerLeft =
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java b/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java
index 0adbdeb..e79468f 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java
@@ -98,9 +98,10 @@
                 - (int) parent.getContext().getResources().getDimension(
                 R.dimen.car_ui_recyclerview_divider_bottom_margin);
 
-        int childCount = parent.getChildCount();
+        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+        int childCount = layoutManager.getChildCount();
         for (int i = 0; i < childCount - 1; i++) {
-            View child = parent.getChildAt(i);
+            View child = layoutManager.getChildAt(i);
 
             RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
 
@@ -127,9 +128,10 @@
                 - (int) parent.getContext().getResources().getDimension(
                 R.dimen.car_ui_recyclerview_divider_end_margin);
 
-        int childCount = parent.getChildCount();
+        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+        int childCount = layoutManager.getChildCount();
         for (int i = 0; i < childCount - 1; i++) {
-            View child = parent.getChildAt(i);
+            View child = layoutManager.getChildAt(i);
 
             RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
 
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java b/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
index d757f05..37ed388 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
@@ -164,7 +164,8 @@
             parentLeft = parent.getPaddingLeft();
             offsetDrawableRight = parentLeft + mOffsetDrawable.getIntrinsicWidth();
         } else {
-            View lastChild = parent.getChildAt(parent.getChildCount() - 1);
+            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+            View lastChild = layoutManager.getChildAt(layoutManager.getChildCount() - 1);
             RecyclerView.LayoutParams lastChildLayoutParams =
                     (RecyclerView.LayoutParams) lastChild.getLayoutParams();
             parentLeft = lastChild.getRight() + lastChildLayoutParams.rightMargin;
@@ -186,7 +187,8 @@
             parentTop = parent.getPaddingTop();
             offsetDrawableBottom = parentTop + mOffsetDrawable.getIntrinsicHeight();
         } else {
-            View lastChild = parent.getChildAt(parent.getChildCount() - 1);
+            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+            View lastChild = layoutManager.getChildAt(layoutManager.getChildCount() - 1);
             RecyclerView.LayoutParams lastChildLayoutParams =
                     (RecyclerView.LayoutParams) lastChild.getLayoutParams();
             parentTop = lastChild.getBottom() + lastChildLayoutParams.bottomMargin;
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/ProgressBarController.java b/car-ui-lib/src/com/android/car/ui/toolbar/ProgressBarController.java
new file mode 100644
index 0000000..d42ac1b
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/ProgressBarController.java
@@ -0,0 +1,46 @@
+/*
+ * 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.toolbar;
+
+/**
+ * Interface for a Progress Bar. It's methods are a subset of the methods of
+ * {@link android.widget.ProgressBar}. This is so that an application doesn't
+ * have access to customize the {@link android.widget.ProgressBar} or other
+ * views in it's hierarchy in ways that were not intended.
+ */
+public interface ProgressBarController {
+
+    /** Shows/hides the progress bar */
+    void setVisible(boolean visible);
+    /** Returns true if the progress bar is visible */
+    boolean isVisible();
+    /** Equivalent to {@link android.widget.ProgressBar#setIndeterminate(boolean)} */
+    void setIndeterminate(boolean indeterminate);
+    /** Equivalent to {@link android.widget.ProgressBar#isIndeterminate()} */
+    boolean isIndeterminate();
+    /** Equivalent to {@link android.widget.ProgressBar#setMax(int)} */
+    void setMax(int max);
+    /** Equivalent to {@link android.widget.ProgressBar#getMax()} */
+    int getMax();
+    /** Equivalent to {@link android.widget.ProgressBar#setMin(int)} */
+    void setMin(int min);
+    /** Equivalent to {@link android.widget.ProgressBar#getMin()} */
+    int getMin();
+    /** Equivalent to {@link android.widget.ProgressBar#setProgress(int)} */
+    void setProgress(int progress);
+    /** Equivalent to {@link android.widget.ProgressBar#getProgress()} */
+    int getProgress();
+}
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/ProgressBarControllerImpl.java b/car-ui-lib/src/com/android/car/ui/toolbar/ProgressBarControllerImpl.java
new file mode 100644
index 0000000..d22dc11
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/ProgressBarControllerImpl.java
@@ -0,0 +1,87 @@
+/*
+ * 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.toolbar;
+
+import android.view.View;
+import android.widget.ProgressBar;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Implementation of {@link ProgressBarController}.
+ *
+ * <p>This class accepts a {@link ProgressBar} in it's constructor and forwards the methods
+ * of {@link ProgressBarController} to it.
+ */
+class ProgressBarControllerImpl implements ProgressBarController {
+
+    @NonNull
+    private ProgressBar mProgressBar;
+
+    ProgressBarControllerImpl(@NonNull ProgressBar progressBar) {
+        mProgressBar = progressBar;
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        mProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
+    }
+
+    @Override
+    public boolean isVisible() {
+        return mProgressBar.getVisibility() == View.VISIBLE;
+    }
+
+    @Override
+    public void setIndeterminate(boolean indeterminate) {
+        mProgressBar.setIndeterminate(indeterminate);
+    }
+
+    @Override
+    public boolean isIndeterminate() {
+        return mProgressBar.isIndeterminate();
+    }
+
+    @Override
+    public void setMax(int max) {
+        mProgressBar.setMax(max);
+    }
+
+    @Override
+    public int getMax() {
+        return mProgressBar.getMax();
+    }
+
+    @Override
+    public void setMin(int min) {
+        mProgressBar.setMin(min);
+    }
+
+    @Override
+    public int getMin() {
+        return mProgressBar.getMin();
+    }
+
+    @Override
+    public void setProgress(int progress) {
+        mProgressBar.setProgress(progress);
+    }
+
+    @Override
+    public int getProgress() {
+        return mProgressBar.getProgress();
+    }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/TabLayout.java b/car-ui-lib/src/com/android/car/ui/toolbar/TabLayout.java
index 828e54a..1f973d2 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/TabLayout.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/TabLayout.java
@@ -19,8 +19,6 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.util.ArraySet;
 import android.util.AttributeSet;
@@ -35,7 +33,6 @@
 import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.StyleRes;
 
 import com.android.car.ui.R;
 
@@ -181,8 +178,6 @@
         private final TabLayout mTabLayout;
         @LayoutRes
         private final int mTabItemLayoutRes;
-        private final Typeface mUnselectedTypeface;
-        private final Typeface mSelectedTypeface;
         private final List<Tab> mTabList;
 
         private TabAdapter(Context context, @LayoutRes int res, TabLayout tabLayout) {
@@ -190,10 +185,6 @@
             mContext = context;
             mTabItemLayoutRes = res;
             mTabLayout = tabLayout;
-            mUnselectedTypeface = createStyledTypeface(context,
-                    R.style.TextAppearance_CarUi_Widget_Toolbar_Tab);
-            mSelectedTypeface = createStyledTypeface(context,
-                    R.style.TextAppearance_CarUi_Widget_Toolbar_Tab_Selected);
         }
 
         private void add(@NonNull Tab tab) {
@@ -282,26 +273,9 @@
             tab.bindText(textView);
             tab.bindIcon(iconView);
             tabItemView.setActivated(tab.mIsSelected);
-            textView.setTypeface(tab.mIsSelected ? mSelectedTypeface : mUnselectedTypeface);
-        }
-
-        private static Typeface createStyledTypeface(Context context, @StyleRes int styleResId) {
-            TypedArray ta = context.obtainStyledAttributes(styleResId, new int[] {
-                    android.R.attr.textStyle,
-                    android.R.attr.textFontWeight
-            });
-
-            try {
-                // If not specified, default to 0, which stands for normal.
-                int textStyle = ta.getInteger(0, 0);
-                // If not specified, default value will be 0 which is a light font.
-                int textFontWeight = ta.getInteger(1, 0);
-
-                return Typeface.create(Typeface.defaultFromStyle(textStyle), textFontWeight,
-                        (textStyle & Typeface.ITALIC) != 0);
-            } finally {
-                ta.recycle();
-            }
+            textView.setTextAppearance(tab.mIsSelected
+                    ? R.style.TextAppearance_CarUi_Widget_Toolbar_Tab_Selected
+                    : R.style.TextAppearance_CarUi_Widget_Toolbar_Tab);
         }
     }
 
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java b/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
index 697c783..524e3c2 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
@@ -23,7 +23,6 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.widget.FrameLayout;
-import android.widget.ProgressBar;
 
 import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
@@ -239,6 +238,31 @@
     }
 
     /**
+     * Sets the subtitle of the toolbar to a string resource.
+     *
+     * <p>The title may not always be shown, for example with one row layout with tabs.
+     */
+    @Override
+    public void setSubtitle(@StringRes int title) {
+        mController.setSubtitle(title);
+    }
+
+    /**
+     * 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.
+     */
+    @Override
+    public void setSubtitle(CharSequence title) {
+        mController.setSubtitle(title);
+    }
+
+    @Override
+    public CharSequence getSubtitle() {
+        return mController.getSubtitle();
+    }
+
+    /**
      * Gets the {@link TabLayout} for this toolbar.
      */
     public TabLayout getTabLayout() {
@@ -415,8 +439,8 @@
      * </MenuItems>
      * </pre>
      *
-     * @see #setMenuItems(List)
      * @return The MenuItems that were loaded from XML.
+     * @see #setMenuItems(List)
      */
     public List<MenuItem> setMenuItems(@XmlRes int resId) {
         return mController.setMenuItems(resId);
@@ -586,18 +610,8 @@
         return mController.unregisterOnBackListener(listener);
     }
 
-    /** Shows the progress bar */
-    public void showProgressBar() {
-        mController.showProgressBar();
-    }
-
-    /** Hides the progress bar */
-    public void hideProgressBar() {
-        mController.hideProgressBar();
-    }
-
     /** Returns the progress bar */
-    public ProgressBar getProgressBar() {
+    public ProgressBarController getProgressBar() {
         return mController.getProgressBar();
     }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/ToolbarController.java b/car-ui-lib/src/com/android/car/ui/toolbar/ToolbarController.java
index 2f70ab5..5404fb1 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/ToolbarController.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/ToolbarController.java
@@ -17,7 +17,6 @@
 package com.android.car.ui.toolbar;
 
 import android.graphics.drawable.Drawable;
-import android.widget.ProgressBar;
 
 import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
@@ -58,6 +57,25 @@
     CharSequence getTitle();
 
     /**
+     * Sets the subtitle of the toolbar to a string resource.
+     *
+     * <p>The title may not always be shown, for example with one row layout with tabs.
+     */
+    void setSubtitle(@StringRes int title);
+
+    /**
+     * 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(CharSequence title);
+
+    /**
+     * Gets the current toolbar subtitle.
+     */
+    CharSequence getSubtitle();
+
+    /**
      * Gets the {@link TabLayout} for this toolbar.
      */
     TabLayout getTabLayout();
@@ -253,12 +271,26 @@
     /** Unregisters an existing {@link Toolbar.OnBackListener} from the list of listeners. */
     boolean unregisterOnBackListener(Toolbar.OnBackListener listener);
 
-    /** Shows the progress bar */
-    void showProgressBar();
+    /** Gets a {@link ProgressBarController} */
+    ProgressBarController getProgressBar();
 
-    /** Hides the progress bar */
-    void hideProgressBar();
+    /**
+     * Shows the progress bar.
+     *
+     * @deprecated use {@link #getProgressBar()} instead.
+     */
+    @Deprecated
+    default void showProgressBar() {
+        getProgressBar().setVisible(true);
+    }
 
-    /** Returns the progress bar */
-    ProgressBar getProgressBar();
+    /**
+     * Hides the progress bar
+     *
+     * @deprecated use {@link #getProgressBar()} instead.
+     */
+    @Deprecated
+    default void hideProgressBar() {
+        getProgressBar().setVisible(false);
+    }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/ToolbarControllerImpl.java b/car-ui-lib/src/com/android/car/ui/toolbar/ToolbarControllerImpl.java
index eb49186..1b44d86 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/ToolbarControllerImpl.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/ToolbarControllerImpl.java
@@ -26,12 +26,13 @@
 import android.app.AlertDialog;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+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.ProgressBar;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.annotation.DrawableRes;
@@ -63,7 +64,9 @@
     private ImageView mNavIcon;
     private ImageView mLogoInNavIconSpace;
     private ViewGroup mNavIconContainer;
+    private LinearLayout mTitleContainer;
     private TextView mTitle;
+    private TextView mSubtitle;
     private ImageView mTitleLogo;
     private ViewGroup mTitleLogoContainer;
     private TabLayout mTabLayout;
@@ -102,11 +105,12 @@
     private boolean mNavIconSpaceReserved;
     private boolean mLogoFillsNavIconSpace;
     private boolean mShowLogo;
-    private ProgressBar mProgressBar;
+    private ProgressBarController mProgressBar;
     private MenuItem.Listener mOverflowItemListener = () -> {
         createOverflowDialog();
         setState(getState());
     };
+
     // Despite the warning, this has to be a field so it's not garbage-collected.
     // The only other reference to it is a weak reference
     private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener
@@ -140,6 +144,7 @@
                 R.bool.car_ui_toolbar_logo_fills_nav_icon_space);
         mShowLogo = getContext().getResources().getBoolean(
                 R.bool.car_ui_toolbar_show_logo);
+        mSearchHint = getContext().getString(R.string.car_ui_toolbar_default_search_hint);
 
         mBackground = requireViewByRefId(view, R.id.car_ui_toolbar_background);
         mTabLayout = requireViewByRefId(view, R.id.car_ui_toolbar_tabs);
@@ -147,11 +152,14 @@
         mLogoInNavIconSpace = requireViewByRefId(view, R.id.car_ui_toolbar_logo);
         mNavIconContainer = requireViewByRefId(view, R.id.car_ui_toolbar_nav_icon_container);
         mMenuItemsContainer = requireViewByRefId(view, R.id.car_ui_toolbar_menu_items_container);
+        mTitleContainer = requireViewByRefId(view, R.id.car_ui_toolbar_title_container);
+        mSubtitle = requireViewByRefId(view, R.id.car_ui_toolbar_subtitle);
         mTitle = requireViewByRefId(view, R.id.car_ui_toolbar_title);
         mTitleLogoContainer = requireViewByRefId(view, R.id.car_ui_toolbar_title_logo_container);
         mTitleLogo = requireViewByRefId(view, R.id.car_ui_toolbar_title_logo);
         mSearchViewContainer = requireViewByRefId(view, R.id.car_ui_toolbar_search_view_container);
-        mProgressBar = requireViewByRefId(view, R.id.car_ui_toolbar_progress_bar);
+        mProgressBar = new ProgressBarControllerImpl(
+                requireViewByRefId(view, R.id.car_ui_toolbar_progress_bar));
 
         mTabLayout.addListener(new TabLayout.Listener() {
             @Override
@@ -214,6 +222,33 @@
     }
 
     /**
+     * Sets the subtitle of the toolbar to a string resource.
+     *
+     * <p>The title may not always be shown, for example with one row layout with tabs.
+     */
+    @Override
+    public void setSubtitle(@StringRes int title) {
+        mSubtitle.setText(title);
+        setState(getState());
+    }
+
+    /**
+     * 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.
+     */
+    @Override
+    public void setSubtitle(CharSequence title) {
+        mSubtitle.setText(title);
+        setState(getState());
+    }
+
+    @Override
+    public CharSequence getSubtitle() {
+        return mSubtitle.getText();
+    }
+
+    /**
      * Gets the {@link TabLayout} for this toolbar.
      */
     public TabLayout getTabLayout() {
@@ -654,15 +689,22 @@
         mNavIconContainer.setOnClickListener(
                 state != Toolbar.State.HOME ? backClickListener : null);
         mNavIconContainer.setClickable(state != Toolbar.State.HOME);
+        mNavIconContainer.setContentDescription(state != Toolbar.State.HOME
+                ? mContext.getString(R.string.car_ui_toolbar_nav_icon_content_description)
+                : null);
 
         boolean hasTabs = mTabLayout.getTabCount() > 0
                 && (state == Toolbar.State.HOME
                 || (state == Toolbar.State.SUBPAGE && mShowTabsInSubpage));
         // Show the title if we're in the subpage state, or in the home state with no tabs or tabs
         // on the second row
-        mTitle.setVisibility((state == Toolbar.State.SUBPAGE || state == Toolbar.State.HOME)
+        int visibility = (state == Toolbar.State.SUBPAGE || state == Toolbar.State.HOME)
                 && (!hasTabs || mIsTabsInSecondRow)
-                ? VISIBLE : GONE);
+                ? VISIBLE : GONE;
+        mTitleContainer.setVisibility(visibility);
+        mSubtitle.setVisibility(
+                TextUtils.isEmpty(mSubtitle.getText()) ? GONE : VISIBLE);
+
         mTabLayout.setVisibility(hasTabs ? VISIBLE : GONE);
 
         if (mSearchView != null) {
@@ -750,18 +792,8 @@
         return mOnBackListeners.remove(listener);
     }
 
-    /** Shows the progress bar */
-    public void showProgressBar() {
-        mProgressBar.setVisibility(View.VISIBLE);
-    }
-
-    /** Hides the progress bar */
-    public void hideProgressBar() {
-        mProgressBar.setVisibility(View.GONE);
-    }
-
     /** Returns the progress bar */
-    public ProgressBar getProgressBar() {
+    public ProgressBarController getProgressBar() {
         return mProgressBar;
     }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/utils/CarUiUtils.java b/car-ui-lib/src/com/android/car/ui/utils/CarUiUtils.java
index 54d64f1..497d028 100644
--- a/car-ui-lib/src/com/android/car/ui/utils/CarUiUtils.java
+++ b/car-ui-lib/src/com/android/car/ui/utils/CarUiUtils.java
@@ -20,8 +20,10 @@
 import android.content.ContextWrapper;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.util.TypedValue;
 import android.view.View;
+import android.view.ViewGroup;
 
 import androidx.annotation.DimenRes;
 import androidx.annotation.IdRes;
@@ -29,13 +31,15 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.StyleRes;
 import androidx.annotation.UiThread;
+import androidx.core.view.ViewCompat;
 
 /**
  * Collection of utility methods
  */
 public final class CarUiUtils {
     /** This is a utility class */
-    private CarUiUtils() {}
+    private CarUiUtils() {
+    }
 
     /**
      * Reads a float value from a dimens resource. This is necessary as {@link Resources#getFloat}
@@ -118,4 +122,68 @@
         }
         return view;
     }
+
+    /**
+     * Updates the preference view enabled state. If the view is disabled we just disable the child
+     * of preference like TextView, ImageView. The preference itself is always enabled to get the
+     * click events. Ripple effect in background is also removed by default. If the ripple is
+     * needed see
+     * {@link IDisabledPreferenceCallback#setShouldShowRippleOnDisabledPreference(boolean)}
+     */
+    public static Drawable setPreferenceViewEnabled(boolean viewEnabled, View itemView,
+            Drawable background, boolean shouldShowRippleOnDisabledPreference) {
+        if (viewEnabled) {
+            if (background != null) {
+                ViewCompat.setBackground(itemView, background);
+            }
+            setChildViewsEnabled(itemView, true, false);
+        } else {
+            itemView.setEnabled(true);
+            if (background == null) {
+                // store the original background.
+                background = itemView.getBackground();
+            }
+            updateRippleStateOnDisabledPreference(false, shouldShowRippleOnDisabledPreference,
+                    background, itemView);
+            setChildViewsEnabled(itemView, false, true);
+        }
+        return background;
+    }
+
+    /**
+     * Sets the enabled state on the views of the preference. If the view is being disabled we want
+     * only child views of preference to be disabled.
+     */
+    private static void setChildViewsEnabled(View view, boolean enabled, boolean isRootView) {
+        if (!isRootView) {
+            view.setEnabled(enabled);
+        }
+        if (view instanceof ViewGroup) {
+            ViewGroup grp = (ViewGroup) view;
+            for (int index = 0; index < grp.getChildCount(); index++) {
+                setChildViewsEnabled(grp.getChildAt(index), enabled, false);
+            }
+        }
+    }
+
+    /**
+     * Updates the ripple state on the given preference.
+     *
+     * @param isEnabled whether the preference is enabled or not
+     * @param shouldShowRippleOnDisabledPreference should ripple be displayed when the preference is
+     * clicked
+     * @param background drawable that represents the ripple
+     * @param preference preference on which drawable will be applied
+     */
+    public static void updateRippleStateOnDisabledPreference(boolean isEnabled,
+            boolean shouldShowRippleOnDisabledPreference, Drawable background, View preference) {
+        if (isEnabled || preference == null) {
+            return;
+        }
+        if (shouldShowRippleOnDisabledPreference && background != null) {
+            ViewCompat.setBackground(preference, background);
+        } else {
+            ViewCompat.setBackground(preference, null);
+        }
+    }
 }
diff --git a/car-ui-lib/tests/apitest/auto-generate-resources.py b/car-ui-lib/tests/apitest/auto-generate-resources.py
index 9645ef5..035959c 100755
--- a/car-ui-lib/tests/apitest/auto-generate-resources.py
+++ b/car-ui-lib/tests/apitest/auto-generate-resources.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright 2019, The Android Open Source Project
 #
@@ -17,13 +17,30 @@
 import argparse
 import os
 import sys
-from resource_utils import get_all_resources, get_resources_from_single_file, remove_layout_resources
+from resource_utils import get_all_resources, get_resources_from_single_file, add_resource_to_set, Resource
 from git_utils import has_chassis_changes
+from datetime import date
 
 # path to 'packages/apps/Car/libs/car-ui-lib/'
 ROOT_FOLDER = os.path.dirname(os.path.abspath(__file__)) + '/../..'
 OUTPUT_FILE_PATH = ROOT_FOLDER + '/tests/apitest/'
 
+
+COPYRIGHT_STR = """Copyright (C) %s The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.""" % (date.today().strftime("%Y"))
+
+
 """
 Script used to update the 'current.xml' file. This is being used as part of pre-submits to
 verify whether resources previously exposed to OEMs are being changed by a CL, potentially
@@ -48,9 +65,10 @@
         compare_resources(ROOT_FOLDER+'/res', OUTPUT_FILE_PATH + 'current.xml')
     else:
         generate_current_file(ROOT_FOLDER+'/res', output_file)
+        generate_overlayable_file(ROOT_FOLDER+'/res')
 
 def generate_current_file(res_folder, output_file='current.xml'):
-    resources = remove_layout_resources(get_all_resources(res_folder))
+    resources = get_all_resources(res_folder)
     resources = sorted(resources, key=lambda x: x.type + x.name)
 
     # defer importing lxml to here so that people who aren't editing chassis don't have to have
@@ -67,13 +85,96 @@
 
     data = etree.ElementTree(root)
 
-    with open(OUTPUT_FILE_PATH + output_file, 'w') as f:
+    with open(OUTPUT_FILE_PATH + output_file, 'wb') as f:
+        data.write(f, pretty_print=True, xml_declaration=True, encoding='utf-8')
+
+def generate_overlayable_file(res_folder):
+    resources = get_all_resources(res_folder)
+    # 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'))
+    resources = sorted(resources, key=lambda x: x.type + x.name)
+
+    # defer importing lxml to here so that people who aren't editing chassis don't have to have
+    # lxml installed
+    import lxml.etree as etree
+
+    root = etree.Element('resources')
+
+    root.addprevious(etree.Comment(COPYRIGHT_STR))
+    root.addprevious(etree.Comment('THIS FILE IS AUTO GENERATED, DO NOT EDIT MANUALLY.'))
+
+    overlayable = etree.SubElement(root, 'overlayable')
+    overlayable.set('name', 'car-ui-lib')
+
+    policy = etree.SubElement(overlayable, 'policy')
+    policy.set('type', 'public')
+
+    for resource in resources:
+        item = etree.SubElement(policy, 'item')
+        item.set('type', resource.type)
+        item.set('name', resource.name)
+
+    data = etree.ElementTree(root)
+
+    output_file=ROOT_FOLDER+'/res-overlayable/values/overlayable.xml'
+    with open(output_file, 'wb') as f:
         data.write(f, pretty_print=True, xml_declaration=True, encoding='utf-8')
 
 def compare_resources(res_folder, res_public_file):
     old_mapping = get_resources_from_single_file(res_public_file)
 
-    new_mapping = remove_layout_resources(get_all_resources(res_folder))
+    new_mapping = get_all_resources(res_folder)
 
     removed = old_mapping.difference(new_mapping)
     added = new_mapping.difference(old_mapping)
diff --git a/car-ui-lib/tests/apitest/current.xml b/car-ui-lib/tests/apitest/current.xml
index 3856c54..99adc12 100644
--- a/car-ui-lib/tests/apitest/current.xml
+++ b/car-ui-lib/tests/apitest/current.xml
@@ -50,10 +50,7 @@
   <public type="dimen" name="car_ui_dialog_edittext_margin_top"/>
   <public type="dimen" name="car_ui_dialog_icon_size"/>
   <public type="dimen" name="car_ui_dialog_title_margin"/>
-  <public type="dimen" name="car_ui_keyline_1"/>
-  <public type="dimen" name="car_ui_keyline_2"/>
-  <public type="dimen" name="car_ui_keyline_3"/>
-  <public type="dimen" name="car_ui_keyline_4"/>
+  <public type="dimen" name="car_ui_header_list_item_text_start_margin"/>
   <public type="dimen" name="car_ui_letter_spacing_body1"/>
   <public type="dimen" name="car_ui_letter_spacing_body3"/>
   <public type="dimen" name="car_ui_list_item_action_divider_height"/>
@@ -126,11 +123,15 @@
   <public type="dimen" name="car_ui_scrollbar_deceleration_times_divisor"/>
   <public type="dimen" name="car_ui_scrollbar_margin"/>
   <public type="dimen" name="car_ui_scrollbar_milliseconds_per_inch"/>
-  <public type="dimen" name="car_ui_scrollbar_padding_end"/>
-  <public type="dimen" name="car_ui_scrollbar_padding_start"/>
+  <public type="dimen" name="car_ui_scrollbar_min_thumb_height"/>
+  <public type="dimen" name="car_ui_scrollbar_padding_bottom"/>
+  <public type="dimen" name="car_ui_scrollbar_padding_top"/>
   <public type="dimen" name="car_ui_scrollbar_separator_margin"/>
   <public type="dimen" name="car_ui_scrollbar_thumb_radius"/>
   <public type="dimen" name="car_ui_scrollbar_thumb_width"/>
+  <public type="dimen" name="car_ui_sub1_size"/>
+  <public type="dimen" name="car_ui_sub2_size"/>
+  <public type="dimen" name="car_ui_sub3_size"/>
   <public type="dimen" name="car_ui_toolbar_bottom_inset"/>
   <public type="dimen" name="car_ui_toolbar_bottom_view_height"/>
   <public type="dimen" name="car_ui_toolbar_end_inset"/>
@@ -157,6 +158,7 @@
   <public type="dimen" name="car_ui_toolbar_tab_text_width"/>
   <public type="dimen" name="car_ui_toolbar_title_logo_padding"/>
   <public type="dimen" name="car_ui_toolbar_title_margin_start"/>
+  <public type="dimen" name="car_ui_toolbar_title_no_logo_margin_start"/>
   <public type="dimen" name="car_ui_toolbar_top_inset"/>
   <public type="dimen" name="car_ui_touch_target_height"/>
   <public type="dimen" name="car_ui_touch_target_width"/>
@@ -166,6 +168,7 @@
   <public type="drawable" name="car_ui_icon_arrow_back"/>
   <public type="drawable" name="car_ui_icon_close"/>
   <public type="drawable" name="car_ui_icon_down"/>
+  <public type="drawable" name="car_ui_icon_lock"/>
   <public type="drawable" name="car_ui_icon_overflow_menu"/>
   <public type="drawable" name="car_ui_icon_search"/>
   <public type="drawable" name="car_ui_icon_search_nav_icon"/>
@@ -174,6 +177,7 @@
   <public type="drawable" name="car_ui_list_item_avatar_icon_outline"/>
   <public type="drawable" name="car_ui_list_item_background"/>
   <public type="drawable" name="car_ui_list_item_divider"/>
+  <public type="drawable" name="car_ui_list_limiting_message_background"/>
   <public type="drawable" name="car_ui_preference_icon_chevron"/>
   <public type="drawable" name="car_ui_preference_icon_chevron_disabled"/>
   <public type="drawable" name="car_ui_preference_icon_chevron_enabled"/>
@@ -188,8 +192,117 @@
   <public type="drawable" name="car_ui_toolbar_menu_item_icon_ripple"/>
   <public type="drawable" name="car_ui_toolbar_search_close_icon"/>
   <public type="drawable" name="car_ui_toolbar_search_search_icon"/>
+  <public type="id" name="action_container"/>
+  <public type="id" name="action_container_touch_interceptor"/>
+  <public type="id" name="action_divider"/>
+  <public type="id" name="avatar_icon"/>
+  <public type="id" name="body"/>
+  <public type="id" name="car_ui_alert_icon"/>
+  <public type="id" name="car_ui_alert_subtitle"/>
+  <public type="id" name="car_ui_alert_title"/>
+  <public type="id" name="car_ui_base_layout_content_container"/>
+  <public type="id" name="car_ui_check_box_end_guideline"/>
+  <public type="id" name="car_ui_check_box_start_guideline"/>
+  <public type="id" name="car_ui_list_item_end_guideline"/>
+  <public type="id" name="car_ui_list_item_start_guideline"/>
+  <public type="id" name="car_ui_list_limiting_message"/>
+  <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"/>
+  <public type="id" name="car_ui_scrollbar_page_up"/>
+  <public type="id" name="car_ui_scrollbar_thumb"/>
+  <public type="id" name="car_ui_scrollbar_track"/>
+  <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"/>
+  <public type="id" name="car_ui_toolbar_end_guideline"/>
+  <public type="id" name="car_ui_toolbar_logo"/>
+  <public type="id" name="car_ui_toolbar_menu_item_icon"/>
+  <public type="id" name="car_ui_toolbar_menu_item_icon_container"/>
+  <public type="id" name="car_ui_toolbar_menu_item_switch"/>
+  <public type="id" name="car_ui_toolbar_menu_item_text"/>
+  <public type="id" name="car_ui_toolbar_menu_item_text_with_icon"/>
+  <public type="id" name="car_ui_toolbar_menu_items_container"/>
+  <public type="id" name="car_ui_toolbar_nav_icon"/>
+  <public type="id" name="car_ui_toolbar_nav_icon_container"/>
+  <public type="id" name="car_ui_toolbar_progress_bar"/>
+  <public type="id" name="car_ui_toolbar_row_separator"/>
+  <public type="id" name="car_ui_toolbar_row_separator_guideline"/>
+  <public type="id" name="car_ui_toolbar_search_bar"/>
+  <public type="id" name="car_ui_toolbar_search_close"/>
+  <public type="id" name="car_ui_toolbar_search_icon"/>
+  <public type="id" name="car_ui_toolbar_search_view_container"/>
+  <public type="id" name="car_ui_toolbar_start_guideline"/>
+  <public type="id" name="car_ui_toolbar_subtitle"/>
+  <public type="id" name="car_ui_toolbar_tab_item_icon"/>
+  <public type="id" name="car_ui_toolbar_tab_item_text"/>
+  <public type="id" name="car_ui_toolbar_tabs"/>
+  <public type="id" name="car_ui_toolbar_title"/>
+  <public type="id" name="car_ui_toolbar_title_container"/>
+  <public type="id" name="car_ui_toolbar_title_logo"/>
+  <public type="id" name="car_ui_toolbar_title_logo_container"/>
+  <public type="id" name="car_ui_toolbar_top_guideline"/>
+  <public type="id" name="check_box_container"/>
+  <public type="id" name="checkbox"/>
+  <public type="id" name="checkbox_widget"/>
+  <public type="id" name="container"/>
+  <public type="id" name="content_icon"/>
+  <public type="id" name="icon"/>
+  <public type="id" name="icon_container"/>
+  <public type="id" name="list"/>
+  <public type="id" name="nested_recycler_view_layout"/>
+  <public type="id" name="radio_button_widget"/>
+  <public type="id" name="recycler_view"/>
+  <public type="id" name="reduced_touch_interceptor"/>
   <public type="id" name="search"/>
+  <public type="id" name="seekbar"/>
+  <public type="id" name="seekbar_value"/>
+  <public type="id" name="spinner"/>
+  <public type="id" name="supplemental_icon"/>
+  <public type="id" name="switch_widget"/>
+  <public type="id" name="text"/>
+  <public type="id" name="textbox"/>
+  <public type="id" name="title"/>
+  <public type="id" name="title_template"/>
+  <public type="id" name="toolbar"/>
+  <public type="id" name="touch_interceptor"/>
   <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"/>
+  <public type="layout" name="car_ui_alert_dialog_edit_text"/>
+  <public type="layout" name="car_ui_alert_dialog_list"/>
+  <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_check_box_list_item"/>
+  <public type="layout" name="car_ui_header_list_item"/>
+  <public type="layout" name="car_ui_list_item"/>
+  <public type="layout" name="car_ui_list_limiting_message"/>
+  <public type="layout" name="car_ui_list_preference"/>
+  <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_widget_checkbox"/>
+  <public type="layout" name="car_ui_preference_widget_seekbar"/>
+  <public type="layout" name="car_ui_preference_widget_switch"/>
+  <public type="layout" name="car_ui_recycler_view"/>
+  <public type="layout" name="car_ui_recycler_view_item"/>
+  <public type="layout" name="car_ui_recyclerview_scrollbar"/>
+  <public type="layout" name="car_ui_toolbar"/>
+  <public type="layout" name="car_ui_toolbar_menu_item"/>
+  <public type="layout" name="car_ui_toolbar_search_view"/>
+  <public type="layout" name="car_ui_toolbar_tab_item"/>
+  <public type="layout" name="car_ui_toolbar_tab_item_flexible"/>
+  <public type="layout" name="car_ui_toolbar_tab_item_layout"/>
+  <public type="layout" name="car_ui_toolbar_tab_item_layout_flexible"/>
+  <public type="layout" name="car_ui_toolbar_two_row"/>
   <public type="string" name="car_ui_alert_dialog_default_button"/>
   <public type="string" name="car_ui_dialog_preference_negative"/>
   <public type="string" name="car_ui_dialog_preference_positive"/>
@@ -202,10 +315,12 @@
   <public type="string" name="car_ui_scrollbar_component"/>
   <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_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"/>
@@ -225,6 +340,7 @@
   <public type="style" name="PreferenceFragmentList.CarUi"/>
   <public type="style" name="TextAppearance.CarUi"/>
   <public type="style" name="TextAppearance.CarUi.AlertDialog.Subtitle"/>
+  <public type="style" name="TextAppearance.CarUi.AlertDialog.Title"/>
   <public type="style" name="TextAppearance.CarUi.Body1"/>
   <public type="style" name="TextAppearance.CarUi.Body2"/>
   <public type="style" name="TextAppearance.CarUi.Body3"/>
@@ -235,6 +351,9 @@
   <public type="style" name="TextAppearance.CarUi.PreferenceEditTextDialogMessage"/>
   <public type="style" name="TextAppearance.CarUi.PreferenceSummary"/>
   <public type="style" name="TextAppearance.CarUi.PreferenceTitle"/>
+  <public type="style" name="TextAppearance.CarUi.Sub1"/>
+  <public type="style" name="TextAppearance.CarUi.Sub2"/>
+  <public type="style" name="TextAppearance.CarUi.Sub3"/>
   <public type="style" name="TextAppearance.CarUi.Widget"/>
   <public type="style" name="TextAppearance.CarUi.Widget.Toolbar"/>
   <public type="style" name="TextAppearance.CarUi.Widget.Toolbar.Tab"/>
@@ -268,6 +387,7 @@
   <public type="style" name="Widget.CarUi.Toolbar.Search.EditText"/>
   <public type="style" name="Widget.CarUi.Toolbar.Search.SearchIcon"/>
   <public type="style" name="Widget.CarUi.Toolbar.SeparatorView"/>
+  <public type="style" name="Widget.CarUi.Toolbar.Subtitle"/>
   <public type="style" name="Widget.CarUi.Toolbar.Tab"/>
   <public type="style" name="Widget.CarUi.Toolbar.Tab.Container"/>
   <public type="style" name="Widget.CarUi.Toolbar.Tab.Icon"/>
diff --git a/car-ui-lib/tests/apitest/git_utils.py b/car-ui-lib/tests/apitest/git_utils.py
index d3b83a5..9d19432 100755
--- a/car-ui-lib/tests/apitest/git_utils.py
+++ b/car-ui-lib/tests/apitest/git_utils.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright 2019, The Android Open Source Project
 #
diff --git a/car-ui-lib/tests/apitest/resource_utils.py b/car-ui-lib/tests/apitest/resource_utils.py
index ab0de6d..763c5a0 100755
--- a/car-ui-lib/tests/apitest/resource_utils.py
+++ b/car-ui-lib/tests/apitest/resource_utils.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright 2019, The Android Open Source Project
 #
@@ -15,6 +15,7 @@
 # limitations under the License.
 
 import os
+import re
 
 class ResourceLocation:
     def __init__(self, file, line=None):
@@ -74,6 +75,9 @@
                 add_resource_to_set(resources,
                                     Resource(file[:-4], type,
                                              ResourceLocation(os.path.join(resDir, dir, file))))
+                if dir.startswith("layout"):
+                    for resource in get_ids_from_layout_file(os.path.join(resDir, dir, file)):
+                        add_resource_to_set(resources, resource)
 
     for dir in valuesDirs:
         for file in os.listdir(os.path.join(resDir, dir)):
@@ -83,6 +87,14 @@
 
     return resources
 
+def get_ids_from_layout_file(filename):
+    result = set()
+    with open(filename, 'r') as file:
+        r = re.compile("@\+id/([a-zA-Z0-9_]+)")
+        for i in r.findall(file.read()):
+            add_resource_to_set(result, Resource(i, 'id', ResourceLocation(filename)))
+    return result
+
 def get_resources_from_single_file(filename):
     # defer importing lxml to here so that people who aren't editing chassis don't have to have
     # lxml installed
@@ -94,19 +106,15 @@
         if resource.tag == 'declare-styleable' or resource.tag is etree.Comment:
             continue
 
+        resName = resource.get('name')
+        resType = resource.tag
         if resource.tag == 'item' or resource.tag == 'public':
-            add_resource_to_set(result, Resource(resource.get('name'), resource.get('type'),
-                                                 ResourceLocation(filename, resource.sourceline)))
-        else:
-            add_resource_to_set(result, Resource(resource.get('name'), resource.tag,
-                                                 ResourceLocation(filename, resource.sourceline)))
-    return result
+            resType = resource.get('type')
 
-def remove_layout_resources(resourceSet):
-    result = set()
-    for resource in resourceSet:
-        if resource.type != 'layout':
-            result.add(resource)
+        if resType != 'overlayable':
+            add_resource_to_set(result, Resource(resName, resType,
+                                                 ResourceLocation(filename, resource.sourceline)))
+
     return result
 
 # Used to get objects out of sets
diff --git a/car-ui-lib/tests/apitest/verify_rro.py b/car-ui-lib/tests/apitest/verify_rro.py
index a58210d..3221123 100755
--- a/car-ui-lib/tests/apitest/verify_rro.py
+++ b/car-ui-lib/tests/apitest/verify_rro.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright 2019, The Android Open Source Project
 #
@@ -16,7 +16,7 @@
 
 import argparse
 import sys
-from resource_utils import get_all_resources, remove_layout_resources, merge_resources
+from resource_utils import get_all_resources, merge_resources
 from git_utils import has_chassis_changes
 
 def main():
@@ -36,11 +36,11 @@
 
     rro_resources = set()
     for resDir in args.rro:
-        merge_resources(rro_resources, remove_layout_resources(get_all_resources(resDir[0])))
+        merge_resources(rro_resources, get_all_resources(resDir[0]))
 
     base_resources = set()
     for resDir in args.base:
-        merge_resources(base_resources, remove_layout_resources(get_all_resources(resDir[0])))
+        merge_resources(base_resources, get_all_resources(resDir[0]))
 
     extras = rro_resources.difference(base_resources)
     if len(extras) > 0:
diff --git a/car-ui-lib/tests/paintbooth/AndroidManifest-gradle.xml b/car-ui-lib/tests/paintbooth/AndroidManifest-gradle.xml
index 61ca559..84ed351 100644
--- a/car-ui-lib/tests/paintbooth/AndroidManifest-gradle.xml
+++ b/car-ui-lib/tests/paintbooth/AndroidManifest-gradle.xml
@@ -19,6 +19,7 @@
     package="com.android.car.ui.paintbooth">
 
   <application
+      android:supportsRtl="true"
       android:icon="@drawable/ic_launcher"
       android:label="@string/app_name"
       android:theme="@style/Theme.CarUi.WithToolbar">
@@ -54,6 +55,13 @@
       <meta-data android:name="distractionOptimized" android:value="true"/>
     </activity>
     <activity
+        android:name=".toolbar.OldToolbarActivity"
+        android:exported="false"
+        android:parentActivityName=".MainActivity"
+        android:theme="@style/Theme.CarUi">
+      <meta-data android:name="distractionOptimized" android:value="true"/>
+    </activity>
+    <activity
         android:name=".overlays.OverlayActivity"
         android:exported="false"
         android:parentActivityName=".MainActivity">
diff --git a/car-ui-lib/tests/paintbooth/AndroidManifest.xml b/car-ui-lib/tests/paintbooth/AndroidManifest.xml
index ba222d2..b8dd1ca 100644
--- a/car-ui-lib/tests/paintbooth/AndroidManifest.xml
+++ b/car-ui-lib/tests/paintbooth/AndroidManifest.xml
@@ -26,8 +26,21 @@
   <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
   <uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
   <uses-permission android:name="android.permission.MANAGE_USERS"/>
+  <!-- Required to use the TYPE_DISPLAY_OVERLAY layout param for the current activity overlay -->
+  <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
+  <!-- Required for listening to android task stack changes -->
+  <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+  <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+  <!-- Required for using TYPE_APPLICATION_OVERLAY to display overlays -->
+  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+  <!-- Required to test permission dialogs -->
+  <uses-permission android:name="android.permission.CAMERA"/>
+  <uses-permission android:name="android.permission.READ_CONTACTS"/>
+  <uses-permission android:name="android.permission.SEND_SMS"/>
 
   <application
+      android:supportsRtl="true"
       android:icon="@drawable/ic_launcher"
       android:label="@string/app_name"
       android:theme="@style/Theme.CarUi.WithToolbar">
@@ -63,6 +76,13 @@
       <meta-data android:name="distractionOptimized" android:value="true"/>
     </activity>
     <activity
+        android:name=".toolbar.OldToolbarActivity"
+        android:exported="false"
+        android:parentActivityName=".MainActivity"
+        android:theme="@style/Theme.CarUi">
+      <meta-data android:name="distractionOptimized" android:value="true"/>
+    </activity>
+    <activity
         android:name=".overlays.OverlayActivity"
         android:exported="false"
         android:parentActivityName=".MainActivity">
diff --git a/car-ui-lib/tests/paintbooth/build.gradle b/car-ui-lib/tests/paintbooth/build.gradle
index 0ef3514..a7265cd 100644
--- a/car-ui-lib/tests/paintbooth/build.gradle
+++ b/car-ui-lib/tests/paintbooth/build.gradle
@@ -17,11 +17,11 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 28
+    compileSdkVersion 29
     defaultConfig {
         applicationId "com.android.car.ui.paintbooth"
         minSdkVersion 28
-        targetSdkVersion 28
+        targetSdkVersion 29
         versionCode 1
         versionName "1.0"
     }
diff --git a/car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_activity.xml b/car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_activity.xml
index 15b6fe6..afc6bc8 100644
--- a/car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_activity.xml
+++ b/car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_activity.xml
@@ -19,4 +19,5 @@
     android:id="@+id/list"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@drawable/car_ui_activity_background"/>
\ No newline at end of file
+    android:clipToPadding="false"
+    android:background="@drawable/car_ui_activity_background"/>
diff --git a/car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_activity_with_old_toolbar.xml b/car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_activity_with_old_toolbar.xml
new file mode 100644
index 0000000..266e9b8
--- /dev/null
+++ b/car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_activity_with_old_toolbar.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/car_ui_activity_background">
+
+    <com.android.car.ui.recyclerview.CarUiRecyclerView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipToPadding="false"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+    <com.android.car.ui.toolbar.Toolbar
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-ui-lib/tests/paintbooth/res/layout/grid_car_ui_recycler_view_activity.xml b/car-ui-lib/tests/paintbooth/res/layout/grid_car_ui_recycler_view_activity.xml
index 5c97fde..1a019dc 100644
--- a/car-ui-lib/tests/paintbooth/res/layout/grid_car_ui_recycler_view_activity.xml
+++ b/car-ui-lib/tests/paintbooth/res/layout/grid_car_ui_recycler_view_activity.xml
@@ -18,7 +18,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/list"
-    app:layoutStyle="grid"
     app:numOfColumns="4"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
diff --git a/car-ui-lib/tests/paintbooth/res/values/strings.xml b/car-ui-lib/tests/paintbooth/res/values/strings.xml
index cd45f77..470b416 100644
--- a/car-ui-lib/tests/paintbooth/res/values/strings.xml
+++ b/car-ui-lib/tests/paintbooth/res/values/strings.xml
@@ -248,9 +248,18 @@
   <!-- Button that shows a dialog with a subtitle and icon [CHAR_LIMIT=50]-->
   <string name="dialog_show_subtitle_and_icon">Show Dialog with title, subtitle, and icon</string>
 
+  <!-- Button that shows a dialog with a long title, subtitle and icon [CHAR_LIMIT=500]-->
+  <string name="dialog_show_long_subtitle_and_icon">Show Dialog with a long title, subtitle, and icon</string>
+
   <!-- Text to show Dialog with single choice items-->
   <string name="dialog_show_single_choice">Show with single choice items</string>
 
+  <!-- Text to show a permission Dialog-->
+  <string name="dialog_show_permission_dialog">Show permission dialog</string>
+
+  <!-- Text to show a permission Dialog for multiple permissions-->
+  <string name="dialog_show_multi_permission_dialog">Show multiple permissions dialog</string>
+
   <!--This section is for widget attributes -->
   <eat-comment/>
   <!-- Text for checkbox [CHAR_LIMIT=16]-->
diff --git a/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml b/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
index b49dd0d..e483435 100644
--- a/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
+++ b/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
@@ -19,132 +19,145 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     app:title="@string/preferences_screen_title">
 
-  <PreferenceCategory
-      android:title="@string/basic_preferences">
+    <PreferenceCategory
+        android:title="@string/basic_preferences">
 
-    <Preference
-        android:key="preference"
-        android:title="@string/title_basic_preference"
-        android:summary="@string/summary_basic_preference"/>
+        <Preference
+            android:key="preference"
+            android:summary="@string/summary_basic_preference"
+            android:title="@string/title_basic_preference"/>
 
-    <Preference
-        android:key="stylized"
-        android:title="@string/title_stylish_preference"
-        android:summary="@string/summary_stylish_preference"/>
+        <Preference
+            android:key="preference_disabled_without_ripple"
+            android:summary="Without ripple"
+            android:title="@string/title_basic_preference"/>
 
-    <Preference
-        android:key="icon"
-        android:title="@string/title_icon_preference"
-        android:summary="@string/summary_icon_preference"
-        android:icon="@drawable/ic_settings_wifi"/>
+        <Preference
+            android:key="preference_disabled_with_ripple"
+            android:summary="With ripple"
+            android:title="@string/title_basic_preference"/>
 
-    <Preference
-        android:key="single_line_title"
-        android:title="@string/title_single_line_title_preference"
-        android:summary="@string/summary_single_line_title_preference"
-        app:singleLineTitle="true"/>
+        <Preference
+            android:key="stylized"
+            android:dependency="preference"
+            android:summary="@string/summary_stylish_preference"
+            android:title="@string/title_stylish_preference"/>
 
-    <Preference
-        android:key="single_line_no_summary"
-        android:title="@string/title_single_line_no_summary"
-        app:singleLineTitle="true"/>
-  </PreferenceCategory>
+        <Preference
+            android:icon="@drawable/ic_settings_wifi"
+            android:key="icon"
+            android:summary="@string/summary_icon_preference"
+            android:title="@string/title_icon_preference"/>
 
-  <PreferenceCategory
-      android:title="@string/widgets">
+        <Preference
+            android:key="single_line_title"
+            android:summary="@string/summary_single_line_title_preference"
+            android:title="@string/title_single_line_title_preference"
+            app:singleLineTitle="true"/>
 
-    <CheckBoxPreference
-        android:key="checkbox"
-        android:title="@string/title_checkbox_preference"
-        android:summary="@string/summary_checkbox_preference"/>
+        <Preference
+            android:key="single_line_no_summary"
+            android:title="@string/title_single_line_no_summary"
+            app:singleLineTitle="true"/>
+    </PreferenceCategory>
 
-    <SwitchPreference
-        android:key="switch"
-        android:title="@string/title_switch_preference"
-        android:summary="@string/summary_switch_preference"/>
+    <PreferenceCategory
+        android:title="@string/widgets">
 
-    <DropDownPreference
-        android:key="dropdown"
-        android:title="@string/title_dropdown_preference"
-        android:entries="@array/entries"
-        app:useSimpleSummaryProvider="true"
-        android:entryValues="@array/entry_values"/>
+        <CheckBoxPreference
+            android:key="checkbox"
+            android:summary="@string/summary_checkbox_preference"
+            android:title="@string/title_checkbox_preference"/>
 
-    <SeekBarPreference
-        android:key="seekbar"
-        android:title="@string/title_seekbar_preference"
-        android:max="10"
-        android:defaultValue="5"/>
-  </PreferenceCategory>
+        <DropDownPreference
+            android:entries="@array/entries"
+            android:entryValues="@array/entry_values"
+            android:key="dropdown"
+            android:title="@string/title_dropdown_preference"
+            app:useSimpleSummaryProvider="true"/>
 
-  <PreferenceCategory
-      android:title="@string/dialogs">
+        <SeekBarPreference
+            android:defaultValue="5"
+            android:key="seekbar"
+            android:max="10"
+            android:title="@string/title_seekbar_preference"/>
 
-    <EditTextPreference
-        android:key="edittext"
-        android:title="@string/title_edittext_preference"
-        app:useSimpleSummaryProvider="true"
-        android:dialogTitle="@string/dialog_title_edittext_preference"/>
+        <SwitchPreference
+            android:key="switch"
+            android:summary="@string/summary_switch_preference"
+            android:title="@string/title_switch_preference"/>
 
-    <ListPreference
-        android:key="list"
-        android:title="@string/title_list_preference"
-        app:useSimpleSummaryProvider="true"
-        android:entries="@array/entries"
-        android:entryValues="@array/entry_values"
-        android:dialogTitle="@string/dialog_title_list_preference"/>
+    </PreferenceCategory>
 
-    <MultiSelectListPreference
-        android:key="multi_select_list"
-        android:title="@string/title_multi_list_preference"
-        android:summary="@string/summary_multi_list_preference"
-        android:entries="@array/entries"
-        android:entryValues="@array/entry_values"
-        android:dialogTitle="@string/dialog_title_multi_list_preference"/>
-  </PreferenceCategory>
+    <PreferenceCategory
+        android:title="@string/dialogs">
 
-  <PreferenceCategory
-      android:key="@string/advanced_preference"
-      android:title="@string/advanced_attributes"
-      app:initialExpandedChildrenCount="1">
+        <EditTextPreference
+            android:dialogTitle="@string/dialog_title_edittext_preference"
+            android:key="edittext"
+            android:title="@string/title_edittext_preference"
+            app:useSimpleSummaryProvider="true"/>
 
-    <Preference
-        android:key="expandable"
-        android:title="@string/title_expandable_preference"
-        android:summary="@string/summary_expandable_preference"/>
+        <ListPreference
+            android:dialogTitle="@string/dialog_title_list_preference"
+            android:entries="@array/entries"
+            android:entryValues="@array/entry_values"
+            android:key="list"
+            android:title="@string/title_list_preference"
+            app:useSimpleSummaryProvider="true"/>
 
-    <Preference
-        android:title="@string/title_intent_preference"
-        android:summary="@string/summary_intent_preference">
+        <MultiSelectListPreference
+            android:dialogTitle="@string/dialog_title_multi_list_preference"
+            android:entries="@array/entries"
+            android:entryValues="@array/entry_values"
+            android:key="multi_select_list"
+            android:summary="@string/summary_multi_list_preference"
+            android:title="@string/title_multi_list_preference"/>
 
-      <intent android:action="android.intent.action.VIEW"
-          android:data="http://www.android.com"/>
+    </PreferenceCategory>
 
-    </Preference>
+    <PreferenceCategory
+        android:key="@string/advanced_preference"
+        android:title="@string/advanced_attributes"
+        app:initialExpandedChildrenCount="1">
 
-    <SwitchPreference
-        android:key="parent"
-        android:title="@string/title_parent_preference"
-        android:summary="@string/summary_parent_preference"/>
+        <Preference
+            android:key="expandable"
+            android:summary="@string/summary_expandable_preference"
+            android:title="@string/title_expandable_preference"/>
 
-    <SwitchPreference
-        android:key="child"
-        android:dependency="parent"
-        android:title="@string/title_child_preference"
-        android:summary="@string/summary_child_preference"/>
+        <Preference
+            android:summary="@string/summary_intent_preference"
+            android:title="@string/title_intent_preference">
 
-    <SwitchPreference
-        android:key="toggle_summary"
-        android:title="@string/title_toggle_summary_preference"
-        android:summaryOn="@string/summary_on_toggle_summary_preference"
-        android:summaryOff="@string/summary_off_toggle_summary_preference"/>
+            <intent android:action="android.intent.action.VIEW"
+                    android:data="http://www.android.com"/>
 
-    <Preference
-        android:key="copyable"
-        android:title="@string/title_copyable_preference"
-        android:summary="@string/summary_copyable_preference"
-        android:selectable="false"
-        app:enableCopying="true"/>
-  </PreferenceCategory>
+        </Preference>
+
+        <Preference
+            android:key="copyable"
+            android:selectable="false"
+            android:summary="@string/summary_copyable_preference"
+            android:title="@string/title_copyable_preference"
+            app:enableCopying="true"/>
+
+        <SwitchPreference
+            android:dependency="parent"
+            android:key="child"
+            android:summary="@string/summary_child_preference"
+            android:title="@string/title_child_preference"/>
+
+        <SwitchPreference
+            android:key="toggle_summary"
+            android:summaryOff="@string/summary_off_toggle_summary_preference"
+            android:summaryOn="@string/summary_on_toggle_summary_preference"
+            android:title="@string/title_toggle_summary_preference"/>
+
+        <SwitchPreference
+            android:key="parent"
+            android:summary="@string/summary_parent_preference"
+            android:title="@string/title_parent_preference"/>
+    </PreferenceCategory>
 
 </PreferenceScreen>
\ No newline at end of file
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java
index 3cf2b9f..e4a0a3d 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java
@@ -39,6 +39,7 @@
 import com.android.car.ui.paintbooth.dialogs.DialogsActivity;
 import com.android.car.ui.paintbooth.overlays.OverlayActivity;
 import com.android.car.ui.paintbooth.preferences.PreferenceActivity;
+import com.android.car.ui.paintbooth.toolbar.OldToolbarActivity;
 import com.android.car.ui.paintbooth.toolbar.ToolbarActivity;
 import com.android.car.ui.paintbooth.widgets.WidgetActivity;
 import com.android.car.ui.recyclerview.CarUiRecyclerView;
@@ -63,6 +64,7 @@
             Pair.create("Preferences sample", PreferenceActivity.class),
             Pair.create("Overlays", OverlayActivity.class),
             Pair.create("Toolbar sample", ToolbarActivity.class),
+            Pair.create("Old Toolbar sample", OldToolbarActivity.class),
             Pair.create("Widget sample", WidgetActivity.class),
             Pair.create("ListItem sample", CarUiListItemActivity.class)
     );
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java
index 586b633..7f16d41 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java
@@ -19,6 +19,8 @@
 import android.app.Activity;
 import android.os.Bundle;
 
+import androidx.recyclerview.widget.GridLayoutManager;
+
 import com.android.car.ui.baselayout.Insets;
 import com.android.car.ui.baselayout.InsetsChangedListener;
 import com.android.car.ui.core.CarUi;
@@ -45,6 +47,7 @@
         toolbar.setState(Toolbar.State.SUBPAGE);
 
         CarUiRecyclerView recyclerView = findViewById(R.id.list);
+        recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
 
         RecyclerViewAdapter adapter = new RecyclerViewAdapter(generateDummyData());
         recyclerView.setAdapter(adapter);
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
index 1f5f26a..c9760ec 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
@@ -16,7 +16,9 @@
 
 package com.android.car.ui.paintbooth.dialogs;
 
+import android.Manifest;
 import android.app.Activity;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.util.Pair;
 import android.view.LayoutInflater;
@@ -76,9 +78,14 @@
                 v -> showDialogWithSubtitle()));
         mButtons.add(Pair.create(R.string.dialog_show_subtitle_and_icon,
                 v -> showDialogWithSubtitleAndIcon()));
+        mButtons.add(Pair.create(R.string.dialog_show_long_subtitle_and_icon,
+                v -> showDialogWithLongSubtitleAndIcon()));
         mButtons.add(Pair.create(R.string.dialog_show_single_choice,
                 v -> showDialogWithSingleChoiceItems()));
-
+        mButtons.add(Pair.create(R.string.dialog_show_permission_dialog,
+                v -> showPermissionDialog()));
+        mButtons.add(Pair.create(R.string.dialog_show_multi_permission_dialog,
+                v -> showMultiPermissionDialog()));
 
         CarUiRecyclerView recyclerView = requireViewById(R.id.list);
         recyclerView.setAdapter(mAdapter);
@@ -196,6 +203,55 @@
                 .show();
     }
 
+    private void showDialogWithLongSubtitleAndIcon() {
+        new AlertDialogBuilder(this)
+                .setTitle("This is a very long title. It should likely span across "
+                            + "multiple lines or something. It shouldn't get cut off.")
+                .setSubtitle("This is a very long subtitle. It should likely span across "
+                        + "multiple lines or something. It shouldn't get cut off.")
+                .setMessage("My Message!")
+                .setIcon(R.drawable.ic_tracklist)
+                .show();
+    }
+
+    private void showPermissionDialog() {
+        if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
+            Toast.makeText(this, "Permission already granted. Remove CAMERA permission from "
+                    + "Settings > All apps > PaintBooth", Toast.LENGTH_SHORT).show();
+            return;
+        }
+        requestPermissions(new String[]{Manifest.permission.CAMERA}, 1);
+    }
+
+    private void showMultiPermissionDialog() {
+        if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
+                && checkSelfPermission(Manifest.permission.SEND_SMS)
+                    == PackageManager.PERMISSION_GRANTED
+                && checkSelfPermission(Manifest.permission.READ_CONTACTS)
+                    == PackageManager.PERMISSION_GRANTED) {
+            Toast.makeText(this, "Permissions are already granted. Remove CAMERA, SEND_SMS or "
+                    + "READ_CONTACTS permission from Settings > All apps > PaintBooth",
+                    Toast.LENGTH_SHORT).show();
+            return;
+        }
+        requestPermissions(new String[]{Manifest.permission.CAMERA,
+                Manifest.permission.READ_CONTACTS, Manifest.permission.SEND_SMS}, 1);
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions,
+            int[] grantResults) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Permission ");
+        for (int i = 0; i < permissions.length; i++) {
+            sb.append(permissions[i]);
+            sb.append("=");
+            sb.append(grantResults[i] == PackageManager.PERMISSION_GRANTED ? "granted" : "denied");
+            sb.append("\n");
+        }
+        Toast.makeText(this, sb.toString(), Toast.LENGTH_SHORT).show();
+    }
+
     private static class ViewHolder extends CarUiRecyclerView.ViewHolder {
 
         private final Button mButton;
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java
index ccd21ef..233c873 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java
@@ -19,6 +19,8 @@
 import android.os.Bundle;
 
 import com.android.car.ui.paintbooth.R;
+import com.android.car.ui.preference.CarUiPreference;
+import com.android.car.ui.preference.CarUiSwitchPreference;
 import com.android.car.ui.preference.PreferenceFragment;
 
 /**
@@ -30,5 +32,23 @@
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         // Load the preferences from an XML resource
         setPreferencesFromResource(R.xml.preference_samples, rootKey);
+        CarUiPreference preferenceDisabledWithoutRipple = findPreference(
+                "preference_disabled_without_ripple");
+        preferenceDisabledWithoutRipple.setEnabled(false);
+        preferenceDisabledWithoutRipple.setMessageToShowWhenDisabledPreferenceClicked(
+                "I am disabled because...");
+        preferenceDisabledWithoutRipple.setShouldShowRippleOnDisabledPreference(false);
+
+        CarUiPreference preferenceDisabledWithRipple = findPreference(
+                "preference_disabled_with_ripple");
+        preferenceDisabledWithRipple.setEnabled(false);
+        preferenceDisabledWithRipple.setMessageToShowWhenDisabledPreferenceClicked(
+                "I am disabled because...");
+        preferenceDisabledWithRipple.setShouldShowRippleOnDisabledPreference(true);
+
+        CarUiSwitchPreference carUiSwitchPreference = findPreference("switch");
+        carUiSwitchPreference.setEnabled(false);
+        carUiSwitchPreference.setMessageToShowWhenDisabledPreferenceClicked(
+                "I am disabled because...");
     }
 }
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/OldToolbarActivity.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/OldToolbarActivity.java
new file mode 100644
index 0000000..f427990
--- /dev/null
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/OldToolbarActivity.java
@@ -0,0 +1,42 @@
+/*
+ * 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.toolbar;
+
+import android.os.Bundle;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.paintbooth.R;
+import com.android.car.ui.toolbar.Toolbar;
+
+public class OldToolbarActivity extends ToolbarActivity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Toolbar toolbar = requireViewById(R.id.toolbar);
+        RecyclerView rv = requireViewById(R.id.list);
+        toolbar.registerToolbarHeightChangeListener(height -> {
+            rv.setPadding(0, height, 0, 0);
+        });
+    }
+
+    @Override
+    protected int getLayout() {
+        return R.layout.car_ui_recycler_view_activity_with_old_toolbar;
+    }
+}
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
index cb1fdc2..593b4ee 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
@@ -55,9 +55,13 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.car_ui_recycler_view_activity);
+        setContentView(getLayout());
 
-        ToolbarController toolbar = CarUi.requireToolbar(this);
+        ToolbarController toolbarNonFinal = CarUi.getToolbar(this);
+        if (toolbarNonFinal == null) {
+            toolbarNonFinal = requireViewById(R.id.toolbar);
+        }
+        ToolbarController toolbar = toolbarNonFinal;
         toolbar.setTitle(getTitle());
         toolbar.setState(Toolbar.State.SUBPAGE);
         toolbar.setLogo(R.drawable.ic_launcher);
@@ -79,18 +83,41 @@
         toolbar.setMenuItems(mMenuItems);
 
         mButtons.add(Pair.create("Toggle progress bar", v -> {
-            if (toolbar.getProgressBar().getVisibility() == View.GONE) {
-                toolbar.showProgressBar();
-                Toast.makeText(this, "showing progress bar", Toast.LENGTH_SHORT).show();
-            } else {
-                toolbar.hideProgressBar();
-                Toast.makeText(this, "hiding progress bar", Toast.LENGTH_SHORT).show();
-            }
+            toolbar.getProgressBar().setVisible(!toolbar.getProgressBar().isVisible());
         }));
 
         mButtons.add(Pair.create("Change title", v ->
                 toolbar.setTitle(toolbar.getTitle() + " X")));
 
+        mButtons.add(Pair.create("Add subtitle", v -> {
+            toolbar.setSubtitle("subtitle");
+        }));
+
+        mButtons.add(Pair.create("Update subtitle every 1 sec", v -> {
+            final int[] count = {0};
+            Thread t = new Thread() {
+                @Override
+                public void run() {
+                    try {
+                        while (count[0] < 6) {
+                            Thread.sleep(1000);
+                            runOnUiThread(new Runnable() {
+                                @Override
+                                public void run() {
+                                    toolbar.setSubtitle(
+                                            "subtitle updating count: " + count[0]);
+                                    count[0]++;
+                                }
+                            });
+                        }
+                    } catch (InterruptedException e) {
+                        // Do nothing
+                    }
+                }
+            };
+            t.start();
+        }));
+
         mButtons.add(Pair.create(getString(R.string.toolbar_set_xml_resource), v -> {
             mMenuItems.clear();
             toolbar.setMenuItems(R.xml.menuitems);
@@ -295,6 +322,11 @@
         prv.setAdapter(mAdapter);
     }
 
+    /** Override in subclasses to change the layout */
+    protected int getLayout() {
+        return R.layout.car_ui_recycler_view_activity;
+    }
+
     @Override
     public void onCarUiInsetsChanged(Insets insets) {
         requireViewById(R.id.list)
diff --git a/car-ui-lib/tests/robotests/build.gradle b/car-ui-lib/tests/robotests/build.gradle
index b5876a3..0784a74 100644
--- a/car-ui-lib/tests/robotests/build.gradle
+++ b/car-ui-lib/tests/robotests/build.gradle
@@ -42,11 +42,11 @@
 apply plugin: 'com.android.library'
 
 android {
-    compileSdkVersion 28
+    compileSdkVersion 29
 
     defaultConfig {
         minSdkVersion 28
-        targetSdkVersion 28
+        targetSdkVersion 29
         versionCode 1
         versionName "1.0"
     }
@@ -79,6 +79,7 @@
     testImplementation "androidx.test.ext:junit:1.1.1"
     testImplementation "org.robolectric:robolectric:4.0-alpha-3"
     testImplementation "org.mockito:mockito-core:2.19.0"
+    testImplementation 'org.mockito:mockito-inline:2.19.0'
     testImplementation "com.google.truth:truth:0.29"
     testImplementation "org.testng:testng:6.9.9"
 
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/DefaultScrollBarTest.java b/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/DefaultScrollBarTest.java
index 612f463..d043064 100644
--- a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/DefaultScrollBarTest.java
+++ b/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/DefaultScrollBarTest.java
@@ -16,8 +16,6 @@
 
 package com.android.car.ui.recyclerview;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -112,24 +110,6 @@
     }
 
     @Test
-    public void setPadding_shouldSetStartAndEndPadding() {
-        when(mRecyclerView.getContext()).thenReturn(mContext);
-        when(mRecyclerView.getParent()).thenReturn(mParent);
-        when(mRecyclerView.getRecycledViewPool()).thenReturn(mRecycledViewPool);
-        when(mParent.generateLayoutParams(any())).thenReturn(mLayoutParams);
-
-        View scrollView = LayoutInflater.from(mContext).inflate(
-                R.layout.car_ui_recyclerview_scrollbar, null);
-        mScrollBar.initialize(mRecyclerView, scrollView);
-        mScrollBar.setPadding(10, 20);
-
-        DefaultScrollBar defaultScrollBar = (DefaultScrollBar) mScrollBar;
-
-        assertThat(defaultScrollBar.mPaddingStart).isEqualTo(10);
-        assertThat(defaultScrollBar.mPaddingEnd).isEqualTo(20);
-    }
-
-    @Test
     public void setPadding_shouldThrowErrorWithoutInitialization() {
         assertThrows(NullPointerException.class, () -> mScrollBar.setPadding(10, 20));
     }
diff --git a/EncryptionRunner/Android.bp b/car-uxr-client-lib/Android.bp
similarity index 62%
copy from EncryptionRunner/Android.bp
copy to car-uxr-client-lib/Android.bp
index 54316ff..72751f3 100644
--- a/EncryptionRunner/Android.bp
+++ b/car-uxr-client-lib/Android.bp
@@ -1,4 +1,5 @@
-// Copyright (C) 2019 The Android Open Source Project
+//
+// 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.
@@ -13,19 +14,23 @@
 // limitations under the License.
 
 android_library {
-    name: "EncryptionRunner-lib",
-    min_sdk_version: "23",
-    product_variables: {
-        pdk: {
-            enabled: false,
-        },
-    },
-    static_libs: [
-      "ukey2",
-    ],
-    srcs: [
-        "src/**/*.java",
-    ],
-    installable: true,
-}
 
+    name: "car-uxr-client-lib",
+
+    srcs: ["src/**/*.java"],
+
+    optimize: {
+        enabled: false,
+    },
+
+    libs: [
+        "android.car-system-stubs",
+    ],
+    sdk_version: "system_current",
+
+    static_libs: [
+        "androidx.recyclerview_recyclerview",
+        "androidx.lifecycle_lifecycle-common-java8",
+        "car-ui-lib-bp",
+    ],
+}
diff --git a/car-uxr-client-lib/AndroidManifest.xml b/car-uxr-client-lib/AndroidManifest.xml
new file mode 100644
index 0000000..9550419
--- /dev/null
+++ b/car-uxr-client-lib/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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.uxr">
+</manifest>
diff --git a/car-uxr-client-lib/OWNERS b/car-uxr-client-lib/OWNERS
new file mode 100644
index 0000000..55f7d43
--- /dev/null
+++ b/car-uxr-client-lib/OWNERS
@@ -0,0 +1,9 @@
+# People who can approve changes for submission.
+johnchoi@google.com
+stenning@google.com
+igorr@google.com
+
+# Engs
+pardis@google.com
+jjoz@google.com
+
diff --git a/car-uxr-client-lib/README.md b/car-uxr-client-lib/README.md
new file mode 100644
index 0000000..7f57376
--- /dev/null
+++ b/car-uxr-client-lib/README.md
@@ -0,0 +1,7 @@
+# Android Automotive App-side User Experience Restriction (UXR) library
+Components and resources designed to reduce the amount of work needed by
+Automotive app developers to add User Experience Restriction Engine
+support to their apps.
+
+Source: /packages/apps/Car/libs/car-uxr-client-lib
+
diff --git a/car-uxr-client-lib/res/values/attrs.xml b/car-uxr-client-lib/res/values/attrs.xml
new file mode 100644
index 0000000..084e30d
--- /dev/null
+++ b/car-uxr-client-lib/res/values/attrs.xml
@@ -0,0 +1,41 @@
+<!--
+  ~ 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
+  -->
+<!--
+  ~ An example uxr_config.xml file would look like:
+  ~ <Mapping xmlns:app="http://schemas.android.com/apk/res-auto">
+  ~     <ListConfig
+  ~         app:id="@+id/call_log_list_uxr_config"
+  ~         app:maxLength="10"
+  ~         app:message="@string/call_log_scrolling_limited_message"
+  ~     />
+  ~ </Mapping>
+  -->
+<resources>
+    <!-- Global container of uxr related app configs -->
+    <declare-styleable name="CarUxRestrictionsAppConfig"/>
+    <!-- The mapping of lists to their uxr related override values. -->
+    <declare-styleable name="CarUxRestrictionsAppConfig_Mapping"/>
+
+    <!-- Uxr related overrides for a specific list -->
+    <declare-styleable name="CarUxRestrictionsAppConfig_ListConfig">
+        <!-- Id of ListConfig, used to differentiate them -->
+        <attr name="id" format="reference"/>
+        <!-- Used to limit the length of a list. -->
+        <attr name="maxLength" format="integer"/>
+        <!-- Used to educate users why their scrolling experience is limited. -->
+        <attr name="message" format="string"/>
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/car-uxr-client-lib/src/com/android/car/uxr/CarUxRestrictionsAppConfig.java b/car-uxr-client-lib/src/com/android/car/uxr/CarUxRestrictionsAppConfig.java
new file mode 100644
index 0000000..49634ca
--- /dev/null
+++ b/car-uxr-client-lib/src/com/android/car/uxr/CarUxRestrictionsAppConfig.java
@@ -0,0 +1,181 @@
+/*
+ * 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.uxr;
+
+import android.content.Context;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.annotation.XmlRes;
+
+import java.util.Map;
+
+/**
+ * A container class for app specific Car User Experience Restriction override configurations.
+ *
+ * <p>{@link #getInstance(Context, int)} will returned a lazily populated cached reference to the
+ * configurations object that is read using
+ * {@link CarUxRestrictionsAppConfigParser#parseConfig(Context, int)}.
+ *
+ * <p>{@link #getMapping()} can be used to access the mapping of component IDs to configurations
+ * specific to that component.
+ */
+public class CarUxRestrictionsAppConfig {
+
+    private final Map<Integer, ListConfig> mMapping;
+    private static CarUxRestrictionsAppConfig sInstance;
+
+    CarUxRestrictionsAppConfig(Map<Integer, ListConfig> mapping) {
+        mMapping = mapping;
+    }
+
+    /**
+     * Returns a cached reference to the {@link CarUxRestrictionsAppConfig} object
+     * resulting from parsing the contents of {@code xmlRes} xml resource.
+     *
+     * @param context - the app context
+     * @param xmlRes  - the xml resource that contains the UXR override configs.
+     */
+    public static CarUxRestrictionsAppConfig getInstance(Context context, @XmlRes int xmlRes) {
+        if (sInstance == null) {
+            sInstance = CarUxRestrictionsAppConfigParser.parseConfig(context, xmlRes);
+        }
+
+        return sInstance;
+    }
+
+    /**
+     * Returns a {@link Map} of Resource Ids as ints to {@link ListConfig} objects.
+     */
+    public Map<Integer, ListConfig> getMapping() {
+        return mMapping;
+    }
+
+    /**
+     * A class representing Car User Experience Restriction override configurations for a list UI
+     * component.
+     */
+    public static class ListConfig {
+        @IdRes
+        private final int mId;
+        private final Integer mContentLimit;
+        @StringRes
+        private final Integer mScrollingLimitedMessageResId;
+
+        private ListConfig(@IdRes int id, @Nullable Integer contentLimit,
+                @StringRes Integer scrollingLimitedMessageResId) {
+            mId = id;
+            mContentLimit = contentLimit;
+            mScrollingLimitedMessageResId = scrollingLimitedMessageResId;
+        }
+
+        /**
+         * Returns a {@code Builder} that can be used to build a {@link ListConfig} object for a
+         * component identified with the provided {@code id}.
+         *
+         * @param id - an identifier for the component whose behavior needs to be overridden with
+         *           the configurations specified in the resulting {@link ListConfig} object.
+         */
+        public static Builder builder(@IdRes int id) {
+            return new Builder(id);
+        }
+
+        /**
+         * Returns the identifier for the component whose behavior needs to be overridden by this
+         * config object.
+         */
+        @IdRes
+        public int getId() {
+            return mId;
+        }
+
+        /**
+         * Returns the item limit to impose on the contents of the corresponding list component.
+         */
+        @Nullable
+        public Integer getContentLimit() {
+            return mContentLimit;
+        }
+
+        /**
+         * Returns the string resource ID to use when educating users about why the content in the
+         * list they're browsing has been limited.
+         */
+        @Nullable
+        @StringRes
+        public Integer getScrollingLimitedMessageResId() {
+            return mScrollingLimitedMessageResId;
+        }
+
+        /**
+         * A Builder for {@link ListConfig}.
+         */
+        public static class Builder {
+            @IdRes
+            private final int mId;
+            private Integer mContentLimit;
+            @StringRes
+            private Integer mScrollingLimitedMessageResId;
+
+
+            /**
+             * Constructs a {@code Builder} that can be used to build a {@link ListConfig} object
+             * for a component identified with the provided {@code id}.
+             *
+             * @param id - an identifier for the component whose behavior needs to be overridden
+             *           with the configurations specified in the resulting {@link ListConfig}
+             *           object.
+             */
+            private Builder(@IdRes int id) {
+                mId = id;
+            }
+
+            /**
+             * Sets the item limit to impose on the contents of the corresponding list component.
+             *
+             * @param contentLimit - the item limit
+             * @return this {@code Builder} object to facilitate chaining.
+             */
+            public Builder setContentLimit(int contentLimit) {
+                mContentLimit = contentLimit;
+                return this;
+            }
+
+            /**
+             * Sets the string resource ID to use when educating users about why the content in the
+             * * list they're browsing has been limited.
+             *
+             * @param scrollingLimitedMessageResId - an educational message string resource ID
+             * @return this {@code Builder} object to facilitate chaining.
+             */
+            public Builder setScrollingLimitedMessageResId(
+                    @StringRes int scrollingLimitedMessageResId) {
+                mScrollingLimitedMessageResId = scrollingLimitedMessageResId;
+                return this;
+            }
+
+            /**
+             * Build and return a {@link ListConfig} object with the values provided to this
+             * {@code Builder} object.
+             */
+            public ListConfig build() {
+                return new ListConfig(mId, mContentLimit, mScrollingLimitedMessageResId);
+            }
+        }
+    }
+}
diff --git a/car-uxr-client-lib/src/com/android/car/uxr/CarUxRestrictionsAppConfigParser.java b/car-uxr-client-lib/src/com/android/car/uxr/CarUxRestrictionsAppConfigParser.java
new file mode 100644
index 0000000..341e426
--- /dev/null
+++ b/car-uxr-client-lib/src/com/android/car/uxr/CarUxRestrictionsAppConfigParser.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.uxr;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import android.view.View;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.uxr.CarUxRestrictionsAppConfig.ListConfig;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A parser that can read an XML resource file and construct the corresponding
+ * {@link CarUxRestrictionsAppConfig} object.
+ *
+ * See car-uxr-client-lib/res/values/attrs.xml for the definition of the relevant XML tags.
+ */
+public class CarUxRestrictionsAppConfigParser {
+    private static final String TAG = "UxrAppConfigParser";
+
+    static CarUxRestrictionsAppConfig parseConfig(Context context, @XmlRes int xmlRes) {
+        try (XmlResourceParser parser = context.getResources().getXml(xmlRes)) {
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            Map<Integer, ListConfig> mapping = new HashMap<>();
+
+            // Skip over the xml version tag
+            parser.next();
+            // Skip over the copyright comment block
+            parser.next();
+            parser.require(XmlPullParser.START_TAG, null, "Mapping");
+            while (parser.next() != XmlPullParser.END_TAG) {
+                ListConfig listConfig = parseListConfigItem(context, parser, attrs);
+                mapping.put(listConfig.getId(), listConfig);
+            }
+
+            return new CarUxRestrictionsAppConfig(mapping);
+        } catch (XmlPullParserException | IOException e) {
+            throw new RuntimeException("Unable to parse CarUxRestrictionsAppConfig", e);
+        }
+    }
+
+    private static ListConfig parseListConfigItem(
+            Context context, XmlResourceParser parser, AttributeSet attrs)
+            throws XmlPullParserException, IOException {
+
+        parser.require(XmlPullParser.START_TAG, null, "ListConfig");
+
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.CarUxRestrictionsAppConfig_ListConfig);
+
+        try {
+            int id = a.getResourceId(R.styleable.CarUxRestrictionsAppConfig_ListConfig_id,
+                    View.NO_ID);
+            if (id == View.NO_ID) {
+                throw new IllegalStateException("Id field is required");
+            }
+
+            boolean messageExists = a.hasValue(
+                    R.styleable.CarUxRestrictionsAppConfig_ListConfig_message);
+            int messageResId = a.getResourceId(
+                    R.styleable.CarUxRestrictionsAppConfig_ListConfig_message, View.NO_ID);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, messageExists
+                        ? "message field is set to " + messageResId
+                        : "message field not specified");
+            }
+
+            boolean maxLengthExists = a.hasValue(
+                    R.styleable.CarUxRestrictionsAppConfig_ListConfig_maxLength);
+            int maxLengthInt = a.getInt(
+                    R.styleable.CarUxRestrictionsAppConfig_ListConfig_maxLength, 0);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, maxLengthExists
+                        ? "maxLength field is set to " + maxLengthInt
+                        : "maxLength field not specified");
+            }
+
+            parser.next();
+            parser.require(XmlPullParser.END_TAG, null, "ListConfig");
+
+            ListConfig.Builder builder = ListConfig.builder(id);
+            if (maxLengthExists) {
+                builder.setContentLimit(maxLengthInt);
+            }
+            if (messageExists) {
+                builder.setScrollingLimitedMessageResId(messageResId);
+            }
+            return builder.build();
+        } finally {
+            a.recycle();
+        }
+    }
+}
diff --git a/car-uxr-client-lib/src/com/android/car/uxr/LifeCycleObserverUxrContentLimiter.java b/car-uxr-client-lib/src/com/android/car/uxr/LifeCycleObserverUxrContentLimiter.java
new file mode 100644
index 0000000..3c8905e
--- /dev/null
+++ b/car-uxr-client-lib/src/com/android/car/uxr/LifeCycleObserverUxrContentLimiter.java
@@ -0,0 +1,61 @@
+/*
+ * 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.uxr;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+import com.android.car.ui.recyclerview.ContentLimiting;
+
+/**
+ * An implementation of {@link UxrContentLimiter} interface that also provides the functionality
+ * necessary for a {@link DefaultLifecycleObserver}.
+ *
+ * <p>Relies heavily on the {@link UxrContentLimiterImpl} implementation of the
+ * {@link UxrContentLimiter} interface.
+ *
+ * <p>For example, you could do the following to get yourself a lifecycle aware {@link
+ * UxrContentLimiter}:
+ * <pre>{@code
+ * new LifeCycleObserverUxrContentLimiter(new UxrContentLimiterImpl(context,xmlRes));
+ * }</pre>
+ */
+public class LifeCycleObserverUxrContentLimiter
+        implements UxrContentLimiter, DefaultLifecycleObserver {
+
+    private final UxrContentLimiterImpl mDelegate;
+
+    public LifeCycleObserverUxrContentLimiter(UxrContentLimiterImpl delegate) {
+        mDelegate = delegate;
+    }
+
+    @Override
+    public void onStart(@NonNull LifecycleOwner owner) {
+        mDelegate.start();
+    }
+
+    @Override
+    public void onStop(@NonNull LifecycleOwner owner) {
+        mDelegate.stop();
+    }
+
+    @Override
+    public void setAdapter(ContentLimiting adapter) {
+        mDelegate.setAdapter(adapter);
+    }
+}
diff --git a/car-uxr-client-lib/src/com/android/car/uxr/UxrContentLimiter.java b/car-uxr-client-lib/src/com/android/car/uxr/UxrContentLimiter.java
new file mode 100644
index 0000000..9fd877e
--- /dev/null
+++ b/car-uxr-client-lib/src/com/android/car/uxr/UxrContentLimiter.java
@@ -0,0 +1,37 @@
+/*
+ * 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.uxr;
+
+import com.android.car.ui.recyclerview.ContentLimiting;
+
+/**
+ * An interface to facilitate the content limiting ability of {@link ContentLimiting}
+ * {@link androidx.recyclerview.widget.RecyclerView.Adapter} objects based on changes to the state
+ * of the car.
+ */
+public interface UxrContentLimiter {
+
+    /**
+     * Registers the given {@link ContentLimiting} with this {@code UxrContentLimiter}.
+     *
+     * <p>That means that when the car state changes, if necessary, this
+     * {@code UxrContentLimiter} will limit the content in the given adapter.
+     *
+     * @param adapter - the adapter to associate with this content limiter.
+     */
+    void setAdapter(ContentLimiting adapter);
+}
diff --git a/car-uxr-client-lib/src/com/android/car/uxr/UxrContentLimiterImpl.java b/car-uxr-client-lib/src/com/android/car/uxr/UxrContentLimiterImpl.java
new file mode 100644
index 0000000..ce22cca
--- /dev/null
+++ b/car-uxr-client-lib/src/com/android/car/uxr/UxrContentLimiterImpl.java
@@ -0,0 +1,141 @@
+/*
+ * 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.uxr;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.XmlRes;
+
+import com.android.car.ui.recyclerview.ContentLimiting;
+import com.android.car.ui.utils.CarUxRestrictionsUtil;
+import com.android.car.uxr.CarUxRestrictionsAppConfig.ListConfig;
+
+/**
+ * A class that can work together with a {@link ContentLimiting} {@link
+ * androidx.recyclerview.widget.RecyclerView.Adapter} object to provide content limiting ability
+ * based on changes to the state of the car, by listening for the latest {@link CarUxRestrictions}.
+ *
+ * <p>This class manages 3 things:
+ * <ul>
+ *     <li> Communications with the User Experience Restriction Engine
+ *     <li> Looking up app-side overrides for customizing the content-limiting behavior of a given
+ *     list of items in a particular screen
+ *     <li> Relaying the relevant parts of that information to the registered
+ *     adapter at the right time
+ * </ul>
+ *
+ * <p>The app-side overrides are accessed via the {@link CarUxRestrictionsAppConfig} object.
+ *
+ * <p>Because all but one of the dependencies for this class can be instantiated as soon as a
+ * {@link Context} is available, we provide a separate {@link #setAdapter(ContentLimiting)}
+ * API for linking the targeted adapter. That way the registration can happen in a different part of
+ * code, and potentially in a different lifecycle method to provide maximum flexibility.
+ */
+public class UxrContentLimiterImpl implements UxrContentLimiter {
+
+    private ContentLimiting mAdapter;
+    private ListConfig mListConfig;
+
+    private final CarUxRestrictionsUtil mCarUxRestrictionsUtil;
+    private final CarUxRestrictionsAppConfig mCarUxRestrictionsAppConfig;
+    private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener =
+            new Listener();
+
+    private class Listener implements CarUxRestrictionsUtil.OnUxRestrictionsChangedListener {
+        private static final String TAG = "ContentLimitListener";
+
+        @Override
+        public void onRestrictionsChanged(@NonNull CarUxRestrictions carUxRestrictions) {
+            if (mAdapter == null) {
+                Log.w(TAG, "No adapter registered.");
+                return;
+            }
+
+            int maxItems = getMaxItemsToUse(carUxRestrictions, mAdapter.getConfigurationId());
+            logD("New limit " + maxItems);
+            mAdapter.setMaxItems(maxItems);
+        }
+
+        private int getMaxItemsToUse(CarUxRestrictions carUxRestrictions, @IdRes int id) {
+            // Unrelated restrictions are active. Quit early.
+            if ((carUxRestrictions.getActiveRestrictions()
+                    & CarUxRestrictions.UX_RESTRICTIONS_LIMIT_CONTENT)
+                    == 0) {
+                logD("Lists are unrestricted.");
+                return ContentLimiting.UNLIMITED;
+            }
+
+            if (mListConfig == null || mListConfig.getContentLimit() == null) {
+                logD("No configs found for adapter with the ID: " + id
+                        + " Using the default limit");
+                return carUxRestrictions.getMaxCumulativeContentItems();
+            }
+
+            logD("Using the provided override.");
+            return mListConfig.getContentLimit();
+        }
+
+        private void logD(String s) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, s);
+            }
+        }
+    }
+
+    /**
+     * Constructs a {@link UxrContentLimiterImpl} object given the app context and the XML resource
+     * file to parse the User Experience Restriction override configs from.
+     *
+     * @param context - the app context
+     * @param xmlRes  - the UXR override config XML resource
+     */
+    public UxrContentLimiterImpl(Context context, @XmlRes int xmlRes) {
+        mCarUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(context);
+        mCarUxRestrictionsAppConfig = CarUxRestrictionsAppConfig.getInstance(context, xmlRes);
+    }
+
+    @Override
+    public void setAdapter(ContentLimiting adapter) {
+        mAdapter = adapter;
+        int key = mAdapter.getConfigurationId();
+        if (mCarUxRestrictionsAppConfig.getMapping().containsKey(key)) {
+            mListConfig = mCarUxRestrictionsAppConfig.getMapping().get(key);
+            Integer overriddenMessageResId = mListConfig.getScrollingLimitedMessageResId();
+            if (overriddenMessageResId != null) {
+                mAdapter.setScrollingLimitedMessageResId(overriddenMessageResId);
+            }
+        }
+    }
+
+    /**
+     * Start listening for changes to {@link CarUxRestrictions}.
+     */
+    public void start() {
+        mCarUxRestrictionsUtil.register(mListener);
+    }
+
+    /**
+     * Stop listening for changes to {@link CarUxRestrictions}.
+     */
+    public void stop() {
+        mCarUxRestrictionsUtil.unregister(mListener);
+    }
+}
diff --git a/connected-device-lib/Android.bp b/connected-device-lib/Android.bp
index 85490be..ca1de00 100644
--- a/connected-device-lib/Android.bp
+++ b/connected-device-lib/Android.bp
@@ -27,17 +27,19 @@
         enabled: false,
     },
 
-    libs: ["android.car"],
+    libs: ["android.car-stubs"],
 
     static_libs: [
-        "EncryptionRunner-lib",
         "androidx.room_room-runtime",
         "connected-device-protos",
+        "encryption-runner",
+        "guava",
     ],
 
     plugins: [
         "car-androidx-room-compiler",
     ],
 
-    platform_apis: true,
+    sdk_version: "system_current",
+    min_sdk_version: "29",
 }
diff --git a/connected-device-lib/res/values/config.xml b/connected-device-lib/res/values/config.xml
index 0f88ddc..b090b92 100644
--- a/connected-device-lib/res/values/config.xml
+++ b/connected-device-lib/res/values/config.xml
@@ -16,9 +16,11 @@
   -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <string name="car_service_uuid" translatable="false">5e2a68a8-27be-43f9-8d1e-4546976fabd7</string>
-    <string name="car_association_service_uuid" translatable="false">5e2a68a4-27be-43f9-8d1e-4546976fabd7</string>
+    <!-- These values must be modified for the connected device lib to function properly.-->
+    <string name="car_service_uuid" translatable="false">00000000-0000-0000-0000-000000000000</string>
+    <string name="car_association_service_uuid" translatable="false">00000000-0000-0000-0000-000000000000</string>
+    <string name="car_reconnect_service_uuid" translatable="false">00000000-0000-0000-0000-000000000000</string>
+    <string name="car_reconnect_data_uuid" translatable="false">00000000-0000-0000-0000-000000000000</string>
     <string name="car_bg_mask" translatable="false">00000000000000000000000000000000</string>
 
     <string name="car_secure_read_uuid" translatable="false">5e2a68a6-27be-43f9-8d1e-4546976fabd7</string>
@@ -26,5 +28,10 @@
 
     <string name="connected_device_shared_preferences" translatable="false">com.android.car.connecteddevice</string>
 
-    <integer name="car_reconnect_timeout_sec">60</integer>
+    <!--
+    This value must be between 23 and 185. 23 is the default MTU size for Android, and 185 is
+    the max MTU size supported for iOS. Verify your device and target companion devices support a
+    larger MTU prior to modifying.
+    -->
+    <integer name="car_default_mtu_size">23</integer>
 </resources>
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/AssociationCallback.java b/connected-device-lib/src/com/android/car/connecteddevice/AssociationCallback.java
index fb7000b..d628459 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/AssociationCallback.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/AssociationCallback.java
@@ -16,7 +16,7 @@
 
 package com.android.car.connecteddevice;
 
-import android.annotation.NonNull;
+import androidx.annotation.NonNull;
 
 /** Callbacks that will be invoked during associating a new client. */
 public interface AssociationCallback {
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ConnectedDeviceManager.java b/connected-device-lib/src/com/android/car/connecteddevice/ConnectedDeviceManager.java
index f8805a1..6e8b6e0 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ConnectedDeviceManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ConnectedDeviceManager.java
@@ -22,12 +22,13 @@
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
 
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.car.connecteddevice.ble.BleCentralManager;
 import com.android.car.connecteddevice.ble.BlePeripheralManager;
 import com.android.car.connecteddevice.ble.CarBleCentralManager;
@@ -41,9 +42,9 @@
 import com.android.car.connecteddevice.util.ByteUtils;
 import com.android.car.connecteddevice.util.EventLog;
 import com.android.car.connecteddevice.util.ThreadSafeCallbacks;
-import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.annotation.Retention;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -72,6 +73,10 @@
     // Subtracting 2 bytes used by header, we have 8 bytes for device name.
     private static final int DEVICE_NAME_LENGTH_LIMIT = 8;
 
+    // The mac address randomly rotates every 7-15 minutes. To be safe, we will rotate our
+    // reconnect advertisement every 6 minutes to avoid crossing a rotation.
+    private static final Duration MAX_ADVERTISEMENT_DURATION = Duration.ofMinutes(6);
+
     private final ConnectedDeviceStorage mStorage;
 
     private final CarBleCentralManager mCentralManager;
@@ -96,7 +101,7 @@
             new ConcurrentHashMap<>();
 
     // recipientId -> (deviceId -> message bytes)
-    private final Map<UUID, Map<String, byte[]>> mRecipientMissedMessages =
+    private final Map<UUID, Map<String, List<byte[]>>> mRecipientMissedMessages =
             new ConcurrentHashMap<>();
 
     // Recipient ids that received multiple callback registrations indicate that the recipient id
@@ -109,8 +114,6 @@
 
     private final AtomicBoolean mHasStarted = new AtomicBoolean(false);
 
-    private final int mReconnectTimeoutSeconds;
-
     private String mNameForAssociation;
 
     private AssociationCallback mAssociationCallback;
@@ -118,20 +121,18 @@
     private MessageDeliveryDelegate mMessageDeliveryDelegate;
 
     @Retention(SOURCE)
-    @IntDef(prefix = { "DEVICE_ERROR_" },
-            value = {
-                    DEVICE_ERROR_INVALID_HANDSHAKE,
-                    DEVICE_ERROR_INVALID_MSG,
-                    DEVICE_ERROR_INVALID_DEVICE_ID,
-                    DEVICE_ERROR_INVALID_VERIFICATION,
-                    DEVICE_ERROR_INVALID_CHANNEL_STATE,
-                    DEVICE_ERROR_INVALID_ENCRYPTION_KEY,
-                    DEVICE_ERROR_STORAGE_FAILURE,
-                    DEVICE_ERROR_INVALID_SECURITY_KEY,
-                    DEVICE_ERROR_INSECURE_RECIPIENT_ID_DETECTED,
-                    DEVICE_ERROR_UNEXPECTED_DISCONNECTION
-            }
-    )
+    @IntDef({
+            DEVICE_ERROR_INVALID_HANDSHAKE,
+            DEVICE_ERROR_INVALID_MSG,
+            DEVICE_ERROR_INVALID_DEVICE_ID,
+            DEVICE_ERROR_INVALID_VERIFICATION,
+            DEVICE_ERROR_INVALID_CHANNEL_STATE,
+            DEVICE_ERROR_INVALID_ENCRYPTION_KEY,
+            DEVICE_ERROR_STORAGE_FAILURE,
+            DEVICE_ERROR_INVALID_SECURITY_KEY,
+            DEVICE_ERROR_INSECURE_RECIPIENT_ID_DETECTED,
+            DEVICE_ERROR_UNEXPECTED_DISCONNECTION
+    })
     public @interface DeviceError {}
     public static final int DEVICE_ERROR_INVALID_HANDSHAKE = 0;
     public static final int DEVICE_ERROR_INVALID_MSG = 1;
@@ -149,10 +150,12 @@
                 new BlePeripheralManager(context),
                 UUID.fromString(context.getString(R.string.car_service_uuid)),
                 UUID.fromString(context.getString(R.string.car_association_service_uuid)),
+                UUID.fromString(context.getString(R.string.car_reconnect_service_uuid)),
+                UUID.fromString(context.getString(R.string.car_reconnect_data_uuid)),
                 context.getString(R.string.car_bg_mask),
                 UUID.fromString(context.getString(R.string.car_secure_write_uuid)),
                 UUID.fromString(context.getString(R.string.car_secure_read_uuid)),
-                context.getResources().getInteger(R.integer.car_reconnect_timeout_sec));
+                context.getResources().getInteger(R.integer.car_default_mtu_size));
     }
 
     private ConnectedDeviceManager(
@@ -162,23 +165,25 @@
             @NonNull BlePeripheralManager blePeripheralManager,
             @NonNull UUID serviceUuid,
             @NonNull UUID associationServiceUuid,
+            @NonNull UUID reconnectServiceUuid,
+            @NonNull UUID reconnectDataUuid,
             @NonNull String bgMask,
             @NonNull UUID writeCharacteristicUuid,
             @NonNull UUID readCharacteristicUuid,
-            int reconnectTimeoutSeconds) {
+            int defaultMtuSize) {
         this(storage,
                 new CarBleCentralManager(context, bleCentralManager, storage, serviceUuid, bgMask,
                         writeCharacteristicUuid, readCharacteristicUuid),
                 new CarBlePeripheralManager(blePeripheralManager, storage, associationServiceUuid,
-                        writeCharacteristicUuid, readCharacteristicUuid), reconnectTimeoutSeconds);
+                        reconnectServiceUuid, reconnectDataUuid, writeCharacteristicUuid,
+                        readCharacteristicUuid, MAX_ADVERTISEMENT_DURATION, defaultMtuSize));
     }
 
     @VisibleForTesting
     ConnectedDeviceManager(
             @NonNull ConnectedDeviceStorage storage,
             @NonNull CarBleCentralManager centralManager,
-            @NonNull CarBlePeripheralManager peripheralManager,
-            int reconnectTimeoutSeconds) {
+            @NonNull CarBlePeripheralManager peripheralManager) {
         Executor callbackExecutor = Executors.newSingleThreadExecutor();
         mStorage = storage;
         mCentralManager = centralManager;
@@ -187,7 +192,6 @@
         mPeripheralManager.registerCallback(generateCarBleCallback(peripheralManager),
                 callbackExecutor);
         mStorage.setAssociatedDeviceCallback(mAssociatedDeviceCallback);
-        mReconnectTimeoutSeconds = reconnectTimeoutSeconds;
     }
 
     /**
@@ -237,7 +241,7 @@
      * @param executor {@link Executor} to execute triggers on.
      */
     public void registerDeviceAssociationCallback(@NonNull DeviceAssociationCallback callback,
-            @NonNull @CallbackExecutor Executor executor) {
+            @NonNull Executor executor) {
         mDeviceAssociationCallbacks.add(callback, executor);
     }
 
@@ -258,7 +262,7 @@
      * @param executor {@link Executor} to execute triggers on.
      */
     public void registerActiveUserConnectionCallback(@NonNull ConnectionCallback callback,
-            @NonNull @CallbackExecutor Executor executor) {
+            @NonNull Executor executor) {
         mActiveUserConnectionCallbacks.add(callback, executor);
     }
 
@@ -283,7 +287,8 @@
 
     private void connectToActiveUserDeviceInternal() {
         try {
-            if (mIsConnectingToUserDevice.get()) {
+            boolean isLockAcquired = mIsConnectingToUserDevice.compareAndSet(false, true);
+            if (!isLockAcquired) {
                 logd(TAG, "A request has already been made to connect to this user's device. "
                         + "Ignoring redundant request.");
                 return;
@@ -291,6 +296,7 @@
             List<AssociatedDevice> userDevices = mStorage.getActiveUserAssociatedDevices();
             if (userDevices.isEmpty()) {
                 logw(TAG, "No devices associated with active user. Ignoring.");
+                mIsConnectingToUserDevice.set(false);
                 return;
             }
 
@@ -299,19 +305,20 @@
             AssociatedDevice userDevice = userDevices.get(0);
             if (!userDevice.isConnectionEnabled()) {
                 logd(TAG, "Connection is disabled on device " + userDevice + ".");
+                mIsConnectingToUserDevice.set(false);
                 return;
             }
             if (mConnectedDevices.containsKey(userDevice.getDeviceId())) {
                 logd(TAG, "Device has already been connected. No need to attempt connection "
                         + "again.");
+                mIsConnectingToUserDevice.set(false);
                 return;
             }
             EventLog.onStartDeviceSearchStarted();
-            mIsConnectingToUserDevice.set(true);
-            mPeripheralManager.connectToDevice(UUID.fromString(userDevice.getDeviceId()),
-                    mReconnectTimeoutSeconds);
+            mPeripheralManager.connectToDevice(UUID.fromString(userDevice.getDeviceId()));
         } catch (Exception e) {
             loge(TAG, "Exception while attempting connection with active user's device.", e);
+            mIsConnectingToUserDevice.set(false);
         }
     }
 
@@ -372,7 +379,7 @@
     public void enableAssociatedDeviceConnection(@NonNull String deviceId) {
         logd(TAG, "enableAssociatedDeviceConnection() called on " + deviceId);
         mStorage.updateAssociatedDeviceConnectionEnabled(deviceId,
-                /* isConnectionEnabled = */ true);
+                /* isConnectionEnabled= */ true);
         connectToActiveUserDevice();
     }
 
@@ -384,7 +391,7 @@
     public void disableAssociatedDeviceConnection(@NonNull String deviceId) {
         logd(TAG, "disableAssociatedDeviceConnection() called on " + deviceId);
         mStorage.updateAssociatedDeviceConnectionEnabled(deviceId,
-                /* isConnectionEnabled = */ false);
+                /* isConnectionEnabled= */ false);
         disconnectDevice(deviceId);
     }
 
@@ -405,7 +412,7 @@
      * @param executor {@link Executor} on which to execute callback.
      */
     public void registerDeviceCallback(@NonNull ConnectedDevice device, @NonNull UUID recipientId,
-            @NonNull DeviceCallback callback, @NonNull @CallbackExecutor Executor executor) {
+            @NonNull DeviceCallback callback, @NonNull Executor executor) {
         if (isRecipientBlacklisted(recipientId)) {
             notifyOfBlacklisting(device, recipientId, callback, executor);
             return;
@@ -414,7 +421,7 @@
                 + recipientId);
         String deviceId = device.getDeviceId();
         Map<UUID, ThreadSafeCallbacks<DeviceCallback>> recipientCallbacks =
-                mDeviceCallbacks.computeIfAbsent(deviceId, key -> new HashMap<>());
+                mDeviceCallbacks.computeIfAbsent(deviceId, key -> new ConcurrentHashMap<>());
 
         // Device already has a callback registered with this recipient UUID. For the
         // protection of the user, this UUID is now blacklisted from future subscriptions
@@ -429,10 +436,12 @@
         newCallbacks.add(callback, executor);
         recipientCallbacks.put(recipientId, newCallbacks);
 
-        byte[] message = popMissedMessage(recipientId, device.getDeviceId());
-        if (message != null) {
-            newCallbacks.invoke(deviceCallback ->
-                    deviceCallback.onMessageReceived(device, message));
+        List<byte[]> messages = popMissedMessages(recipientId, device.getDeviceId());
+        if (messages != null) {
+            for (byte[] message : messages) {
+                newCallbacks.invoke(deviceCallback ->
+                        deviceCallback.onMessageReceived(device, message));
+            }
         }
     }
 
@@ -458,8 +467,8 @@
         // Store last message in case recipient registers callbacks in the future.
         logd(TAG, "No recipient registered for device " + deviceId + " and recipient "
                 + recipientId + " combination. Saving message.");
-        mRecipientMissedMessages.putIfAbsent(recipientId, new HashMap<>());
-        mRecipientMissedMessages.get(recipientId).putIfAbsent(deviceId, message);
+        mRecipientMissedMessages.computeIfAbsent(recipientId, __ -> new HashMap<>())
+                .computeIfAbsent(deviceId, __ -> new ArrayList<>()).add(message);
     }
 
     /**
@@ -468,12 +477,12 @@
      *
      * @param recipientId Recipient's id
      * @param deviceId Device id
-     * @return The last missed {@code byte[]} of the message, or {@code null} if no messages were
+     * @return The missed {@code byte[]} messages, or {@code null} if no messages were
      *         missed.
      */
     @Nullable
-    private byte[] popMissedMessage(@NonNull UUID recipientId, @NonNull String deviceId) {
-        Map<String, byte[]> missedMessages = mRecipientMissedMessages.get(recipientId);
+    private List<byte[]> popMissedMessages(@NonNull UUID recipientId, @NonNull String deviceId) {
+        Map<String, List<byte[]>> missedMessages = mRecipientMissedMessages.get(recipientId);
         if (missedMessages == null) {
             return null;
         }
@@ -519,7 +528,7 @@
      */
     public void sendMessageSecurely(@NonNull ConnectedDevice device, @NonNull UUID recipientId,
             @NonNull byte[] message) throws IllegalStateException {
-        sendMessage(device, recipientId, message, /* isEncrypted = */ true);
+        sendMessage(device, recipientId, message, /* isEncrypted= */ true);
     }
 
     /**
@@ -531,7 +540,7 @@
      */
     public void sendMessageUnsecurely(@NonNull ConnectedDevice device, @NonNull UUID recipientId,
             @NonNull byte[] message) {
-        sendMessage(device, recipientId, message, /* isEncrypted = */ false);
+        sendMessage(device, recipientId, message, /* isEncrypted= */ false);
     }
 
     private void sendMessage(@NonNull ConnectedDevice device, @NonNull UUID recipientId,
@@ -596,9 +605,9 @@
         logd(TAG, "New device with id " + deviceId + " connected.");
         ConnectedDevice connectedDevice = new ConnectedDevice(
                 deviceId,
-                /* deviceName = */ null,
+                /* deviceName= */ null,
                 mStorage.getActiveUserAssociatedDeviceIds().contains(deviceId),
-                /* hasSecureChannel = */ false
+                /* hasSecureChannel= */ false
         );
 
         mConnectedDevices.put(deviceId, new InternalConnectedDevice(connectedDevice, bleManager));
@@ -642,7 +651,7 @@
         ConnectedDevice connectedDevice = mConnectedDevices.get(deviceId).mConnectedDevice;
         ConnectedDevice updatedConnectedDevice = new ConnectedDevice(connectedDevice.getDeviceId(),
                 connectedDevice.getDeviceName(), connectedDevice.isAssociatedWithActiveUser(),
-                /* hasSecureChannel = */ true);
+                /* hasSecureChannel= */ true);
 
         boolean notifyCallbacks = getConnectedDeviceForManager(deviceId, bleManager) != null;
 
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/AssociationSecureChannel.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/AssociationSecureChannel.java
new file mode 100644
index 0000000..f397922
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/AssociationSecureChannel.java
@@ -0,0 +1,213 @@
+/*
+ * 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.connecteddevice.ble;
+
+import static android.car.encryptionrunner.EncryptionRunnerFactory.EncryptionRunnerType;
+import static android.car.encryptionrunner.EncryptionRunnerFactory.newRunner;
+import static android.car.encryptionrunner.HandshakeMessage.HandshakeState;
+
+import static com.android.car.connecteddevice.util.SafeLog.logd;
+import static com.android.car.connecteddevice.util.SafeLog.loge;
+
+import android.car.encryptionrunner.EncryptionRunner;
+import android.car.encryptionrunner.HandshakeException;
+import android.car.encryptionrunner.HandshakeMessage;
+import android.car.encryptionrunner.Key;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
+import com.android.car.connecteddevice.util.ByteUtils;
+
+import java.security.InvalidParameterException;
+import java.util.Arrays;
+import java.util.UUID;
+
+/**
+ * A secure channel established with the association flow.
+ */
+class AssociationSecureChannel extends SecureBleChannel {
+
+    private static final String TAG = "AssociationSecureChannel";
+
+    private static final int DEVICE_ID_BYTES = 16;
+
+    private final ConnectedDeviceStorage mStorage;
+
+    private ShowVerificationCodeListener mShowVerificationCodeListener;
+
+    @HandshakeState
+    private int mState = HandshakeState.UNKNOWN;
+
+    private Key mPendingKey;
+
+    private String mDeviceId;
+
+    AssociationSecureChannel(BleDeviceMessageStream stream, ConnectedDeviceStorage storage) {
+        this(stream, storage, newRunner(EncryptionRunnerType.UKEY2));
+    }
+
+    AssociationSecureChannel(BleDeviceMessageStream stream, ConnectedDeviceStorage storage,
+            EncryptionRunner encryptionRunner) {
+        super(stream, encryptionRunner);
+        encryptionRunner.setIsReconnect(false);
+        mStorage = storage;
+    }
+
+    @Override
+    void processHandshake(@NonNull byte[] message) throws HandshakeException {
+        switch (mState) {
+            case HandshakeState.UNKNOWN:
+                processHandshakeUnknown(message);
+                break;
+            case HandshakeState.IN_PROGRESS:
+                processHandshakeInProgress(message);
+                break;
+            case HandshakeState.FINISHED:
+                processHandshakeDeviceIdAndSecret(message);
+                break;
+            default:
+                loge(TAG, "Encountered unexpected handshake state: " + mState + ".");
+                notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE);
+        }
+    }
+
+    private void processHandshakeUnknown(@NonNull byte[] message) throws HandshakeException {
+        logd(TAG, "Responding to handshake init request.");
+        HandshakeMessage handshakeMessage = getEncryptionRunner().respondToInitRequest(message);
+        mState = handshakeMessage.getHandshakeState();
+        sendHandshakeMessage(handshakeMessage.getNextMessage(), /* isEncrypted= */ false);
+    }
+
+    private void processHandshakeInProgress(@NonNull byte[] message) throws HandshakeException {
+        logd(TAG, "Continuing handshake.");
+        HandshakeMessage handshakeMessage = getEncryptionRunner().continueHandshake(message);
+        mState = handshakeMessage.getHandshakeState();
+
+        if (mState != HandshakeState.VERIFICATION_NEEDED) {
+            loge(TAG, "processHandshakeInProgress: Encountered unexpected handshake state: "
+                    + mState + ".");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE);
+            return;
+        }
+
+        String code = handshakeMessage.getVerificationCode();
+        if (code == null) {
+            loge(TAG, "Unable to get verification code.");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_VERIFICATION);
+            return;
+        }
+
+        if (mShowVerificationCodeListener == null) {
+            loge(TAG, "No verification code listener has been set. Unable to display verification "
+                    + "code to user.");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE);
+            return;
+        }
+
+        logd(TAG, "Showing pairing code: " + code);
+        mShowVerificationCodeListener.showVerificationCode(code);
+    }
+
+    private void processHandshakeDeviceIdAndSecret(@NonNull byte[] message) {
+        UUID deviceId = ByteUtils.bytesToUUID(Arrays.copyOf(message, DEVICE_ID_BYTES));
+        if (deviceId == null) {
+            loge(TAG, "Received invalid device id. Aborting.");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_DEVICE_ID);
+            return;
+        }
+        mDeviceId = deviceId.toString();
+        notifyCallback(callback -> callback.onDeviceIdReceived(mDeviceId));
+
+        mStorage.saveEncryptionKey(mDeviceId, mPendingKey.asBytes());
+        mPendingKey = null;
+        try {
+            mStorage.saveChallengeSecret(mDeviceId,
+                    Arrays.copyOfRange(message, DEVICE_ID_BYTES, message.length));
+        } catch (InvalidParameterException e) {
+            loge(TAG, "Error saving challenge secret.", e);
+            notifySecureChannelFailure(CHANNEL_ERROR_STORAGE_ERROR);
+            return;
+        }
+
+        notifyCallback(Callback::onSecureChannelEstablished);
+    }
+
+    /** Set the listener that notifies to show verification code. {@code null} to clear. */
+    void setShowVerificationCodeListener(@Nullable ShowVerificationCodeListener listener) {
+        mShowVerificationCodeListener = listener;
+    }
+
+    @VisibleForTesting
+    @Nullable
+    ShowVerificationCodeListener getShowVerificationCodeListener() {
+        return mShowVerificationCodeListener;
+    }
+
+    /**
+     * Called by the client to notify that the user has accepted a pairing code or any out-of-band
+     * confirmation, and send confirmation signals to remote bluetooth device.
+     */
+    void notifyOutOfBandAccepted() {
+        HandshakeMessage message;
+        try {
+            message = getEncryptionRunner().verifyPin();
+        } catch (HandshakeException e) {
+            loge(TAG, "Error during PIN verification", e);
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_VERIFICATION);
+            return;
+        }
+        if (message.getHandshakeState() != HandshakeState.FINISHED) {
+            loge(TAG, "Handshake not finished after calling verify PIN. Instead got "
+                    + "state: " + message.getHandshakeState() + ".");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE);
+            return;
+        }
+
+        Key localKey = message.getKey();
+        if (localKey == null) {
+            loge(TAG, "Unable to finish association, generated key is null.");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_ENCRYPTION_KEY);
+            return;
+        }
+        mState = message.getHandshakeState();
+        setEncryptionKey(localKey);
+        mPendingKey = localKey;
+        logd(TAG, "Pairing code successfully verified.");
+        sendUniqueIdToClient();
+    }
+
+    private void sendUniqueIdToClient() {
+        UUID uniqueId = mStorage.getUniqueId();
+        DeviceMessage deviceMessage = new DeviceMessage(/* recipient= */ null,
+                /* isMessageEncrypted= */ true, ByteUtils.uuidToBytes(uniqueId));
+        logd(TAG, "Sending car's device id of " + uniqueId + " to device.");
+        sendHandshakeMessage(ByteUtils.uuidToBytes(uniqueId), /* isEncrypted= */ true);
+    }
+
+    /** Listener that will be invoked to display verification code. */
+    interface ShowVerificationCodeListener {
+        /**
+         * Invoke when a verification need to be displayed during device association.
+         *
+         * @param code The verification code to show.
+         */
+        void showVerificationCode(@NonNull String code);
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/BleCentralManager.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/BleCentralManager.java
index ca83a05..7a01366 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/BleCentralManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/BleCentralManager.java
@@ -20,9 +20,6 @@
 import static com.android.car.connecteddevice.util.SafeLog.loge;
 import static com.android.car.connecteddevice.util.SafeLog.logw;
 
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.le.BluetoothLeScanner;
 import android.bluetooth.le.ScanCallback;
@@ -33,6 +30,10 @@
 import android.content.pm.PackageManager;
 import android.os.Handler;
 
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/BleDeviceMessageStream.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/BleDeviceMessageStream.java
index f91693b..3e19d41 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/BleDeviceMessageStream.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/BleDeviceMessageStream.java
@@ -23,24 +23,26 @@
 import static com.android.car.connecteddevice.util.SafeLog.loge;
 import static com.android.car.connecteddevice.util.SafeLog.logw;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGattCharacteristic;
 import android.os.Handler;
 import android.os.Looper;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.car.connecteddevice.BleStreamProtos.BleDeviceMessageProto.BleDeviceMessage;
 import com.android.car.connecteddevice.util.ByteUtils;
 import com.android.car.protobuf.ByteString;
 import com.android.car.protobuf.InvalidProtocolBufferException;
-import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.ArrayDeque;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -51,9 +53,9 @@
 
     private static final String TAG = "BleDeviceMessageStream";
 
-    // Only version 2 of the messaging and version 1 of the security supported.
+    // Only version 2 of the messaging and version 2 of the security supported.
     private static final int MESSAGING_VERSION = 2;
-    private static final int SECURITY_VERSION = 1;
+    private static final int SECURITY_VERSION = 2;
 
     /*
      * During bandwidth testing, it was discovered that allowing the stream to send as fast as it
@@ -66,8 +68,10 @@
 
     private final ArrayDeque<BlePacket> mPacketQueue = new ArrayDeque<>();
 
-    private final HashMap<Integer, ByteArrayOutputStream> mPendingData =
-            new HashMap<>();
+    private final Map<Integer, ByteArrayOutputStream> mPendingData = new HashMap<>();
+
+    // messageId -> nextExpectedPacketNumber
+    private final Map<Integer, Integer> mPendingPacketNumber = new HashMap<>();
 
     private final MessageIdGenerator mMessageIdGenerator = new MessageIdGenerator();
 
@@ -91,22 +95,20 @@
 
     private MessageReceivedErrorListener mMessageReceivedErrorListener;
 
-    /*
-     * This initial value is 20 because BLE has a default write of 23 bytes. However, 3 bytes are
-     * subtracted due to bytes being reserved for the command type and attribute ID.
-     */
-    private int mMaxWriteSize = 20;
+    private int mMaxWriteSize;
 
     BleDeviceMessageStream(@NonNull BlePeripheralManager blePeripheralManager,
             @NonNull BluetoothDevice device,
             @NonNull BluetoothGattCharacteristic writeCharacteristic,
-            @NonNull BluetoothGattCharacteristic readCharacteristic) {
+            @NonNull BluetoothGattCharacteristic readCharacteristic,
+            int defaultMaxWriteSize) {
         mBlePeripheralManager = blePeripheralManager;
         mDevice = device;
         mWriteCharacteristic = writeCharacteristic;
         mReadCharacteristic = readCharacteristic;
         mBlePeripheralManager.addOnCharacteristicWriteListener(this::onCharacteristicWrite);
         mBlePeripheralManager.addOnCharacteristicReadListener(this::onCharacteristicRead);
+        mMaxWriteSize = defaultMaxWriteSize;
     }
 
     /**
@@ -132,7 +134,8 @@
      * @param operationType The {@link OperationType} of this message.
      */
     void writeMessage(@NonNull DeviceMessage deviceMessage, OperationType operationType) {
-        logd(TAG, "Writing message to device: " + mDevice.getAddress() + ".");
+        logd(TAG, "Writing message with " + deviceMessage.getMessage().length + " bytes to device: "
+                + mDevice.getAddress() + ".");
         BleDeviceMessage.Builder builder = BleDeviceMessage.newBuilder()
                 .setOperation(operationType)
                 .setIsPayloadEncrypted(deviceMessage.isMessageEncrypted())
@@ -163,18 +166,18 @@
                 logd(TAG, "No more packets to send.");
                 return;
             }
-            if (mIsSendingInProgress.get()) {
+            boolean isLockAcquired = mIsSendingInProgress.compareAndSet(false, true);
+            if (!isLockAcquired) {
                 logd(TAG, "Unable to send packet at this time.");
                 return;
             }
 
-            mIsSendingInProgress.set(true);
             BlePacket packet = mPacketQueue.remove();
             logd(TAG, "Writing packet " + packet.getPacketNumber() + " of "
                     + packet.getTotalPackets() + " for " + packet.getMessageId() + ".");
             mWriteCharacteristic.setValue(packet.toByteArray());
             mBlePeripheralManager.notifyCharacteristicChanged(mDevice, mWriteCharacteristic,
-                    /* confirm = */ false);
+                    /* confirm= */ false);
         }, mThrottleDelay.get());
     }
 
@@ -261,7 +264,7 @@
                 .build();
         mWriteCharacteristic.setValue(headunitVersion.toByteArray());
         mBlePeripheralManager.notifyCharacteristicChanged(device, mWriteCharacteristic,
-                /* confirm = */ false);
+                /* confirm= */ false);
         mIsVersionExchanged.set(true);
         logd(TAG, "Sent supported version to the phone.");
     }
@@ -273,6 +276,24 @@
         mThrottleDelay.set(THROTTLE_WAIT_MS);
 
         int messageId = packet.getMessageId();
+        int packetNumber = packet.getPacketNumber();
+        int expectedPacket = mPendingPacketNumber.getOrDefault(messageId, 1);
+        if (packetNumber == expectedPacket - 1) {
+            logw(TAG, "Received duplicate packet " + packet.getPacketNumber() + " for message "
+                    + messageId + ". Ignoring.");
+            return;
+        }
+        if (packetNumber != expectedPacket) {
+            loge(TAG, "Received unexpected packet " + packetNumber + " for message "
+                    + messageId + ".");
+            if (mMessageReceivedErrorListener != null) {
+                mMessageReceivedErrorListener.onMessageReceivedError(
+                        new IllegalStateException("Packet received out of order."));
+            }
+            return;
+        }
+        mPendingPacketNumber.put(messageId, packetNumber + 1);
+
         ByteArrayOutputStream currentPayloadStream =
                 mPendingData.getOrDefault(messageId, new ByteArrayOutputStream());
         mPendingData.putIfAbsent(messageId, currentPayloadStream);
@@ -325,6 +346,9 @@
 
     /** The maximum amount of bytes that can be written over BLE. */
     void setMaxWriteSize(int maxWriteSize) {
+        if (maxWriteSize <= 0) {
+            return;
+        }
         mMaxWriteSize = maxWriteSize;
     }
 
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePacketFactory.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePacketFactory.java
index a0d0bb1..24249a3 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePacketFactory.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePacketFactory.java
@@ -17,9 +17,10 @@
 
 import static com.android.car.connecteddevice.util.SafeLog.loge;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.car.connecteddevice.BleStreamProtos.BlePacketProto.BlePacket;
 import com.android.car.protobuf.ByteString;
-import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePeripheralManager.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePeripheralManager.java
index 6d50f63..a25cb0b 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePeripheralManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePeripheralManager.java
@@ -20,8 +20,6 @@
 import static com.android.car.connecteddevice.util.SafeLog.loge;
 import static com.android.car.connecteddevice.util.SafeLog.logw;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGatt;
@@ -41,6 +39,9 @@
 import android.content.pm.PackageManager;
 import android.os.Handler;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.car.connecteddevice.util.ByteUtils;
 
 import java.util.HashSet;
@@ -84,12 +85,13 @@
     private int mMtuSize = 20;
 
     private BluetoothManager mBluetoothManager;
-    private BluetoothLeAdvertiser mAdvertiser;
+    private AtomicReference<BluetoothLeAdvertiser> mAdvertiser = new AtomicReference<>();
     private int mAdvertiserStartCount;
     private int mGattServerRetryStartCount;
     private BluetoothGattService mBluetoothGattService;
     private AdvertiseCallback mAdvertiseCallback;
     private AdvertiseData mAdvertiseData;
+    private AdvertiseData mScanResponse;
 
     public BlePeripheralManager(Context context) {
         mContext = context;
@@ -168,11 +170,13 @@
      *
      * @param service           {@link BluetoothGattService} that will be discovered by clients
      * @param data              {@link AdvertiseData} data to advertise
+     * @param scanResponse      {@link AdvertiseData} scan response
      * @param advertiseCallback {@link AdvertiseCallback} callback for advertiser
      */
     void startAdvertising(
-            BluetoothGattService service, AdvertiseData data, AdvertiseCallback advertiseCallback) {
-        logd(TAG, "startAdvertising: " + service.getUuid());
+            BluetoothGattService service, AdvertiseData data,
+            AdvertiseData scanResponse, AdvertiseCallback advertiseCallback) {
+        logd(TAG, "Request to start advertising with service " + service.getUuid() + ".");
         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
             loge(TAG, "Attempted start advertising, but system does not support BLE. Ignoring.");
             return;
@@ -182,6 +186,7 @@
         mBluetoothGattService = service;
         mAdvertiseCallback = advertiseCallback;
         mAdvertiseData = data;
+        mScanResponse = scanResponse;
         mGattServerRetryStartCount = 0;
         mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
         mGattServer.set(mBluetoothManager.openGattServer(mContext, mGattServerCallback));
@@ -194,9 +199,10 @@
      * @param advertiseCallback The callback that is associated with the advertisement.
      */
     void stopAdvertising(AdvertiseCallback advertiseCallback) {
-        if (mAdvertiser != null) {
-            logd(TAG, "Stop advertising.");
-            mAdvertiser.stopAdvertising(advertiseCallback);
+        BluetoothLeAdvertiser advertiser = mAdvertiser.getAndSet(null);
+        if (advertiser != null) {
+            advertiser.stopAdvertising(advertiseCallback);
+            logd(TAG, "Advertising stopped.");
         }
     }
 
@@ -228,25 +234,25 @@
      * Cleans up the BLE GATT server state.
      */
     void cleanup() {
+        logd(TAG, "Cleaning up manager.");
         // Stops the advertiser, scanner and GATT server. This needs to be done to avoid leaks.
-        if (mAdvertiser != null) {
-            mAdvertiser.stopAdvertising(mAdvertiseCallback);
-        }
+        stopAdvertising(mAdvertiseCallback);
         // Clears all registered listeners. IHU only supports single connection in peripheral role.
         mReadListeners.clear();
         mWriteListeners.clear();
-        mAdvertiser = null;
 
         BluetoothGattServer gattServer = mGattServer.getAndSet(null);
         if (gattServer == null) {
             return;
         }
 
-        logd(TAG, "stopGattServer");
+        logd(TAG, "Stopping gatt server.");
         BluetoothGatt bluetoothGatt = mBluetoothGatt.getAndSet(null);
         if (bluetoothGatt != null) {
             gattServer.cancelConnection(bluetoothGatt.getDevice());
+            logd(TAG, "Disconnecting gatt.");
             bluetoothGatt.disconnect();
+            bluetoothGatt.close();
         }
         gattServer.clearServices();
         gattServer.close();
@@ -266,7 +272,7 @@
                             .setConnectable(true)
                             .build();
             mAdvertiserStartCount = 0;
-            startAdvertisingInternally(settings, mAdvertiseData, mAdvertiseCallback);
+            startAdvertisingInternally(settings, mAdvertiseData, mScanResponse, mAdvertiseCallback);
             mGattServerRetryStartCount = 0;
         } else if (mGattServerRetryStartCount < GATT_SERVER_RETRY_LIMIT) {
             mGattServer.set(mBluetoothManager.openGattServer(mContext, mGattServerCallback));
@@ -278,18 +284,21 @@
     }
 
     private void startAdvertisingInternally(
-            AdvertiseSettings settings, AdvertiseData data, AdvertiseCallback advertiseCallback) {
+            AdvertiseSettings settings, AdvertiseData advertisement, AdvertiseData scanResponse,
+            AdvertiseCallback advertiseCallback) {
         if (BluetoothAdapter.getDefaultAdapter() != null) {
-            mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
+            mAdvertiser.compareAndSet(null,
+                    BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser());
         }
-
-        if (mAdvertiser != null) {
+        BluetoothLeAdvertiser advertiser = mAdvertiser.get();
+        if (advertiser != null) {
             logd(TAG, "Advertiser created, retry count: " + mAdvertiserStartCount);
-            mAdvertiser.startAdvertising(settings, data, advertiseCallback);
+            advertiser.startAdvertising(settings, advertisement, scanResponse, advertiseCallback);
             mAdvertiserStartCount = 0;
         } else if (mAdvertiserStartCount < BLE_RETRY_LIMIT) {
             mHandler.postDelayed(
-                    () -> startAdvertisingInternally(settings, data, advertiseCallback),
+                    () -> startAdvertisingInternally(settings, advertisement, scanResponse,
+                            advertiseCallback),
                     BLE_RETRY_INTERVAL_MS);
             mAdvertiserStartCount += 1;
         } else {
@@ -303,14 +312,20 @@
                 @Override
                 public void onConnectionStateChange(BluetoothDevice device, int status,
                                                     int newState) {
-                    logd(TAG, "BLE Connection State Change: " + newState);
                     switch (newState) {
                         case BluetoothProfile.STATE_CONNECTED:
+                            logd(TAG, "BLE Connection State Change: CONNECTED");
+                            BluetoothGattServer gattServer = mGattServer.get();
+                            if (gattServer == null) {
+                                return;
+                            }
+                            gattServer.connect(device, /* autoConnect= */ false);
                             for (Callback callback : mCallbacks) {
                                 callback.onRemoteDeviceConnected(device);
                             }
                             break;
                         case BluetoothProfile.STATE_DISCONNECTED:
+                            logd(TAG, "BLE Connection State Change: DISCONNECTED");
                             for (Callback callback : mCallbacks) {
                                 callback.onRemoteDeviceDisconnected(device);
                             }
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleCentralManager.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleCentralManager.java
index a9168a8..21bec2b 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleCentralManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleCentralManager.java
@@ -21,7 +21,6 @@
 import static com.android.car.connecteddevice.util.SafeLog.logw;
 import static com.android.car.connecteddevice.util.ScanDataAnalyzer.containsUuidsInOverflow;
 
-import android.annotation.NonNull;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGatt;
 import android.bluetooth.BluetoothGattCallback;
@@ -36,6 +35,8 @@
 import android.content.Context;
 import android.os.ParcelUuid;
 
+import androidx.annotation.NonNull;
+
 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
 
 import java.math.BigInteger;
@@ -110,7 +111,7 @@
     @Override
     public void start() {
         super.start();
-        mBleCentralManager.startScanning(/* filters = */ null, mScanSettings, mScanCallback);
+        mBleCentralManager.startScanning(/* filters= */ null, mScanSettings, mScanCallback);
     }
 
     @Override
@@ -199,7 +200,7 @@
     }
 
     private void startDeviceConnection(@NonNull BluetoothDevice device) {
-        BluetoothGatt gatt = device.connectGatt(mContext, /* autoConnect = */ false,
+        BluetoothGatt gatt = device.connectGatt(mContext, /* autoConnect= */ false,
                 mConnectionCallback, BluetoothDevice.TRANSPORT_LE);
         if (gatt == null) {
             return;
@@ -322,7 +323,7 @@
                 return;
             }
 
-            if (!gatt.setCharacteristicNotification(readCharacteristic, /* enable = */ true)) {
+            if (!gatt.setCharacteristicNotification(readCharacteristic, /* enable= */ true)) {
                 loge(TAG, "Set notifications to read characteristic failed.");
                 gatt.disconnect();
                 return;
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleManager.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleManager.java
index 0b05906..f63d591 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleManager.java
@@ -19,11 +19,12 @@
 import static com.android.car.connecteddevice.util.SafeLog.logd;
 import static com.android.car.connecteddevice.util.SafeLog.logw;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGatt;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
 import com.android.car.connecteddevice.util.ThreadSafeCallbacks;
 
@@ -111,13 +112,7 @@
         }
 
         logd(TAG, "Writing " + message.getMessage().length + " bytes to " + deviceId + ".");
-
-
-        if (message.isMessageEncrypted()) {
-            device.mSecureChannel.sendEncryptedMessage(message);
-        } else {
-            device.mSecureChannel.getStream().writeMessage(message);
-        }
+        device.mSecureChannel.sendClientMessage(message);
     }
 
     /**
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBlePeripheralManager.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBlePeripheralManager.java
index 6f279dd..3819f0d 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBlePeripheralManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBlePeripheralManager.java
@@ -16,13 +16,12 @@
 
 package com.android.car.connecteddevice.ble;
 
+import static com.android.car.connecteddevice.ConnectedDeviceManager.DEVICE_ERROR_INVALID_HANDSHAKE;
 import static com.android.car.connecteddevice.ConnectedDeviceManager.DEVICE_ERROR_UNEXPECTED_DISCONNECTION;
 import static com.android.car.connecteddevice.util.SafeLog.logd;
 import static com.android.car.connecteddevice.util.SafeLog.loge;
 import static com.android.car.connecteddevice.util.SafeLog.logw;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGattCharacteristic;
@@ -31,22 +30,23 @@
 import android.bluetooth.le.AdvertiseCallback;
 import android.bluetooth.le.AdvertiseData;
 import android.bluetooth.le.AdvertiseSettings;
-import android.car.encryptionrunner.EncryptionRunnerFactory;
 import android.os.Handler;
-import android.os.Looper;
+import android.os.HandlerThread;
 import android.os.ParcelUuid;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.car.connecteddevice.AssociationCallback;
 import com.android.car.connecteddevice.model.AssociatedDevice;
 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
+import com.android.car.connecteddevice.util.ByteUtils;
 import com.android.car.connecteddevice.util.EventLog;
-import com.android.internal.annotations.VisibleForTesting;
 
+import java.time.Duration;
+import java.util.Arrays;
 import java.util.UUID;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Communication manager that allows for targeted connections to a specific device in the car.
@@ -59,40 +59,50 @@
     // bytes.
     private static final int ATT_PROTOCOL_BYTES = 3;
 
-    // Arbitrary delay time for a retry of association advertising if bluetooth adapter name change
-    // fails.
-    private static final long ASSOCIATE_ADVERTISING_DELAY_MS = 10L;
-
     private static final UUID CLIENT_CHARACTERISTIC_CONFIG =
             UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
 
+    private static final int SALT_BYTES = 8;
+
+    private static final int TOTAL_AD_DATA_BYTES = 16;
+
+    private static final int TRUNCATED_BYTES = 3;
+
+    private static final String TIMEOUT_HANDLER_THREAD_NAME = "peripheralThread";
+
     private final BluetoothGattDescriptor mDescriptor =
             new BluetoothGattDescriptor(CLIENT_CHARACTERISTIC_CONFIG,
                     BluetoothGattDescriptor.PERMISSION_READ
                             | BluetoothGattDescriptor.PERMISSION_WRITE);
 
-    private final ScheduledExecutorService mScheduler =
-            Executors.newSingleThreadScheduledExecutor();
-
     private final BlePeripheralManager mBlePeripheralManager;
 
     private final UUID mAssociationServiceUuid;
 
+    private final UUID mReconnectServiceUuid;
+
+    private final UUID mReconnectDataUuid;
+
     private final BluetoothGattCharacteristic mWriteCharacteristic;
 
     private final BluetoothGattCharacteristic mReadCharacteristic;
 
-    private final Handler mTimeoutHandler;
+    private HandlerThread mTimeoutHandlerThread;
 
-    // BLE default is 23, minus 3 bytes for ATT_PROTOCOL.
-    private int mWriteSize = 20;
+    private Handler mTimeoutHandler;
 
-    private String mOriginalBluetoothName;
+    private final Duration mMaxReconnectAdvertisementDuration;
+
+    private final int mDefaultMtuSize;
 
     private String mClientDeviceName;
 
     private String mClientDeviceAddress;
 
+    private String mReconnectDeviceId;
+
+    private byte[] mReconnectChallenge;
+
     private AssociationCallback mAssociationCallback;
 
     private AdvertiseCallback mAdvertiseCallback;
@@ -100,19 +110,31 @@
     /**
      * Initialize a new instance of manager.
      *
-     * @param blePeripheralManager {@link BlePeripheralManager} for establishing connection.
-     * @param connectedDeviceStorage Shared {@link ConnectedDeviceStorage} for companion features.
-     * @param associationServiceUuid {@link UUID} of association service.
+     * @param blePeripheralManager    {@link BlePeripheralManager} for establishing connection.
+     * @param connectedDeviceStorage  Shared {@link ConnectedDeviceStorage} for companion features.
+     * @param associationServiceUuid  {@link UUID} of association service.
+     * @param reconnectServiceUuid    {@link UUID} of reconnect service.
+     * @param reconnectDataUuid       {@link UUID} key of reconnect advertisement data.
      * @param writeCharacteristicUuid {@link UUID} of characteristic the car will write to.
-     * @param readCharacteristicUuid {@link UUID} of characteristic the device will write to.
+     * @param readCharacteristicUuid  {@link UUID} of characteristic the device will write to.
+     * @param maxReconnectAdvertisementDuration Maximum duration to advertise for reconnect before
+     *                                          restarting.
+     * @param defaultMtuSize          Default MTU size for new channels.
      */
     public CarBlePeripheralManager(@NonNull BlePeripheralManager blePeripheralManager,
             @NonNull ConnectedDeviceStorage connectedDeviceStorage,
-            @NonNull UUID associationServiceUuid, @NonNull UUID writeCharacteristicUuid,
-            @NonNull UUID readCharacteristicUuid) {
+            @NonNull UUID associationServiceUuid,
+            @NonNull UUID reconnectServiceUuid,
+            @NonNull UUID reconnectDataUuid,
+            @NonNull UUID writeCharacteristicUuid,
+            @NonNull UUID readCharacteristicUuid,
+            @NonNull Duration maxReconnectAdvertisementDuration,
+            int defaultMtuSize) {
         super(connectedDeviceStorage);
         mBlePeripheralManager = blePeripheralManager;
         mAssociationServiceUuid = associationServiceUuid;
+        mReconnectServiceUuid = reconnectServiceUuid;
+        mReconnectDataUuid = reconnectDataUuid;
         mDescriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
         mWriteCharacteristic = new BluetoothGattCharacteristic(writeCharacteristicUuid,
                 BluetoothGattCharacteristic.PROPERTY_NOTIFY,
@@ -122,41 +144,32 @@
                         | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
                 BluetoothGattCharacteristic.PERMISSION_WRITE);
         mReadCharacteristic.addDescriptor(mDescriptor);
-        mTimeoutHandler = new Handler(Looper.getMainLooper());
+        mMaxReconnectAdvertisementDuration = maxReconnectAdvertisementDuration;
+        mDefaultMtuSize = defaultMtuSize;
     }
 
     @Override
     public void start() {
         super.start();
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        if (adapter == null) {
-            return;
-        }
-        String originalBluetoothName = mStorage.getStoredBluetoothName();
-        if (originalBluetoothName == null) {
-            return;
-        }
-        if (originalBluetoothName.equals(adapter.getName())) {
-            mStorage.removeStoredBluetoothName();
-            return;
-        }
-
-        logw(TAG, "Discovered mismatch in bluetooth adapter name. Resetting back to "
-                + originalBluetoothName + ".");
-        adapter.setName(originalBluetoothName);
-        mScheduler.schedule(
-                () -> verifyBluetoothNameRestored(originalBluetoothName),
-                ASSOCIATE_ADVERTISING_DELAY_MS, TimeUnit.MILLISECONDS);
+        mTimeoutHandlerThread = new HandlerThread(TIMEOUT_HANDLER_THREAD_NAME);
+        mTimeoutHandlerThread.start();
+        mTimeoutHandler = new Handler(mTimeoutHandlerThread.getLooper());
     }
 
     @Override
     public void stop() {
         super.stop();
+        mTimeoutHandlerThread.quit();
         reset();
     }
 
     @Override
     public void disconnectDevice(@NonNull String deviceId) {
+        if (deviceId.equals(mReconnectDeviceId)) {
+            logd(TAG, "Reconnection canceled for device " + deviceId + ".");
+            reset();
+            return;
+        }
         BleDevice connectedDevice = getConnectedDevice();
         if (connectedDevice == null || !deviceId.equals(connectedDevice.mDeviceId)) {
             return;
@@ -165,16 +178,17 @@
     }
 
     private void reset() {
-        resetBluetoothAdapterName();
         mClientDeviceAddress = null;
         mClientDeviceName = null;
         mAssociationCallback = null;
         mBlePeripheralManager.cleanup();
         mConnectedDevices.clear();
+        mReconnectDeviceId = null;
+        mReconnectChallenge = null;
     }
 
-    /** Attempt to connect to device with provided id within set timeout period. */
-    public void connectToDevice(@NonNull UUID deviceId, int timeoutSeconds) {
+    /** Attempt to connect to device with provided id. */
+    public void connectToDevice(@NonNull UUID deviceId) {
         for (BleDevice device : mConnectedDevices) {
             if (UUID.fromString(device.mDeviceId).equals(deviceId)) {
                 logd(TAG, "Already connected to device " + deviceId + ".");
@@ -185,21 +199,51 @@
 
         // Clear any previous session before starting a new one.
         reset();
-
+        mReconnectDeviceId = deviceId.toString();
         mAdvertiseCallback = new AdvertiseCallback() {
             @Override
             public void onStartSuccess(AdvertiseSettings settingsInEffect) {
                 super.onStartSuccess(settingsInEffect);
                 mTimeoutHandler.postDelayed(mTimeoutRunnable,
-                        TimeUnit.SECONDS.toMillis(timeoutSeconds));
-                logd(TAG, "Successfully started advertising for device " + deviceId
-                        + " for " + timeoutSeconds + " seconds.");
+                        mMaxReconnectAdvertisementDuration.toMillis());
+                logd(TAG, "Successfully started advertising for device " + deviceId + ".");
             }
         };
         mBlePeripheralManager.unregisterCallback(mAssociationPeripheralCallback);
         mBlePeripheralManager.registerCallback(mReconnectPeripheralCallback);
         mTimeoutHandler.removeCallbacks(mTimeoutRunnable);
-        startAdvertising(deviceId, mAdvertiseCallback, /* includeDeviceName = */ false);
+        byte[] advertiseData = createReconnectData(mReconnectDeviceId);
+        if (advertiseData == null) {
+            loge(TAG, "Unable to create advertisement data. Aborting reconnect.");
+            return;
+        }
+        startAdvertising(mReconnectServiceUuid, mAdvertiseCallback, advertiseData,
+                mReconnectDataUuid, /* scanResponse= */null, /* scanResponseUuid */ null);
+    }
+
+    /**
+     * Create data for reconnection advertisement.
+     *
+     * <p></p><p>Process:</p>
+     * <ol>
+     * <li>Generate random {@value SALT_BYTES} byte salt and zero-pad to
+     * {@value TOTAL_AD_DATA_BYTES} bytes.
+     * <li>Hash with stored challenge secret and truncate to {@value TRUNCATED_BYTES} bytes.
+     * <li>Concatenate hashed {@value TRUNCATED_BYTES} bytes with salt and return.
+     * </ol>
+     */
+    @Nullable
+    private byte[] createReconnectData(String deviceId) {
+        byte[] salt = ByteUtils.randomBytes(SALT_BYTES);
+        byte[] zeroPadded = ByteUtils.concatByteArrays(salt,
+                new byte[TOTAL_AD_DATA_BYTES - SALT_BYTES]);
+        mReconnectChallenge = mStorage.hashWithChallengeSecret(deviceId, zeroPadded);
+        if (mReconnectChallenge == null) {
+            return null;
+        }
+        return ByteUtils.concatByteArrays(Arrays.copyOf(mReconnectChallenge, TRUNCATED_BYTES),
+                salt);
+
     }
 
     @Nullable
@@ -221,13 +265,6 @@
 
         reset();
         mAssociationCallback = callback;
-        if (mOriginalBluetoothName == null) {
-            mOriginalBluetoothName = adapter.getName();
-            mStorage.storeBluetoothName(mOriginalBluetoothName);
-        }
-        adapter.setName(nameForAssociation);
-        logd(TAG, "Changing bluetooth adapter name from " + mOriginalBluetoothName + " to "
-                + nameForAssociation + ".");
         mBlePeripheralManager.unregisterCallback(mReconnectPeripheralCallback);
         mBlePeripheralManager.registerCallback(mAssociationPeripheralCallback);
         mAdvertiseCallback = new AdvertiseCallback() {
@@ -245,7 +282,8 @@
                 logd(TAG, "Failed to start advertising for association. Error code: " + errorCode);
             }
         };
-        attemptAssociationAdvertising(nameForAssociation, callback);
+        startAdvertising(mAssociationServiceUuid, mAdvertiseCallback, /* advertiseData= */ null,
+                /* advertiseDataUuid*/ null, nameForAssociation.getBytes(), mReconnectDataUuid);
     }
 
     /** Stop the association with any device. */
@@ -256,39 +294,33 @@
         reset();
     }
 
-    private void attemptAssociationAdvertising(@NonNull String adapterName,
-            @NonNull AssociationCallback callback) {
-        if (mOriginalBluetoothName != null
-                && adapterName.equals(BluetoothAdapter.getDefaultAdapter().getName())) {
-            startAdvertising(mAssociationServiceUuid, mAdvertiseCallback,
-                    /* includeDeviceName = */ true);
-            return;
-        }
-
-        ScheduledFuture future = mScheduler.schedule(
-                () -> attemptAssociationAdvertising(adapterName, callback),
-                ASSOCIATE_ADVERTISING_DELAY_MS, TimeUnit.MILLISECONDS);
-        if (future.isCancelled()) {
-            // Association failed to start.
-            callback.onAssociationStartFailure();
-            return;
-        }
-        logd(TAG, "Adapter name change has not taken affect prior to advertising attempt. Trying "
-                + "again in " + ASSOCIATE_ADVERTISING_DELAY_MS + "  milliseconds.");
-    }
-
     private void startAdvertising(@NonNull UUID serviceUuid, @NonNull AdvertiseCallback callback,
-            boolean includeDeviceName) {
+            @Nullable byte[] advertiseData, @Nullable UUID advertiseDataUuid,
+            @Nullable byte[] scanResponse, @Nullable UUID scanResponseUuid) {
         BluetoothGattService gattService = new BluetoothGattService(serviceUuid,
                 BluetoothGattService.SERVICE_TYPE_PRIMARY);
         gattService.addCharacteristic(mWriteCharacteristic);
         gattService.addCharacteristic(mReadCharacteristic);
 
-        AdvertiseData advertiseData = new AdvertiseData.Builder()
-                .setIncludeDeviceName(includeDeviceName)
-                .addServiceUuid(new ParcelUuid(serviceUuid))
-                .build();
-        mBlePeripheralManager.startAdvertising(gattService, advertiseData, callback);
+        AdvertiseData.Builder advertisementBuilder = new AdvertiseData.Builder();
+        ParcelUuid uuid = new ParcelUuid(serviceUuid);
+        advertisementBuilder.addServiceUuid(uuid);
+        if (advertiseData != null) {
+            ParcelUuid dataUuid = uuid;
+            if (advertiseDataUuid != null) {
+                dataUuid = new ParcelUuid(advertiseDataUuid);
+            }
+            advertisementBuilder.addServiceData(dataUuid, advertiseData);
+        }
+
+        AdvertiseData.Builder scanResponseBuilder = new AdvertiseData.Builder();
+        if (scanResponse != null && scanResponseUuid != null) {
+            ParcelUuid scanResponseParcelUuid = new ParcelUuid(scanResponseUuid);
+            scanResponseBuilder.addServiceData(scanResponseParcelUuid, scanResponse);
+        }
+
+        mBlePeripheralManager.startAdvertising(gattService, advertisementBuilder.build(),
+                scanResponseBuilder.build(), callback);
     }
 
     /** Notify that the user has accepted a pairing code or other out-of-band confirmation. */
@@ -299,7 +331,8 @@
             return;
         }
 
-        SecureBleChannel secureChannel = getConnectedDevice().mSecureChannel;
+        AssociationSecureChannel secureChannel =
+                (AssociationSecureChannel) getConnectedDevice().mSecureChannel;
         if (secureChannel == null) {
             disconnectWithError("Null SecureBleChannel found for the current connected device "
                     + "when out-of-band confirmation received.");
@@ -334,33 +367,12 @@
 
     private void disconnectWithError(@NonNull String errorMessage) {
         loge(TAG, errorMessage);
+        if (isAssociating()) {
+            mAssociationCallback.onAssociationError(DEVICE_ERROR_INVALID_HANDSHAKE);
+        }
         reset();
     }
 
-    private void resetBluetoothAdapterName() {
-        if (mOriginalBluetoothName == null) {
-            return;
-        }
-        logd(TAG, "Changing bluetooth adapter name back to " + mOriginalBluetoothName + ".");
-        BluetoothAdapter.getDefaultAdapter().setName(mOriginalBluetoothName);
-        mOriginalBluetoothName = null;
-    }
-
-    private void verifyBluetoothNameRestored(@NonNull String expectedName) {
-        String currentName = BluetoothAdapter.getDefaultAdapter().getName();
-        if (expectedName.equals(currentName)) {
-            logd(TAG, "Bluetooth adapter name restoration completed successfully. Removing stored "
-                    + "adapter name.");
-            mStorage.removeStoredBluetoothName();
-            return;
-        }
-        logd(TAG, "Bluetooth adapter name restoration has not taken affect yet. Checking again in "
-                + ASSOCIATE_ADVERTISING_DELAY_MS + " milliseconds.");
-        mScheduler.schedule(
-                () -> verifyBluetoothNameRestored(expectedName),
-                ASSOCIATE_ADVERTISING_DELAY_MS, TimeUnit.MILLISECONDS);
-    }
-
     private void addConnectedDevice(BluetoothDevice device, boolean isReconnect) {
         EventLog.onDeviceConnected();
         mBlePeripheralManager.stopAdvertising(mAdvertiseCallback);
@@ -374,23 +386,37 @@
         }
 
         BleDeviceMessageStream secureStream = new BleDeviceMessageStream(mBlePeripheralManager,
-                device, mWriteCharacteristic, mReadCharacteristic);
-        secureStream.setMaxWriteSize(mWriteSize);
-        SecureBleChannel secureChannel = new SecureBleChannel(secureStream, mStorage, isReconnect,
-                EncryptionRunnerFactory.newRunner());
+                device, mWriteCharacteristic, mReadCharacteristic,
+                mDefaultMtuSize - ATT_PROTOCOL_BYTES);
+        secureStream.setMessageReceivedErrorListener(
+                exception -> {
+                    disconnectWithError("Error occurred in stream: " + exception.getMessage());
+                });
+        SecureBleChannel secureChannel;
+        if (isReconnect) {
+            secureChannel = new ReconnectSecureChannel(secureStream, mStorage, mReconnectDeviceId,
+                    mReconnectChallenge);
+        } else {
+            secureChannel = new AssociationSecureChannel(secureStream, mStorage);
+        }
         secureChannel.registerCallback(mSecureChannelCallback);
-        BleDevice bleDevice = new BleDevice(device, /* gatt = */ null);
+        BleDevice bleDevice = new BleDevice(device, /* gatt= */ null);
         bleDevice.mSecureChannel = secureChannel;
         addConnectedDevice(bleDevice);
+        if (isReconnect) {
+            setDeviceId(mReconnectDeviceId);
+            mReconnectDeviceId = null;
+            mReconnectChallenge = null;
+        }
     }
 
     private void setMtuSize(int mtuSize) {
-        mWriteSize = mtuSize - ATT_PROTOCOL_BYTES;
         BleDevice connectedDevice = getConnectedDevice();
         if (connectedDevice != null
                 && connectedDevice.mSecureChannel != null
                 && connectedDevice.mSecureChannel.getStream() != null) {
-            connectedDevice.mSecureChannel.getStream().setMaxWriteSize(mWriteSize);
+            connectedDevice.mSecureChannel.getStream()
+                    .setMaxWriteSize(mtuSize - ATT_PROTOCOL_BYTES);
         }
     }
 
@@ -418,7 +444,7 @@
 
                 @Override
                 public void onRemoteDeviceDisconnected(BluetoothDevice device) {
-                    String deviceId = null;
+                    String deviceId = mReconnectDeviceId;
                     BleDevice connectedDevice = getConnectedDevice(device);
                     // Reset before invoking callbacks to avoid a race condition with reconnect
                     // logic.
@@ -427,10 +453,13 @@
                         deviceId = connectedDevice.mDeviceId;
                     }
                     final String finalDeviceId = deviceId;
-                    if (finalDeviceId != null) {
-                        logd(TAG, "Connected device " + finalDeviceId + " disconnected.");
-                        mCallbacks.invoke(callback -> callback.onDeviceDisconnected(finalDeviceId));
+                    if (finalDeviceId == null) {
+                        logw(TAG, "Callbacks were not issued for disconnect because the device id "
+                                + "was null.");
+                        return;
                     }
+                    logd(TAG, "Connected device " + finalDeviceId + " disconnected.");
+                    mCallbacks.invoke(callback -> callback.onDeviceDisconnected(finalDeviceId));
                 }
             };
 
@@ -456,24 +485,25 @@
 
                 @Override
                 public void onRemoteDeviceConnected(BluetoothDevice device) {
-                    resetBluetoothAdapterName();
-                    addConnectedDevice(device, /* isReconnect = */ false);
+                    addConnectedDevice(device, /* isReconnect= */ false);
                     BleDevice connectedDevice = getConnectedDevice();
                     if (connectedDevice == null || connectedDevice.mSecureChannel == null) {
                         return;
                     }
-                    connectedDevice.mSecureChannel.setShowVerificationCodeListener(
-                            code -> {
-                                if (!isAssociating()) {
-                                    loge(TAG, "No valid callback for association.");
-                                    return;
-                                }
-                                mAssociationCallback.onVerificationCodeAvailable(code);
-                            });
+                    ((AssociationSecureChannel) connectedDevice.mSecureChannel)
+                            .setShowVerificationCodeListener(
+                                    code -> {
+                                        if (!isAssociating()) {
+                                            loge(TAG, "No valid callback for association.");
+                                            return;
+                                        }
+                                        mAssociationCallback.onVerificationCodeAvailable(code);
+                                    });
                 }
 
                 @Override
                 public void onRemoteDeviceDisconnected(BluetoothDevice device) {
+                    logd(TAG, "Remote device disconnected.");
                     BleDevice connectedDevice = getConnectedDevice(device);
                     if (isAssociating()) {
                         mAssociationCallback.onAssociationError(
@@ -482,10 +512,12 @@
                     // Reset before invoking callbacks to avoid a race condition with reconnect
                     // logic.
                     reset();
-                    if (connectedDevice != null && connectedDevice.mDeviceId != null) {
-                        mCallbacks.invoke(callback -> callback.onDeviceDisconnected(
-                                connectedDevice.mDeviceId));
+                    if (connectedDevice == null || connectedDevice.mDeviceId == null) {
+                        logw(TAG, "Callbacks were not issued for disconnect.");
+                        return;
                     }
+                    mCallbacks.invoke(callback -> callback.onDeviceDisconnected(
+                            connectedDevice.mDeviceId));
                 }
             };
 
@@ -510,7 +542,7 @@
                                 + "association of that device for current user.");
                         mStorage.addAssociatedDeviceForActiveUser(
                                 new AssociatedDevice(deviceId, mClientDeviceAddress,
-                                        mClientDeviceName, /* isConnectionEnabled = */ true));
+                                        mClientDeviceName, /* isConnectionEnabled= */ true));
                         if (mAssociationCallback != null) {
                             mAssociationCallback.onAssociationCompleted(deviceId);
                             mAssociationCallback = null;
@@ -532,8 +564,9 @@
 
                     if (isAssociating()) {
                         mAssociationCallback.onAssociationError(error);
-                        disconnectWithError("Error while establishing secure connection.");
                     }
+
+                    disconnectWithError("Error while establishing secure connection.");
                 }
 
                 @Override
@@ -548,7 +581,7 @@
                             + " with " + deviceMessage.getMessage().length + " bytes in its "
                             + "payload. Notifying " + mCallbacks.size() + " callbacks.");
                     mCallbacks.invoke(
-                            callback ->callback.onMessageReceived(connectedDevice.mDeviceId,
+                            callback -> callback.onMessageReceived(connectedDevice.mDeviceId,
                                     deviceMessage));
                 }
 
@@ -556,10 +589,15 @@
                 public void onMessageReceivedError(Exception exception) {
                     // TODO(b/143879960) Extend the message error from here to continue up the
                     // chain.
+                    disconnectWithError("Error while receiving message.");
                 }
 
                 @Override
                 public void onDeviceIdReceived(String deviceId) {
+                    if (deviceId == null) {
+                        loge(TAG, "Received a null device id. Ignoring.");
+                        return;
+                    }
                     setDeviceId(deviceId);
                 }
             };
@@ -567,8 +605,9 @@
     private final Runnable mTimeoutRunnable = new Runnable() {
         @Override
         public void run() {
-            logd(TAG, "Timeout period expired without a connection. Stopping advertisement.");
+            logd(TAG, "Timeout period expired without a connection. Restarting advertisement.");
             mBlePeripheralManager.stopAdvertising(mAdvertiseCallback);
+            connectToDevice(UUID.fromString(mReconnectDeviceId));
         }
     };
 }
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/DeviceMessage.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/DeviceMessage.java
index 9d3ac48..a746864 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/DeviceMessage.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/DeviceMessage.java
@@ -18,8 +18,8 @@
 
 import static com.android.car.connecteddevice.BleStreamProtos.BleDeviceMessageProto.BleDeviceMessage;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import java.util.Arrays;
 import java.util.Objects;
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/ReconnectSecureChannel.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/ReconnectSecureChannel.java
new file mode 100644
index 0000000..60eff1c
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/ReconnectSecureChannel.java
@@ -0,0 +1,177 @@
+/*
+ * 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.connecteddevice.ble;
+
+import static com.android.car.connecteddevice.util.SafeLog.logd;
+import static com.android.car.connecteddevice.util.SafeLog.loge;
+
+import android.car.encryptionrunner.EncryptionRunner;
+import android.car.encryptionrunner.EncryptionRunnerFactory;
+import android.car.encryptionrunner.HandshakeException;
+import android.car.encryptionrunner.HandshakeMessage;
+import android.car.encryptionrunner.HandshakeMessage.HandshakeState;
+import android.car.encryptionrunner.Key;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
+import com.android.car.connecteddevice.util.ByteUtils;
+
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A secure channel established with the reconnection flow.
+ */
+class ReconnectSecureChannel extends SecureBleChannel {
+
+    private static final String TAG = "ReconnectSecureChannel";
+
+    private final ConnectedDeviceStorage mStorage;
+
+    private final String mDeviceId;
+
+    private final byte[] mExpectedChallengeResponse;
+
+    @HandshakeState
+    private int mState = HandshakeState.UNKNOWN;
+
+    private AtomicBoolean mHasVerifiedDevice = new AtomicBoolean(false);
+
+    /**
+     * Create a new secure reconnection channel.
+     *
+     * @param stream The {@link BleDeviceMessageStream} for communication with the device.
+     * @param storage {@link ConnectedDeviceStorage} for secure storage.
+     * @param deviceId Id of the device being reconnected.
+     * @param expectedChallengeResponse Expected response to challenge issued in reconnect.
+     */
+    ReconnectSecureChannel(@NonNull BleDeviceMessageStream stream,
+            @NonNull ConnectedDeviceStorage storage, @NonNull String deviceId,
+            @NonNull byte[] expectedChallengeResponse) {
+        super(stream, newReconnectRunner());
+        mStorage = storage;
+        mDeviceId = deviceId;
+        mExpectedChallengeResponse = expectedChallengeResponse;
+    }
+
+    private static EncryptionRunner newReconnectRunner() {
+        EncryptionRunner encryptionRunner = EncryptionRunnerFactory.newRunner();
+        encryptionRunner.setIsReconnect(true);
+        return encryptionRunner;
+    }
+
+    @Override
+    void processHandshake(byte[] message) throws HandshakeException {
+        switch (mState) {
+            case HandshakeState.UNKNOWN:
+                if (!mHasVerifiedDevice.get()) {
+                    processHandshakeDeviceVerification(message);
+                } else {
+                    processHandshakeInitialization(message);
+                }
+                break;
+            case HandshakeState.IN_PROGRESS:
+                processHandshakeInProgress(message);
+                break;
+            case HandshakeState.RESUMING_SESSION:
+                processHandshakeResumingSession(message);
+                break;
+            default:
+                loge(TAG, "Encountered unexpected handshake state: " + mState + ".");
+                notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE);
+        }
+    }
+
+    private void processHandshakeDeviceVerification(byte[] message) {
+        byte[] challengeResponse = Arrays.copyOf(message,
+                mExpectedChallengeResponse.length);
+        byte[] deviceChallenge = Arrays.copyOfRange(message,
+                mExpectedChallengeResponse.length, message.length);
+        if (!Arrays.equals(mExpectedChallengeResponse, challengeResponse)) {
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_ENCRYPTION_KEY);
+            return;
+        }
+        logd(TAG, "Responding to challenge " + ByteUtils.byteArrayToHexString(deviceChallenge)
+                + ".");
+        byte[] deviceChallengeResponse = mStorage.hashWithChallengeSecret(mDeviceId,
+                deviceChallenge);
+        if (deviceChallengeResponse == null) {
+            notifySecureChannelFailure(CHANNEL_ERROR_STORAGE_ERROR);
+        }
+        sendHandshakeMessage(deviceChallengeResponse, /* isEncrypted= */ false);
+        mHasVerifiedDevice.set(true);
+    }
+
+    private void processHandshakeInitialization(byte[] message) throws HandshakeException {
+        logd(TAG, "Responding to handshake init request.");
+        HandshakeMessage handshakeMessage = getEncryptionRunner().respondToInitRequest(message);
+        mState = handshakeMessage.getHandshakeState();
+        sendHandshakeMessage(handshakeMessage.getNextMessage(), /* isEncrypted= */ false);
+    }
+
+    private void processHandshakeInProgress(@NonNull byte[] message) throws HandshakeException {
+        logd(TAG, "Continuing handshake.");
+        HandshakeMessage handshakeMessage = getEncryptionRunner().continueHandshake(message);
+        mState = handshakeMessage.getHandshakeState();
+    }
+
+    private void processHandshakeResumingSession(@NonNull byte[] message)
+            throws HandshakeException {
+        logd(TAG, "Start reconnection authentication.");
+
+        byte[] previousKey = mStorage.getEncryptionKey(mDeviceId);
+        if (previousKey == null) {
+            loge(TAG, "Unable to resume session, previous key is null.");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_ENCRYPTION_KEY);
+            return;
+        }
+
+        HandshakeMessage handshakeMessage = getEncryptionRunner().authenticateReconnection(message,
+                previousKey);
+        mState = handshakeMessage.getHandshakeState();
+        if (mState != HandshakeState.FINISHED) {
+            loge(TAG, "Unable to resume session, unexpected next handshake state: " + mState + ".");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE);
+            return;
+        }
+
+        Key newKey = handshakeMessage.getKey();
+        if (newKey == null) {
+            loge(TAG, "Unable to resume session, new key is null.");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_ENCRYPTION_KEY);
+            return;
+        }
+
+        logd(TAG, "Saved new key for reconnection.");
+        mStorage.saveEncryptionKey(mDeviceId, newKey.asBytes());
+        setEncryptionKey(newKey);
+        sendServerAuthToClient(handshakeMessage.getNextMessage());
+        notifyCallback(Callback::onSecureChannelEstablished);
+    }
+
+    private void sendServerAuthToClient(@Nullable byte[] message) {
+        if (message == null) {
+            loge(TAG, "Unable to send server authentication message to client, message is null.");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_MSG);
+            return;
+        }
+
+        sendHandshakeMessage(message, /* isEncrypted= */ false);
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/SecureBleChannel.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/SecureBleChannel.java
index a821186..c0012a0 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/SecureBleChannel.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/SecureBleChannel.java
@@ -19,25 +19,20 @@
 import static com.android.car.connecteddevice.util.SafeLog.logd;
 import static com.android.car.connecteddevice.util.SafeLog.loge;
 
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.car.encryptionrunner.EncryptionRunner;
-import android.car.encryptionrunner.EncryptionRunnerFactory;
 import android.car.encryptionrunner.HandshakeException;
-import android.car.encryptionrunner.HandshakeMessage;
-import android.car.encryptionrunner.HandshakeMessage.HandshakeState;
 import android.car.encryptionrunner.Key;
 
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.car.connecteddevice.BleStreamProtos.BleOperationProto.OperationType;
-import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
-import com.android.car.connecteddevice.util.ByteUtils;
-import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.SignatureException;
-import java.util.UUID;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 
@@ -45,20 +40,20 @@
  * Establishes a secure channel with {@link EncryptionRunner} over {@link BleDeviceMessageStream} as
  * server side, sends and receives messages securely after the secure channel has been established.
  */
-class SecureBleChannel {
+abstract class SecureBleChannel {
+
+    private static final String TAG = "SecureBleChannel";
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "CHANNEL_ERROR" },
-            value = {
-                    CHANNEL_ERROR_INVALID_HANDSHAKE,
-                    CHANNEL_ERROR_INVALID_MSG,
-                    CHANNEL_ERROR_INVALID_DEVICE_ID,
-                    CHANNEL_ERROR_INVALID_VERIFICATION,
-                    CHANNEL_ERROR_INVALID_STATE,
-                    CHANNEL_ERROR_INVALID_ENCRYPTION_KEY,
-                    CHANNEL_ERROR_STORAGE_ERROR
-            }
-    )
+    @IntDef({
+            CHANNEL_ERROR_INVALID_HANDSHAKE,
+            CHANNEL_ERROR_INVALID_MSG,
+            CHANNEL_ERROR_INVALID_DEVICE_ID,
+            CHANNEL_ERROR_INVALID_VERIFICATION,
+            CHANNEL_ERROR_INVALID_STATE,
+            CHANNEL_ERROR_INVALID_ENCRYPTION_KEY,
+            CHANNEL_ERROR_STORAGE_ERROR
+    })
     @interface ChannelError { }
 
     /** Indicates an error during a Handshake of EncryptionRunner. */
@@ -71,211 +66,67 @@
     static final int CHANNEL_ERROR_INVALID_VERIFICATION = 3;
     /** Encountered an unexpected handshake state. */
     static final int CHANNEL_ERROR_INVALID_STATE = 4;
-    /** Failed to get a valid previous/new encryption key.*/
+    /** Failed to get a valid previous/new encryption key. */
     static final int CHANNEL_ERROR_INVALID_ENCRYPTION_KEY = 5;
-    /** Failed to save the encryption key*/
+    /** Failed to save or retrieve security keys. */
     static final int CHANNEL_ERROR_STORAGE_ERROR = 6;
 
-    @VisibleForTesting
-    static final byte[] CONFIRMATION_SIGNAL = "True".getBytes();
-
-    private static final String TAG = "SecureBleChannel";
 
     private final BleDeviceMessageStream mStream;
 
-    private final ConnectedDeviceStorage mStorage;
-
-    private final boolean mIsReconnect;
-
     private final EncryptionRunner mEncryptionRunner;
 
     private final AtomicReference<Key> mEncryptionKey = new AtomicReference<>();
 
-    private @HandshakeState int mState = HandshakeState.UNKNOWN;
-
-    private String mDeviceId;
-
     private Callback mCallback;
 
-    private ShowVerificationCodeListener mShowVerificationCodeListener;
-
     SecureBleChannel(@NonNull BleDeviceMessageStream stream,
-            @NonNull ConnectedDeviceStorage storage) {
-        this(stream, storage, /* isReconnect = */ true, EncryptionRunnerFactory.newRunner());
-    }
-
-    SecureBleChannel(@NonNull BleDeviceMessageStream stream,
-            @NonNull ConnectedDeviceStorage storage, boolean isReconnect,
             @NonNull EncryptionRunner encryptionRunner) {
         mStream = stream;
-        mStorage = storage;
-        mIsReconnect = isReconnect;
         mEncryptionRunner = encryptionRunner;
-        mEncryptionRunner.setIsReconnect(isReconnect);
-        mStream.setMessageReceivedListener(mStreamListener);
+        mStream.setMessageReceivedListener(this::onMessageReceived);
     }
 
-    private void processHandshake(@NonNull byte[] message) throws HandshakeException {
-        switch (mState) {
-            case HandshakeState.UNKNOWN:
-                processHandshakeUnknown(message);
-                break;
-            case HandshakeState.IN_PROGRESS:
-                processHandshakeInProgress(message);
-                break;
-            case HandshakeState.RESUMING_SESSION:
-                processHandshakeResumingSession(message);
-                break;
-            default:
-                loge(TAG, "Encountered unexpected handshake state: " + mState + ". Received "
-                        + "message: " + ByteUtils.byteArrayToHexString(message) + ".");
-                notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE);
-        }
-    }
+    /** Logic for processing a handshake message from device. */
+    abstract void processHandshake(byte[] message) throws HandshakeException;
 
-    private void processHandshakeUnknown(@NonNull byte[] message) throws HandshakeException {
-        if (mDeviceId != null) {
-            logd(TAG, "Responding to handshake init request.");
-            HandshakeMessage handshakeMessage = mEncryptionRunner.respondToInitRequest(message);
-            mState = handshakeMessage.getHandshakeState();
-            sendHandshakeMessage(handshakeMessage.getNextMessage());
-            return;
-        }
-        UUID deviceId = ByteUtils.bytesToUUID(message);
-        if (deviceId == null) {
-            loge(TAG, "Received invalid device id. Ignoring.");
-            return;
-        }
-        mDeviceId = deviceId.toString();
-        if (mIsReconnect && !hasEncryptionKey(mDeviceId)) {
-            loge(TAG, "Attempted to reconnect device but no key found. Aborting secure channel.");
-            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_DEVICE_ID);
-            return;
-        }
-        notifyCallback(callback -> callback.onDeviceIdReceived(mDeviceId));
-        sendUniqueIdToClient();
-    }
-
-    private void processHandshakeInProgress(@NonNull byte[] message) throws HandshakeException {
-        logd(TAG, "Continuing handshake.");
-        HandshakeMessage handshakeMessage = mEncryptionRunner.continueHandshake(message);
-        mState = handshakeMessage.getHandshakeState();
-
-        boolean isValidStateForAssociation = !mIsReconnect
-                && mState == HandshakeState.VERIFICATION_NEEDED;
-        boolean isValidStateForReconnect = mIsReconnect
-                && mState == HandshakeState.RESUMING_SESSION;
-        if (!isValidStateForAssociation && !isValidStateForReconnect) {
-            loge(TAG, "processHandshakeInProgress: Encountered unexpected handshake state: "
-                    + mState + ".");
-            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE);
-            return;
-        }
-
-        if (!isValidStateForAssociation) {
-            return;
-        }
-
-        String code = handshakeMessage.getVerificationCode();
-        if (code == null) {
-            loge(TAG, "Unable to get verification code.");
-            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_VERIFICATION);
-            return;
-        }
-
-        if (mShowVerificationCodeListener != null) {
-            logd(TAG, "Showing pairing code: " + code);
-            mShowVerificationCodeListener.showVerificationCode(code);
-        }
-    }
-
-    private void processHandshakeResumingSession(@NonNull byte[] message)
-            throws HandshakeException {
-        logd(TAG, "Start reconnection authentication.");
-        if (mDeviceId == null) {
-            loge(TAG, "processHandshakeResumingSession: Unable to resume session, device id is "
-                    + "null.");
-            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_DEVICE_ID);
-            return;
-        }
-
-        byte[] previousKey = mStorage.getEncryptionKey(mDeviceId);
-        if (previousKey == null) {
-            loge(TAG, "Unable to resume session, previous key is null.");
-            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_ENCRYPTION_KEY);
-            return;
-        }
-
-        HandshakeMessage handshakeMessage = mEncryptionRunner.authenticateReconnection(message,
-                previousKey);
-        mState = handshakeMessage.getHandshakeState();
-        if (mState != HandshakeState.FINISHED) {
-            loge(TAG, "Unable to resume session, unexpected next handshake state: " + mState + ".");
-            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE);
-            return;
-        }
-
-        Key newKey = handshakeMessage.getKey();
-        if (newKey == null) {
-            loge(TAG, "Unable to resume session, new key is null.");
-            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_ENCRYPTION_KEY);
-            return;
-        }
-
-        logd(TAG, "Saved new key for reconnection.");
-        mStorage.saveEncryptionKey(mDeviceId, newKey.asBytes());
-        mEncryptionKey.set(newKey);
-        sendServerAuthToClient(handshakeMessage.getNextMessage());
-        notifyCallback(callback -> callback.onSecureChannelEstablished());
-    }
-
-    private void sendUniqueIdToClient() {
-        UUID uniqueId = mStorage.getUniqueId();
-        DeviceMessage deviceMessage = new DeviceMessage(/* recipient = */ null,
-                /* isMessageEncrypted = */ false, ByteUtils.uuidToBytes(uniqueId));
-        logd(TAG, "Sending car's device id of " + uniqueId + " to device.");
-        mStream.writeMessage(deviceMessage, OperationType.ENCRYPTION_HANDSHAKE);
-    }
-
-    private boolean hasEncryptionKey(@NonNull String id) {
-        return mStorage.getEncryptionKey(id) != null;
-    }
-
-    private void sendHandshakeMessage(@Nullable byte[] message) {
+    void sendHandshakeMessage(@Nullable byte[] message, boolean isEncrypted) {
         if (message == null) {
             loge(TAG, "Unable to send next handshake message, message is null.");
             notifySecureChannelFailure(CHANNEL_ERROR_INVALID_MSG);
             return;
         }
 
-        logd(TAG, "Send handshake message: " + ByteUtils.byteArrayToHexString(message) + ".");
-        DeviceMessage deviceMessage = new DeviceMessage(/* recipient = */ null,
-                /* isMessageEncrypted = */ false, message);
+        logd(TAG, "Sending handshake message.");
+        DeviceMessage deviceMessage = new DeviceMessage(/* recipient= */ null,
+                isEncrypted, message);
+        if (deviceMessage.isMessageEncrypted()) {
+            encryptMessage(deviceMessage);
+        }
         mStream.writeMessage(deviceMessage, OperationType.ENCRYPTION_HANDSHAKE);
     }
 
-    private void sendServerAuthToClient(@Nullable byte[] message) {
-        if (message == null) {
-            loge(TAG, "Unable to send server authentication message to client, message is null.");
-            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_MSG);
-            return;
-        }
-        DeviceMessage deviceMessage = new DeviceMessage(/* recipient = */ null,
-                /* isMessageEncrypted = */ false, message);
-        mStream.writeMessage(deviceMessage, OperationType.ENCRYPTION_HANDSHAKE);
+    /** Set the encryption key that secures this channel. */
+    void setEncryptionKey(@Nullable Key encryptionKey) {
+        mEncryptionKey.set(encryptionKey);
     }
 
     /**
-     * Send an encrypted message.
-     * <p>Note: This should be called only after the secure channel has been established.</p>
+     * Send a client message.
+     * <p>Note: This should be called with an encrypted message only after the secure channel has
+     * been established.</p>
      *
-     * @param deviceMessage The {@link DeviceMessage} to encrypt and send.
+     * @param deviceMessage The {@link DeviceMessage} to send.
      */
-    void sendEncryptedMessage(@NonNull DeviceMessage deviceMessage) throws IllegalStateException {
-        if (!deviceMessage.isMessageEncrypted()) {
-            loge(TAG, "Encryption not required for this message " + deviceMessage + ".");
-            return;
+    void sendClientMessage(@NonNull DeviceMessage deviceMessage)
+            throws IllegalStateException {
+        if (deviceMessage.isMessageEncrypted()) {
+            encryptMessage(deviceMessage);
         }
+        mStream.writeMessage(deviceMessage, OperationType.CLIENT_MESSAGE);
+    }
+
+    private void encryptMessage(@NonNull DeviceMessage deviceMessage) {
         Key key = mEncryptionKey.get();
         if (key == null) {
             throw new IllegalStateException("Secure channel has not been established.");
@@ -283,50 +134,6 @@
 
         byte[] encryptedMessage = key.encryptData(deviceMessage.getMessage());
         deviceMessage.setMessage(encryptedMessage);
-        mStream.writeMessage(deviceMessage, OperationType.CLIENT_MESSAGE);
-    }
-
-    /**
-     * Called by the client to notify that the user has accepted a pairing code or any out-of-band
-     * confirmation, and send confirmation signals to remote bluetooth device.
-     */
-    void notifyOutOfBandAccepted() {
-        HandshakeMessage message;
-        try {
-            message = mEncryptionRunner.verifyPin();
-        } catch (HandshakeException e) {
-            loge(TAG, "Error during PIN verification", e);
-            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_VERIFICATION);
-            return;
-        }
-        if (message.getHandshakeState() != HandshakeState.FINISHED) {
-            loge(TAG, "Handshake not finished after calling verify PIN. Instead got "
-                    + "state: " + message.getHandshakeState() + ".");
-            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE);
-            return;
-        }
-
-        Key localKey = message.getKey();
-        if (localKey == null) {
-            loge(TAG, "Unable to finish association, generated key is null.");
-            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_ENCRYPTION_KEY);
-            return;
-        }
-
-        mState = message.getHandshakeState();
-        mStorage.saveEncryptionKey(mDeviceId, localKey.asBytes());
-        mEncryptionKey.set(localKey);
-        if (mDeviceId == null) {
-            loge(TAG, "Unable to finish association, device id is null.");
-            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_DEVICE_ID);
-            return;
-        }
-        logd(TAG, "Pairing code successfully verified and encryption key saved. Sending "
-                + "confirmation to device.");
-        notifyCallback(Callback::onSecureChannelEstablished);
-        DeviceMessage deviceMessage = new DeviceMessage(/* recipient = */ null,
-                /* isMessageEncrypted = */ false, CONFIRMATION_SIGNAL);
-        mStream.writeMessage(deviceMessage, OperationType.ENCRYPTION_HANDSHAKE);
     }
 
     /** Get the BLE stream backing this channel. */
@@ -335,17 +142,6 @@
         return mStream;
     }
 
-    /**Set the listener that notifies to show verification code. {@code null} to clear.*/
-    void setShowVerificationCodeListener(@Nullable ShowVerificationCodeListener listener) {
-        mShowVerificationCodeListener = listener;
-    }
-
-    @VisibleForTesting
-    @Nullable
-    ShowVerificationCodeListener getShowVerificationCodeListener() {
-        return mShowVerificationCodeListener;
-    }
-
     /** Register a callback that notifies secure channel events. */
     void registerCallback(Callback callback) {
         mCallback = callback;
@@ -364,64 +160,92 @@
         return mCallback;
     }
 
-    private void notifyCallback(Consumer<Callback> notification) {
+    void notifyCallback(@NonNull Consumer<Callback> notification) {
         if (mCallback != null) {
             notification.accept(mCallback);
         }
     }
 
-    private void notifySecureChannelFailure(@ChannelError int error) {
+    /** Notify callbacks that an error has occurred. */
+    void notifySecureChannelFailure(@ChannelError int error) {
         loge(TAG, "Secure channel error: " + error);
         notifyCallback(callback -> callback.onEstablishSecureChannelFailure(error));
     }
 
-    private final BleDeviceMessageStream.MessageReceivedListener mStreamListener =
-            new BleDeviceMessageStream.MessageReceivedListener() {
-                @Override
-                public void onMessageReceived(DeviceMessage deviceMessage,
-                        OperationType operationType) {
-                    byte[] message = deviceMessage.getMessage();
-                    switch(operationType) {
-                        case ENCRYPTION_HANDSHAKE:
-                            logd(TAG, "Message received and handed off to handshake.");
-                            try {
-                                processHandshake(message);
-                            } catch (HandshakeException e) {
-                                loge(TAG, "Handshake failed.", e);
-                                notifyCallback(callback -> callback.onEstablishSecureChannelFailure(
-                                        CHANNEL_ERROR_INVALID_HANDSHAKE));
-                            }
-                            break;
-                        case CLIENT_MESSAGE:
-                            logd(TAG, "Received client message.");
-                            if (!deviceMessage.isMessageEncrypted()) {
-                                notifyCallback(callback -> callback.onMessageReceived(
-                                        deviceMessage));
-                                return;
-                            }
-                            Key key = mEncryptionKey.get();
-                            if (key == null) {
-                                loge(TAG, "Received encrypted message before secure channel has "
-                                        + "been established.");
-                                notifyCallback(callback -> callback.onMessageReceivedError(null));
-                                return;
-                            }
-                            try {
-                                byte[] decryptedPayload =
-                                        key.decryptData(deviceMessage.getMessage());
-                                deviceMessage.setMessage(decryptedPayload);
-                                notifyCallback(
-                                        callback -> callback.onMessageReceived(deviceMessage));
-                            } catch (SignatureException e) {
-                                loge(TAG, "Could not decrypt client credentials.", e);
-                                notifyCallback(callback -> callback.onMessageReceivedError(e));
-                            }
-                            break;
-                        default:
-                            loge(TAG, "Received unexpected operation type: " + operationType + ".");
-                    }
+    /** Return the {@link EncryptionRunner} for this channel. */
+    @NonNull
+    EncryptionRunner getEncryptionRunner() {
+        return mEncryptionRunner;
+    }
+
+    /**
+     * Process the inner message and replace with decrypted value if necessary. If an error occurs
+     * the inner message will be replaced with {@code null} and call
+     * {@link Callback#onMessageReceivedError(Exception)} on the registered callback.
+     *
+     * @param deviceMessage The message to process.
+     * @return {@code true} if message was successfully processed. {@code false} if an error
+     * occurred.
+     */
+    @VisibleForTesting
+    boolean processMessage(@NonNull DeviceMessage deviceMessage) {
+        if (!deviceMessage.isMessageEncrypted()) {
+            logd(TAG, "Message was not decrypted. No further action necessary.");
+            return true;
+        }
+        Key key = mEncryptionKey.get();
+        if (key == null) {
+            loge(TAG, "Received encrypted message before secure channel has "
+                    + "been established.");
+            notifyCallback(callback -> callback.onMessageReceivedError(null));
+            deviceMessage.setMessage(null);
+            return false;
+        }
+        try {
+            byte[] decryptedMessage = key.decryptData(deviceMessage.getMessage());
+            deviceMessage.setMessage(decryptedMessage);
+            logd(TAG, "Decrypted secure message.");
+            return true;
+        } catch (SignatureException e) {
+            loge(TAG, "Could not decrypt client credentials.", e);
+            notifyCallback(callback -> callback.onMessageReceivedError(e));
+            deviceMessage.setMessage(null);
+
+            return false;
+        }
+    }
+
+    @VisibleForTesting
+    void onMessageReceived(@NonNull DeviceMessage deviceMessage, OperationType operationType) {
+        boolean success = processMessage(deviceMessage);
+        switch(operationType) {
+            case ENCRYPTION_HANDSHAKE:
+                if (!success) {
+                    notifyCallback(callback -> callback.onEstablishSecureChannelFailure(
+                            CHANNEL_ERROR_INVALID_HANDSHAKE));
+                    break;
                 }
-            };
+                logd(TAG, "Received handshake message.");
+                try {
+                    processHandshake(deviceMessage.getMessage());
+                } catch (HandshakeException e) {
+                    loge(TAG, "Handshake failed.", e);
+                    notifyCallback(callback -> callback.onEstablishSecureChannelFailure(
+                            CHANNEL_ERROR_INVALID_HANDSHAKE));
+                }
+                break;
+            case CLIENT_MESSAGE:
+                if (!success || deviceMessage.getMessage() == null) {
+                    break;
+                }
+                logd(TAG, "Received client message.");
+                notifyCallback(
+                        callback -> callback.onMessageReceived(deviceMessage));
+                break;
+            default:
+                loge(TAG, "Received unexpected operation type: " + operationType + ".");
+        }
+    }
 
     /**
      * Callbacks that will be invoked during establishing secure channel, sending and receiving
@@ -431,7 +255,7 @@
         /**
          * Invoked when secure channel has been established successfully.
          */
-        void onSecureChannelEstablished();
+        default void onSecureChannelEstablished() { }
 
         /**
          * Invoked when a {@link ChannelError} has been encountered in attempting to establish
@@ -439,39 +263,27 @@
          *
          * @param error The failure indication.
          */
-        void onEstablishSecureChannelFailure(@SecureBleChannel.ChannelError int error);
+        default void onEstablishSecureChannelFailure(@SecureBleChannel.ChannelError int error) { }
 
         /**
          * Invoked when a complete message is received securely from the client and decrypted.
          *
          * @param deviceMessage The {@link DeviceMessage} with decrypted message.
          */
-        void onMessageReceived(@NonNull DeviceMessage deviceMessage);
+        default void onMessageReceived(@NonNull DeviceMessage deviceMessage) { }
 
         /**
          * Invoked when there was an error during a processing or decrypting of a client message.
          *
          * @param exception The error.
          */
-        void onMessageReceivedError(@Nullable Exception exception);
+        default void onMessageReceivedError(@Nullable Exception exception) { }
 
         /**
          * Invoked when the device id was received from the client.
          *
          * @param deviceId The unique device id of client.
          */
-        void onDeviceIdReceived(@NonNull String deviceId);
-    }
-
-    /**
-     * Listener that will be invoked to display verification code.
-     */
-    interface ShowVerificationCodeListener {
-        /**
-         * Invoke when a verification need to be displayed during device association.
-         *
-         * @param code The verification code to show.
-         */
-        void showVerificationCode(@NonNull String code);
+        default void onDeviceIdReceived(@NonNull String deviceId) { }
     }
 }
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/model/AssociatedDevice.java b/connected-device-lib/src/com/android/car/connecteddevice/model/AssociatedDevice.java
index 88fce6c..8622617 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/model/AssociatedDevice.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/model/AssociatedDevice.java
@@ -16,8 +16,8 @@
 
 package com.android.car.connecteddevice.model;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import java.util.Objects;
 
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/model/ConnectedDevice.java b/connected-device-lib/src/com/android/car/connecteddevice/model/ConnectedDevice.java
index d65f97d..9e49a37 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/model/ConnectedDevice.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/model/ConnectedDevice.java
@@ -16,8 +16,8 @@
 
 package com.android.car.connecteddevice.model;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import java.util.Objects;
 
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/storage/AssociatedDeviceChallengeSecretEntity.java b/connected-device-lib/src/com/android/car/connecteddevice/storage/AssociatedDeviceChallengeSecretEntity.java
new file mode 100644
index 0000000..ab802e6
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/storage/AssociatedDeviceChallengeSecretEntity.java
@@ -0,0 +1,42 @@
+/*
+ * 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.connecteddevice.storage;
+
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+/**
+ * Table entity representing an challenge key for an associated device reconnection advertisement.
+ */
+@Entity(tableName = "associated_devices_challenge_secrets")
+public class AssociatedDeviceChallengeSecretEntity {
+
+    /** Id of the device. */
+    @PrimaryKey
+    @NonNull
+    public String id;
+
+    /** Encrypted challenge key. */
+    @NonNull
+    public String encryptedChallengeSecret;
+
+    public AssociatedDeviceChallengeSecretEntity(String id, String encryptedChallengeSecret) {
+        this.id = id;
+        this.encryptedChallengeSecret = encryptedChallengeSecret;
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/storage/AssociatedDeviceDao.java b/connected-device-lib/src/com/android/car/connecteddevice/storage/AssociatedDeviceDao.java
index c041d58..2a1e023 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/storage/AssociatedDeviceDao.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/storage/AssociatedDeviceDao.java
@@ -63,4 +63,21 @@
     /** Remove a {@link AssociatedDeviceKeyEntity}. */
     @Delete
     void removeAssociatedDeviceKey(AssociatedDeviceKeyEntity keyEntity);
+
+    /** Get the challenge secret associated with a device id. */
+    @Query("SELECT * FROM associated_devices_challenge_secrets WHERE id LIKE :deviceId LIMIT 1")
+    AssociatedDeviceChallengeSecretEntity getAssociatedDeviceChallengeSecret(String deviceId);
+
+    /**
+     * Add a {@link AssociatedDeviceChallengeSecretEntity}. Replace if a secret already exists with
+     * the same device id.
+     */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void addOrReplaceAssociatedDeviceChallengeSecret(
+            AssociatedDeviceChallengeSecretEntity challengeSecretEntity);
+
+    /** Remove a {@link AssociatedDeviceChallengeSecretEntity}. */
+    @Delete
+    void removeAssociatedDeviceChallengeSecret(
+            AssociatedDeviceChallengeSecretEntity challengeSecretEntity);
 }
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/storage/ConnectedDeviceDatabase.java b/connected-device-lib/src/com/android/car/connecteddevice/storage/ConnectedDeviceDatabase.java
index 3671440..98a7b0a 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/storage/ConnectedDeviceDatabase.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/storage/ConnectedDeviceDatabase.java
@@ -20,9 +20,12 @@
 import androidx.room.RoomDatabase;
 
 /** Database for connected devices. */
-@Database(entities = { AssociatedDeviceEntity.class, AssociatedDeviceKeyEntity.class }, version = 1,
+@Database(entities = { AssociatedDeviceEntity.class, AssociatedDeviceKeyEntity.class,
+        AssociatedDeviceChallengeSecretEntity.class },
+        version = 2,
         exportSchema = false)
 public abstract class ConnectedDeviceDatabase extends RoomDatabase {
+
     /** Return the DAO for the associated device table. */
     public abstract AssociatedDeviceDao associatedDeviceDao();
 }
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/storage/ConnectedDeviceStorage.java b/connected-device-lib/src/com/android/car/connecteddevice/storage/ConnectedDeviceStorage.java
index 433ee1d..b62a43c 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/storage/ConnectedDeviceStorage.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/storage/ConnectedDeviceStorage.java
@@ -20,8 +20,6 @@
 import static com.android.car.connecteddevice.util.SafeLog.loge;
 import static com.android.car.connecteddevice.util.SafeLog.logw;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -29,6 +27,9 @@
 import android.security.keystore.KeyProperties;
 import android.util.Base64;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.room.Room;
 
 import com.android.car.connecteddevice.R;
@@ -37,6 +38,7 @@
 import java.io.IOException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
 import java.security.Key;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
@@ -52,8 +54,10 @@
 import javax.crypto.Cipher;
 import javax.crypto.IllegalBlockSizeException;
 import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
 import javax.crypto.NoSuchPaddingException;
 import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
 
 /** Storage for connected devices in a car. */
 public class ConnectedDeviceStorage {
@@ -66,6 +70,8 @@
     private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
     private static final String DATABASE_NAME = "connected-device-database";
     private static final String IV_SPEC_SEPARATOR = ";";
+
+    private static final String CHALLENGE_HASHING_ALGORITHM = "HmacSHA256";
     // This delimiter separates deviceId and deviceInfo, so it has to differ from the
     // TrustedDeviceInfo delimiter. Once new API can be added, deviceId will be added to
     // TrustedDeviceInfo and this delimiter will be removed.
@@ -75,6 +81,9 @@
     // possible value.
     private static final int GCM_AUTHENTICATION_TAG_LENGTH = 128;
 
+    @VisibleForTesting
+    static final int CHALLENGE_SECRET_BYTES = 32;
+
     private final Context mContext;
 
     private SharedPreferences mSharedPreferences;
@@ -110,7 +119,7 @@
     }
 
     /**
-     * Get communication encryption key for the given device
+     * Get communication encryption key for the given device.
      *
      * @param deviceId id of trusted device
      * @return encryption key, null if device id is not recognized
@@ -136,9 +145,9 @@
     }
 
     /**
-     * Save encryption key for the given device
+     * Save encryption key for the given device.
      *
-     * @param deviceId      did of trusted device
+     * @param deviceId id of the device
      * @param encryptionKey encryption key
      */
     public void saveEncryptionKey(@NonNull String deviceId, @NonNull byte[] encryptionKey) {
@@ -149,6 +158,78 @@
     }
 
     /**
+     * Save challenge secret for the given device.
+     *
+     * @param deviceId id of the device
+     * @param secret   Secret associated with this device. Note: must be
+     *                 {@value CHALLENGE_SECRET_BYTES} bytes in length or an
+     *                 {@link InvalidParameterException} will be thrown.
+     */
+    public void saveChallengeSecret(@NonNull String deviceId, @NonNull byte[] secret) {
+        if (secret.length != CHALLENGE_SECRET_BYTES) {
+            throw new InvalidParameterException("Secrets must be " + CHALLENGE_SECRET_BYTES
+                    + " bytes in length.");
+        }
+
+        String encryptedKey = encryptWithKeyStore(KEY_ALIAS, secret);
+        AssociatedDeviceChallengeSecretEntity entity = new AssociatedDeviceChallengeSecretEntity(
+                deviceId, encryptedKey);
+        mAssociatedDeviceDatabase.addOrReplaceAssociatedDeviceChallengeSecret(entity);
+        logd(TAG, "Successfully wrote challenge secret.");
+    }
+
+    /** Get the challenge secret associated with a device. */
+    public byte[] getChallengeSecret(@NonNull String deviceId) {
+        AssociatedDeviceChallengeSecretEntity entity =
+                mAssociatedDeviceDatabase.getAssociatedDeviceChallengeSecret(deviceId);
+        if (entity == null) {
+            logd(TAG, "Challenge secret not found!");
+            return null;
+        }
+        String[] values = entity.encryptedChallengeSecret.split(IV_SPEC_SEPARATOR, -1);
+
+        if (values.length != 2) {
+            logd(TAG, "Stored encryption key had the wrong length.");
+            return null;
+        }
+
+        byte[] encryptedSecret = Base64.decode(values[0], Base64.DEFAULT);
+        byte[] ivSpec = Base64.decode(values[1], Base64.DEFAULT);
+        return decryptWithKeyStore(KEY_ALIAS, encryptedSecret, ivSpec);
+    }
+
+    /**
+     * Hash provided value with device's challenge secret and return result. Returns {@code null} if
+     * unsuccessful.
+     */
+    @Nullable
+    public byte[] hashWithChallengeSecret(@NonNull String deviceId, @NonNull byte[] value) {
+        byte[] challengeSecret = getChallengeSecret(deviceId);
+        if (challengeSecret == null) {
+            loge(TAG, "Unable to find challenge secret for device " + deviceId + ".");
+            return null;
+        }
+
+        Mac mac;
+        try {
+            mac = Mac.getInstance(CHALLENGE_HASHING_ALGORITHM);
+        } catch (NoSuchAlgorithmException e) {
+            loge(TAG, "Unable to find hashing algorithm " + CHALLENGE_HASHING_ALGORITHM + ".", e);
+            return null;
+        }
+
+        SecretKeySpec keySpec = new SecretKeySpec(challengeSecret, CHALLENGE_HASHING_ALGORITHM);
+        try {
+            mac.init(keySpec);
+        } catch (InvalidKeyException e) {
+            loge(TAG, "Exception while initializing HMAC.", e);
+            return null;
+        }
+
+        return mac.doFinal(value);
+    }
+
+    /**
      * Encrypt value with designated key
      *
      * <p>The encrypted value is of the form:
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/util/ByteUtils.java b/connected-device-lib/src/com/android/car/connecteddevice/util/ByteUtils.java
index 3d07227..adffa73 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/util/ByteUtils.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/util/ByteUtils.java
@@ -16,10 +16,11 @@
 
 package com.android.car.connecteddevice.util;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/util/RemoteCallbackBinder.java b/connected-device-lib/src/com/android/car/connecteddevice/util/RemoteCallbackBinder.java
index e18366b..98ad4db 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/util/RemoteCallbackBinder.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/util/RemoteCallbackBinder.java
@@ -64,7 +64,14 @@
 
     @Override
     public boolean equals(Object obj) {
-        return mCallbackBinder.equals(obj);
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof RemoteCallbackBinder)) {
+            return false;
+        }
+        RemoteCallbackBinder remoteCallbackBinder = (RemoteCallbackBinder) obj;
+        return mCallbackBinder.equals(remoteCallbackBinder.mCallbackBinder);
     }
 
     @Override
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/util/SafeLog.java b/connected-device-lib/src/com/android/car/connecteddevice/util/SafeLog.java
index 6ab18ce..8419e0f 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/util/SafeLog.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/util/SafeLog.java
@@ -16,10 +16,11 @@
 
 package com.android.car.connecteddevice.util;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 /**
  * Convenience logging methods that respect whitelisted tags.
  */
@@ -57,7 +58,7 @@
 
     /** Log message if tag is whitelisted for {@code Log.ERROR}. */
     public static void loge(@NonNull String tag, @NonNull String message) {
-        loge(tag, message, /* exception = */ null);
+        loge(tag, message, /* exception= */ null);
     }
 
     /** Log message and optional exception if tag is whitelisted for {@code Log.ERROR}. */
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/util/ScanDataAnalyzer.java b/connected-device-lib/src/com/android/car/connecteddevice/util/ScanDataAnalyzer.java
index 6748bba..fcd6dc5 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/util/ScanDataAnalyzer.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/util/ScanDataAnalyzer.java
@@ -18,9 +18,10 @@
 
 import static com.android.car.connecteddevice.util.SafeLog.logw;
 
-import android.annotation.NonNull;
 import android.bluetooth.le.ScanResult;
 
+import androidx.annotation.NonNull;
+
 import java.math.BigInteger;
 
 /**
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/util/ThreadSafeCallbacks.java b/connected-device-lib/src/com/android/car/connecteddevice/util/ThreadSafeCallbacks.java
index b3d3ef1..152c464 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/util/ThreadSafeCallbacks.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/util/ThreadSafeCallbacks.java
@@ -16,8 +16,7 @@
 
 package com.android.car.connecteddevice.util;
 
-import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
+import androidx.annotation.NonNull;
 
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
@@ -33,7 +32,7 @@
     private final ConcurrentHashMap<T, Executor> mCallbacks = new ConcurrentHashMap<>();
 
     /** Add a callback to be notified on its executor. */
-    public void add(@NonNull T callback, @NonNull @CallbackExecutor Executor executor) {
+    public void add(@NonNull T callback, @NonNull Executor executor) {
         mCallbacks.put(callback, executor);
     }
 
diff --git a/connected-device-lib/tests/unit/Android.bp b/connected-device-lib/tests/unit/Android.bp
index 9cf29ba..f39e737 100644
--- a/connected-device-lib/tests/unit/Android.bp
+++ b/connected-device-lib/tests/unit/Android.bp
@@ -33,6 +33,9 @@
         "connected-device-lib",
         "mockito-target-extended-minus-junit4",
         "testables",
+        // TODO: remove once Android migrates to JUnit 4.13,
+        // which provides assertThrows
+        "testng",
         "truth-prebuilt",
     ],
 
@@ -42,7 +45,7 @@
         "libstaticjvmtiagent",
     ],
 
-    platform_apis: true,
+    min_sdk_version: "29",
 
     certificate: "platform",
 
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ConnectedDeviceManagerTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ConnectedDeviceManagerTest.java
index 579fe7d..92a62b6 100644
--- a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ConnectedDeviceManagerTest.java
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ConnectedDeviceManagerTest.java
@@ -22,8 +22,8 @@
 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.clearInvocations;
 import static org.mockito.Mockito.mockitoSession;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
@@ -31,8 +31,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.NonNull;
-
+import androidx.annotation.NonNull;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.car.connecteddevice.ConnectedDeviceManager.ConnectionCallback;
@@ -58,6 +57,7 @@
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
@@ -73,12 +73,15 @@
 
     private static final String TEST_DEVICE_NAME = "TEST_DEVICE_NAME";
 
-    private static final int DEFAULT_RECONNECT_TIMEOUT = 5;
 
     private final Executor mCallbackExecutor = Executors.newSingleThreadExecutor();
 
     private final UUID mRecipientId = UUID.randomUUID();
 
+    private final List<String> mUserDeviceIds = new ArrayList<>();
+
+    private final List<AssociatedDevice> mUserDevices = new ArrayList<>();
+
     @Mock
     private ConnectedDeviceStorage mMockStorage;
 
@@ -98,13 +101,15 @@
     public void setUp() {
         mMockingSession = mockitoSession()
                 .initMocks(this)
-                .strictness(Strictness.LENIENT)
+                .strictness(Strictness.WARN)
                 .startMocking();
         ArgumentCaptor<AssociatedDeviceCallback> callbackCaptor = ArgumentCaptor
                 .forClass(AssociatedDeviceCallback.class);
         mConnectedDeviceManager = new ConnectedDeviceManager(mMockStorage, mMockCentralManager,
-            mMockPeripheralManager, DEFAULT_RECONNECT_TIMEOUT);
+            mMockPeripheralManager);
         verify(mMockStorage).setAssociatedDeviceCallback(callbackCaptor.capture());
+        when(mMockStorage.getActiveUserAssociatedDevices()).thenReturn(mUserDevices);
+        when(mMockStorage.getActiveUserAssociatedDeviceIds()).thenReturn(mUserDeviceIds);
         mAssociatedDeviceCallback = callbackCaptor.getValue();
         mConnectedDeviceManager.start();
     }
@@ -126,8 +131,8 @@
         String deviceId = connectNewDevice(mMockCentralManager);
         List<ConnectedDevice> activeUserDevices =
                 mConnectedDeviceManager.getActiveUserConnectedDevices();
-        ConnectedDevice expectedDevice = new ConnectedDevice(deviceId, /* deviceName = */ null,
-                /* belongsToActiveUser = */ true, /* hasSecureChannel = */ false);
+        ConnectedDevice expectedDevice = new ConnectedDevice(deviceId, /* deviceName= */ null,
+                /* belongsToActiveUser= */ true, /* hasSecureChannel= */ false);
         assertThat(activeUserDevices).containsExactly(expectedDevice);
     }
 
@@ -451,6 +456,27 @@
     }
 
     @Test
+    public void registerDeviceCallback_sendsMultipleMissedMessagesAfterRegistration()
+            throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        connectNewDevice(mMockCentralManager);
+        ConnectedDevice connectedDevice =
+                mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
+        byte[] payload1 = ByteUtils.randomBytes(10);
+        byte[] payload2 = ByteUtils.randomBytes(10);
+        DeviceMessage message1 = new DeviceMessage(mRecipientId, false, payload1);
+        DeviceMessage message2 = new DeviceMessage(mRecipientId, false, payload2);
+        mConnectedDeviceManager.onMessageReceived(connectedDevice.getDeviceId(), message1);
+        mConnectedDeviceManager.onMessageReceived(connectedDevice.getDeviceId(), message2);
+        DeviceCallback deviceCallback = createDeviceCallback(semaphore);
+        mConnectedDeviceManager.registerDeviceCallback(connectedDevice, mRecipientId,
+                deviceCallback, mCallbackExecutor);
+        assertThat(tryAcquire(semaphore)).isTrue();
+        verify(deviceCallback).onMessageReceived(connectedDevice, payload1);
+        verify(deviceCallback, timeout(1000)).onMessageReceived(connectedDevice, payload2);
+    }
+
+    @Test
     public void registerDeviceCallback_doesNotSendMissedMessageForDifferentRecipient()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
@@ -511,7 +537,7 @@
         mConnectedDeviceManager.registerDeviceAssociationCallback(callback, mCallbackExecutor);
         String deviceId = UUID.randomUUID().toString();
         AssociatedDevice testDevice = new AssociatedDevice(deviceId, TEST_DEVICE_ADDRESS,
-                TEST_DEVICE_NAME, /* isConnectionEnabled = */ true);
+                TEST_DEVICE_NAME, /* isConnectionEnabled= */ true);
         mAssociatedDeviceCallback.onAssociatedDeviceAdded(testDevice);
         assertThat(tryAcquire(semaphore)).isTrue();
         verify(callback).onAssociatedDeviceAdded(eq(testDevice));
@@ -524,7 +550,7 @@
         mConnectedDeviceManager.registerDeviceAssociationCallback(callback, mCallbackExecutor);
         String deviceId = UUID.randomUUID().toString();
         AssociatedDevice testDevice = new AssociatedDevice(deviceId, TEST_DEVICE_ADDRESS,
-                TEST_DEVICE_NAME, /* isConnectionEnabled = */ true);
+                TEST_DEVICE_NAME, /* isConnectionEnabled= */ true);
         mAssociatedDeviceCallback.onAssociatedDeviceRemoved(testDevice);
         assertThat(tryAcquire(semaphore)).isTrue();
         verify(callback).onAssociatedDeviceRemoved(eq(testDevice));
@@ -537,7 +563,7 @@
         mConnectedDeviceManager.registerDeviceAssociationCallback(callback, mCallbackExecutor);
         String deviceId = UUID.randomUUID().toString();
         AssociatedDevice testDevice = new AssociatedDevice(deviceId, TEST_DEVICE_ADDRESS,
-                TEST_DEVICE_NAME, /* isConnectionEnabled = */ true);
+                TEST_DEVICE_NAME, /* isConnectionEnabled= */ true);
         mAssociatedDeviceCallback.onAssociatedDeviceUpdated(testDevice);
         assertThat(tryAcquire(semaphore)).isTrue();
         verify(callback).onAssociatedDeviceUpdated(eq(testDevice));
@@ -549,13 +575,14 @@
         when(mMockStorage.getActiveUserAssociatedDeviceIds()).thenReturn(
                 Collections.singletonList(deviceId));
         AssociatedDevice device = new AssociatedDevice(deviceId, TEST_DEVICE_ADDRESS,
-                TEST_DEVICE_NAME, /* isConnectionEnabled = */ true);
+                TEST_DEVICE_NAME, /* isConnectionEnabled= */ true);
         when(mMockStorage.getActiveUserAssociatedDevices()).thenReturn(
                 Collections.singletonList(device));
+        clearInvocations(mMockPeripheralManager);
         mConnectedDeviceManager.addConnectedDevice(deviceId, mMockPeripheralManager);
         mConnectedDeviceManager.removeConnectedDevice(deviceId, mMockPeripheralManager);
         verify(mMockPeripheralManager, timeout(1000))
-                .connectToDevice(eq(UUID.fromString(deviceId)), anyInt());
+                .connectToDevice(eq(UUID.fromString(deviceId)));
     }
 
     @Test
@@ -565,13 +592,14 @@
         when(mMockStorage.getActiveUserAssociatedDeviceIds()).thenReturn(
                 Collections.singletonList(userDeviceId));
         AssociatedDevice userDevice = new AssociatedDevice(userDeviceId, TEST_DEVICE_ADDRESS,
-                TEST_DEVICE_NAME, /* isConnectionEnabled = */ true);
+                TEST_DEVICE_NAME, /* isConnectionEnabled= */ true);
         when(mMockStorage.getActiveUserAssociatedDevices()).thenReturn(
                 Collections.singletonList(userDevice));
         mConnectedDeviceManager.addConnectedDevice(deviceId, mMockPeripheralManager);
+        clearInvocations(mMockPeripheralManager);
         mConnectedDeviceManager.removeConnectedDevice(deviceId, mMockPeripheralManager);
         verify(mMockPeripheralManager, timeout(1000))
-                .connectToDevice(eq(UUID.fromString(userDeviceId)), anyInt());
+                .connectToDevice(eq(UUID.fromString(userDeviceId)));
     }
 
     @Test
@@ -581,14 +609,15 @@
         when(mMockStorage.getActiveUserAssociatedDeviceIds()).thenReturn(
                 Collections.singletonList(userDeviceId));
         AssociatedDevice userDevice = new AssociatedDevice(userDeviceId, TEST_DEVICE_ADDRESS,
-                TEST_DEVICE_NAME, /* isConnectionEnabled = */ true);
+                TEST_DEVICE_NAME, /* isConnectionEnabled= */ true);
         when(mMockStorage.getActiveUserAssociatedDevices()).thenReturn(
                 Collections.singletonList(userDevice));
+        clearInvocations(mMockPeripheralManager);
         mConnectedDeviceManager.addConnectedDevice(deviceId, mMockPeripheralManager);
         mConnectedDeviceManager.addConnectedDevice(userDeviceId, mMockCentralManager);
         mConnectedDeviceManager.removeConnectedDevice(deviceId, mMockPeripheralManager);
         verify(mMockPeripheralManager, timeout(1000).times(0))
-                .connectToDevice(eq(UUID.fromString(userDeviceId)), anyInt());
+                .connectToDevice(any());
     }
 
     @Test
@@ -678,11 +707,9 @@
     private String connectNewDevice(@NonNull CarBleManager carBleManager) {
         String deviceId = UUID.randomUUID().toString();
         AssociatedDevice device = new AssociatedDevice(deviceId, TEST_DEVICE_ADDRESS,
-                TEST_DEVICE_NAME, /* isConnectionEnabled = */ true);
-        when(mMockStorage.getActiveUserAssociatedDevices()).thenReturn(
-                Collections.singletonList(device));
-        when(mMockStorage.getActiveUserAssociatedDeviceIds()).thenReturn(
-                Collections.singletonList(deviceId));
+                TEST_DEVICE_NAME, /* isConnectionEnabled= */ true);
+        mUserDeviceIds.add(deviceId);
+        mUserDevices.add(device);
         mConnectedDeviceManager.addConnectedDevice(deviceId, carBleManager);
         return deviceId;
     }
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/AssociationSecureChannelTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/AssociationSecureChannelTest.java
new file mode 100644
index 0000000..1a4941c
--- /dev/null
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/AssociationSecureChannelTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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.connecteddevice.ble;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mockitoSession;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.encryptionrunner.DummyEncryptionRunner;
+import android.car.encryptionrunner.EncryptionRunner;
+import android.car.encryptionrunner.EncryptionRunnerFactory;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.connecteddevice.BleStreamProtos.BleOperationProto.OperationType;
+import com.android.car.connecteddevice.ble.BleDeviceMessageStream.MessageReceivedListener;
+import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
+import com.android.car.connecteddevice.util.ByteUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.UUID;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public final class AssociationSecureChannelTest {
+    private static final UUID CLIENT_DEVICE_ID =
+            UUID.fromString("a5645523-3280-410a-90c1-582a6c6f4969");
+    private static final UUID SERVER_DEVICE_ID =
+            UUID.fromString("a29f0c74-2014-4b14-ac02-be6ed15b545a");
+    private static final byte[] CLIENT_SECRET = ByteUtils.randomBytes(32);
+
+    @Mock
+    private BleDeviceMessageStream mStreamMock;
+    @Mock
+    private ConnectedDeviceStorage mStorageMock;
+    @Mock
+    private AssociationSecureChannel.ShowVerificationCodeListener mShowVerificationCodeListenerMock;
+    private MockitoSession mMockitoSession;
+
+    private AssociationSecureChannel mChannel;
+    private MessageReceivedListener mMessageReceivedListener;
+
+    @Before
+    public void setUp() {
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.WARN)
+                .startMocking();
+        when(mStorageMock.getUniqueId()).thenReturn(SERVER_DEVICE_ID);
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void testEncryptionHandshake_Association() throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        ChannelCallback callbackSpy = spy(new ChannelCallback(semaphore));
+        setupAssociationSecureChannel(callbackSpy, EncryptionRunnerFactory::newDummyRunner);
+        ArgumentCaptor<String> deviceIdCaptor = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<DeviceMessage> messageCaptor =
+                ArgumentCaptor.forClass(DeviceMessage.class);
+
+        initHandshakeMessage();
+        verify(mStreamMock).writeMessage(messageCaptor.capture(), any());
+        byte[] response = messageCaptor.getValue().getMessage();
+        assertThat(response).isEqualTo(DummyEncryptionRunner.INIT_RESPONSE.getBytes());
+
+        respondToContinueMessage();
+        verify(mShowVerificationCodeListenerMock).showVerificationCode(anyString());
+
+        mChannel.notifyOutOfBandAccepted();
+        sendDeviceId();
+        assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
+        verify(callbackSpy).onDeviceIdReceived(deviceIdCaptor.capture());
+        verify(mStreamMock, times(2)).writeMessage(messageCaptor.capture(), any());
+        byte[] deviceIdMessage = messageCaptor.getValue().getMessage();
+        assertThat(deviceIdMessage).isEqualTo(ByteUtils.uuidToBytes(SERVER_DEVICE_ID));
+        assertThat(deviceIdCaptor.getValue()).isEqualTo(CLIENT_DEVICE_ID.toString());
+        verify(mStorageMock).saveEncryptionKey(eq(CLIENT_DEVICE_ID.toString()), any());
+        verify(mStorageMock).saveChallengeSecret(CLIENT_DEVICE_ID.toString(), CLIENT_SECRET);
+
+        assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
+        verify(callbackSpy).onSecureChannelEstablished();
+    }
+
+    @Test
+    public void testEncryptionHandshake_Association_wrongInitHandshakeMessage()
+            throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        ChannelCallback callbackSpy = spy(new ChannelCallback(semaphore));
+        setupAssociationSecureChannel(callbackSpy, EncryptionRunnerFactory::newDummyRunner);
+
+        // Wrong init handshake message
+        respondToContinueMessage();
+        assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
+        verify(callbackSpy).onEstablishSecureChannelFailure(
+                eq(SecureBleChannel.CHANNEL_ERROR_INVALID_HANDSHAKE)
+        );
+    }
+
+    @Test
+    public void testEncryptionHandshake_Association_wrongRespondToContinueMessage()
+            throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        ChannelCallback callbackSpy = spy(new ChannelCallback(semaphore));
+        setupAssociationSecureChannel(callbackSpy, EncryptionRunnerFactory::newDummyRunner);
+
+        initHandshakeMessage();
+
+        // Wrong respond to continue message
+        initHandshakeMessage();
+        assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
+        verify(callbackSpy).onEstablishSecureChannelFailure(
+                eq(SecureBleChannel.CHANNEL_ERROR_INVALID_HANDSHAKE)
+        );
+    }
+
+    private void setupAssociationSecureChannel(ChannelCallback callback,
+            EncryptionRunnerProvider encryptionRunnerProvider) {
+        mChannel = new AssociationSecureChannel(mStreamMock, mStorageMock,
+                encryptionRunnerProvider.getEncryptionRunner());
+        mChannel.registerCallback(callback);
+        mChannel.setShowVerificationCodeListener(mShowVerificationCodeListenerMock);
+        ArgumentCaptor<MessageReceivedListener> listenerCaptor =
+                ArgumentCaptor.forClass(MessageReceivedListener.class);
+        verify(mStreamMock).setMessageReceivedListener(listenerCaptor.capture());
+        mMessageReceivedListener = listenerCaptor.getValue();
+    }
+
+    private void sendDeviceId() {
+        DeviceMessage message = new DeviceMessage(
+                /* recipient= */ null,
+                /* isMessageEncrypted= */ true,
+                ByteUtils.concatByteArrays(ByteUtils.uuidToBytes(CLIENT_DEVICE_ID), CLIENT_SECRET)
+        );
+        mMessageReceivedListener.onMessageReceived(message, OperationType.ENCRYPTION_HANDSHAKE);
+    }
+
+    private void initHandshakeMessage() {
+        DeviceMessage message = new DeviceMessage(
+                /* recipient= */ null,
+                /* isMessageEncrypted= */ false,
+                DummyEncryptionRunner.INIT.getBytes()
+        );
+        mMessageReceivedListener.onMessageReceived(message, OperationType.ENCRYPTION_HANDSHAKE);
+    }
+
+    private void respondToContinueMessage() {
+        DeviceMessage message = new DeviceMessage(
+                /* recipient= */ null,
+                /* isMessageEncrypted= */ false,
+                DummyEncryptionRunner.CLIENT_RESPONSE.getBytes()
+        );
+        mMessageReceivedListener.onMessageReceived(message, OperationType.ENCRYPTION_HANDSHAKE);
+    }
+
+    /**
+     * Add the thread control logic into {@link SecureBleChannel.Callback} only for spy purpose.
+     *
+     * <p>The callback will release the semaphore which hold by one test when this callback
+     * is called, telling the test that it can verify certain behaviors which will only occurred
+     * after the callback is notified. This is needed mainly because of the callback is notified
+     * in a different thread.
+     */
+    private static class ChannelCallback implements SecureBleChannel.Callback {
+        private final Semaphore mSemaphore;
+
+        ChannelCallback(Semaphore semaphore) {
+            mSemaphore = semaphore;
+        }
+
+        @Override
+        public void onSecureChannelEstablished() {
+            mSemaphore.release();
+        }
+
+        @Override
+        public void onEstablishSecureChannelFailure(int error) {
+            mSemaphore.release();
+        }
+
+        @Override
+        public void onMessageReceived(DeviceMessage deviceMessage) {
+            mSemaphore.release();
+        }
+
+        @Override
+        public void onMessageReceivedError(Exception exception) {
+            mSemaphore.release();
+        }
+
+        @Override
+        public void onDeviceIdReceived(String deviceId) {
+            mSemaphore.release();
+        }
+    }
+
+    interface EncryptionRunnerProvider {
+        EncryptionRunner getEncryptionRunner();
+    }
+}
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/BleDeviceMessageStreamTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/BleDeviceMessageStreamTest.java
index b45b6f2..ed6fb44 100644
--- a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/BleDeviceMessageStreamTest.java
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/BleDeviceMessageStreamTest.java
@@ -19,6 +19,7 @@
 import static com.android.car.connecteddevice.BleStreamProtos.BleDeviceMessageProto.BleDeviceMessage;
 import static com.android.car.connecteddevice.BleStreamProtos.BleOperationProto.OperationType;
 import static com.android.car.connecteddevice.BleStreamProtos.BlePacketProto.BlePacket;
+import static com.android.car.connecteddevice.ble.BleDeviceMessageStream.MessageReceivedErrorListener;
 import static com.android.car.connecteddevice.ble.BleDeviceMessageStream.MessageReceivedListener;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -57,7 +58,7 @@
 @RunWith(AndroidJUnit4.class)
 public class BleDeviceMessageStreamTest {
 
-    private static final String TAG = "BleDeviceMessageStreamTest";
+    private static final int PACKET_SIZE = 500;
 
     private BleDeviceMessageStream mStream;
 
@@ -83,7 +84,7 @@
                 .startMocking();
 
         mStream = new BleDeviceMessageStream(mMockBlePeripheralManager, mMockBluetoothDevice,
-                mMockWriteCharacteristic, mMockReadCharacteristic);
+                mMockWriteCharacteristic, mMockReadCharacteristic, PACKET_SIZE);
     }
 
     @After
@@ -126,7 +127,7 @@
         Semaphore semaphore = new Semaphore(0);
         MessageReceivedListener listener = createMessageReceivedListener(semaphore);
         mStream.setMessageReceivedListener(listener);
-        byte[] data = ByteUtils.randomBytes(750);
+        byte[] data = ByteUtils.randomBytes((int) (PACKET_SIZE * 1.5));
         List<BlePacket> packets1 = createPackets(data);
         List<BlePacket> packets2 = createPackets(data);
 
@@ -154,9 +155,9 @@
     public void processPacket_doesNotNotifyOfNewMessageIfNotAllPacketsReceived()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        MessageReceivedListener listener = createMessageReceivedListener(semaphore);
-        mStream.setMessageReceivedListener(listener);
-        byte[] data = ByteUtils.randomBytes(750);
+        mStream.setMessageReceivedListener(createMessageReceivedListener(semaphore));
+        mStream.setMessageReceivedErrorListener(createMessageReceivedErrorListener(semaphore));
+        byte[] data = ByteUtils.randomBytes((int) (PACKET_SIZE * 1.5));
         List<BlePacket> packets = createPackets(data);
         for (int i = 0; i < packets.size() - 1; i++) {
             mStream.processPacket(packets.get(i));
@@ -164,6 +165,44 @@
         assertThat(tryAcquire(semaphore)).isFalse();
     }
 
+    @Test
+    public void processPacket_ignoresDuplicatePacket() {
+        Semaphore semaphore = new Semaphore(0);
+        byte[] data = ByteUtils.randomBytes((int) (PACKET_SIZE * 2.5));
+        MessageReceivedListener listener = createMessageReceivedListener(semaphore);
+        mStream.setMessageReceivedListener(listener);
+        ArgumentCaptor<DeviceMessage> messageCaptor = ArgumentCaptor.forClass(DeviceMessage.class);
+        List<BlePacket> packets = createPackets(data);
+        for (int i = 0; i < packets.size(); i++) {
+            mStream.processPacket(packets.get(i));
+            mStream.processPacket(packets.get(i)); // Process each packet twice.
+        }
+        verify(listener).onMessageReceived(messageCaptor.capture(), any());
+        assertThat(Arrays.equals(data, messageCaptor.getValue().getMessage())).isTrue();
+    }
+
+    @Test
+    public void processPacket_packetBeforeExpectedRangeNotifiesMessageError()
+            throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        mStream.setMessageReceivedErrorListener(createMessageReceivedErrorListener(semaphore));
+        List<BlePacket> packets = createPackets(ByteUtils.randomBytes((int) (PACKET_SIZE * 2.5)));
+        mStream.processPacket(packets.get(0));
+        mStream.processPacket(packets.get(1));
+        mStream.processPacket(packets.get(0));
+        assertThat(tryAcquire(semaphore)).isTrue();
+    }
+
+    @Test
+    public void processPacket_packetAfterExpectedNotifiesMessageError()
+            throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        mStream.setMessageReceivedErrorListener(createMessageReceivedErrorListener(semaphore));
+        List<BlePacket> packets = createPackets(ByteUtils.randomBytes((int) (PACKET_SIZE * 1.5)));
+        mStream.processPacket(packets.get(1));
+        assertThat(tryAcquire(semaphore)).isTrue();
+    }
+
     @NonNull
     private List<BlePacket> createPackets(byte[] data) {
         try {
@@ -172,7 +211,7 @@
                     .setOperation(OperationType.CLIENT_MESSAGE)
                     .build();
             return BlePacketFactory.makeBlePackets(message.toByteArray(),
-                    ThreadLocalRandom.current().nextInt(), 500);
+                    ThreadLocalRandom.current().nextInt(), PACKET_SIZE);
         } catch (Exception e) {
             assertWithMessage("Uncaught exception while making packets.").fail();
             return new ArrayList<>();
@@ -186,14 +225,18 @@
         }
     }
 
-    private boolean tryAcquire(Semaphore semaphore) throws InterruptedException {
+    private boolean tryAcquire(@NonNull Semaphore semaphore) throws InterruptedException {
         return semaphore.tryAcquire(100, TimeUnit.MILLISECONDS);
     }
 
     @NonNull
-    private MessageReceivedListener createMessageReceivedListener(
-            Semaphore semaphore) {
+    private MessageReceivedListener createMessageReceivedListener(@NonNull Semaphore semaphore) {
         return spy((deviceMessage, operationType) -> semaphore.release());
     }
 
+    @NonNull
+    private MessageReceivedErrorListener createMessageReceivedErrorListener(
+            @NonNull Semaphore semaphore) {
+        return exception -> semaphore.release();
+    }
 }
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/CarBlePeripheralManagerTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/CarBlePeripheralManagerTest.java
index 986f11e..acec1f5 100644
--- a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/CarBlePeripheralManagerTest.java
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/CarBlePeripheralManagerTest.java
@@ -21,18 +21,16 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mockitoSession;
-import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
-import android.annotation.NonNull;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.le.AdvertiseCallback;
 import android.bluetooth.le.AdvertiseData;
 import android.bluetooth.le.AdvertiseSettings;
-import android.car.encryptionrunner.EncryptionRunnerFactory;
-import android.car.encryptionrunner.Key;
 import android.os.ParcelUuid;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -43,9 +41,7 @@
 import com.android.car.connecteddevice.util.ByteUtils;
 
 import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -53,41 +49,44 @@
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 
+import java.time.Duration;
 import java.util.UUID;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class CarBlePeripheralManagerTest {
     private static final UUID ASSOCIATION_SERVICE_UUID = UUID.randomUUID();
+    private static final UUID RECONNECT_SERVICE_UUID = UUID.randomUUID();
+    private static final UUID RECONNECT_DATA_UUID = UUID.randomUUID();
     private static final UUID WRITE_UUID = UUID.randomUUID();
     private static final UUID READ_UUID = UUID.randomUUID();
     private static final int DEVICE_NAME_LENGTH_LIMIT = 8;
     private static final String TEST_REMOTE_DEVICE_ADDRESS = "00:11:22:33:AA:BB";
     private static final UUID TEST_REMOTE_DEVICE_ID = UUID.randomUUID();
     private static final String TEST_VERIFICATION_CODE = "000000";
-    private static final byte[] TEST_KEY = "Key".getBytes();
-    private static String sAdapterName;
+    private static final Duration RECONNECT_ADVERTISEMENT_DURATION = Duration.ofSeconds(2);
+    private static final int DEFAULT_MTU_SIZE = 23;
 
-    @Mock private BlePeripheralManager mMockPeripheralManager;
-    @Mock private ConnectedDeviceStorage mMockStorage;
+    @Mock
+    private BlePeripheralManager mMockPeripheralManager;
+    @Mock
+    private ConnectedDeviceStorage mMockStorage;
+    @Mock
+    private AssociationCallback mAssociationCallback;
 
     private CarBlePeripheralManager mCarBlePeripheralManager;
 
     private MockitoSession mMockitoSession;
 
-    @BeforeClass
-    public static void beforeSetUp() {
-        sAdapterName = BluetoothAdapter.getDefaultAdapter().getName();
-    }
     @Before
     public void setUp() {
         mMockitoSession = mockitoSession()
                 .initMocks(this)
-                .strictness(Strictness.LENIENT)
+                .strictness(Strictness.WARN)
                 .startMocking();
         mCarBlePeripheralManager = new CarBlePeripheralManager(mMockPeripheralManager, mMockStorage,
-                ASSOCIATION_SERVICE_UUID, WRITE_UUID, READ_UUID);
+                ASSOCIATION_SERVICE_UUID, RECONNECT_SERVICE_UUID, RECONNECT_DATA_UUID,
+                WRITE_UUID, READ_UUID, RECONNECT_ADVERTISEMENT_DURATION, DEFAULT_MTU_SIZE);
+        mCarBlePeripheralManager.start();
     }
 
     @After
@@ -100,115 +99,112 @@
         }
     }
 
-    @AfterClass
-    public static void afterTearDown() {
-        BluetoothAdapter.getDefaultAdapter().setName(sAdapterName);
-    }
-
     @Test
     public void testStartAssociationAdvertisingSuccess() {
-        Semaphore semaphore = new Semaphore(0);
-        AssociationCallback callback = createAssociationCallback(semaphore);
         String testDeviceName = getNameForAssociation();
-        startAssociation(callback, testDeviceName);
-        ArgumentCaptor<AdvertiseData> dataCaptor = ArgumentCaptor.forClass(AdvertiseData.class);
-        verify(mMockPeripheralManager, timeout(3000)).startAdvertising(any(),
-                dataCaptor.capture(), any());
-        AdvertiseData data = dataCaptor.getValue();
-        assertThat(data.getIncludeDeviceName()).isTrue();
-        ParcelUuid expected = new ParcelUuid(ASSOCIATION_SERVICE_UUID);
-        assertThat(data.getServiceUuids().get(0)).isEqualTo(expected);
-        assertThat(BluetoothAdapter.getDefaultAdapter().getName()).isEqualTo(testDeviceName);
+        startAssociation(mAssociationCallback, testDeviceName);
+        ArgumentCaptor<AdvertiseData> advertiseDataCaptor =
+                ArgumentCaptor.forClass(AdvertiseData.class);
+        ArgumentCaptor<AdvertiseData> scanResponseDataCaptor =
+                ArgumentCaptor.forClass(AdvertiseData.class);
+        verify(mMockPeripheralManager).startAdvertising(any(), advertiseDataCaptor.capture(),
+                scanResponseDataCaptor.capture(), any());
+        AdvertiseData advertisementData = advertiseDataCaptor.getValue();
+        ParcelUuid serviceUuid = new ParcelUuid(ASSOCIATION_SERVICE_UUID);
+        assertThat(advertisementData.getServiceUuids()).contains(serviceUuid);
+        AdvertiseData scanResponseData = scanResponseDataCaptor.getValue();
+        assertThat(scanResponseData.getIncludeDeviceName()).isFalse();
+        ParcelUuid dataUuid = new ParcelUuid(RECONNECT_DATA_UUID);
+        assertThat(scanResponseData.getServiceData().get(dataUuid)).isEqualTo(
+                testDeviceName.getBytes());
     }
 
     @Test
-    public void testStartAssociationAdvertisingFailure() throws InterruptedException {
-        Semaphore semaphore = new Semaphore(0);
-        AssociationCallback callback = createAssociationCallback(semaphore);
-        startAssociation(callback, getNameForAssociation());
+    public void testStartAssociationAdvertisingFailure() {
+        startAssociation(mAssociationCallback, getNameForAssociation());
         ArgumentCaptor<AdvertiseCallback> callbackCaptor =
                 ArgumentCaptor.forClass(AdvertiseCallback.class);
-        verify(mMockPeripheralManager, timeout(3000))
-                .startAdvertising(any(), any(), callbackCaptor.capture());
+        verify(mMockPeripheralManager).startAdvertising(any(), any(), any(),
+                callbackCaptor.capture());
         AdvertiseCallback advertiseCallback = callbackCaptor.getValue();
         int testErrorCode = 2;
         advertiseCallback.onStartFailure(testErrorCode);
-        assertThat(tryAcquire(semaphore)).isTrue();
-        verify(callback).onAssociationStartFailure();
+        verify(mAssociationCallback).onAssociationStartFailure();
     }
 
     @Test
-    public void testNotifyAssociationSuccess() throws InterruptedException {
-        Semaphore semaphore = new Semaphore(0);
-        AssociationCallback callback = createAssociationCallback(semaphore);
+    public void testNotifyAssociationSuccess() {
         String testDeviceName = getNameForAssociation();
-        startAssociation(callback, testDeviceName);
+        startAssociation(mAssociationCallback, testDeviceName);
         ArgumentCaptor<AdvertiseCallback> callbackCaptor =
                 ArgumentCaptor.forClass(AdvertiseCallback.class);
-        verify(mMockPeripheralManager, timeout(3000))
-                .startAdvertising(any(), any(), callbackCaptor.capture());
+        verify(mMockPeripheralManager).startAdvertising(any(), any(), any(),
+                callbackCaptor.capture());
         AdvertiseCallback advertiseCallback = callbackCaptor.getValue();
         AdvertiseSettings settings = new AdvertiseSettings.Builder().build();
         advertiseCallback.onStartSuccess(settings);
-        assertThat(tryAcquire(semaphore)).isTrue();
-        verify(callback).onAssociationStartSuccess(eq(testDeviceName));
+        verify(mAssociationCallback).onAssociationStartSuccess(eq(testDeviceName));
     }
 
     @Test
-    public void testShowVerificationCode() throws InterruptedException {
-        Semaphore semaphore = new Semaphore(0);
-        AssociationCallback callback = createAssociationCallback(semaphore);
-        SecureBleChannel channel = getChannelForAssociation(callback);
+    public void testShowVerificationCode() {
+        AssociationSecureChannel channel = getChannelForAssociation(mAssociationCallback);
         channel.getShowVerificationCodeListener().showVerificationCode(TEST_VERIFICATION_CODE);
-        assertThat(tryAcquire(semaphore)).isTrue();
-        verify(callback).onVerificationCodeAvailable(eq(TEST_VERIFICATION_CODE));
+        verify(mAssociationCallback).onVerificationCodeAvailable(eq(TEST_VERIFICATION_CODE));
     }
 
     @Test
-    public void testAssociationSuccess() throws InterruptedException {
-        Semaphore semaphore = new Semaphore(0);
-        AssociationCallback callback = createAssociationCallback(semaphore);
-        SecureBleChannel channel = getChannelForAssociation(callback);
+    public void testAssociationSuccess() {
+        SecureBleChannel channel = getChannelForAssociation(mAssociationCallback);
         SecureBleChannel.Callback channelCallback = channel.getCallback();
         assertThat(channelCallback).isNotNull();
         channelCallback.onDeviceIdReceived(TEST_REMOTE_DEVICE_ID.toString());
-        Key key = EncryptionRunnerFactory.newDummyRunner().keyOf(TEST_KEY);
         channelCallback.onSecureChannelEstablished();
         ArgumentCaptor<AssociatedDevice> deviceCaptor =
                 ArgumentCaptor.forClass(AssociatedDevice.class);
         verify(mMockStorage).addAssociatedDeviceForActiveUser(deviceCaptor.capture());
         AssociatedDevice device = deviceCaptor.getValue();
         assertThat(device.getDeviceId()).isEqualTo(TEST_REMOTE_DEVICE_ID.toString());
-        assertThat(tryAcquire(semaphore)).isTrue();
-        verify(callback).onAssociationCompleted(eq(TEST_REMOTE_DEVICE_ID.toString()));
+        verify(mAssociationCallback).onAssociationCompleted(eq(TEST_REMOTE_DEVICE_ID.toString()));
     }
 
     @Test
-    public void testAssociationFailure_channelError() throws InterruptedException {
-        Semaphore semaphore = new Semaphore(0);
-        AssociationCallback callback = createAssociationCallback(semaphore);
-        SecureBleChannel channel = getChannelForAssociation(callback);
+    public void testAssociationFailure_channelError() {
+        SecureBleChannel channel = getChannelForAssociation(mAssociationCallback);
         SecureBleChannel.Callback channelCallback = channel.getCallback();
         int testErrorCode = 1;
         assertThat(channelCallback).isNotNull();
         channelCallback.onDeviceIdReceived(TEST_REMOTE_DEVICE_ID.toString());
         channelCallback.onEstablishSecureChannelFailure(testErrorCode);
-        assertThat(tryAcquire(semaphore)).isTrue();
-        verify(callback).onAssociationError(eq(testErrorCode));
+        verify(mAssociationCallback).onAssociationError(eq(testErrorCode));
     }
 
     @Test
     public void connectToDevice_stopsAdvertisingAfterTimeout() {
-        int timeoutSeconds = 2;
-        mCarBlePeripheralManager.connectToDevice(UUID.randomUUID(), timeoutSeconds);
+        when(mMockStorage.hashWithChallengeSecret(any(), any()))
+                .thenReturn(ByteUtils.randomBytes(32));
+        mCarBlePeripheralManager.connectToDevice(UUID.randomUUID());
         ArgumentCaptor<AdvertiseCallback> callbackCaptor =
                 ArgumentCaptor.forClass(AdvertiseCallback.class);
-        verify(mMockPeripheralManager).startAdvertising(any(), any(), callbackCaptor.capture());
+        verify(mMockPeripheralManager).startAdvertising(any(), any(), any(),
+                callbackCaptor.capture());
         callbackCaptor.getValue().onStartSuccess(null);
-        verify(mMockPeripheralManager, timeout(TimeUnit.SECONDS.toMillis(timeoutSeconds + 1)))
+        verify(mMockPeripheralManager,
+                timeout(RECONNECT_ADVERTISEMENT_DURATION.plusSeconds(1).toMillis()))
                 .stopAdvertising(any(AdvertiseCallback.class));
     }
 
+    @Test
+    public void disconnectDevice_stopsAdvertisingForPendingReconnect() {
+        when(mMockStorage.hashWithChallengeSecret(any(), any()))
+                .thenReturn(ByteUtils.randomBytes(32));
+        UUID deviceId = UUID.randomUUID();
+        mCarBlePeripheralManager.connectToDevice(deviceId);
+        reset(mMockPeripheralManager);
+        mCarBlePeripheralManager.disconnectDevice(deviceId.toString());
+        verify(mMockPeripheralManager).cleanup();
+    }
+
     private BlePeripheralManager.Callback startAssociation(AssociationCallback callback,
             String deviceName) {
         ArgumentCaptor<BlePeripheralManager.Callback> callbackCaptor =
@@ -218,50 +214,17 @@
         return callbackCaptor.getValue();
     }
 
-    private SecureBleChannel getChannelForAssociation(AssociationCallback callback) {
+    private AssociationSecureChannel getChannelForAssociation(AssociationCallback callback) {
         BlePeripheralManager.Callback bleManagerCallback = startAssociation(callback,
                 getNameForAssociation());
         BluetoothDevice bluetoothDevice = BluetoothAdapter.getDefaultAdapter()
                 .getRemoteDevice(TEST_REMOTE_DEVICE_ADDRESS);
         bleManagerCallback.onRemoteDeviceConnected(bluetoothDevice);
-        return mCarBlePeripheralManager.getConnectedDeviceChannel();
-    }
-
-    private boolean tryAcquire(Semaphore semaphore) throws InterruptedException {
-        return semaphore.tryAcquire(100, TimeUnit.MILLISECONDS);
+        return (AssociationSecureChannel) mCarBlePeripheralManager.getConnectedDeviceChannel();
     }
 
     private String getNameForAssociation() {
         return ByteUtils.generateRandomNumberString(DEVICE_NAME_LENGTH_LIMIT);
 
     }
-
-    @NonNull
-    private AssociationCallback createAssociationCallback(@NonNull final Semaphore semaphore) {
-        return spy(new AssociationCallback() {
-            @Override
-            public void onAssociationStartSuccess(String deviceName) {
-                semaphore.release();
-            }
-            @Override
-            public void onAssociationStartFailure() {
-                semaphore.release();
-            }
-
-            @Override
-            public void onAssociationError(int error) {
-                semaphore.release();
-            }
-
-            @Override
-            public void onVerificationCodeAvailable(String code) {
-                semaphore.release();
-            }
-
-            @Override
-            public void onAssociationCompleted(String deviceId) {
-                semaphore.release();
-            }
-        });
-    }
 }
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/SecureBleChannelTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/SecureBleChannelTest.java
index 2960e49..e8f4da4 100644
--- a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/SecureBleChannelTest.java
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/SecureBleChannelTest.java
@@ -16,206 +16,194 @@
 
 package com.android.car.connecteddevice.ble;
 
+import static com.android.car.connecteddevice.BleStreamProtos.BleOperationProto.OperationType.CLIENT_MESSAGE;
+import static com.android.car.connecteddevice.BleStreamProtos.BleOperationProto.OperationType.ENCRYPTION_HANDSHAKE;
+import static com.android.car.connecteddevice.ble.SecureBleChannel.CHANNEL_ERROR_INVALID_HANDSHAKE;
+import static com.android.car.connecteddevice.ble.SecureBleChannel.Callback;
+
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyString;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mockitoSession;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.car.encryptionrunner.DummyEncryptionRunner;
 import android.car.encryptionrunner.EncryptionRunnerFactory;
+import android.car.encryptionrunner.HandshakeException;
+import android.car.encryptionrunner.Key;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.car.connecteddevice.BleStreamProtos.BleOperationProto.OperationType;
-import com.android.car.connecteddevice.ble.BleDeviceMessageStream.MessageReceivedListener;
-import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
 import com.android.car.connecteddevice.util.ByteUtils;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
+import java.security.NoSuchAlgorithmException;
+import java.security.SignatureException;
 import java.util.UUID;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
-public final class SecureBleChannelTest {
-    private static final UUID CLIENT_DEVICE_ID =
-            UUID.fromString("a5645523-3280-410a-90c1-582a6c6f4969");
-    private static final UUID SERVER_DEVICE_ID =
-            UUID.fromString("a29f0c74-2014-4b14-ac02-be6ed15b545a");
+public class SecureBleChannelTest {
 
-    private SecureBleChannel mChannel;
-    private MessageReceivedListener mMessageReceivedListener;
+    @Mock private BleDeviceMessageStream mMockStream;
 
-    @Mock private BleDeviceMessageStream mStreamMock;
-    @Mock private ConnectedDeviceStorage mStorageMock;
-    @Mock private SecureBleChannel.ShowVerificationCodeListener mShowVerificationCodeListenerMock;
+    @Mock private Key mKey = spy(new Key() {
+        @Override
+        public byte[] asBytes() {
+            return new byte[0];
+        }
+
+        @Override
+        public byte[] encryptData(byte[] data) {
+            return data;
+        }
+
+        @Override
+        public byte[] decryptData(byte[] encryptedData) throws SignatureException {
+            return encryptedData;
+        }
+
+        @Override
+        public byte[] getUniqueSession() throws NoSuchAlgorithmException {
+            return new byte[0];
+        }
+    });
+
+    private MockitoSession mMockitoSession;
+
+    private SecureBleChannel mSecureBleChannel;
 
     @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mStorageMock.getUniqueId()).thenReturn(SERVER_DEVICE_ID);
+    public void setUp() throws SignatureException {
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.WARN)
+                .startMocking();
+
+        mSecureBleChannel = new SecureBleChannel(mMockStream,
+                EncryptionRunnerFactory.newDummyRunner()) {
+            @Override
+            void processHandshake(byte[] message) { }
+        };
+        mSecureBleChannel.setEncryptionKey(mKey);
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
     }
 
     @Test
-    public void testEncryptionHandshake_Association() throws InterruptedException {
-        Semaphore semaphore = new Semaphore(0);
-        ChannelCallback callbackSpy = spy(new ChannelCallback(semaphore));
-        setUpSecureBleChannel_Association(callbackSpy);
-        ArgumentCaptor<String> deviceIdCaptor = ArgumentCaptor.forClass(String.class);
-        ArgumentCaptor<DeviceMessage> messageCaptor =
-                ArgumentCaptor.forClass(DeviceMessage.class);
-
-        sendDeviceId();
-        assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
-        verify(callbackSpy).onDeviceIdReceived(deviceIdCaptor.capture());
-        verify(mStreamMock).writeMessage(messageCaptor.capture(), any());
-        byte[] deviceIdMessage = messageCaptor.getValue().getMessage();
-        assertThat(deviceIdMessage).isEqualTo(ByteUtils.uuidToBytes(SERVER_DEVICE_ID));
-        assertThat(deviceIdCaptor.getValue()).isEqualTo(CLIENT_DEVICE_ID.toString());
-
-        initHandshakeMessage();
-        verify(mStreamMock, times(2)).writeMessage(messageCaptor.capture(), any());
-        byte[] response = messageCaptor.getValue().getMessage();
-        assertThat(response).isEqualTo(DummyEncryptionRunner.INIT_RESPONSE.getBytes());
-
-        respondToContinueMessage();
-        verify(mShowVerificationCodeListenerMock).showVerificationCode(anyString());
-
-        mChannel.notifyOutOfBandAccepted();
-        verify(mStreamMock, times(3)).writeMessage(messageCaptor.capture(), any());
-        byte[] confirmMessage = messageCaptor.getValue().getMessage();
-        assertThat(confirmMessage).isEqualTo(SecureBleChannel.CONFIRMATION_SIGNAL);
-        assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
-        verify(callbackSpy).onSecureChannelEstablished();
+    public void processMessage_doesNothingForUnencryptedMessage() throws SignatureException {
+        byte[] payload = ByteUtils.randomBytes(10);
+        DeviceMessage message = new DeviceMessage(UUID.randomUUID(), /* isEncrypted= */ false,
+                payload);
+        mSecureBleChannel.processMessage(message);
+        assertThat(message.getMessage()).isEqualTo(payload);
+        verify(mKey, times(0)).decryptData(any());
     }
 
     @Test
-    public void testEncryptionHandshake_Association_wrongInitHandshakeMessage()
+    public void processMessage_decryptsEncryptedMessage() throws SignatureException {
+        byte[] payload = ByteUtils.randomBytes(10);
+        DeviceMessage message = new DeviceMessage(UUID.randomUUID(), /* isEncrypted= */ true,
+                payload);
+        mSecureBleChannel.processMessage(message);
+        verify(mKey).decryptData(any());
+    }
+
+    @Test
+    public void processMessage_onMessageReceivedErrorForEncryptedMessageWithNoKey()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        ChannelCallback callbackSpy = spy(new ChannelCallback(semaphore));
-        setUpSecureBleChannel_Association(callbackSpy);
+        DeviceMessage message = new DeviceMessage(UUID.randomUUID(), /* isEncrypted= */ true,
+                ByteUtils.randomBytes(10));
 
-        sendDeviceId();
-        assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
-
-        // Wrong init handshake message
-        respondToContinueMessage();
-        assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
-        verify(callbackSpy).onEstablishSecureChannelFailure(
-                eq(SecureBleChannel.CHANNEL_ERROR_INVALID_HANDSHAKE)
-        );
+        mSecureBleChannel.setEncryptionKey(null);
+        mSecureBleChannel.registerCallback(new Callback() {
+            @Override
+            public void onMessageReceivedError(Exception exception) {
+                semaphore.release();
+            }
+        });
+        mSecureBleChannel.processMessage(message);
+        assertThat(tryAcquire(semaphore)).isTrue();
+        assertThat(message.getMessage()).isNull();
     }
 
     @Test
-    public void testEncryptionHandshake_Association_wrongRespondToContinueMessage()
+    public void onMessageReceived_onEstablishSecureChannelFailureBadHandshakeMessage()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        ChannelCallback callbackSpy = spy(new ChannelCallback(semaphore));
-        setUpSecureBleChannel_Association(callbackSpy);
+        DeviceMessage message = new DeviceMessage(UUID.randomUUID(), /* isEncrypted= */ true,
+                ByteUtils.randomBytes(10));
 
-        sendDeviceId();
-        assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
-
-        initHandshakeMessage();
-
-        // Wrong respond to continue message
-        initHandshakeMessage();
-        assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
-        verify(callbackSpy).onEstablishSecureChannelFailure(
-                eq(SecureBleChannel.CHANNEL_ERROR_INVALID_HANDSHAKE)
-        );
+        mSecureBleChannel.setEncryptionKey(null);
+        mSecureBleChannel.registerCallback(new Callback() {
+            @Override
+            public void onEstablishSecureChannelFailure(int error) {
+                assertThat(error).isEqualTo(CHANNEL_ERROR_INVALID_HANDSHAKE);
+                semaphore.release();
+            }
+        });
+        mSecureBleChannel.onMessageReceived(message, ENCRYPTION_HANDSHAKE);
+        assertThat(tryAcquire(semaphore)).isTrue();
     }
 
-    private void setUpSecureBleChannel_Association(ChannelCallback callback) {
-        mChannel = new SecureBleChannel(
-                mStreamMock,
-                mStorageMock,
-                /* isReconnect = */ false,
-                EncryptionRunnerFactory.newDummyRunner()
-        );
-        mChannel.registerCallback(callback);
-        mChannel.setShowVerificationCodeListener(mShowVerificationCodeListenerMock);
-        ArgumentCaptor<MessageReceivedListener> listenerCaptor =
-                ArgumentCaptor.forClass(MessageReceivedListener.class);
-        verify(mStreamMock).setMessageReceivedListener(listenerCaptor.capture());
-        mMessageReceivedListener = listenerCaptor.getValue();
+    @Test
+    public void onMessageReceived_onMessageReceivedNotIssuedForNullMessage()
+            throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        DeviceMessage message = new DeviceMessage(UUID.randomUUID(), /* isEncrypted= */ false,
+                /* message= */ null);
+
+        mSecureBleChannel.registerCallback(new Callback() {
+            @Override
+            public void onMessageReceived(DeviceMessage message) {
+                semaphore.release();
+            }
+        });
+        mSecureBleChannel.onMessageReceived(message, CLIENT_MESSAGE);
+        assertThat(tryAcquire(semaphore)).isFalse();
     }
 
-    private void sendDeviceId() {
-        DeviceMessage message = new DeviceMessage(
-                /* recipient = */ null,
-                /* isMessageEncrypted = */ false,
-                ByteUtils.uuidToBytes(CLIENT_DEVICE_ID)
-        );
-        mMessageReceivedListener.onMessageReceived(message, OperationType.ENCRYPTION_HANDSHAKE);
+    @Test
+    public void onMessageReceived_processHandshakeExceptionIssuesSecureChannelFailureCallback()
+            throws InterruptedException {
+        SecureBleChannel secureChannel = new SecureBleChannel(mMockStream,
+                EncryptionRunnerFactory.newDummyRunner()) {
+            @Override
+            void processHandshake(byte[] message) throws HandshakeException {
+                DummyEncryptionRunner.throwHandshakeException("test");
+            }
+        };
+        Semaphore semaphore = new Semaphore(0);
+        secureChannel.registerCallback(new Callback() {
+            @Override
+            public void onEstablishSecureChannelFailure(int error) {
+                semaphore.release();
+            }
+        });
+        DeviceMessage message = new DeviceMessage(UUID.randomUUID(), /* isEncrypted= */ true,
+                /* message= */ ByteUtils.randomBytes(10));
+
+        secureChannel.onMessageReceived(message, ENCRYPTION_HANDSHAKE);
+        assertThat(tryAcquire(semaphore)).isTrue();
     }
 
-    private void initHandshakeMessage() {
-        DeviceMessage message = new DeviceMessage(
-                /* recipient = */ null,
-                /* isMessageEncrypted = */ false,
-                DummyEncryptionRunner.INIT.getBytes()
-        );
-        mMessageReceivedListener.onMessageReceived(message, OperationType.ENCRYPTION_HANDSHAKE);
-    }
-
-    private void respondToContinueMessage() {
-        DeviceMessage message = new DeviceMessage(
-                /* recipient = */ null,
-                /* isMessageEncrypted = */ false,
-                DummyEncryptionRunner.CLIENT_RESPONSE.getBytes()
-        );
-        mMessageReceivedListener.onMessageReceived(message, OperationType.ENCRYPTION_HANDSHAKE);
-    }
-
-    /**
-     * Add the thread control logic into {@link SecureBleChannel.Callback} only for spy purpose.
-     *
-     * <p>The callback will release the semaphore which hold by one test when this callback
-     * is called, telling the test that it can verify certain behaviors which will only occurred
-     * after the callback is notified. This is needed mainly because of the callback is notified
-     * in a different thread.
-     */
-    class ChannelCallback implements SecureBleChannel.Callback {
-        private final Semaphore mSemaphore;
-        ChannelCallback(Semaphore semaphore) {
-            mSemaphore = semaphore;
-        }
-        @Override
-        public void onSecureChannelEstablished() {
-            mSemaphore.release();
-        }
-
-        @Override
-        public void onEstablishSecureChannelFailure(int error) {
-            mSemaphore.release();
-        }
-
-        @Override
-        public void onMessageReceived(DeviceMessage deviceMessage) {
-            mSemaphore.release();
-        }
-
-        @Override
-        public void onMessageReceivedError(Exception exception) {
-            mSemaphore.release();
-        }
-
-        @Override
-        public void onDeviceIdReceived(String deviceId) {
-            mSemaphore.release();
-        }
+    private boolean tryAcquire(Semaphore semaphore) throws InterruptedException {
+        return semaphore.tryAcquire(100, TimeUnit.MILLISECONDS);
     }
 }
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/storage/ConnectedDeviceStorageTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/storage/ConnectedDeviceStorageTest.java
index 9547bfb..755ce64 100644
--- a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/storage/ConnectedDeviceStorageTest.java
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/storage/ConnectedDeviceStorageTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.testng.Assert.assertThrows;
+
 import android.content.Context;
 import android.util.Pair;
 
@@ -32,6 +34,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.security.InvalidParameterException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
@@ -126,6 +129,15 @@
         assertThat(mConnectedDeviceStorage.getEncryptionKey(UUID.randomUUID().toString())).isNull();
     }
 
+    @Test
+    public void saveChallengeSecret_throwsForInvalidLengthSecret() {
+        byte[] invalidSecret =
+                ByteUtils.randomBytes(ConnectedDeviceStorage.CHALLENGE_SECRET_BYTES - 1);
+        assertThrows(InvalidParameterException.class,
+                () -> mConnectedDeviceStorage.saveChallengeSecret(UUID.randomUUID().toString(),
+                        invalidSecret));
+    }
+
     private AssociatedDevice addRandomAssociatedDevice(int userId) {
         AssociatedDevice device = new AssociatedDevice(UUID.randomUUID().toString(),
                 "00:00:00:00:00:00", "Test Device", true);
diff --git a/EncryptionRunner/Android.bp b/encryption-runner/Android.bp
similarity index 86%
rename from EncryptionRunner/Android.bp
rename to encryption-runner/Android.bp
index 54316ff..dde4aa8 100644
--- a/EncryptionRunner/Android.bp
+++ b/encryption-runner/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// 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.
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 android_library {
-    name: "EncryptionRunner-lib",
+    name: "encryption-runner",
     min_sdk_version: "23",
     product_variables: {
         pdk: {
@@ -21,6 +21,7 @@
         },
     },
     static_libs: [
+      "androidx.annotation_annotation",
       "ukey2",
     ],
     srcs: [
diff --git a/EncryptionRunner/AndroidManifest.xml b/encryption-runner/AndroidManifest.xml
similarity index 93%
rename from EncryptionRunner/AndroidManifest.xml
rename to encryption-runner/AndroidManifest.xml
index 3ebdf42..0d2f71e 100644
--- a/EncryptionRunner/AndroidManifest.xml
+++ b/encryption-runner/AndroidManifest.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
+<!-- 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.
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
similarity index 95%
rename from EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
rename to encryption-runner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
index 5b63dbc..39b14a6 100644
--- a/EncryptionRunner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
+++ b/encryption-runner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -16,9 +16,8 @@
 
 package android.car.encryptionrunner;
 
-import android.annotation.IntDef;
-
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -175,6 +174,11 @@
         mIsReconnect = isReconnect;
     }
 
+    /** Method to throw a HandshakeException for testing. */
+    public static void throwHandshakeException(String message) throws HandshakeException {
+        throw new HandshakeException(message);
+    }
+
     private class DummyKey implements Key {
         @Override
         public byte[] asBytes() {
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/EncryptionRunner.java
similarity index 98%
rename from EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java
rename to encryption-runner/src/android/car/encryptionrunner/EncryptionRunner.java
index f0a34b2..6a2b6d6 100644
--- a/EncryptionRunner/src/android/car/encryptionrunner/EncryptionRunner.java
+++ b/encryption-runner/src/android/car/encryptionrunner/EncryptionRunner.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -16,7 +16,7 @@
 
 package android.car.encryptionrunner;
 
-import android.annotation.NonNull;
+import androidx.annotation.NonNull;
 
 /**
  * A generalized interface that allows for generating shared secrets as well as encrypting
diff --git a/encryption-runner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java b/encryption-runner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java
new file mode 100644
index 0000000..be6cd68
--- /dev/null
+++ b/encryption-runner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java
@@ -0,0 +1,67 @@
+/*
+ * 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})
+    public @interface EncryptionRunnerType {
+        /** Use Ukey2 as underlying key exchange. */
+        int UKEY2 = 0;
+    }
+
+    /**
+     * Creates a new {@link EncryptionRunner} based on {@param type}.
+     */
+    public static EncryptionRunner newRunner(@EncryptionRunnerType int type) {
+        switch (type) {
+            case EncryptionRunnerType.UKEY2:
+                return new Ukey2EncryptionRunner();
+            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 newDummyRunner() {
+        return new DummyEncryptionRunner();
+    }
+}
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/HandshakeException.java b/encryption-runner/src/android/car/encryptionrunner/HandshakeException.java
similarity index 93%
rename from EncryptionRunner/src/android/car/encryptionrunner/HandshakeException.java
rename to encryption-runner/src/android/car/encryptionrunner/HandshakeException.java
index 185a21c..934a83a 100644
--- a/EncryptionRunner/src/android/car/encryptionrunner/HandshakeException.java
+++ b/encryption-runner/src/android/car/encryptionrunner/HandshakeException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java b/encryption-runner/src/android/car/encryptionrunner/HandshakeMessage.java
similarity index 74%
rename from EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java
rename to encryption-runner/src/android/car/encryptionrunner/HandshakeMessage.java
index fa6705d..a79c64e 100644
--- a/EncryptionRunner/src/android/car/encryptionrunner/HandshakeMessage.java
+++ b/encryption-runner/src/android/car/encryptionrunner/HandshakeMessage.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -16,10 +16,12 @@
 
 package android.car.encryptionrunner;
 
-import android.annotation.IntDef;
-import android.annotation.Nullable;
 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;
 
@@ -34,7 +36,8 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({HandshakeState.UNKNOWN, HandshakeState.IN_PROGRESS, HandshakeState.VERIFICATION_NEEDED,
-            HandshakeState.FINISHED, HandshakeState.INVALID, HandshakeState.RESUMING_SESSION,})
+            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.
@@ -60,6 +63,10 @@
          * 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
@@ -67,6 +74,7 @@
     private final Key mKey;
     private final byte[] mNextMessage;
     private final String mVerificationCode;
+    private final byte[] mOobVerificationCode;
 
     /**
      * @return Returns a builder for {@link HandshakeMessage}.
@@ -82,11 +90,13 @@
             @HandshakeState int handshakeState,
             @Nullable Key key,
             @Nullable byte[] nextMessage,
-            @Nullable String verificationCode) {
+            @Nullable String verificationCode,
+            @Nullable byte[] oobVerificationCode) {
         mHandshakeState = handshakeState;
         mKey = key;
         mNextMessage = nextMessage;
         mVerificationCode = verificationCode;
+        mOobVerificationCode = oobVerificationCode;
     }
 
     /**
@@ -121,12 +131,22 @@
         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;
@@ -148,6 +168,11 @@
             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");
@@ -155,9 +180,15 @@
             if (mHandshakeState == HandshakeState.VERIFICATION_NEEDED
                     && TextUtils.isEmpty(mVerificationCode)) {
                 throw new IllegalStateException(
-                        "if state is verification needed, must have verification code");
+                        "State is verification needed, but verification code null.");
             }
-            return new HandshakeMessage(mHandshakeState, mKey, mNextMessage, mVerificationCode);
+            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/EncryptionRunner/src/android/car/encryptionrunner/Key.java b/encryption-runner/src/android/car/encryptionrunner/Key.java
similarity index 94%
rename from EncryptionRunner/src/android/car/encryptionrunner/Key.java
rename to encryption-runner/src/android/car/encryptionrunner/Key.java
index 2e32858..e80be25 100644
--- a/EncryptionRunner/src/android/car/encryptionrunner/Key.java
+++ b/encryption-runner/src/android/car/encryptionrunner/Key.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -16,7 +16,7 @@
 
 package android.car.encryptionrunner;
 
-import android.annotation.NonNull;
+import androidx.annotation.NonNull;
 
 import java.security.NoSuchAlgorithmException;
 import java.security.SignatureException;
diff --git a/EncryptionRunner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
similarity index 97%
rename from EncryptionRunner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
rename to encryption-runner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
index 904d5c2..ac1bf91 100644
--- a/EncryptionRunner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
+++ b/encryption-runner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -16,12 +16,12 @@
 
 package android.car.encryptionrunner;
 
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
+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;
@@ -323,6 +323,14 @@
         }
     }
 
+    protected final Ukey2Handshake getUkey2Client() {
+        return mUkey2client;
+    }
+
+    protected final boolean isReconnect() {
+        return mIsReconnect;
+    }
+
     @HandshakeMessage.HandshakeState
     private int getHandshakeState() {
         checkInitialized();
@@ -362,7 +370,7 @@
         return (UKey2Key) key;
     }
 
-    private void checkInitialized() {
+    protected void checkInitialized() {
         if (mUkey2client == null) {
             throw new IllegalStateException("runner not initialized");
         }