[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: 60c8b8a103 -s ours am: c9f5e64f0e -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: I3f17039fdee40e11da8dfe2575e1bffdbed1c4ac
diff --git a/car-apps-common/Android.bp b/car-apps-common/Android.bp
index ef94de8..4f9ef51 100644
--- a/car-apps-common/Android.bp
+++ b/car-apps-common/Android.bp
@@ -25,8 +25,10 @@
         enabled: false,
     },
 
-    libs: ["android.car-stubs"],
+    libs: ["android.car-stubs",],
+
     sdk_version: "system_current",
+    min_sdk_version: "28",
 
     static_libs: [
         "androidx.annotation_annotation",
diff --git a/car-ui-lib/res/color/car_ui_text_color_primary.xml b/car-apps-common/res/color/primary_text_color.xml
similarity index 100%
copy from car-ui-lib/res/color/car_ui_text_color_primary.xml
copy to car-apps-common/res/color/primary_text_color.xml
diff --git a/car-ui-lib/res/color/car_ui_text_color_secondary.xml b/car-apps-common/res/color/secondary_text_color.xml
similarity index 100%
copy from car-ui-lib/res/color/car_ui_text_color_secondary.xml
copy to car-apps-common/res/color/secondary_text_color.xml
diff --git a/car-apps-common/res/color/uxr_button_text_color_selector.xml b/car-apps-common/res/color/uxr_button_text_color_selector.xml
deleted file mode 100644
index 08b64e1..0000000
--- a/car-apps-common/res/color/uxr_button_text_color_selector.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.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:app="http://schemas.android.com/apk/res-auto">
-    <item app:state_ux_restricted="true" android:color="@color/uxr_button_text_disabled_color"/>
-    <item app:state_ux_restricted="false" android:color="@color/uxr_button_text_color"/>
-</selector>
diff --git a/car-apps-common/res/drawable/control_bar_button_background.xml b/car-apps-common/res/drawable/control_bar_button_background.xml
index 7009ecd..09bd38a 100644
--- a/car-apps-common/res/drawable/control_bar_button_background.xml
+++ b/car-apps-common/res/drawable/control_bar_button_background.xml
@@ -17,10 +17,23 @@
   ~
  -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:state_pressed="true">
+        <shape android:shape="oval">
+            <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
+            <stroke android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
+                    android:color="@color/car_ui_rotary_focus_pressed_stroke_color" />
+            <size android:width="@dimen/control_bar_button_background_radius"
+                  android:height="@dimen/control_bar_button_background_radius"/>
+        </shape>
+    </item>
     <item android:state_focused="true">
-        <ripple android:color="@color/car_ui_rotary_focus_color"
-                android:radius="@dimen/control_bar_button_background_radius">
-        </ripple>
+        <shape android:shape="oval">
+            <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
+            <stroke android:width="@dimen/car_ui_rotary_focus_stroke_width"
+                    android:color="@color/car_ui_rotary_focus_stroke_color" />
+            <size android:width="@dimen/control_bar_button_background_radius"
+                  android:height="@dimen/control_bar_button_background_radius"/>
+        </shape>
     </item>
     <item>
         <ripple android:color="@color/control_bar_button_background_color"
diff --git a/car-apps-common/res/drawable/hero_button_background.xml b/car-apps-common/res/drawable/hero_button_background.xml
index 88177cd..e5aeec5 100644
--- a/car-apps-common/res/drawable/hero_button_background.xml
+++ b/car-apps-common/res/drawable/hero_button_background.xml
@@ -13,12 +13,31 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 -->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@color/car_card_ripple_background">
-    <item>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:state_pressed="true">
         <shape android:shape="rectangle">
-            <solid android:color="@color/hero_button_background_color" />
+            <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
+            <stroke android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
+                    android:color="@color/car_ui_rotary_focus_pressed_stroke_color" />
             <corners android:radius="@dimen/hero_button_corner_radius"/>
         </shape>
     </item>
-</ripple>
+    <item android:state_focused="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
+            <stroke android:width="@dimen/car_ui_rotary_focus_stroke_width"
+                    android:color="@color/car_ui_rotary_focus_stroke_color" />
+            <corners android:radius="@dimen/hero_button_corner_radius"/>
+        </shape>
+    </item>
+    <item>
+        <ripple android:color="@color/car_card_ripple_background">
+            <item>
+                <shape android:shape="rectangle">
+                    <solid android:color="@color/hero_button_background_color" />
+                    <corners android:radius="@dimen/hero_button_corner_radius"/>
+                </shape>
+            </item>
+        </ripple>
+    </item>
+</selector>
diff --git a/car-apps-common/res/values-h600dp/dimens.xml b/car-apps-common/res/values-h600dp/dimens.xml
deleted file mode 100644
index dcbea47..0000000
--- a/car-apps-common/res/values-h600dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright 2019, The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
--->
-<resources>
-    <dimen name="control_bar_button_background_radius">48dp</dimen>
-    <dimen name="control_bar_button_size">96dp</dimen>
-    <dimen name="control_bar_button_padding">26dp</dimen>
-    <dimen name="minimized_control_bar_button_size">96dp</dimen>
-</resources>
diff --git a/car-apps-common/res/values-night/colors.xml b/car-apps-common/res/values-night/colors.xml
index 6a2f4cf..a0a9c3a 100644
--- a/car-apps-common/res/values-night/colors.xml
+++ b/car-apps-common/res/values-night/colors.xml
@@ -21,9 +21,6 @@
     <color name="scrim_overlay_color">#D6000000</color>
     <color name="minimized_control_bar_background_color">#E00E1013</color>
 
-    <color name="primary_text_color">#E0FFFFFF</color>
-    <color name="secondary_text_color">#99FFFFFF</color>
-
     <color name="primary_app_icon_color">#E0FFFFFF</color>
     <color name="secondary_app_icon_color">#99FFFFFF</color>
 
diff --git a/car-apps-common/res/values-w1280dp/styles.xml b/car-apps-common/res/values-w1280dp/styles.xml
index c810097..71af982 100644
--- a/car-apps-common/res/values-w1280dp/styles.xml
+++ b/car-apps-common/res/values-w1280dp/styles.xml
@@ -20,4 +20,9 @@
         <item name="android:layout_width">@dimen/control_bar_width</item>
         <item name="android:layout_height">wrap_content</item>
     </style>
+
+    <style name="MinimizedControlBar">
+        <item name="android:layout_width">@dimen/control_bar_width</item>
+        <item name="android:layout_height">@dimen/minimized_control_bar_height</item>
+    </style>
 </resources>
diff --git a/car-apps-common/res/values/colors.xml b/car-apps-common/res/values/colors.xml
index 6cf7ba7..45a0a18 100644
--- a/car-apps-common/res/values/colors.xml
+++ b/car-apps-common/res/values/colors.xml
@@ -38,7 +38,7 @@
     <color name="improper_image_refs_tint_color">#C8FF0000</color>
 
     <color name="control_bar_background_color">@android:color/transparent</color>
-    <color name="minimized_control_bar_background_color">#F50E1013</color>
+    <color name="minimized_control_bar_background_color">#D60E1013</color>
     <color name="scrim_overlay_color">#C7000000</color>
     <color name="app_bar_background_color">#E0000000</color>
     <color name="icon_tint">@color/car_grey_50</color>
@@ -50,9 +50,6 @@
     <color name="car_tab_unselected_color_dark">#80FFFFFF</color>
     <color name="car_tab_unselected_color_light">#90FFFFFF</color>
 
-    <color name="primary_text_color">#FFFFFFFF</color>
-    <color name="secondary_text_color">#B8FFFFFF</color>
-
     <color name="primary_app_icon_color">#FFFFFFFF</color>
     <color name="secondary_app_icon_color">#B8FFFFFF</color>
     <color name="car_card_ripple_background">#17000000</color>
@@ -60,14 +57,12 @@
     <color name="background_image_30p_black">#4D000000</color>
 
     <color name="uxr_button_image_color">@color/primary_app_icon_color</color>
-    <color name="uxr_button_text_color">@color/primary_text_color</color>
     <color name="uxr_button_image_disabled_color">#80FFFFFF</color>
-    <color name="uxr_button_text_disabled_color">#80FFFFFF</color>
 
     <color name="control_bar_button_background_color">#66ffffff</color>
 
     <color name="hero_button_background_color">@color/car_grey_868</color>
-    <color name="hero_button_text_color">@color/uxr_button_text_color_selector</color>
+    <color name="hero_button_text_color">@color/primary_text_color</color>
 
 
 
diff --git a/car-apps-common/res/values/dimens.xml b/car-apps-common/res/values/dimens.xml
index 85f6d90..c87fa6b 100644
--- a/car-apps-common/res/values/dimens.xml
+++ b/car-apps-common/res/values/dimens.xml
@@ -26,12 +26,12 @@
     <dimen name="control_bar_height">128dp</dimen>
     <dimen name="control_bar_margin_x">@dimen/car_ui_margin</dimen>
     <dimen name="control_bar_margin_bottom">@dimen/car_ui_padding_2</dimen>
-    <dimen name="control_bar_button_size">76dp</dimen>
+    <dimen name="control_bar_button_size">104dp</dimen>
     <dimen name="control_bar_button_slot_height">@dimen/control_bar_height</dimen>
     <dimen name="control_bar_button_slot_width">@dimen/control_bar_button_size</dimen>
     <dimen name="control_bar_elevation">0dp</dimen>
-    <dimen name="control_bar_button_padding">16dp</dimen>
-    <dimen name="control_bar_button_background_radius">38dp</dimen>
+    <dimen name="control_bar_button_padding">30dp</dimen>
+    <dimen name="control_bar_button_background_radius">52dp</dimen>
 
     <!-- Overflow button in control Bar -->
     <dimen name="overflow_button_icon_size">44dp</dimen>
@@ -46,7 +46,7 @@
     <dimen name="minimized_control_bar_edge_padding">@dimen/car_ui_padding_4</dimen>
     <dimen name="minimized_control_bar_text_padding">@dimen/car_ui_padding_3</dimen>
     <dimen name="minimized_control_bar_button_padding">@dimen/car_ui_padding_5</dimen>
-    <dimen name="minimized_control_bar_button_size">76dp</dimen>
+    <dimen name="minimized_control_bar_button_size">104dp</dimen>
 
     <!-- Tabs -->
     <dimen name="car_tab_width">135dp</dimen>
diff --git a/car-apps-common/src/com/android/car/apps/common/ControlBar.java b/car-apps-common/src/com/android/car/apps/common/ControlBar.java
index 5d6d76c..4181867 100644
--- a/car-apps-common/src/com/android/car/apps/common/ControlBar.java
+++ b/car-apps-common/src/com/android/car/apps/common/ControlBar.java
@@ -16,6 +16,8 @@
 
 package com.android.car.apps.common;
 
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
@@ -29,6 +31,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
@@ -41,6 +44,8 @@
 import androidx.core.util.Preconditions;
 import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
 
+import com.android.car.apps.common.util.ViewUtils;
+
 import java.util.Locale;
 
 
@@ -88,6 +93,10 @@
     private boolean mExpandEnabled;
     // Callback for the expand/collapse button
     private ExpandCollapseCallback mExpandCollapseCallback;
+    // The root of the transition animation.
+    private ViewGroup mTransitionRoot;
+    // Whether this control bar has focus.
+    private boolean mHasFocus;
 
     // Default number of columns, if unspecified
     private static final int DEFAULT_COLUMNS = 3;
@@ -158,6 +167,15 @@
         mDefaultExpandCollapseView.setContentDescription(context.getString(
                 R.string.control_bar_expand_collapse_button));
         mDefaultExpandCollapseView.setOnClickListener(v -> onExpandCollapse());
+
+        // Collapse the control bar when it is expanded and loses focus.
+        getViewTreeObserver().addOnGlobalFocusChangeListener((oldFocus, newFocus) -> {
+            boolean hasFocus = hasFocus();
+            if (mHasFocus && !hasFocus && mIsExpanded) {
+                onExpandCollapse();
+            }
+            mHasFocus = hasFocus;
+        });
     }
 
     private int getSlotIndex(@SlotPosition int slotPosition) {
@@ -258,7 +276,7 @@
                 viewToUse = mViews[viewsIndex];
                 viewsIndex++;
             }
-            setView(viewToUse, mSlots[i]);
+            ViewUtils.setView(viewToUse, mSlots[i]);
             if (viewToUse != null) {
                 lastUsedIndex = i;
             }
@@ -290,31 +308,6 @@
         }
     }
 
-    private void setView(@Nullable View view, FrameLayout container) {
-        if (view != null) {
-            // Don't set the view if it stays the same.
-            if (container.getChildCount() == 1 && container.getChildAt(0) == view) {
-                return;
-            }
-
-            ViewGroup parent = (ViewGroup) view.getParent();
-            // As we are removing views (on BT disconnect, for example), some items will be
-            // shifting from expanded to collapsed (like Queue item) - remove those from the
-            // group before adding to the new slot
-            if (view.getParent() != null) {
-                parent.removeView(view);
-            }
-            container.removeAllViews();
-            container.addView(view);
-            container.setVisibility(VISIBLE);
-        } else {
-            if (container.getChildCount() != 0) {
-                container.removeAllViews();
-            }
-            container.setVisibility(INVISIBLE);
-        }
-    }
-
     private void onExpandCollapse() {
         mIsExpanded = !mIsExpanded;
         if (mExpandCollapseView != null) {
@@ -333,12 +326,34 @@
                 .addTransition(new Fade())
                 .setDuration(animationDuration)
                 .setInterpolator(new FastOutSlowInInterpolator());
-        TransitionManager.beginDelayedTransition(this, set);
+        maybeInitTransitionRoot();
+        TransitionManager.beginDelayedTransition(mTransitionRoot, set);
         for (int i = 0; i < mNumExtraRowsInUse; i++) {
             mRowsContainer.getChildAt(i).setVisibility(mIsExpanded ? View.VISIBLE : View.GONE);
         }
     }
 
+    private void maybeInitTransitionRoot() {
+        if (mTransitionRoot != null) {
+            return;
+        }
+        // During the control bar expanding/collapsing animation, the height of the control bar
+        // changes gradually. If the height of its ancestor is WRAP_CONTENT, the height of its
+        // ancestor will not change during the animation, causing janky animation. To fix it the
+        // animation should be played on the highest ancestor that wraps the control bar vertically.
+        mTransitionRoot = this;
+        ViewParent viewParent = getParent();
+        while (viewParent != null && viewParent instanceof ViewGroup) {
+            ViewGroup parent = (ViewGroup) viewParent;
+            if (parent.getLayoutParams().height == WRAP_CONTENT) {
+                mTransitionRoot = parent;
+                viewParent = parent.getParent();
+            } else {
+                break;
+            }
+        }
+    }
+
     /**
      * Returns the view assigned to the given row and column, after layout.
      *
diff --git a/car-apps-common/src/com/android/car/apps/common/MinimizedControlBar.java b/car-apps-common/src/com/android/car/apps/common/MinimizedControlBar.java
index 7158f44..2eeb1b8 100644
--- a/car-apps-common/src/com/android/car/apps/common/MinimizedControlBar.java
+++ b/car-apps-common/src/com/android/car/apps/common/MinimizedControlBar.java
@@ -21,7 +21,6 @@
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.ImageView;
@@ -30,6 +29,8 @@
 import androidx.annotation.Nullable;
 import androidx.constraintlayout.widget.ConstraintLayout;
 
+import com.android.car.apps.common.util.ViewUtils;
+
 /**
  * This is a compact CarControlBar that provides a fixed number of controls (with no overflow),
  * along with some metadata (title, subtitle, icon)
@@ -122,25 +123,7 @@
                 viewToUse = mViews[viewIndex];
                 viewIndex++;
             }
-            setView(viewToUse, mSlots[CarControlBar.getSlotIndex(i, NUM_COLUMNS)]);
+            ViewUtils.setView(viewToUse, mSlots[CarControlBar.getSlotIndex(i, NUM_COLUMNS)]);
         }
     }
-
-    private void setView(@Nullable View view, FrameLayout container) {
-        container.removeAllViews();
-        if (view != null) {
-            ViewGroup parent = (ViewGroup) view.getParent();
-            // As we are removing views (on BT disconnect, for example), some items will be
-            // shifting from expanded to collapsed - remove those from the group before adding to
-            // the new slot
-            if (view.getParent() != null) {
-                parent.removeView(view);
-            }
-            container.addView(view);
-            container.setVisibility(VISIBLE);
-        } else {
-            container.setVisibility(INVISIBLE);
-        }
-    }
-
 }
diff --git a/car-apps-common/src/com/android/car/apps/common/util/ViewUtils.java b/car-apps-common/src/com/android/car/apps/common/util/ViewUtils.java
index e067573..a6cc870 100644
--- a/car-apps-common/src/com/android/car/apps/common/util/ViewUtils.java
+++ b/car-apps-common/src/com/android/car/apps/common/util/ViewUtils.java
@@ -16,11 +16,16 @@
 
 package com.android.car.apps.common.util;
 
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
@@ -100,7 +105,7 @@
                 .setListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationStart(Animator animation) {
-                        view.setVisibility(View.VISIBLE);
+                        view.setVisibility(VISIBLE);
                     }
                 })
                 .alpha(1f);
@@ -123,7 +128,7 @@
     /** Sets the visibility of the (optional) view to {@link View#VISIBLE} or {@link View#GONE}. */
     public static void setVisible(@Nullable View view, boolean visible) {
         if (view != null) {
-            view.setVisibility(visible ? View.VISIBLE : View.GONE);
+            view.setVisibility(visible ? VISIBLE : View.GONE);
         }
     }
 
@@ -139,7 +144,7 @@
      */
     public static void setInvisible(@Nullable View view, boolean invisible) {
         if (view != null) {
-            view.setVisibility(invisible ? View.INVISIBLE : View.VISIBLE);
+            view.setVisibility(invisible ? INVISIBLE : VISIBLE);
         }
     }
 
@@ -201,4 +206,30 @@
         viewIds.recycle();
         return views;
     }
+
+    /** Adds the {@code view} into the {@code container}. */
+    public static void setView(@Nullable View view, FrameLayout container) {
+        if (view != null) {
+            // Don't set the view if it stays the same.
+            if (container.getChildCount() == 1 && container.getChildAt(0) == view) {
+                return;
+            }
+
+            ViewGroup parent = (ViewGroup) view.getParent();
+            // As we are removing views (on BT disconnect, for example), some items will be
+            // shifting from expanded to collapsed (like Queue item) - remove those from the
+            // group before adding to the new slot
+            if (view.getParent() != null) {
+                parent.removeView(view);
+            }
+            container.removeAllViews();
+            container.addView(view);
+            container.setVisibility(VISIBLE);
+        } else {
+            if (container.getChildCount() != 0) {
+                container.removeAllViews();
+            }
+            container.setVisibility(INVISIBLE);
+        }
+    }
 }
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-broadcastradio-support/res/values-bs/strings.xml b/car-broadcastradio-support/res/values-bs/strings.xml
index 42ff388..e29e90e 100644
--- a/car-broadcastradio-support/res/values-bs/strings.xml
+++ b/car-broadcastradio-support/res/values-bs/strings.xml
@@ -20,5 +20,5 @@
     <string name="radio_fm_text" msgid="1973045042281933494">"FM"</string>
     <string name="radio_dab_text" msgid="8456449462266648979">"DAB"</string>
     <string name="program_list_text" msgid="4414150317304422313">"Stanice"</string>
-    <string name="favorites_list_text" msgid="7829827713977109155">"Omiljeni"</string>
+    <string name="favorites_list_text" msgid="7829827713977109155">"Omiljeno"</string>
 </resources>
diff --git a/car-broadcastradio-support/res/values-is/strings.xml b/car-broadcastradio-support/res/values-is/strings.xml
index 1a2074a..b7e9135 100644
--- a/car-broadcastradio-support/res/values-is/strings.xml
+++ b/car-broadcastradio-support/res/values-is/strings.xml
@@ -20,5 +20,5 @@
     <string name="radio_fm_text" msgid="1973045042281933494">"FM"</string>
     <string name="radio_dab_text" msgid="8456449462266648979">"DAB"</string>
     <string name="program_list_text" msgid="4414150317304422313">"Stöðvar"</string>
-    <string name="favorites_list_text" msgid="7829827713977109155">"Eftirlæti"</string>
+    <string name="favorites_list_text" msgid="7829827713977109155">"Uppáhald"</string>
 </resources>
diff --git a/car-broadcastradio-support/res/values-ky/strings.xml b/car-broadcastradio-support/res/values-ky/strings.xml
index 4640491..ee67f46 100644
--- a/car-broadcastradio-support/res/values-ky/strings.xml
+++ b/car-broadcastradio-support/res/values-ky/strings.xml
@@ -20,5 +20,5 @@
     <string name="radio_fm_text" msgid="1973045042281933494">"FM"</string>
     <string name="radio_dab_text" msgid="8456449462266648979">"DAB"</string>
     <string name="program_list_text" msgid="4414150317304422313">"Станциялар"</string>
-    <string name="favorites_list_text" msgid="7829827713977109155">"Сүйүктүүлөр"</string>
+    <string name="favorites_list_text" msgid="7829827713977109155">"Тандалмалар"</string>
 </resources>
diff --git a/car-broadcastradio-support/res/values-uz/strings.xml b/car-broadcastradio-support/res/values-uz/strings.xml
index 5ff7a60..bfb89d7 100644
--- a/car-broadcastradio-support/res/values-uz/strings.xml
+++ b/car-broadcastradio-support/res/values-uz/strings.xml
@@ -20,5 +20,5 @@
     <string name="radio_fm_text" msgid="1973045042281933494">"FM"</string>
     <string name="radio_dab_text" msgid="8456449462266648979">"Raqamli radio"</string>
     <string name="program_list_text" msgid="4414150317304422313">"Radiostansiyalar"</string>
-    <string name="favorites_list_text" msgid="7829827713977109155">"Saralanganlar"</string>
+    <string name="favorites_list_text" msgid="7829827713977109155">"Saralangan"</string>
 </resources>
diff --git a/car-media-common/res/drawable/seekbar_background.xml b/car-media-common/res/drawable/seekbar_background.xml
deleted file mode 100644
index fcd06a2..0000000
--- a/car-media-common/res/drawable/seekbar_background.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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.
--->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:id="@android:id/background"
-        android:drawable="@android:color/transparent" />
-
-    <item android:id="@android:id/secondaryProgress">
-        <scale android:scaleWidth="100%"
-            android:drawable="@android:color/transparent" />
-    </item>
-
-    <item android:id="@android:id/progress">
-        <scale android:scaleWidth="100%"
-            android:drawable="@drawable/progressbar" />
-    </item>
-
-</layer-list>
diff --git a/car-media-common/res/layout/minimized_play_pause_stop_button_layout.xml b/car-media-common/res/layout/minimized_play_pause_stop_button_layout.xml
new file mode 100644
index 0000000..1d70c72
--- /dev/null
+++ b/car-media-common/res/layout/minimized_play_pause_stop_button_layout.xml
@@ -0,0 +1,40 @@
+<?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.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/play_pause_container"
+    android:focusable="false"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <com.android.car.media.common.PlayPauseStopImageView
+        android:id="@+id/play_pause_stop"
+        style="@style/Widget.ActionButton"
+        android:src="@drawable/ic_play_pause_stop_animated"/>
+    <ProgressBar
+        android:id="@+id/circular_progress_bar"
+        android:layout_width="@dimen/fab_spinner_size"
+        android:layout_height="@dimen/fab_spinner_size"
+        android:layout_gravity="center"
+        android:padding="9dp"
+        android:indeterminateDrawable="@drawable/music_buffering"
+        android:indeterminateTint="@color/fab_spinner_indeterminate_color"
+        android:progressDrawable="@drawable/circular_progress_bar"
+        android:progressTint="@color/minimized_progress_bar_highlight"
+        android:progressBackgroundTint="@color/minimized_progress_bar_background"
+        android:focusable="false"
+        android:indeterminateOnly="false"/>
+</FrameLayout>
diff --git a/car-media-common/res/layout/play_pause_stop_button_layout.xml b/car-media-common/res/layout/play_pause_stop_button_layout.xml
index f7700fe..f61a821 100644
--- a/car-media-common/res/layout/play_pause_stop_button_layout.xml
+++ b/car-media-common/res/layout/play_pause_stop_button_layout.xml
@@ -20,11 +20,9 @@
     android:focusable="false"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content">
-    <!-- The invisible foreground ripple stops Android O from drawing an ugly square over the play button -->
     <com.android.car.media.common.PlayPauseStopImageView
         android:id="@+id/play_pause_stop"
         style="@style/Widget.ActionButton"
-        android:foreground="@drawable/fab_empty_foreground"
         android:src="@drawable/ic_play_pause_stop_animated"/>
     <ProgressBar
         android:id="@+id/circular_progress_bar"
diff --git a/car-media-common/res/layout/playback_fragment.xml b/car-media-common/res/layout/playback_fragment.xml
index 5575087..bd77f74 100644
--- a/car-media-common/res/layout/playback_fragment.xml
+++ b/car-media-common/res/layout/playback_fragment.xml
@@ -14,32 +14,33 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<androidx.cardview.widget.CardView
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_height="match_parent"
     android:layout_width="match_parent"
-    app:cardElevation="0dp"
-    app:cardCornerRadius="6dp">
+    android:layout_height="match_parent">
 
-    <com.android.car.apps.common.CrossfadeImageView
-        android:id="@+id/album_background"
-        android:foreground="?android:attr/selectableItemBackground"
-        android:layout_width="match_parent"
+    <androidx.cardview.widget.CardView
         android:layout_height="match_parent"
-        android:focusable="false"
-        android:scaleType="fitStart"/>
-
-    <View
-        android:id="@+id/playback_scrim"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="@color/album_art_scrim"
-        android:alpha="@dimen/album_art_scrim_alpha"/>
+        app:cardElevation="0dp"
+        app:cardCornerRadius="6dp">
 
-    <com.android.car.ui.FocusArea
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        <com.android.car.apps.common.CrossfadeImageView
+            android:id="@+id/album_background"
+            android:foreground="?android:attr/selectableItemBackground"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:focusable="false"
+            android:scaleType="fitStart"/>
+
+        <View
+            android:id="@+id/playback_scrim"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@color/album_art_scrim"
+            android:focusable="true"
+            android:alpha="@dimen/album_art_scrim_alpha"/>
 
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/playback_container"
@@ -96,6 +97,29 @@
                 app:layout_constraintEnd_toStartOf="@+id/app_selector_container"
                 app:layout_constraintTop_toBottomOf="@+id/title"/>
 
+            <com.android.car.apps.common.UxrTextView
+                android:id="@+id/error_message"
+                style="@style/FullScreenErrorMessageStyle"
+                android:layout_marginHorizontal="@dimen/playback_fragment_text_margin_x"
+                android:maxLines="@integer/widget_error_text_max_lines"
+                app:layout_constraintVertical_chainStyle="packed"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/app_name"
+                app:layout_constraintBottom_toTopOf="@+id/error_button"
+            />
+
+            <com.android.car.apps.common.UxrButton
+                android:id="@+id/error_button"
+                style="@style/FullScreenErrorButtonStyle"
+                android:layout_marginTop="@dimen/playback_fragment_error_button_margin_top"
+                android:layout_marginBottom="@dimen/playback_fragment_error_button_margin_bottom"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/error_message"
+                app:layout_constraintBottom_toBottomOf="parent"
+            />
+
             <FrameLayout
                 android:id="@+id/app_selector_container"
                 xmlns:android="http://schemas.android.com/apk/res/android"
@@ -129,6 +153,6 @@
 
         </androidx.constraintlayout.widget.ConstraintLayout>
 
-    </com.android.car.ui.FocusArea>
+    </androidx.cardview.widget.CardView>
 
-</androidx.cardview.widget.CardView>
\ No newline at end of file
+</com.android.car.ui.FocusArea>
diff --git a/car-media-common/res/values-af/strings.xml b/car-media-common/res/values-af/strings.xml
index c4efced..81dc44c 100644
--- a/car-media-common/res/values-af/strings.xml
+++ b/car-media-common/res/values-af/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albumkunswerk"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Titelloos"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Iets is fout. Probeer later."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Kan dit nie op die oomblik doen nie"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Hierdie program kan dit nie doen nie"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Meld aan om hierdie program te gebruik"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premiumtoegang word vereis"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Luister tans op te veel toestelle"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Daardie inhoud word geblokkeer"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Kan nie daardie inhoud hier kry nie"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Speel reeds daardie inhoud"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Kan nie meer snitte oorslaan nie"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Kon nie voltooi word nie. Probeer weer."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Daar is niks anders op die waglys nie"</string>
 </resources>
diff --git a/car-media-common/res/values-am/strings.xml b/car-media-common/res/values-am/strings.xml
index 9888274..1d89404 100644
--- a/car-media-common/res/values-am/strings.xml
+++ b/car-media-common/res/values-am/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"የአልበም ስነ ጥበብ"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"ርዕስ የለም"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"የሆነ ችግር አለ። በኋላ ይሞክሩ።"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"አሁን ይህን ማድረግ አይቻልም"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"ይህ መተግበሪያ ይህን ማድረግ አይችልም"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"ይህን መተግበሪያ ለመጠቀም በመለያ ይግቡ"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"ፕሪሚየም መዳረሻ ያስፈልጋል"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ከልክ በላይ ብዙ በሆኑ መሣሪያዎች ላይ በማዳመጥ ላይ"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ይዘቱ ታግዷል"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ይዘቱን እዚህ ማግኘት አልተቻለም"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"ይዘቱ አስቀድሞ በመጫወት ላይ"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ተጨማሪ ትራኮችን መዝለል አይቻልም"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"መጨረስ አልተቻለም። እንደገና ይሞክሩ።"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"ሌላ ምንም ነገር ወረፋ አልያዘም"</string>
 </resources>
diff --git a/car-media-common/res/values-ar/strings.xml b/car-media-common/res/values-ar/strings.xml
index 609709e..ca21255 100644
--- a/car-media-common/res/values-ar/strings.xml
+++ b/car-media-common/res/values-ar/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"صورة الألبوم"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"بلا عنوان"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"حدث خطأ. يُرجى المحاولة لاحقًا."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"يتعذّر على التطبيق تنفيذ هذا الإجراء في الوقت الحالي."</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"يتعذّر على التطبيق تنفيذ هذا الإجراء."</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"سجِّل دخولك لاستخدام هذا التطبيق."</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"مطلوب الحصول على إذن وصول بحساب مدفوع."</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"يتم الآن الاستماع على أجهزة أكثر من الحدّ المسموح به."</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"تم حظر هذا المحتوى."</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"لا يمكن الحصول على هذا المحتوى من هنا."</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"جارٍ تشغيل هذا المحتوى."</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"لا يمكن تخطّي المزيد من المقاطع الصوتية."</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"تعذَّر الإنهاء. يُرجى إعادة المحاولة."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"لم يتم وضع أي مقطع صوتي آخر في قائمة الانتظار."</string>
 </resources>
diff --git a/car-media-common/res/values-as/strings.xml b/car-media-common/res/values-as/strings.xml
index 1513a5e..6bf03a7 100644
--- a/car-media-common/res/values-as/strings.xml
+++ b/car-media-common/res/values-as/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"এলবাম আৰ্ট"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"কোনো শিৰোনাম নাই"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"কিবা ভুল হ’ল। পাছত চেষ্টা কৰক।"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"সেইটো এই মুহূৰ্তত কৰিব নোৱাৰি"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"এই এপ্‌টোৱে সেইটো কৰিব নোৱাৰে"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"এই এপ্‌টো ব্যৱহাৰ কৰিবলৈ ছাইন ইন কৰক"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium এক্সেছৰ আৱশ্যক"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"বহুকেইটা ডিভাইচত শুনি আছে"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"সেই সমলটো অৱৰোধ কৰা আছে"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"সেই সমলটো ইয়াত পাব নোৱাৰি"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"সেই সমলটো ইতিমধ্যে প্লে’ হৈ আছে"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"আৰু ট্ৰেক এৰি যাব নোৱাৰি"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"সম্পূর্ণ কৰিব পৰা নগ’ল। পুনৰ চেষ্টা কৰক।"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"শাৰীত অন্য একো নাই"</string>
 </resources>
diff --git a/car-media-common/res/values-az/strings.xml b/car-media-common/res/values-az/strings.xml
index 9e8a836..80509ae 100644
--- a/car-media-common/res/values-az/strings.xml
+++ b/car-media-common/res/values-az/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albom təsviri"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Başlıq yoxdur"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Xəta baş verdi. Sonra cəhd edin."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Hazırda onu etmək olmur"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Bu tətbiq onu edə bilmir"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Bu tətbiqi istifadə etmək üçün daxil olun"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium giriş tələb olunur"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Çox cihazda dinləmə aşkarlanıb"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Bu məzmun bloklanıb"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Bu məzmunu əldə etmək mümkün deyil"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Hazırda bu məzmun oxudulur"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Başqa treki keçmək mümkün deyil"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Bitirmək mümkün olmadı. Yenidən cəhd edin."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Növbədə başqa heç nə yoxdur"</string>
 </resources>
diff --git a/car-media-common/res/values-b+sr+Latn/strings.xml b/car-media-common/res/values-b+sr+Latn/strings.xml
index 60ea824..26310be 100644
--- a/car-media-common/res/values-b+sr+Latn/strings.xml
+++ b/car-media-common/res/values-b+sr+Latn/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Omot albuma"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Bez naslova"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Došlo je do greške. Probajte kasnije."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Trenutno ne može to da uradi"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Ova aplikacija ne može to da uradi"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Prijavite se da biste koristili ovu aplikaciju"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Potreban je premijum pristup"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Slušate na previše uređaja"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Taj sadržaj je blokiran"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Ne možete da dobijete taj sadržaj ovde"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Taj sadržaj se već reprodukuje"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Ne možete više da preskačete pesme"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Nismo uspeli da dovršimo radnju. Probajte opet."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ništa drugo nije stavljeno u redosled"</string>
 </resources>
diff --git a/car-media-common/res/values-be/strings.xml b/car-media-common/res/values-be/strings.xml
index 4a98eab..d782e53 100644
--- a/car-media-common/res/values-be/strings.xml
+++ b/car-media-common/res/values-be/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Вокладка альбома"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Без назвы"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Узнікла памылка. Паўтарыце спробу пазней."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Не ўдаецца выканаць гэты запыт"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Дзеянне недаступна ў гэтай праграме"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Каб выкарыстоўваць гэту праграму, увайдзіце"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Патрабуецца платны доступ"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Праслухоўванне адбываецца на занадта вялікай колькасці прылад"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Гэта змесціва заблакіравана"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"У вашым рэгіёне загрузіць гэта змесціва нельга"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Гэта змесціва ўжо прайграецца"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Прапускаць трэкі больш нельга"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Не ўдалося завяршыць. Паўтарыце спробу."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"У чарзе пуста"</string>
 </resources>
diff --git a/car-media-common/res/values-bg/strings.xml b/car-media-common/res/values-bg/strings.xml
index f595ed8..980e0fb 100644
--- a/car-media-common/res/values-bg/strings.xml
+++ b/car-media-common/res/values-bg/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Обложка на албума"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Няма заглавие"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Нещо не е наред. Опитайте по-късно."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Понастоящем тази заявка не може да се изпълни"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Приложението не може да изпълни тази заявка"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Влезте в профила си, за да използвате това приложение"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"За достъп се изисква платен профил"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Слуша се на твърде много устройства"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Това съдържание е блокирано"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Това съдържание не е налице за региона ви"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Това съдържание вече се възпроизвежда"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Не могат да се пропускат повече записи"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Не можа да завърши. Опитайте отново."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Няма нищо друго в опашката"</string>
 </resources>
diff --git a/car-media-common/res/values-bn/strings.xml b/car-media-common/res/values-bn/strings.xml
index c7366d7..d02e247 100644
--- a/car-media-common/res/values-bn/strings.xml
+++ b/car-media-common/res/values-bn/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"অ্যালবাম আর্ট"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"কোনও শীর্ষক নেই"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"কোনও সমস্যা হয়েছে। পরে চেষ্টা করুন।"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"এই কাজটি এখন করা যাবে না"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"এই অ্যাপে এই কাজটি করা যাবে না"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"এই অ্যাপ ব্যবহার করতে সাইন-ইন করুন"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium অ্যাক্সেস থাকতে হবে"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"একাধিক ডিভাইসে শোনা হচ্ছে"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ওই কন্টেন্টটি ব্লক করা আছে"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ওই কন্টেন্টটি এখানে পাওয়া যাবে না"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"কন্টেন্টটি আগে থেকেই চলছে"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"আর কোনও ট্র্যাক এড়িয়ে যেতে পারবেন না"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"সম্পূর্ণ করা যায়নি। আবার চেষ্টা করুন।"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"সারিতে আর কিছু নেই"</string>
 </resources>
diff --git a/car-media-common/res/values-bs/strings.xml b/car-media-common/res/values-bs/strings.xml
index 60ea824..4f1df52 100644
--- a/car-media-common/res/values-bs/strings.xml
+++ b/car-media-common/res/values-bs/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Omot albuma"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Bez naslova"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Nešto nije uredu. Pokušajte kasnije."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Taj zahtjev trenutno nije moguće izvršiti"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Ova aplikacija ne može izvršiti taj zahtjev"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Prijavite se da koristite ovu aplikaciju"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Potreban je premijum pristup"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Previše je uređaja na kojima se sluša"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Sadržaj je blokiran"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Nije moguće preuzeti taj sadržaj ovdje"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Reproduciranje tog sadržaja je već u toku"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Ne možete više preskakati numere"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Dovršavanje nije uspjelo. Pokušajte ponovo."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ništa više nije postavljeno u red čekanja"</string>
 </resources>
diff --git a/car-media-common/res/values-ca/strings.xml b/car-media-common/res/values-ca/strings.xml
index c672d9b..7a44b13 100644
--- a/car-media-common/res/values-ca/strings.xml
+++ b/car-media-common/res/values-ca/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Imatge de l\'àlbum"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Sense títol"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"S\'ha produït un error. Prova-ho més tard."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Ara mateix aquesta acció no es pot dur a terme"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"L\'aplicació no pot dur a terme aquesta acció"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Inicia la sessió per utilitzar aquesta aplicació"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Es requereix accés Premium"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"S\'està escoltant contingut en massa dispositius"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Aquest contingut està bloquejat"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"El contingut no està disponible en aquesta regió"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Aquest contingut ja s\'està reproduint"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"No es poden saltar més cançons"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"No s\'ha pogut acabar. Torna-ho a provar."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"No hi ha res més a la cua"</string>
 </resources>
diff --git a/car-media-common/res/values-cs/strings.xml b/car-media-common/res/values-cs/strings.xml
index dfcffe0..0a45e6d 100644
--- a/car-media-common/res/values-cs/strings.xml
+++ b/car-media-common/res/values-cs/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Obal alba"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Bez názvu"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Někde se stala chyba. Zkuste to později."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Tuto akci teď nelze provést"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Tuto akci aplikace nedokáže provést"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Chcete-li aplikaci použít, přihlaste se"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Je vyžadován prémiový přístup"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Poslech je aktivován v příliš mnoha zařízeních"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Obsah je blokován"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Tento obsah tu nelze načíst"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Tento obsah se už přehrává"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Další skladby nelze přeskočit"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Nelze dokončit. Zkuste to znovu."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ve frontě není nic dalšího"</string>
 </resources>
diff --git a/car-media-common/res/values-da/strings.xml b/car-media-common/res/values-da/strings.xml
index 1640516..a875184 100644
--- a/car-media-common/res/values-da/strings.xml
+++ b/car-media-common/res/values-da/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albumgrafik"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Ingen titel"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Der er noget galt. Prøv senere."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Det er ikke muligt lige nu"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Det kan denne app ikke gøre"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Log ind for at bruge denne app"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Dette kræver en Premium-konto"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Du lytter på for mange enheder"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Indholdet er blokeret"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Der er ikke adgang til indholdet her"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Indholdet afspilles allerede"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Du kan ikke springe flere numre over"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Handlingen kunne ikke afsluttes. Prøv igen."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Der er ikke mere i køen"</string>
 </resources>
diff --git a/car-media-common/res/values-de/strings.xml b/car-media-common/res/values-de/strings.xml
index 7bafae6..b5894cb 100644
--- a/car-media-common/res/values-de/strings.xml
+++ b/car-media-common/res/values-de/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albumcover"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Kein Titel"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Ein Fehler ist aufgetreten. Versuch es später noch mal."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Das ist gerade nicht möglich"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Diese App kann das nicht"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Damit du diese App verwenden kannst, musst du dich zuerst anmelden"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premiumzugriff erforderlich"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Es wird auf zu vielen Geräten gleichzeitig gestreamt"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Der Inhalt ist gesperrt"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Der Inhalt kann hier nicht abgerufen werden"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Der Inhalt wird bereits wiedergegeben"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Du kannst keine weiteren Titel überspringen"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Die Aktion konnte nicht abgeschlossen werden. Versuch es noch einmal."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Es ist sonst nichts in der Warteschlange"</string>
 </resources>
diff --git a/car-media-common/res/values-el/strings.xml b/car-media-common/res/values-el/strings.xml
index 0b5de6d..9bc13cd 100644
--- a/car-media-common/res/values-el/strings.xml
+++ b/car-media-common/res/values-el/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Εξώφυλλο άλμπουμ"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Χωρίς τίτλο"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Παρουσιάστηκε κάποιο πρόβλημα. Δοκιμάστε αργότερα."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Δεν είναι δυνατή η εκτέλεση του αιτήματος αυτήν τη στιγμή."</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Δεν είναι δυνατή η εκτέλεση του αιτήματος από αυτήν την εφαρμογή."</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Συνδεθείτε, για να χρησιμοποιήσετε αυτήν την εφαρμογή."</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Απαιτείται premium πρόσβαση."</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Ακρόαση πάρα πολλών συσκευών"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Αυτό το περιεχόμενο είναι αποκλεισμένο."</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Δεν είναι δυνατή η λήψη αυτού του περιεχομένου εδώ."</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Γίνεται ήδη αναπαραγωγή αυτού του περιεχομένου."</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Δεν είναι δυνατή η παράβλεψη περισσότερων κομματιών."</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Δεν ήταν δυνατή η ολοκλήρωση. Δοκιμάστε ξανά."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Δεν υπάρχει κάτι άλλο στην ουρά."</string>
 </resources>
diff --git a/car-media-common/res/values-en-rAU/strings.xml b/car-media-common/res/values-en-rAU/strings.xml
index 98e3148..b9c3337 100644
--- a/car-media-common/res/values-en-rAU/strings.xml
+++ b/car-media-common/res/values-en-rAU/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Album Art"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"No title"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Something’s wrong. Try later."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Can’t do that at the moment"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"This app can’t do that"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Sign in to use this app"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium access required"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Listening on too many devices"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"That content is blocked"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Can’t get that content here"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Already playing that content"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Can’t skip any more tracks"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Couldn’t finish. Try again."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nothing else is queued up"</string>
 </resources>
diff --git a/car-media-common/res/values-en-rCA/strings.xml b/car-media-common/res/values-en-rCA/strings.xml
index 98e3148..b9c3337 100644
--- a/car-media-common/res/values-en-rCA/strings.xml
+++ b/car-media-common/res/values-en-rCA/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Album Art"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"No title"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Something’s wrong. Try later."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Can’t do that at the moment"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"This app can’t do that"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Sign in to use this app"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium access required"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Listening on too many devices"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"That content is blocked"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Can’t get that content here"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Already playing that content"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Can’t skip any more tracks"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Couldn’t finish. Try again."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nothing else is queued up"</string>
 </resources>
diff --git a/car-media-common/res/values-en-rGB/strings.xml b/car-media-common/res/values-en-rGB/strings.xml
index 98e3148..b9c3337 100644
--- a/car-media-common/res/values-en-rGB/strings.xml
+++ b/car-media-common/res/values-en-rGB/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Album Art"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"No title"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Something’s wrong. Try later."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Can’t do that at the moment"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"This app can’t do that"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Sign in to use this app"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium access required"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Listening on too many devices"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"That content is blocked"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Can’t get that content here"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Already playing that content"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Can’t skip any more tracks"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Couldn’t finish. Try again."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nothing else is queued up"</string>
 </resources>
diff --git a/car-media-common/res/values-en-rIN/strings.xml b/car-media-common/res/values-en-rIN/strings.xml
index 98e3148..b9c3337 100644
--- a/car-media-common/res/values-en-rIN/strings.xml
+++ b/car-media-common/res/values-en-rIN/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Album Art"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"No title"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Something’s wrong. Try later."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Can’t do that at the moment"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"This app can’t do that"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Sign in to use this app"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium access required"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Listening on too many devices"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"That content is blocked"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Can’t get that content here"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Already playing that content"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Can’t skip any more tracks"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Couldn’t finish. Try again."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nothing else is queued up"</string>
 </resources>
diff --git a/car-media-common/res/values-en-rXC/strings.xml b/car-media-common/res/values-en-rXC/strings.xml
index ecdf930..326c0ff 100644
--- a/car-media-common/res/values-en-rXC/strings.xml
+++ b/car-media-common/res/values-en-rXC/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‎‎‎‏‏‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎Album Art‎‏‎‎‏‎"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‏‎‎‎‎‏‏‎‎‏‎No Title‎‏‎‎‏‎"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‎Something’s wrong. Try later.‎‏‎‎‏‎"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‎Can’t do that right now‎‏‎‎‏‎"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎‎‎‎‏‎This app can’t do that‎‏‎‎‏‎"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‎‏‎‎Sign in to use this app‎‏‎‎‏‎"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‎Premium access required‎‏‎‎‏‎"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎Listening on too many devices‎‏‎‎‏‎"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‏‎‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎That content is blocked‎‏‎‎‏‎"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‎‏‎Can’t get that content here‎‏‎‎‏‎"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‎‎‎‏‎‏‎‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‎‏‏‎‏‎Already playing that content‎‏‎‎‏‎"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‏‏‎‏‎‏‎‏‎‎‎‏‎‏‎‏‎‏‎‎Can’t skip any more tracks‎‏‎‎‏‎"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‎‎‏‎‏‏‏‎‎‏‏‏‎‎‏‎‎‏‏‎‎‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‎‏‎Couldn’t finish. Try again.‎‏‎‎‏‎"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‏‎‎‎‏‏‎‏‏‏‎Nothing else is queued up‎‏‎‎‏‎"</string>
 </resources>
diff --git a/car-media-common/res/values-es-rUS/strings.xml b/car-media-common/res/values-es-rUS/strings.xml
index 18ba657..48d6213 100644
--- a/car-media-common/res/values-es-rUS/strings.xml
+++ b/car-media-common/res/values-es-rUS/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Imagen del álbum"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Sin título"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Se produjo un error. Vuelve a intentarlo más tarde."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"No se puede realizar esa acción en este momento"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Esta app no puede realizar esa acción"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Accede para usar esta app"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Se requiere acceso Premium"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Se está escuchando contenido en demasiados dispositivos"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ese contenido está bloqueado"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"No se puede acceder a ese contenido en esta región"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ya se está reproduciendo ese contenido"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"No se pueden omitir más pistas"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"No se pudo completar la acción. Vuelve a intentarlo."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"No hay más contenido en la cola"</string>
 </resources>
diff --git a/car-media-common/res/values-es/strings.xml b/car-media-common/res/values-es/strings.xml
index 18ba657..f70760a 100644
--- a/car-media-common/res/values-es/strings.xml
+++ b/car-media-common/res/values-es/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Imagen del álbum"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Sin título"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Se ha producido un error. Inténtalo más tarde."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"No se puede hacer en estos momentos"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"No se puede hacer con esta aplicación"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Inicia sesión para utilizar esta aplicación"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Se necesita acceso premium"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Se está escuchando en demasiados dispositivos"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ese contenido está bloqueado"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Aquí no se puede reproducir ese contenido"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ya se está reproduciendo ese contenido"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"No se pueden saltar más pistas"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"No se ha podido finalizar. Inténtalo de nuevo."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"No hay nada más en la cola"</string>
 </resources>
diff --git a/car-media-common/res/values-et/strings.xml b/car-media-common/res/values-et/strings.xml
index 8ca4cf2..a5d7fc3 100644
--- a/car-media-common/res/values-et/strings.xml
+++ b/car-media-common/res/values-et/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albumi kujundus"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Pealkiri puudub"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Midagi on valesti. Proovige hiljem."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Seda ei saa praegu teha"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"See rakendus ei saa seda teha"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Rakenduse kasutamiseks logige sisse"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Vaja on Premium-tasemel juurdepääsu"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Kuulatakse liiga paljudes seadmetes"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"See sisu on blokeeritud"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Seda sisu ei saa siin esitada"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Seda sisu juba esitatakse"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Rohkem lugusid ei saa vahele jätta"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Ei saanud lõpetada. Proovige uuesti."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Midagi muud pole järjekorras"</string>
 </resources>
diff --git a/car-media-common/res/values-eu/strings.xml b/car-media-common/res/values-eu/strings.xml
index 896aa2c..c989683 100644
--- a/car-media-common/res/values-eu/strings.xml
+++ b/car-media-common/res/values-eu/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albumaren azala"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Izenik gabea"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Arazoren bat izan da. Saiatu geroago."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Ezin da egin halakorik une honetan"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Aplikazio honek ezin du egin halakorik"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Aplikazioa erabiltzeko, hasi saioa"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium-eko sarbidea behar da"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Gailu gehiegitatik jasotzen ari da soinua"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Eduki hori blokeatuta dago"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Ezin da eskuratu eduki hori lurralde honetan"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Dagoeneko ari da erreproduzitzen eduki hori"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Ezin da saltatu pista gehiagorik"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Ezin izan da amaitu ekintza. Saiatu berriro."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ez dago beste ezer ilaran"</string>
 </resources>
diff --git a/car-media-common/res/values-fa/strings.xml b/car-media-common/res/values-fa/strings.xml
index 24bac8e..b3168a0 100644
--- a/car-media-common/res/values-fa/strings.xml
+++ b/car-media-common/res/values-fa/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"عکس روی جلد آلبوم"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"بدون عنوان"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"مشکلی رخ داد. بعداً امتحان کنید."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"درحال‌حاضر انجام نمی‌شود"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"این برنامه نمی‌تواند این کار را انجام دهد"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"برای استفاده از این برنامه، به سیستم وارد شوید"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"دسترسی ممتاز لازم است"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"درحال گوش کردن به تعداد زیادی دستگاه"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"این محتوا مسدود شده است"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"نمی‌توان این محتوا را در اینجا دریافت کرد"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"این محتوا از قبل درحال پخش است"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"از هیچ آهنگ دیگری نمی‌توان رد شد"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"تکمیل نشد. دوباره امتحان کنید."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"هیچ مورد دیگری در صف پخش نیست"</string>
 </resources>
diff --git a/car-media-common/res/values-fi/strings.xml b/car-media-common/res/values-fi/strings.xml
index 0fcaaf5..c558794 100644
--- a/car-media-common/res/values-fi/strings.xml
+++ b/car-media-common/res/values-fi/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albumin kansitaide"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Ei nimeä"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Jotain meni pieleen. Yritä myöhemmin."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Tämä ei juuri nyt onnistu"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Sovellus ei tue pyyntöä"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Kirjaudu sisään käyttääksesi tätä sovellusta"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Edellyttää premium-tilausta"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Kuuntelu käynnissä liian monella laitteella"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Tämä sisältö on estetty"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Sisältö ei ole saatavilla täällä"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Sisältöä toistetaan jo"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Enimmäismäärä kappaleita ohitettu"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Ei onnistunut. Yritä uudelleen."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ei muuta jonossa"</string>
 </resources>
diff --git a/car-media-common/res/values-fr-rCA/strings.xml b/car-media-common/res/values-fr-rCA/strings.xml
index 66e06e1..eb784b4 100644
--- a/car-media-common/res/values-fr-rCA/strings.xml
+++ b/car-media-common/res/values-fr-rCA/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Image de l\'album"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Aucun titre"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Une erreur s\'est produite. Réessayez plus tard."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Impossible d\'effectuer cette action pour le moment"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Cette application ne peut pas effectuer cette action"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Connectez-vous pour utiliser cette application"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Un accès payant est requis"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Écoute en cours sur trop d\'appareils"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ce contenu est bloqué"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Impossible d\'obtenir ce contenu ici"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ce contenu est déjà en cours de lecture"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Impossible de passer à d\'autres chansons"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Impossible de terminer l\'action. Réessayez."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Rien d\'autre n\'est dans la file d\'attente"</string>
 </resources>
diff --git a/car-media-common/res/values-fr/strings.xml b/car-media-common/res/values-fr/strings.xml
index dfbc082..8a86806 100644
--- a/car-media-common/res/values-fr/strings.xml
+++ b/car-media-common/res/values-fr/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Image de l\'album"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Sans titre"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Une erreur s\'est produite. Réessayez plus tard."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Impossible d\'effectuer cette opération pour le moment"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Cette application ne peut pas effectuer cette opération"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Connectez-vous pour utiliser cette application"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Accès Premium requis"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Écoute en cours sur trop d\'appareils"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ce contenu est bloqué"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Impossible d\'accéder à ce contenu ici"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ce contenu est déjà en cours de lecture"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Impossible de passer d\'autres titres"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Impossible de terminer l\'opération. Veuillez réessayer."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Aucun autre titre dans la file d\'attente"</string>
 </resources>
diff --git a/car-media-common/res/values-gl/strings.xml b/car-media-common/res/values-gl/strings.xml
index 6971016..a1a8fc5 100644
--- a/car-media-common/res/values-gl/strings.xml
+++ b/car-media-common/res/values-gl/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Portada de álbum"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Sen título"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Produciuse un problema. Téntao máis tarde."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Nestes momentos non se pode realizar a acción"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Esta aplicación non pode realizar a acción"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Inicia sesión para utilizar esta aplicación"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Requírese acceso premium"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Estase escoitando contido en demasiados dispositivos"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ese contido está bloqueado"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Aquí non se pode acceder a ese contido"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Xa se está reproducindo ese contido"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Non se poden saltar máis pistas"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Non se puido completar a acción. Téntao de novo."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Non hai nada máis na cola"</string>
 </resources>
diff --git a/car-media-common/res/values-gu/strings.xml b/car-media-common/res/values-gu/strings.xml
index 569394f..b59fa2a 100644
--- a/car-media-common/res/values-gu/strings.xml
+++ b/car-media-common/res/values-gu/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"આલ્બમ આર્ટ"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"કોઈ શીર્ષક નથી"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"કંઈક ખોટું થયું. થોડા સમય પછી પ્રયાસ કરો."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"તે અત્યારે કરી શકતા નથી"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"આ ઍપ તે કરી શકતી નથી"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"આ ઍપનો ઉપયોગ કરવા માટે સાઇન ઇન કરો"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"પ્રીમિયમ ઍક્સેસ જરૂરી છે"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ઘણા બધા ડિવાઇસ પર સાંભળી રહ્યાં છે"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"તે કન્ટેન્ટ બ્લૉક કર્યું છે"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"તે કન્ટેન્ટ અહીં મેળવી શકાતું નથી"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"તે કન્ટેન્ટ પહેલાંથી ચલાવી રહ્યાં છે"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"કોઈ વધુ ટ્રૅક છોડી શકાતા નથી"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"સમાપ્ત કરી શક્યાં નથી. ફરી પ્રયાસ કરો."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"બીજું કંઈ કતારમાં નથી"</string>
 </resources>
diff --git a/car-media-common/res/values-hi/strings.xml b/car-media-common/res/values-hi/strings.xml
index f13088f..58fed76 100644
--- a/car-media-common/res/values-hi/strings.xml
+++ b/car-media-common/res/values-hi/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"एल्‍बम आर्ट"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"कोई शीर्षक नहीं"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"कोई गड़बड़ी हुई. बाद में कोशिश करें."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"अभी नहीं किया जा सकता"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"इस ऐप्लिकेशन पर यह काम नहीं किया जा सकता"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"इस ऐप्लिकेशन का इस्तेमाल करने के लिए साइन इन करें"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"इसके लिए Premium का ऐक्सेस ज़रूरी है"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"आप इसे बहुत सारे डिवाइस पर चला रहे/रही हैं"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"कॉन्टेंट पर रोक लगा दी गई है"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"यह कॉन्टेंट यहां नहीं चलाया जा सकता"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"यह कॉन्टेंट पहले से ही चल रहा है"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"इससे ज़्यादा गाने नहीं छोड़े जा सकते"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"कार्रवाई पूरी नहीं हो सकी. फिर से कोशिश करें."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"सूची में और कुछ नहीं है"</string>
 </resources>
diff --git a/car-media-common/res/values-hr/strings.xml b/car-media-common/res/values-hr/strings.xml
index 00e19d1..007109e 100644
--- a/car-media-common/res/values-hr/strings.xml
+++ b/car-media-common/res/values-hr/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Slika naslovnice albuma"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Bez naslova"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Nešto nije u redu. Pokušajte kasnije."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Trenutačno to ne možemo učiniti"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Ova aplikacija nema tu mogućnost"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Prijavite se za upotrebu te aplikacije."</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Potreban je pristup uz dodatnu naplatu"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Slušate na previše uređaja"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Taj je sadržaj blokiran"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Taj sadržaj nije dostupan ovdje"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Taj se sadržaj već reproducira"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Ne možete više preskakati pjesme"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Završavanje nije uspjelo. Pokušajte ponovo."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nema više ničeg u redu čekanja"</string>
 </resources>
diff --git a/car-media-common/res/values-hu/strings.xml b/car-media-common/res/values-hu/strings.xml
index 8631465..a56ebba 100644
--- a/car-media-common/res/values-hu/strings.xml
+++ b/car-media-common/res/values-hu/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Lemezborító"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Nincs cím"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Hiba történt. Próbálja újra később."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Jelenleg nem lehetséges"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Ez az alkalmazás nem képes erre"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Az alkalmazás használatához jelentkezzen be"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Prémium hozzáférés szükséges"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Túl sok eszköz van használatban"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"A tartalom le van tiltva"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Nem lehet betölteni a tartalmat"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"A tartalom lejátszása már folyamatban van"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Nem lehet több számot átugrani"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Nem sikerült befejezni. Próbálkozzon újra."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Semmi más nincs a sorban"</string>
 </resources>
diff --git a/car-media-common/res/values-hy/strings.xml b/car-media-common/res/values-hy/strings.xml
index 197a067..5fd71be 100644
--- a/car-media-common/res/values-hy/strings.xml
+++ b/car-media-common/res/values-hy/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Ալբոմի շապիկ"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Անանուն"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Սխալ առաջացավ։ Փորձեք ավելի ուշ։"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Այս պահին հնարավոր չէ անել դա"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Հավելվածը չի աջակցում այդ գործողությունը"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Մուտք գործեք՝ հավելվածն օգտագործելու համար"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Անհրաժեշտ է պրեմիում հաշիվ"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Չափից շատ սարքերում է բովանդակություն նվագարկվում"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Բովանդակությունն արգելափակված է"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Բովանդակությունն անհասանելի է այս տարածաշրջանում"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Բովանդակությունն արդեն նվագարկվում է"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Այլևս հնարավոր չէ կատարումներ բաց թողնել"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Չհաջողվեց ավարտել։ Նորից փորձեք։"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Հերթացանկը դատարկ է"</string>
 </resources>
diff --git a/car-media-common/res/values-in/strings.xml b/car-media-common/res/values-in/strings.xml
index e331c0a..3c287f2 100644
--- a/car-media-common/res/values-in/strings.xml
+++ b/car-media-common/res/values-in/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Sampul Album"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Tanpa Judul"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Terjadi masalah. Coba nanti."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Tidak dapat melakukannya saat ini"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Aplikasi ini tidak dapat melakukannya"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Login untuk menggunakan aplikasi ini"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Perlu akses premium"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Terlalu banyak perangkat digunakan untuk mendengarkan"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Konten tersebut diblokir"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Tidak dapat memuat konten tersebut di sini"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Sedang memutar konten tersebut"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Tidak dapat melewati lagu lagi"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Tidak dapat diselesaikan. Coba lagi."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Tidak ada antrean lagi"</string>
 </resources>
diff --git a/car-media-common/res/values-is/strings.xml b/car-media-common/res/values-is/strings.xml
index b26c4c6..34102e5 100644
--- a/car-media-common/res/values-is/strings.xml
+++ b/car-media-common/res/values-is/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Plötuumslag"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Enginn titill"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Eitthvað er ekki í lagi. Reyndu síðar."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Ekki er hægt að framkvæma þetta eins og er"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Forritið getur ekki framkvæmt þetta"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Skráðu þig inn til að nota þetta forrit"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium-aðgangur er nauðsynlegur"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Hlustað í of mörgum tækjum"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Þetta efni er á bannlista"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Efnið er ekki tiltækt hér"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Þegar að spila þetta efni"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Ekki er hægt að sleppa fleiri lögum"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Ekki tókst að ljúka aðgerð. Reyndu aftur."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ekkert annað er í röð"</string>
 </resources>
diff --git a/car-media-common/res/values-it/strings.xml b/car-media-common/res/values-it/strings.xml
index bab7f0a..d0ef413 100644
--- a/car-media-common/res/values-it/strings.xml
+++ b/car-media-common/res/values-it/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Copertina dell\'album"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Nessun titolo"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Si è verificato un problema. Prova più tardi."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Al momento non è possibile svolgere l\'operazione"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Questa app non supporta l\'azione richiesta"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Accedi per usare questa app"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"È necessario l\'accesso Premium"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Ascolto attivo su troppi dispositivi"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Contenuti bloccati"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Qui non è possibile scaricare questi contenuti"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Contenuti già in riproduzione"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Impossibile saltare altre tracce"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Impossibile completare. Riprova."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nient\'altro in coda"</string>
 </resources>
diff --git a/car-media-common/res/values-iw/strings.xml b/car-media-common/res/values-iw/strings.xml
index 9e12f85..a0118e1 100644
--- a/car-media-common/res/values-iw/strings.xml
+++ b/car-media-common/res/values-iw/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"עטיפת אלבום"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"ללא שם"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"משהו השתבש. יש לנסות מאוחר יותר."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"לא ניתן לבצע את הפעולה הזו כרגע"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"אי אפשר לבצע פעולה זו באפליקציה הזו"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"יש להיכנס לחשבון כדי להשתמש באפליקציה הזו"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"נדרשת גישה ל-Premium"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"מתבצעת האזנה ביותר מדי מכשירים"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"התוכן הזה חסום"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"לא ניתן לקבל את התוכן הזה כאן"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"התוכן הזה כבר פועל"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"לא ניתן לדלג יותר על טראקים"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"לא ניתן היה לסיים. יש לנסות שוב."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"אין עוד שירים ברשימת השירים"</string>
 </resources>
diff --git a/car-media-common/res/values-ja/strings.xml b/car-media-common/res/values-ja/strings.xml
index 8d46866..b1a0da3 100644
--- a/car-media-common/res/values-ja/strings.xml
+++ b/car-media-common/res/values-ja/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"アルバムアート"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"タイトルなし"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"エラーが発生しました。しばらくしてからお試しください。"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"現在、利用できません"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"このアプリではサポートされていません"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"このアプリを使用するにはログインしてください"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"プレミアム アカウントが必要です"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"再生しているデバイスが多すぎます"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"このコンテンツはブロックされています"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"このコンテンツはこの地域では利用できません"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"このコンテンツはすでに再生中です"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"これ以上トラックをスキップできません"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"完了できません。もう一度お試しください。"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"キューが一杯で追加できません"</string>
 </resources>
diff --git a/car-media-common/res/values-ka/strings.xml b/car-media-common/res/values-ka/strings.xml
index 1e467fe..39b2549 100644
--- a/car-media-common/res/values-ka/strings.xml
+++ b/car-media-common/res/values-ka/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"ალბომის გარეკანი"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"უსათაურო"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"წარმოიქმნა შეფერხება. ცადეთ მოგვიანებით."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"ამჟამად ამის გაკეთება შეუძლებელია"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"ეს აპი ამას ვერ გააკეთებს"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"ამ აპით სარგებლობისთვის შედით სისტემაში"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"საჭიროა პრემიუმ ტიპის წვდომა"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"მოსმენა მიმდინარეობს მეტისმეტად ბევრ მოწყობილობაზე"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ეს კონტენტი დაბლოკილია"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ამ კონტენტს აქ ვერ მიიღებთ"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"ეს კონტენტი უკვე იკვრება"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"მეტ ჩანაწერს ვერ გამოტოვებთ"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"დასრულება ვერ მოხერხდა. ცადეთ ხელახლა."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"რიგში აღარაფერია"</string>
 </resources>
diff --git a/car-media-common/res/values-kk/strings.xml b/car-media-common/res/values-kk/strings.xml
index ec84f1a..0225852 100644
--- a/car-media-common/res/values-kk/strings.xml
+++ b/car-media-common/res/values-kk/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Альбом мұқабасы"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Атауы жоқ"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Бірдеңе дұрыс емес. Кейінірек қайталап көріңіз."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Мұны дәл қазір істеу мүмкін емес."</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Бұл қолданба мұны істей алмайды."</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Бұл қолданбаны пайдалану үшін есептік жазбаға кіріңіз."</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Премиум рұқсат қажет."</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Тым көп құрылғыларда тыңдалып жатыр."</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Бұл мазмұнға тыйым салынған."</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Мазмұнды алу мүмкін емес."</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Әлдеқашан ойнатылып жатыр."</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Басқа тректерді өткізіп жіберу мүмкін емес."</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Аяқталмады. Қайталап көріңіз."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Кезекке басқа ештеңе қойылмаған."</string>
 </resources>
diff --git a/car-media-common/res/values-km/strings.xml b/car-media-common/res/values-km/strings.xml
index a9320b1..95399ff 100644
--- a/car-media-common/res/values-km/strings.xml
+++ b/car-media-common/res/values-km/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"​ក្រប​អាល់ប៊ុម"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"គ្មាន​ចំណងជើងទេ"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"មានអ្វីមួយ​ខុស​ប្រក្រតី។ សូមព្យាយាម​នៅពេលក្រោយ។"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"មិនអាច​ធ្វើតាមសំណើនោះ​ឥឡូវនេះ​បានទេ"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"កម្មវិធីនេះ​មិនអាចធ្វើ​តាមសំណើនោះ​បានទេ"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"ចូលគណនី ដើម្បីប្រើកម្មវិធីនេះ"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"តម្រូវឱ្យមាន​ការចូលប្រើ​លំដាប់ខ្ពស់"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"កំពុងស្ដាប់​នៅលើ​ឧបករណ៍​ច្រើនពេក"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ខ្លឹមសារនោះ​ត្រូវបានទប់ស្កាត់"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"មិនអាច​យក​ខ្លឹមសារនោះ​នៅទីនេះ​បានទេ"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"កំពុងចាក់​ខ្លឹមសារនោះ​ស្រាប់ហើយ"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"មិនអាច​រំលងចម្រៀង​បានទៀតទេ"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"មិនអាច​បញ្ចប់បានទេ។ សូមព្យាយាមម្ដងទៀត។"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"មិនមានអ្វី​ផ្សេងទៀត​នៅក្នុង​ជួរទេ"</string>
 </resources>
diff --git a/car-media-common/res/values-kn/strings.xml b/car-media-common/res/values-kn/strings.xml
index 4a02cfd..e690790 100644
--- a/car-media-common/res/values-kn/strings.xml
+++ b/car-media-common/res/values-kn/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"ಆಲ್ಬಮ್ ಕಲೆ"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"ಯಾವುದೇ ಶೀರ್ಷಿಕೆಯಿಲ್ಲ"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"ಏನೋ ತಪ್ಪಾಗಿದೆ. ನಂತರ ಪ್ರಯತ್ನಿಸಿ."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"ಸದ್ಯಕ್ಕೆ ಅದನ್ನು ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"ಈ ಆ್ಯಪ್‌ನಿಂದ ಅದನ್ನು ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"ಈ ಆ್ಯಪ್ ಬಳಸಲು ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"ಪ್ರೀಮಿಯಂ ಪ್ರವೇಶದ ಅಗತ್ಯವಿದೆ"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ಹಲವಾರು ಸಾಧನಗಳಲ್ಲಿ ಆಲಿಸಲಾಗುತ್ತಿದೆ"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ವಿಷಯವನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ಆ ವಿಷಯವನ್ನು ಇಲ್ಲಿ ಪಡೆದುಕೊಳ್ಳಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"ಈಗಾಗಲೇ ಆ ವಿಷಯವನ್ನು ಪ್ಲೇ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ಇನ್ನು ಮುಂದೆ ಟ್ರ್ಯಾಕ್‌ಗಳನ್ನು ಸ್ಕಿಪ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"ಪೂರ್ಣಗೊಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"ಸರದಿಯಲ್ಲಿ ಯಾವುದು ಬಾಕಿ ಉಳಿದಿಲ್ಲ"</string>
 </resources>
diff --git a/car-media-common/res/values-ko/strings.xml b/car-media-common/res/values-ko/strings.xml
index 568f6de..d50fd0f 100644
--- a/car-media-common/res/values-ko/strings.xml
+++ b/car-media-common/res/values-ko/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"앨범아트"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"제목 없음"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"문제가 발생했습니다. 나중에 다시 시도해 주세요."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"지금은 요청한 작업을 할 수 없습니다."</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"요청한 작업이 이 앱에서 지원되지 않습니다."</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"이 앱을 사용하려면 로그인하세요."</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"프리미엄 액세스 권한이 필요합니다."</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"너무 많은 기기에서 스트리밍하고 있습니다."</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"차단된 콘텐츠입니다."</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"이 지역에서 재생할 수 없는 콘텐츠입니다."</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"요청한 콘텐츠가 이미 재생 중입니다."</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"트랙을 더 이상 건너뛸 수 없습니다."</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"완료할 수 없습니다. 다시 시도해 주세요."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"현재 재생목록이 비어있습니다."</string>
 </resources>
diff --git a/car-media-common/res/values-ky/strings.xml b/car-media-common/res/values-ky/strings.xml
index 5dd1ccc..8ca5ae0 100644
--- a/car-media-common/res/values-ky/strings.xml
+++ b/car-media-common/res/values-ky/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Альбом мукабасы"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Аталышы жок"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Бир жерден ката кетти. Бир аздан кийин кайталап көрүңүз."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Аны азыр аткаруу мүмкүн эмес"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Бул колдонмо аны аткара албайт"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Бул колдонмону пайдалануу үчүн аккаунтуңузга кириңиз"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Артыкчылыктуу кирүү мүмкүнчүлүгү талап кылынат"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Өтө көп түзмөк угулууда"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ал мазмун бөгөттөлгөн"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Ал мазмунду алуу мүмкүн эмес"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ал мазмун ойнотулууда"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Эми тректерди өткөрүп жиберүүгө болбойт"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Аягына чыккан жок. Кайталап көрүңүз."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Кезекте эч нерсе жок"</string>
 </resources>
diff --git a/car-media-common/res/values-lo/strings.xml b/car-media-common/res/values-lo/strings.xml
index aee4892..7e92949 100644
--- a/car-media-common/res/values-lo/strings.xml
+++ b/car-media-common/res/values-lo/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"ໜ້າປົກອະລະບ້ຳ"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"ບໍ່ມີຊື່"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"ມີບ່າງຢ່າງຜິດພາດ. ລອງໃໝ່ພາຍຫຼັງ."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"ບໍ່ສາມາດເຮັດໄດ້ຕອນນີ້"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"ແອັບນີ້ບໍ່ສາມາດເຮັດສິ່ງນັ້ນໄດ້"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"ເຂົ້າສູ່ລະບົບເພື່ອໃຊ້ແອັບນີ້"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"ຕ້ອງມີສິດເຂົ້າເຖິງລະດັບພຣີມຽມ"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ກຳລັງຟັງຢູ່ໃນຫຼາຍອຸປະກອນເກີນໄປ"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ເນື້ອຫານັ້ນຖືກບລັອກໄວ້"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ບໍ່ສາມາດຮັບເນື້ອຫານັ້ນຢູ່ບ່ອນນີ້"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"ຫຼິ້ນເນື້ອຫານັ້ນແລ້ວ"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ຂ້າມເພງບໍ່ໄດ້ອີກແລ້ວ"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"ບໍ່ສາມາດສຳເລັດໄດ້. ລອງໃໝ່."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"ບໍ່ມີລາຍການອື່ນໃນຄິວ"</string>
 </resources>
diff --git a/car-media-common/res/values-lt/strings.xml b/car-media-common/res/values-lt/strings.xml
index b4f1926..be85d02 100644
--- a/car-media-common/res/values-lt/strings.xml
+++ b/car-media-common/res/values-lt/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albumo viršelis"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Nėra pavadinimo"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Kažkas nepavyko. Bandykite vėliau."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Dabar negalima atlikti to veiksmo"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Ši programa negali atlikti to veiksmo"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Prisijunkite, kad galėtumėte naudoti šią programą"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Būtina mokama prieiga"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Klausoma naudojant per daug įrenginių"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Tas turinys užblokuotas"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Negalima gauti to turinio čia"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Tas turinys jau leidžiamas"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Daugiau takelių praleisti nebegalima"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Nepavyko užbaigti. Bandykite dar kartą."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Eilėje nieko nebėra"</string>
 </resources>
diff --git a/car-media-common/res/values-lv/strings.xml b/car-media-common/res/values-lv/strings.xml
index d4b6044..754a59d 100644
--- a/car-media-common/res/values-lv/strings.xml
+++ b/car-media-common/res/values-lv/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albuma noformējums"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Bez nosaukuma"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Radās problēma. Mēģiniet vēlāk."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Pašlaik nevar veikt šo darbību."</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Šo darbību nevar veikt šajā lietotnē."</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Pierakstieties, lai izmantotu šo lietotni."</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Nepieciešama maksas piekļuve."</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Klausīšanās notiek pārāk daudz ierīcēs."</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Šis saturs ir bloķēts."</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Nevar šeit parādīt šo saturu."</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Šis saturs jau tiek atskaņots."</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Vairs nevar izlaist nevienu ierakstu."</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Nevarēja pabeigt. Mēģiniet vēlreiz."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Rindā vairs nav nekā cita."</string>
 </resources>
diff --git a/car-media-common/res/values-mk/strings.xml b/car-media-common/res/values-mk/strings.xml
index d46c551..5e71b5a 100644
--- a/car-media-common/res/values-mk/strings.xml
+++ b/car-media-common/res/values-mk/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Корица на албум"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Без наслов"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Нешто не е во ред. Обидете се подоцна."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Не може да се направи тоа во моментов"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Апликацијава не може да го направи тоа"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Најавете се за да ја користите апликацијава"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Потребен е пристап со Premium"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Се слуша на премногу уреди"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Таа содржина е блокирана"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Не може да се добие таа содржина тука"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Веќе е пуштена таа содржина"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Не може да прескокне повеќе песни"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Не може да заврши. Обидете се повторно."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Веќе ништо не чека на ред"</string>
 </resources>
diff --git a/car-media-common/res/values-ml/strings.xml b/car-media-common/res/values-ml/strings.xml
index cd1743b..edcd0e1 100644
--- a/car-media-common/res/values-ml/strings.xml
+++ b/car-media-common/res/values-ml/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"ആൽബം ആർട്ട്"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"പേരില്ല"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"എന്തോ കുഴപ്പമുണ്ട്. പിന്നീട് ശ്രമിക്കുക."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"അത് ഇപ്പോൾ ചെയ്യാനാകില്ല"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"ഈ ആപ്പിന് അത് ചെയ്യാനാകില്ല"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"ഈ ആപ്പ് ഉപയോഗിക്കാൻ സെെൻ ഇൻ ചെയ്യുക"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"പ്രീമിയം ആക്‌സസ് ആവശ്യമാണ്"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"നിരവധി ഉപകരണങ്ങളിൽ കേൾക്കുന്നു"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ആ ഉള്ളടക്കം ബ്ലോക്ക് ചെയ്‌തിരിക്കുന്നു"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ആ ഉള്ളടക്കം ഇവിടെ ലഭ്യമല്ല"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"ആ ഉള്ളടക്കം നിലവിൽ പ്ലേ ചെയ്യുകയാണ്"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ഇനിയും ട്രാക്കുകൾ ഒഴിവാക്കാനാകില്ല"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"പൂർത്തിയാക്കാനായില്ല. വീണ്ടും ശ്രമിക്കുക."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"മറ്റൊന്നും ക്യൂവിലില്ല"</string>
 </resources>
diff --git a/car-media-common/res/values-mn/strings.xml b/car-media-common/res/values-mn/strings.xml
index 0cbec6a..b9cb37c 100644
--- a/car-media-common/res/values-mn/strings.xml
+++ b/car-media-common/res/values-mn/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Цомгийн зураг"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Гарчиг алга"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Алдаа гарлаа. Дараа оролдоно уу."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Яг одоо үүнийг хийх боломжгүй"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Энэ апп үүнийг хийх боломжгүй"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Энэ аппыг ашиглахын тулд нэвтэрнэ үү"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium хандалт шаардлагатай"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Хэт олон төхөөрөмж дээр сонсож байна"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Энэ контентыг блоклосон байна"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Энэ контентыг энд авах боломжгүй"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Энэ контентыг аль хэдийн тоглуулж байна"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Бичлэг дахин алгасах боломжгүй"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Дуусгаж чадсангүй. Дахин оролдоно уу."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Дараалалд өөр юу ч алга"</string>
 </resources>
diff --git a/car-media-common/res/values-mr/strings.xml b/car-media-common/res/values-mr/strings.xml
index 450fddd..27547ba 100644
--- a/car-media-common/res/values-mr/strings.xml
+++ b/car-media-common/res/values-mr/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"अल्बम कला"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"शीर्षक नाही"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"काहीतरी चूक झाली. नंतर प्रयत्न करा."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"ते आता करू शकत नाही"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"हे ॲप ते करू शकत नाही"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"हे ॲप वापरण्यासाठी साइन इन करा"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"प्रीमियम ॲक्सेस आवश्यक आहे"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"खूप जास्‍त डिव्हाइसवर ऐकले जात आहे"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"तो आशय ब्लॉक केलेला आहे"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"तो आशय येथे मिळवू शकत नाही"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"तो आशय आधीपासून प्ले करत आहे"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"आणखी ट्रॅक वगळू शकत नाही"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"पूर्ण करता आले नाही. पुन्हा प्रयत्न करा."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"इतर काहीही क्यू केलेले नाही"</string>
 </resources>
diff --git a/car-media-common/res/values-ms/strings.xml b/car-media-common/res/values-ms/strings.xml
index f5660bf..3a16397 100644
--- a/car-media-common/res/values-ms/strings.xml
+++ b/car-media-common/res/values-ms/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Seni Album"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Tiada Tajuk"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Ada yang tidak kena. Cuba nanti."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Tidak dapat berbuat demikian sekarang"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Apl ini tidak dapat berbuat demikian"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Log masuk untuk menggunakan apl ini"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Akses premium diperlukan"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Mendengar pada terlalu banyak peranti"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Kandungan itu disekat"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Tidak boleh mendapatkan kandungan itu di sini"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Sudah pun memainkan kandungan itu"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Tidak boleh melangkau apa-apa lagu lagi"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Tidak dapat diselesaikan. Cuba lagi."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Tiada lagu lain dalam baris gilir"</string>
 </resources>
diff --git a/car-media-common/res/values-my/strings.xml b/car-media-common/res/values-my/strings.xml
index e717f45..f4723b0 100644
--- a/car-media-common/res/values-my/strings.xml
+++ b/car-media-common/res/values-my/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"အယ်လ်ဘမ်ပုံ"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"ခေါင်းစဉ် မရှိပါ"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"တစ်ခုခု မှားနေသည်။ နောက်မှ စမ်းကြည့်ပါ။"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"၎င်းကို ယခု မပြုလုပ်နိုင်ပါ"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"ဤအက်ပ်က ၎င်းကို မပြုလုပ်နိုင်ပါ"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"ဤအက်ပ်အသုံးပြုရန် လက်မှတ်ထိုးဝင်ပါ"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"ပရီမီယံ အသုံးပြုခွင့် လိုအပ်သည်"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"စက်ပစ္စည်းအများအပြားတွင် နားထောင်နေသည်"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ထိုအကြောင်းအရာကို ပိတ်ထားသည်"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ထိုအကြောင်းအရာကို ဤနေရာတွင် မရရှိနိုင်ပါ"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"ထိုအကြောင်းအရာကို ဖွင့်နေပြီဖြစ်သည်"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"နောက်ထပ်သီချင်းပုဒ်များ ကျော်၍မရတော့ပါ"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"အပြီးသတ်၍ မရပါ။ ထပ်စမ်းကြည့်ပါ။"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"အခြားစီထားသည်များ မရှိပါ"</string>
 </resources>
diff --git a/car-media-common/res/values-nb/strings.xml b/car-media-common/res/values-nb/strings.xml
index b01654e..878beb2 100644
--- a/car-media-common/res/values-nb/strings.xml
+++ b/car-media-common/res/values-nb/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albumgrafikk"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Ingen tittel"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Noe gikk galt. Prøv senere."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Kan ikke gjøre det akkurat nå"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Denne appen kan ikke gjøre det"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Logg på for å bruke denne appen"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premiumtilgang kreves"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Du lytter på for mange enheter"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Innholdet er blokkert"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Innholdet er ikke tilgjengelig her"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Du spiller allerede av innholdet"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Kan ikke hoppe over flere spor"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Kunne ikke fullføre. Prøv på nytt."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ingenting annet står i køen"</string>
 </resources>
diff --git a/car-media-common/res/values-ne/strings.xml b/car-media-common/res/values-ne/strings.xml
index 8e2bf79..5eed37d 100644
--- a/car-media-common/res/values-ne/strings.xml
+++ b/car-media-common/res/values-ne/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"एल्बम आर्ट"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"शीर्षक छैन"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"केही चिज गडबड छ। पछि प्रयास गर्नुहोस्।"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"त्यो कार्य अहिले नै गर्न सकिँदैन"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"यस अनुप्रयोगले उक्त कार्य गर्न सक्दैन"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"यो एप प्रयोग गर्न साइन इन गर्नुहोस्"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"यसका लागि प्रिमियम खातामाथिको पहुँच आवश्यक हुन्छ"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"अत्यधिक यन्त्रहरूबाट सुनिँदै छ"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"उक्त सामग्री ब्लक गरिएको छ"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"उक्त सामग्री यहाँ प्राप्त गर्न सकिँदैन"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"उक्त सामग्री प्ले भइरहेको छ"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"योभन्दा धेरै गीतहरू स्किप गर्न सकिँदैन"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"कार्य पूरा गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"सूचीमा अरू केही पनि छैन"</string>
 </resources>
diff --git a/car-media-common/res/values-nl/strings.xml b/car-media-common/res/values-nl/strings.xml
index 8259676..e8acdbc 100644
--- a/car-media-common/res/values-nl/strings.xml
+++ b/car-media-common/res/values-nl/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albumhoes"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Geen titel"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Er is iets misgegaan. Probeer het later opnieuw."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Kan dat nu niet doen"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Deze app kan dat niet doen"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Log in om deze app te gebruiken"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium toegang vereist"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Er wordt op te veel apparaten geluisterd"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Die content is geblokkeerd"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Kan die content hier niet krijgen"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Die content wordt al afgespeeld"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Kan geen nummers meer overslaan"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Kan niet voltooien. Probeer het opnieuw."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Er staat niets anders in de wachtrij"</string>
 </resources>
diff --git a/car-media-common/res/values-or/strings.xml b/car-media-common/res/values-or/strings.xml
index a714293..e503cd8 100644
--- a/car-media-common/res/values-or/strings.xml
+++ b/car-media-common/res/values-or/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"ଆଲବମ୍ ଆର୍ଟ"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"କୌଣସି ଟାଇଟେଲ୍ ନାହିଁ"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"କିଛି ସମସ୍ୟା ଅଛି। ପରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"ବର୍ତ୍ତମାନ ତାହା କରାଯାଇପାରିବ ନାହିଁ"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"ଏହି ଆପ୍ ତାହା କରିପାରିବ ନାହିଁ"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"ଏହି ଆପ୍ ବ୍ୟବହାର କରିବାକୁ ସାଇନ୍ ଇନ୍ କରନ୍ତୁ"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"ପ୍ରିମିୟମ୍ ଆକ୍ସେସ୍ ଆବଶ୍ୟକ"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ଅନେକ ଡିଭାଇସରେ ଶୁଣାଯାଉଛି"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ସେହି ବିଷୟବସ୍ତୁକୁ ବ୍ଲକ୍ କରାଯାଇଛି"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ଏଠାରେ ସେହି ବିଷୟବସ୍ତୁ ପାଇପାରିବେ ନାହିଁ"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"ପୂର୍ବରୁ ସେହି ବିଷୟବସ୍ତୁ ଚଲାଯାଉଛି"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ଆଉ ଅଧିକ ଟ୍ରାକକୁ ବାଦ୍ ଦିଆଯାଇପାରିବ ନାହିଁ"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"ସମାପ୍ତ କରାଯାଇପାରିଲା ନାହିଁ। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"ଧାଡ଼ିରେ ଆଉ କିଛି ନାହିଁ"</string>
 </resources>
diff --git a/car-media-common/res/values-pa/strings.xml b/car-media-common/res/values-pa/strings.xml
index e3a607b..5d02f20 100644
--- a/car-media-common/res/values-pa/strings.xml
+++ b/car-media-common/res/values-pa/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"ਐਲਬਮ ਕਲਾ"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"ਕੋਈ ਸਿਰਲੇਖ ਨਹੀਂ"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"ਕੋਈ ਗੜਬੜ ਹੈ। ਬਾਅਦ ਵਿੱਚ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"ਫਿਲਹਾਲ ਇਹ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"ਇਹ ਐਪ ਇਹ ਨਹੀਂ ਕਰ ਸਕਦੀ"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"ਇਸ ਐਪ ਨੂੰ ਵਰਤਣ ਲਈ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"ਪ੍ਰੀਮੀਅਮ ਪਹੁੰਚ ਦੀ ਲੋੜ ਹੈ"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ਬਹੁਤ ਜ਼ਿਆਦਾ ਡੀਵਾਈਸਾਂ \'ਤੇ ਸੁਣਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ਇਸ ਸਮੱਗਰੀ ਨੂੰ ਬਲਾਕ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ਇੱਥੇ ਇਹ ਸਮੱਗਰੀ ਨਹੀਂ ਮਿਲ ਸਕਦੀ"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"ਇਹ ਸਮੱਗਰੀ ਪਹਿਲਾਂ ਹੀ ਚੱਲ ਰਹੀ ਹੈ"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ਕਿਸੇ ਹੋਰ ਟਰੈਕ ਨੂੰ ਛੱਡਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"ਪੂਰਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"ਹੋਰ ਕੁਝ ਵੀ ਕਤਾਰਬੱਧ ਨਹੀਂ ਹੈ"</string>
 </resources>
diff --git a/car-media-common/res/values-pl/strings.xml b/car-media-common/res/values-pl/strings.xml
index fd00ae5..1bdcf9a 100644
--- a/car-media-common/res/values-pl/strings.xml
+++ b/car-media-common/res/values-pl/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Okładka albumu"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Bez tytułu"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Coś poszło nie tak. Spróbuj później."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Teraz nie można tego zrobić"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Ta aplikacja nie ma takiej funkcji"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Zaloguj się, by używać tej aplikacji"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Wymagany jest dostęp na poziomie premium"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Słuchasz na zbyt wielu urządzeniach"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Te materiały są zablokowane"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Te materiały są niedostępne w tym regionie"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Te materiały są już odtwarzane"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Nie można pominąć większej liczby utworów"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Nie udało się dokończyć. Spróbuj ponownie."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nie ma już nic w kolejce"</string>
 </resources>
diff --git a/car-media-common/res/values-pt-rPT/strings.xml b/car-media-common/res/values-pt-rPT/strings.xml
index 2ae65bf..ab6e0ff 100644
--- a/car-media-common/res/values-pt-rPT/strings.xml
+++ b/car-media-common/res/values-pt-rPT/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Imagem do álbum"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Sem título"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Ocorreu um problema. Tente mais tarde."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Não é possível responder a esse pedido neste momento."</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Esta app não consegue responder a esse pedido."</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Inicie sessão para utilizar esta app."</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Acesso premium necessário."</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"A ouvir em demasiados dispositivos."</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Esse conteúdo está bloqueado."</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Não é possível obter esse conteúdo aqui."</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Esse conteúdo já está em reprodução."</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Não é possível ignorar mais faixas."</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Não foi possível terminar. Tente novamente."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Não existem mais elementos em fila."</string>
 </resources>
diff --git a/car-media-common/res/values-pt/strings.xml b/car-media-common/res/values-pt/strings.xml
index 1b39a7b..2a295f8 100644
--- a/car-media-common/res/values-pt/strings.xml
+++ b/car-media-common/res/values-pt/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Arte do álbum"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Sem título"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Algo deu errado. Tente mais tarde."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Não é possível fazer isso no momento"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Não é possível fazer isso com este app"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Faça login para usar este app"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"É necessário ter acesso Premium"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Ouvindo em muitos dispositivos"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Esse conteúdo está bloqueado"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Não é possível usar esse conteúdo aqui"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Esse conteúdo já está tocando"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Não é possível pular mais faixas"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Não foi possível concluir esta ação. Tente novamente."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Não há mais nada na fila"</string>
 </resources>
diff --git a/car-media-common/res/values-ro/strings.xml b/car-media-common/res/values-ro/strings.xml
index 38c53cc..3bcb08d 100644
--- a/car-media-common/res/values-ro/strings.xml
+++ b/car-media-common/res/values-ro/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Grafica albumului"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Fără titlu"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"A apărut o problemă. Încercați mai târziu."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Acțiunea nu poate fi realizată momentan"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Aplicația nu poate realiza această acțiune"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Conectați-vă pentru a folosi aplicația"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Este necesar accesul premium"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Se ascultă pe prea multe dispozitive"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Conținutul este blocat"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Conținutul nu poate fi descărcat aici"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Conținutul se redă deja"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Nu mai pot fi omise melodii"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Nu s-a putut finaliza. Încercați din nou."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nu mai există elemente în coadă"</string>
 </resources>
diff --git a/car-media-common/res/values-ru/strings.xml b/car-media-common/res/values-ru/strings.xml
index 990650f..6f4a6ba 100644
--- a/car-media-common/res/values-ru/strings.xml
+++ b/car-media-common/res/values-ru/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Обложка альбома"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Без названия"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Ошибка. Повторите попытку позже."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Сейчас это действие недоступно."</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Действие недоступно в этом приложении."</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Чтобы использовать это приложение, войдите в аккаунт."</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Требуется премиум-доступ."</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Слишком много устройств, на которых воспроизводится аудио."</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Контент заблокирован."</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Контент недоступен в вашем регионе."</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Этот контент уже воспроизводится."</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Пропускать треки больше нельзя."</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Произошла ошибка. Повторите попытку."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"В очереди воспроизведения нет других файлов."</string>
 </resources>
diff --git a/car-media-common/res/values-si/strings.xml b/car-media-common/res/values-si/strings.xml
index 7da552b..f748730 100644
--- a/car-media-common/res/values-si/strings.xml
+++ b/car-media-common/res/values-si/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"ඇල්බම කලාව"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"මාතෘකාවක් නැත"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"යම් දෙයක් වැරදියි. පසුව උත්සාහ කරන්න."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"මේ දැන් එය කළ නොහැක"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"මෙම යෙදුමට එය කළ නොහැක"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"මෙම යෙදුම භාවිත කිරීමට පුරන්න"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"පාරිතෝෂික ප්‍රවේශය අවශ්‍යයි"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ඕනෑවට වඩා උපාංග මත සවන් දීම"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"එම අන්තර්ගතය අවහිර කර ඇත"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"එම අන්තර්ගතය මෙතැනින් ලබා ගත නොහැක"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"දැනටමත් එම අන්තර්ගතය වාදනය කරයි"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"තවත් ගීත මඟ හැරිය නොහැක"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"අවසන් කිරීමට නොහැකි විය. නැවත උත්සාහ කරන්න."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"වෙන කිසිවක් පෝලිමේ නැත"</string>
 </resources>
diff --git a/car-media-common/res/values-sk/strings.xml b/car-media-common/res/values-sk/strings.xml
index d94bac4..7a12c96 100644
--- a/car-media-common/res/values-sk/strings.xml
+++ b/car-media-common/res/values-sk/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Obrázok albumu"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Bez názvu"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Vyskytol sa problém. Skúste to znova."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Teraz to nie je možné"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Táto aplikácia to nedokáže"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Ak chcete použiť túto aplikáciu, prihláste sa"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Vyžaduje sa prémiový prístup"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Počúva sa na príliš veľa zariadeniach"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Daný obsah je blokovaný"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Daný obsah tu nie je k dispozícii"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Tento obsah sa už prehráva"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Nie je možné preskočiť žiadne ďalšie skladby"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Akciu sa nepodarilo dokončiť. Skúste to znova."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Poradie je prázdne"</string>
 </resources>
diff --git a/car-media-common/res/values-sl/strings.xml b/car-media-common/res/values-sl/strings.xml
index 683507c..aec70f5 100644
--- a/car-media-common/res/values-sl/strings.xml
+++ b/car-media-common/res/values-sl/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Slika albuma"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Brez naslova"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Prišlo je do težave. Poskusite pozneje."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Trenutno to ni izvedljivo"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Ta aplikacija ne more izvesti tega"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Prijavite se, če želite uporabljati to aplikacijo"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Potrebujete plačljiv dostop"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Poslušanje v preveč napravah"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ta vsebina je blokirana"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Te vsebine tukaj ni mogoče pridobiti"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ta vsebina se že predvaja"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Dosegli ste omejitev preskakovanja skladb"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Ni bilo mogoče dokončati. Poskusite znova."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Čakalna vrsta je prazna"</string>
 </resources>
diff --git a/car-media-common/res/values-sq/strings.xml b/car-media-common/res/values-sq/strings.xml
index 354b5ed..d4d02e1 100644
--- a/car-media-common/res/values-sq/strings.xml
+++ b/car-media-common/res/values-sq/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Kopertina e albumit"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Pa titull"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Ndodhi një gabim. Provo më vonë."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Këtë nuk mund ta bësh në këtë moment"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Ky aplikacion nuk mund ta bëjë këtë"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Identifikohu për të përdorur këtë aplikacion"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Kërkohet qasje Premium"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Po dëgjon në shumë pajisje"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Kjo përmbajtje është bllokuar"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Ajo përmbajtje nuk mund të merret këtu"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ajo përmbajtje po luhet tashmë"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Nuk mund të kapërcejë këngë të tjera"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Nuk mundi të përfundojë. Provo sërish."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Asgjë tjetër nuk është në radhë"</string>
 </resources>
diff --git a/car-media-common/res/values-sr/strings.xml b/car-media-common/res/values-sr/strings.xml
index e6173ba..69c898d 100644
--- a/car-media-common/res/values-sr/strings.xml
+++ b/car-media-common/res/values-sr/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Омот албума"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Без наслова"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Дошло је до грешке. Пробајте касније."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Тренутно не може то да уради"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Ова апликација не може то да уради"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Пријавите се да бисте користили ову апликацију"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Потребан је премијум приступ"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Слушате на превише уређаја"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Тај садржај је блокиран"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Не можете да добијете тај садржај овде"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Тај садржај се већ репродукује"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Не можете више да прескачете песме"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Нисмо успели да довршимо радњу. Пробајте опет."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ништа друго није стављено у редослед"</string>
 </resources>
diff --git a/car-media-common/res/values-sv/strings.xml b/car-media-common/res/values-sv/strings.xml
index ce52ecf..05a0d28 100644
--- a/car-media-common/res/values-sv/strings.xml
+++ b/car-media-common/res/values-sv/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Skivomslag"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Ingen titel"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Något är fel. Försök igen senare."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Det går inte just nu"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Det går inte med den här appen"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Logga in om du vill använda appen"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium-konto krävs"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"För många enheter streamas"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Det innehållet har blockerats"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Det innehållet är inte tillgängligt här"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Det innehållet spelas redan"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Det går inte att hoppa över fler låtar"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Det gick inte att slutföra. Försök igen."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Inget annat har lagts i uppspelningskön"</string>
 </resources>
diff --git a/car-media-common/res/values-sw/strings.xml b/car-media-common/res/values-sw/strings.xml
index 282555c..30937ac 100644
--- a/car-media-common/res/values-sw/strings.xml
+++ b/car-media-common/res/values-sw/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Sanaa ya Albamu"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Hakuna Jina"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Hitilafu fulani imetokea. Jaribu baadaye."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Imeshindwa kutekeleza ombi kwa sasa"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Programu hii imeshindwa kutekeleza ombi lako"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Ingia katika akaunti ili utumie programu hii"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Unahitaji kutumia akaunti ya kulipia"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Unasikiliza kwenye vifaa vingi mno"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Maudhui hayo yamezuiwa"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Imeshindwa kupata maudhui haya hapa"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Tayari inacheza wimbo huo"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Imeshindwa kuruka nyimbo zaidi"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Imeshindwa kumaliza. Jaribu tena."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Hakuna wimbo mwingine kwenye foleni"</string>
 </resources>
diff --git a/car-media-common/res/values-ta/strings.xml b/car-media-common/res/values-ta/strings.xml
index dd522b8..6fc14ba 100644
--- a/car-media-common/res/values-ta/strings.xml
+++ b/car-media-common/res/values-ta/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"ஆல்பம் ஆர்ட்"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"தலைப்பு இல்லை"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"ஏதோ தவறாகிவிட்டது. பிறகு முயலவும்."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"தற்சமயம் இதைச் செய்ய இயலாது"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"இந்த ஆப்ஸால் அதைச் செய்ய இயலாது"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"இந்த ஆப்ஸை உபயோகிக்க உள்நுழைய வேண்டும்"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium அணுகல் தேவை"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"நிறைய சாதனங்களைக் கவனிக்கிறது"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"அந்த உள்ளடக்கம் தடுக்கப்பட்டுள்ளது"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"அந்த உள்ளடக்கத்தை இங்கே பெற முடியவில்லை"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"அந்த உள்ளடக்கம் ஏற்கெனவே பிளே செய்யப்பட்டுக் கொண்டிருக்கிறது"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"இதற்கு மேல் டிராக்குகளைத் தவிர்க்க முடியாது"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"நிறைவடையவில்லை. மீண்டும் முயலவும்."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"வேறு எதுவும் வரிசையில் இல்லை"</string>
 </resources>
diff --git a/car-media-common/res/values-te/strings.xml b/car-media-common/res/values-te/strings.xml
index 62a7464..f3dec11 100644
--- a/car-media-common/res/values-te/strings.xml
+++ b/car-media-common/res/values-te/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"ఆల్బమ్ ఆర్ట్"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"శీర్షిక లేదు"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"ఏదో తప్పు జరిగింది. తర్వాత ట్రై చేయండి."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"దానిని ఇప్పుడు చేయడం సాధ్యపడదు"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"ఈ యాప్ దానిని చేయలేదు"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"ఈ యాప్‌ను ఉపయోగించడానికి సైన్ ఇన్ చేయండి"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"ప్రీమియం యాక్సెస్ అవసరం"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"అనేక పరికరాలలో వింటున్నారు"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ఆ కంటెంట్ బ్లాక్ చేయబడింది"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ఆ కంటెంట్‌ను ఇక్కడ పొందడం సాధ్యపడదు"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"ఇప్పటికే ఆ కంటెంట్ ప్లే అవుతోంది"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ఇంక ఏ ట్రాక్‌లనూ స్కిప్ చేయడం సాధ్యపడదు"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"పూర్తి చేయడం సాధ్యపడలేదు. మళ్లీ ట్రై చేయండి."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"క్రమ వరుసలో ఏమీ లేదు"</string>
 </resources>
diff --git a/car-media-common/res/values-th/strings.xml b/car-media-common/res/values-th/strings.xml
index f642719..3e3aa59 100644
--- a/car-media-common/res/values-th/strings.xml
+++ b/car-media-common/res/values-th/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"ปกอัลบั้ม"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"ไม่มีชื่อ"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"มีข้อผิดพลาดเกิดขึ้น ลองอีกครั้งในภายหลัง"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"ดำเนินการดังกล่าวไม่ได้ในขณะนี้"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"แอปนี้ดำเนินการดังกล่าวไม่ได้"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"ลงชื่อเข้าใช้เพื่อใช้แอปนี้"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"ต้องมีสิทธิ์เข้าถึงระดับพรีเมียม"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"กำลังเปิดฟังจากหลายอุปกรณ์เกินไป"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"เนื้อหาดังกล่าวถูกบล็อก"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"เปิดเนื้อหาดังกล่าวไม่ได้ที่นี่"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"กำลังเล่นเนื้อหาดังกล่าวอยู่แล้ว"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ข้ามแทร็กอื่นอีกไม่ได้แล้ว"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"ดำเนินการไม่สำเร็จ ลองใหม่"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"ไม่มีรายการอื่นในคิว"</string>
 </resources>
diff --git a/car-media-common/res/values-tl/strings.xml b/car-media-common/res/values-tl/strings.xml
index a2da896..39bc081 100644
--- a/car-media-common/res/values-tl/strings.xml
+++ b/car-media-common/res/values-tl/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Album Art"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Walang Pamagat"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"May problema. Subukan sa ibang pagkakataon."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Hindi iyon magagawa ngayon"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Hindi iyon magagawa ng app na ito"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Mag-sign in para gamitin ang app na ito"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Kailangan ng premium na access"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Nakikinig sa masyadong maraming device"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Naka-block ang content na iyon"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Hindi makukuha ang content na iyon dito"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Pine-play na ang content na iyon"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Hindi na makakalaktaw pa ng track"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Hindi matapos. Subukan ulit."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Wala nang naka-queue"</string>
 </resources>
diff --git a/car-media-common/res/values-tr/strings.xml b/car-media-common/res/values-tr/strings.xml
index 6f717ac..7fc645e 100644
--- a/car-media-common/res/values-tr/strings.xml
+++ b/car-media-common/res/values-tr/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albüm Kapağı"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Başlıksız"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Bir şeyler ters gitti. Daha sonra tekrar deneyin."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"İşlem şu anda gerçekleştirilemiyor"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Uygulama bu işlemi gerçekleştiremiyor"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Bu uygulamayı kullanmak için oturum açın"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium erişim gerekli"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Çok fazla cihazda dinleme yapılıyor"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Bu içerik engellendi"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"İçeriğe buradan erişilemiyor"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Bu içerik zaten oynatılıyor"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Daha fazla parça atlanamıyor"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Tamamlanamadı. Tekrar deneyin."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Sıraya eklenmiş başka bir şey yok"</string>
 </resources>
diff --git a/car-media-common/res/values-uk/strings.xml b/car-media-common/res/values-uk/strings.xml
index 0661490..e377c98 100644
--- a/car-media-common/res/values-uk/strings.xml
+++ b/car-media-common/res/values-uk/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Обкладинка альбому"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Без назви"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Сталася помилка. Повторіть спробу пізніше."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Наразі ця дія недоступна"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Додаток не підтримує цю дію"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Увійдіть, щоб використовувати цей додаток"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Потрібен преміум-доступ"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Забагато пристроїв, на яких відтворюється контент"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Цей контент заблоковано"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Цей контент недоступний у вашому регіоні"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Цей контент уже відтворюється"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Більше не можна пропускати композиції"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Не вдалося закінчити. Повторіть спробу."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Черга порожня"</string>
 </resources>
diff --git a/car-media-common/res/values-ur/strings.xml b/car-media-common/res/values-ur/strings.xml
index ccf505c..19dfce7 100644
--- a/car-media-common/res/values-ur/strings.xml
+++ b/car-media-common/res/values-ur/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"البم آرٹ"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"کوئی عنوان نہیں ہے"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"کچھ غلط ہو گیا۔ بعد میں کوشش کریں۔"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"ابھی وہ ایسا نہیں کر سکتا"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"یہ ایپ ایسا نہیں کر سکتی"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"اس ایپ کا استعمال کرنے کے لیے سائن ان کریں"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"پریمیم رسائی درکار ہے"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"بہت سے آلات پر سنا جا رہا ہے"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"وہ مواد مسدود ہے"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"یہاں وہ مواد حاصل نہیں کر سکتے"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"وہ مواد پہلے سے ہی چلایا جا رہا ہے"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"مزید کسی اور ٹریکس کو نظر انداز نہیں کر سکتے"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"پورا نہیں ہو سکا۔ پھر آزمائيں۔"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"مزید کچھ اور کی قطار نہیں ہے"</string>
 </resources>
diff --git a/car-media-common/res/values-uz/strings.xml b/car-media-common/res/values-uz/strings.xml
index f3e8826..b2bfd1e 100644
--- a/car-media-common/res/values-uz/strings.xml
+++ b/car-media-common/res/values-uz/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Albom muqovasi"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Nomsiz"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Nimadir xato ketdi Keyinroq qayta urining."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Buni hozir amalga oshira olmaydi"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Bu ilova bu amalni bajara olmaydi"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Bu ilovadan foydalanish uchun hisobingizga kiring"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Ishonchli ruxsat kerak"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Koʻplab qurilmalar tinglanmoqda"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Bu kontent bloklangan"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Bu kontentni bu yerga olish imkonsiz"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Bu kontent allaqachon ijro etilmoqda"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Boshqa treklarni qoldirib ketish imkonsiz"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Tugallanmadi. Qayta urining."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ijro navbatida boshqa fayllar mavjud emas"</string>
 </resources>
diff --git a/car-media-common/res/values-vi/strings.xml b/car-media-common/res/values-vi/strings.xml
index 2a99840..f75fe5a 100644
--- a/car-media-common/res/values-vi/strings.xml
+++ b/car-media-common/res/values-vi/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Ảnh bìa album"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Không có tiêu đề"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Đã xảy ra lỗi. Hãy thử lại sau."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Không thể thực hiện yêu cầu đó ngay bây giờ"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Ứng dụng này không thể thực hiện yêu cầu đó"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Hãy đăng nhập để dùng ứng dụng này"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Yêu cầu quyền truy cập đặc biệt"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Đang nghe trên quá nhiều thiết bị"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Nội dung đó đã bị chặn"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Không thể xem nội dung đó tại đây"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Đang phát nội dung đó rồi"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Không thể bỏ qua bản nhạc nào nữa"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Không thể hoàn tất. Hãy thử lại."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Không có nội dung nào khác trong hàng đợi"</string>
 </resources>
diff --git a/car-media-common/res/values-zh-rCN/strings.xml b/car-media-common/res/values-zh-rCN/strings.xml
index b3ddef1..9688f0e 100644
--- a/car-media-common/res/values-zh-rCN/strings.xml
+++ b/car-media-common/res/values-zh-rCN/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"专辑封面"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"无标题"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"出了点问题。请稍后重试。"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"目前无法执行该操作"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"此应用无法执行该操作"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"登录才能使用此应用"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"需要有付费帐号才能访问"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"同时使用太多设备聆听"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"该内容已遭到屏蔽"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"所在地区不提供这项内容"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"系统已在播放这项内容"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"无法再跳过更多曲目"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"无法完成操作。请重试。"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"队列中没有任何其他内容"</string>
 </resources>
diff --git a/car-media-common/res/values-zh-rHK/strings.xml b/car-media-common/res/values-zh-rHK/strings.xml
index 6ce371a..1e84ca7 100644
--- a/car-media-common/res/values-zh-rHK/strings.xml
+++ b/car-media-common/res/values-zh-rHK/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"專輯封面"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"無標題"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"發生錯誤,請稍後再試。"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"目前無法執行此操作"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"應用程式不支援此操作"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"登入帳戶才能使用此應用程式"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"必須付費才能存取"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"同時使用太多裝置聆聽音樂"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"內容已被封鎖"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"所在地區不提供該內容"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"系統已開始播放該內容"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"無法再略過曲目"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"無法完成此操作,請再試一次。"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"序列上沒有其他項目"</string>
 </resources>
diff --git a/car-media-common/res/values-zh-rTW/strings.xml b/car-media-common/res/values-zh-rTW/strings.xml
index 6ce371a..1b50b13 100644
--- a/car-media-common/res/values-zh-rTW/strings.xml
+++ b/car-media-common/res/values-zh-rTW/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"專輯封面"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"無標題"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"發生錯誤,請稍後再試。"</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"目前無法執行這項操作"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"應用程式不支援這項操作"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"必須登入帳戶,才能使用這個應用程式"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"必須付費才能存取"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"同時使用太多裝置聆聽音樂"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"內容已遭到封鎖"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"所在地區不提供該內容"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"系統已開始播放該內容"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"無法再略過曲目"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"無法完成這項操作,請再試一次。"</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"待播清單上沒有其他項目"</string>
 </resources>
diff --git a/car-media-common/res/values-zu/strings.xml b/car-media-common/res/values-zu/strings.xml
index cde3b48..6f06f6d 100644
--- a/car-media-common/res/values-zu/strings.xml
+++ b/car-media-common/res/values-zu/strings.xml
@@ -19,4 +19,16 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="album_art" msgid="3392647029019061691">"Ubuciko be-albhamu"</string>
     <string name="metadata_default_title" msgid="5902775732281325081">"Asikho isihloko"</string>
+    <string name="default_error_message" msgid="4044331619453864482">"Kukhona okungalungile. Zama emuva kwesikhathi."</string>
+    <string name="error_code_app_error" msgid="3608680401453743688">"Ayikwazi ukwenza lokho khona manje"</string>
+    <string name="error_code_not_supported" msgid="8004310657548193089">"Lolu hlelo lokusebenza alukwazi ukwenza lokho"</string>
+    <string name="error_code_authentication_expired" msgid="1727285213286610186">"Ngena ngemvume ukuze usebenzise lolu hlelo lokusebenza"</string>
+    <string name="error_code_premium_account_required" msgid="2328664287270814966">"Ukufinyelela kwe-premium kuyadingeka"</string>
+    <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Ilalele kumadivayisi amaningi kakhulu"</string>
+    <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Lokho okuqukethwe kuvinjelwe"</string>
+    <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Ayikwazi ukuletha lokho okuqukethwe lapha"</string>
+    <string name="error_code_content_already_playing" msgid="1306236349553004461">"Isivele idlala lokho okuqukethwe"</string>
+    <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Ayikwazi ukweqa amanye amathrekhi amaningi"</string>
+    <string name="error_code_action_aborted" msgid="8611777981356536501">"Ayikwazanga ukuqeda. Zama futhi."</string>
+    <string name="error_code_end_of_queue" msgid="6935022448319288887">"Akukho okunye okufakwe kulayini"</string>
 </resources>
diff --git a/car-media-common/res/values/dimens.xml b/car-media-common/res/values/dimens.xml
index d888e4b..058ec8e 100644
--- a/car-media-common/res/values/dimens.xml
+++ b/car-media-common/res/values/dimens.xml
@@ -24,6 +24,8 @@
     <!-- playback_fragment.xml -->
     <dimen name="playback_fragment_text_margin_top">@dimen/car_ui_padding_4</dimen>
     <dimen name="playback_fragment_text_margin_x">@dimen/car_ui_padding_4</dimen>
+    <dimen name="playback_fragment_error_button_margin_top">@dimen/car_ui_padding_5</dimen>
+    <dimen name="playback_fragment_error_button_margin_bottom">@dimen/car_ui_padding_4</dimen>
 
     <dimen name="playback_fragment_app_icon_margin_right">@dimen/car_ui_padding_4</dimen>
     <dimen name="playback_fragment_controls_margin_bottom">@dimen/car_ui_padding_4</dimen>
diff --git a/car-media-common/res/values/integers.xml b/car-media-common/res/values/integers.xml
index 2c6177d..cda2e06 100644
--- a/car-media-common/res/values/integers.xml
+++ b/car-media-common/res/values/integers.xml
@@ -36,4 +36,7 @@
     -->
     <integer name="media_items_bitmap_max_size_px">256</integer>
 
+    <!-- The maximum number of lines for the error message in the media widget. -->
+    <integer name="widget_error_text_max_lines">3</integer>
+
 </resources>
diff --git a/car-media-common/res/values/strings.xml b/car-media-common/res/values/strings.xml
index e5831bc..424e9f7 100644
--- a/car-media-common/res/values/strings.xml
+++ b/car-media-common/res/values/strings.xml
@@ -19,4 +19,29 @@
     <string name="album_art">Album Art</string>
     <!-- Default title of current track's metadata. [CHAR LIMIT=50] -->
     <string name="metadata_default_title">No Title</string>
+
+    <!-- Default error message [CHAR LIMIT=100] -->
+    <string name="default_error_message">Something’s wrong. Try later.</string>
+    <!-- Error message set when the application state is invalid to fulfill the request. [CHAR LIMIT=100] -->
+    <string name="error_code_app_error">Can’t do that right now</string>
+    <!-- Error message set when the request is not supported by the application. [CHAR LIMIT=100] -->
+    <string name="error_code_not_supported">This app can’t do that</string>
+    <!-- Error message set when the request cannot be performed because authentication has expired. [CHAR LIMIT=100] -->
+    <string name="error_code_authentication_expired">Sign in to use this app</string>
+    <!-- Error message set when a premium account is required for the request to succeed. [CHAR LIMIT=100] -->
+    <string name="error_code_premium_account_required">Premium access required</string>
+    <!-- Error message set when too many concurrent streams are detected. [CHAR LIMIT=100] -->
+    <string name="error_code_concurrent_stream_limit">Listening on too many devices</string>
+    <!-- Error message set when the content is blocked due to parental controls. [CHAR LIMIT=100] -->
+    <string name="error_code_parental_control_restricted">That content is blocked</string>
+    <!-- Error message set when the content is blocked due to being regionally unavailable. [CHAR LIMIT=100] -->
+    <string name="error_code_not_available_in_region">Can’t get that content here</string>
+    <!-- Error message set when the requested content is already playing. [CHAR LIMIT=100] -->
+    <string name="error_code_content_already_playing">Already playing that content</string>
+    <!-- Error message set when the application cannot skip any more songs because skip limit is reached. [CHAR LIMIT=100] -->
+    <string name="error_code_skip_limit_reached">Can’t skip any more tracks</string>
+    <!-- Error message set when the action is interrupted due to some external event. [CHAR LIMIT=100] -->
+    <string name="error_code_action_aborted">Couldn’t finish. Try again.</string>
+    <!-- Error message set when the playback navigation (previous, next) is not possible because the queue was exhausted. [CHAR LIMIT=100] -->
+    <string name="error_code_end_of_queue">Nothing else is queued up</string>
 </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/MediaButtonController.java b/car-media-common/src/com/android/car/media/common/MediaButtonController.java
index 48e0406..c399089 100644
--- a/car-media-common/src/com/android/car/media/common/MediaButtonController.java
+++ b/car-media-common/src/com/android/car/media/common/MediaButtonController.java
@@ -41,7 +41,9 @@
 import com.android.car.media.common.source.MediaSourceColors;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -54,6 +56,8 @@
 
     private static final String TAG = "MediaButton";
 
+    private final Map<String, ImageButton> mImageButtons = new HashMap<>();
+
     private Context mContext;
     private PlayPauseStopImageView mPlayPauseStopImageView;
     private View mPlayPauseStopImageContainer;
@@ -134,6 +138,7 @@
         mControlBar.setView(null, ControlBar.SLOT_RIGHT);
         mSkipNextAdded = false;
         mSkipPrevAdded = false;
+        mImageButtons.clear();
     }
 
     private ImageButton createIconButton(Drawable icon) {
@@ -228,12 +233,7 @@
             imageButtons.addAll(state.getCustomActions()
                     .stream()
                     .map(rawAction -> rawAction.fetchDrawable(mContext))
-                    .map(action -> {
-                        ImageButton button = createIconButton(action.mIcon);
-                        button.setOnClickListener(view ->
-                                mController.doCustomAction(action.mAction, action.mExtras));
-                        return button;
-                    })
+                    .map(action -> getOrCreateIconButton(action))
                     .collect(Collectors.toList()));
         }
         if (!mSkipPrevAdded && !imageButtons.isEmpty()) {
@@ -245,6 +245,21 @@
         mControlBar.setViews(imageButtons.toArray(new ImageButton[0]));
     }
 
+    private ImageButton getOrCreateIconButton(CustomPlaybackAction action) {
+        // Reuse the ImageButton with the same action identifier if it exists, because if the
+        // ImageButton is focused, replacing it with a new one will make it lose focus.
+        ImageButton button = mImageButtons.get(action.mAction);
+        if (button != null) {
+            button.setImageDrawable(action.mIcon);
+        } else {
+            button = createIconButton(action.mIcon);
+            mImageButtons.put(action.mAction, button);
+        }
+        button.setOnClickListener(view ->
+                mController.doCustomAction(action.mAction, action.mExtras));
+        return button;
+    }
+
     private void onPlayPauseStopClicked(View view) {
         if (mController == null) {
             return;
diff --git a/car-media-common/src/com/android/car/media/common/MinimizedPlaybackControlBar.java b/car-media-common/src/com/android/car/media/common/MinimizedPlaybackControlBar.java
index 9cfda34..17a37b2 100644
--- a/car-media-common/src/com/android/car/media/common/MinimizedPlaybackControlBar.java
+++ b/car-media-common/src/com/android/car/media/common/MinimizedPlaybackControlBar.java
@@ -62,7 +62,7 @@
 
     private void init(Context context) {
         mMediaButtonController = new MediaButtonController(context, this,
-                R.color.playback_control_color, R.layout.play_pause_stop_button_layout,
+                R.color.playback_control_color, R.layout.minimized_play_pause_stop_button_layout,
                 R.drawable.ic_skip_previous, R.drawable.ic_skip_next);
 
         mShowLinearProgressBar = context.getResources().getBoolean(R.bool.show_linear_progress_bar);
diff --git a/car-media-common/src/com/android/car/media/common/PlaybackErrorViewController.java b/car-media-common/src/com/android/car/media/common/PlaybackErrorViewController.java
new file mode 100644
index 0000000..2d4c7c8
--- /dev/null
+++ b/car-media-common/src/com/android/car/media/common/PlaybackErrorViewController.java
@@ -0,0 +1,93 @@
+/*
+ * 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.media.common;
+
+import android.app.PendingIntent;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.apps.common.UxrButton;
+import com.android.car.apps.common.UxrTextView;
+import com.android.car.apps.common.util.ViewUtils;
+
+
+/** Simple controller for the error message and the error button. */
+public class PlaybackErrorViewController {
+
+    private static final String TAG = "PlaybckErrViewCtrlr";
+
+    // mErrorMessageView is defined explicitly as a UxrTextView instead of a TextView to
+    // provide clarity as it may be misleading to assume that mErrorMessageView extends all TextView
+    // methods. In addition, it increases discoverability of runtime issues that may occur.
+    private final UxrTextView mErrorMessageView;
+    private final UxrButton mErrorButton;
+
+    private final int mFadeDuration;
+
+    public PlaybackErrorViewController(View content) {
+        mErrorMessageView = content.findViewById(R.id.error_message);
+        mErrorButton = content.findViewById(R.id.error_button);
+
+        Resources res = content.getContext().getResources();
+        mFadeDuration = res.getInteger(R.integer.new_album_art_fade_in_duration);
+    }
+
+    /** Animates away the error views. */
+    public void hideError() {
+        ViewUtils.hideViewAnimated(mErrorMessageView, mFadeDuration);
+        ViewUtils.hideViewAnimated(mErrorButton, mFadeDuration);
+    }
+
+    /** Hides the error views without animation. */
+    public void hideErrorNoAnim() {
+        ViewUtils.hideViewAnimated(mErrorMessageView, 0);
+        ViewUtils.hideViewAnimated(mErrorButton, 0);
+    }
+
+    /** Sets the error message and optionally the error button. */
+    public void setError(String message, @Nullable String label,
+            @Nullable PendingIntent pendingIntent, boolean isDistractionOptimized) {
+        mErrorMessageView.setText(message);
+        ViewUtils.showViewAnimated(mErrorMessageView, mFadeDuration);
+
+        // Only show the error button if the error is actionable.
+        if (label != null && pendingIntent != null) {
+            mErrorButton.setText(label);
+
+            mErrorButton.setUxRestrictions(isDistractionOptimized
+                    ? CarUxRestrictions.UX_RESTRICTIONS_BASELINE
+                    : CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP);
+
+            mErrorButton.setOnClickListener(v -> {
+                try {
+                    pendingIntent.send();
+                } catch (PendingIntent.CanceledException e) {
+                    if (Log.isLoggable(TAG, Log.ERROR)) {
+                        Log.e(TAG, "Pending intent canceled");
+                    }
+                }
+            });
+            ViewUtils.showViewAnimated(mErrorButton, mFadeDuration);
+        } else {
+            ViewUtils.hideViewAnimated(mErrorButton, mFadeDuration);
+        }
+    }
+}
diff --git a/car-media-common/src/com/android/car/media/common/PlaybackErrorsHelper.java b/car-media-common/src/com/android/car/media/common/PlaybackErrorsHelper.java
new file mode 100644
index 0000000..475346d
--- /dev/null
+++ b/car-media-common/src/com/android/car/media/common/PlaybackErrorsHelper.java
@@ -0,0 +1,150 @@
+/*
+ * 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.media.common;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.car.media.common.playback.PlaybackViewModel.PlaybackStateWrapper;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Abstract class to factorize most of the error handling logic.
+ */
+public abstract class PlaybackErrorsHelper {
+
+    private static final Map<Integer, Integer> ERROR_CODE_MESSAGES_MAP;
+
+    static {
+        Map<Integer, Integer> map = new HashMap<>();
+        map.put(PlaybackStateCompat.ERROR_CODE_APP_ERROR, R.string.error_code_app_error);
+        map.put(PlaybackStateCompat.ERROR_CODE_NOT_SUPPORTED, R.string.error_code_not_supported);
+        map.put(PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED,
+                R.string.error_code_authentication_expired);
+        map.put(PlaybackStateCompat.ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED,
+                R.string.error_code_premium_account_required);
+        map.put(PlaybackStateCompat.ERROR_CODE_CONCURRENT_STREAM_LIMIT,
+                R.string.error_code_concurrent_stream_limit);
+        map.put(PlaybackStateCompat.ERROR_CODE_PARENTAL_CONTROL_RESTRICTED,
+                R.string.error_code_parental_control_restricted);
+        map.put(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION,
+                R.string.error_code_not_available_in_region);
+        map.put(PlaybackStateCompat.ERROR_CODE_CONTENT_ALREADY_PLAYING,
+                R.string.error_code_content_already_playing);
+        map.put(PlaybackStateCompat.ERROR_CODE_SKIP_LIMIT_REACHED,
+                R.string.error_code_skip_limit_reached);
+        map.put(PlaybackStateCompat.ERROR_CODE_ACTION_ABORTED, R.string.error_code_action_aborted);
+        map.put(PlaybackStateCompat.ERROR_CODE_END_OF_QUEUE, R.string.error_code_end_of_queue);
+        ERROR_CODE_MESSAGES_MAP = Collections.unmodifiableMap(map);
+    }
+
+    private final Context mContext;
+    private PlaybackStateWrapper mCurrentPlaybackStateWrapper;
+
+    public PlaybackErrorsHelper(Context context) {
+        mContext = context;
+    }
+
+    protected abstract void handleNewPlaybackState(String displayedMessage, PendingIntent intent,
+            String label);
+
+    /**
+     * Triggers updates of the error state.
+     * Must be called when the children list of the root of the browse tree changes AND when
+     * the playback state changes.
+     */
+    public void handlePlaybackState(@NonNull String tag, PlaybackStateWrapper state,
+            boolean ignoreSameState) {
+        if (Log.isLoggable(tag, Log.DEBUG)) {
+            Log.d(tag,
+                    "handlePlaybackState(); state change: " + (mCurrentPlaybackStateWrapper != null
+                            ? mCurrentPlaybackStateWrapper.getState() : null) + " -> " + (
+                            state != null ? state.getState() : null));
+        }
+
+        if (state == null) {
+            mCurrentPlaybackStateWrapper = null;
+            return;
+        }
+
+        String displayedMessage = getDisplayedMessage(mContext, state);
+        if (Log.isLoggable(tag, Log.DEBUG)) {
+            Log.d(tag, "Displayed error message: [" + displayedMessage + "]");
+        }
+        if (ignoreSameState && mCurrentPlaybackStateWrapper != null
+                && mCurrentPlaybackStateWrapper.getState() == state.getState()
+                && TextUtils.equals(displayedMessage,
+                getDisplayedMessage(mContext, mCurrentPlaybackStateWrapper))) {
+            if (Log.isLoggable(tag, Log.DEBUG)) {
+                Log.d(tag, "Ignore same playback state.");
+            }
+            return;
+        }
+
+        mCurrentPlaybackStateWrapper = state;
+
+        PendingIntent intent = getErrorResolutionIntent(state);
+        String label = getErrorResolutionLabel(state);
+        handleNewPlaybackState(displayedMessage, intent, label);
+    }
+
+
+    @Nullable
+    private String getDisplayedMessage(Context ctx, @Nullable PlaybackStateWrapper state) {
+        if (state == null) {
+            return null;
+        }
+        if (!TextUtils.isEmpty(state.getErrorMessage())) {
+            return state.getErrorMessage().toString();
+        }
+        // ERROR_CODE_UNKNOWN_ERROR means there is no error in PlaybackState.
+        if (state.getErrorCode() != PlaybackStateCompat.ERROR_CODE_UNKNOWN_ERROR) {
+            Integer messageId = ERROR_CODE_MESSAGES_MAP.get(state.getErrorCode());
+            return messageId != null ? ctx.getString(messageId) : ctx.getString(
+                    R.string.default_error_message);
+        }
+        if (state.getState() == PlaybackStateCompat.STATE_ERROR) {
+            return ctx.getString(R.string.default_error_message);
+        }
+        return null;
+    }
+
+    @Nullable
+    private PendingIntent getErrorResolutionIntent(@NonNull PlaybackStateWrapper state) {
+        Bundle extras = state.getExtras();
+        return extras == null ? null : extras.getParcelable(
+                MediaConstants.ERROR_RESOLUTION_ACTION_INTENT);
+    }
+
+    @Nullable
+    private String getErrorResolutionLabel(@NonNull PlaybackStateWrapper state) {
+        Bundle extras = state.getExtras();
+        return extras == null ? null : extras.getString(
+                MediaConstants.ERROR_RESOLUTION_ACTION_LABEL);
+    }
+
+}
diff --git a/car-media-common/src/com/android/car/media/common/PlaybackFragment.java b/car-media-common/src/com/android/car/media/common/PlaybackFragment.java
index a9285ba..60f4d18 100644
--- a/car-media-common/src/com/android/car/media/common/PlaybackFragment.java
+++ b/car-media-common/src/com/android/car/media/common/PlaybackFragment.java
@@ -21,10 +21,13 @@
 import static com.android.car.arch.common.LiveDataFunctions.mapNonNull;
 
 import android.app.Application;
+import android.app.PendingIntent;
 import android.car.Car;
+import android.car.content.pm.CarPackageManager;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.os.Bundle;
+import android.text.TextUtils;
 import android.util.Size;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -38,66 +41,105 @@
 import androidx.fragment.app.FragmentActivity;
 import androidx.lifecycle.AndroidViewModel;
 import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
 import androidx.lifecycle.ViewModelProviders;
 
 import com.android.car.apps.common.BitmapUtils;
 import com.android.car.apps.common.CrossfadeImageView;
 import com.android.car.apps.common.imaging.ImageBinder;
 import com.android.car.apps.common.imaging.ImageBinder.PlaceholderType;
+import com.android.car.apps.common.util.CarPackageManagerUtils;
 import com.android.car.apps.common.util.ViewUtils;
+import com.android.car.arch.common.FutureData;
+import com.android.car.media.common.browse.BrowsedMediaItems;
+import com.android.car.media.common.browse.MediaBrowserViewModel;
 import com.android.car.media.common.playback.PlaybackViewModel;
+import com.android.car.media.common.playback.PlaybackViewModel.PlaybackStateWrapper;
 import com.android.car.media.common.source.MediaSource;
 import com.android.car.media.common.source.MediaSourceViewModel;
 
+import java.util.List;
+
 /**
  * {@link Fragment} that can be used to display and control the currently playing media item. Its
  * requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the hosting
  * application.
  */
 public class PlaybackFragment extends Fragment {
+    private static final String TAG = "PlaybackFragmentWidget";
+
+    private Car mCar;
+    private CarPackageManager mCarPackageManager;
     private Intent mAppSelectorIntent;
     private MediaSourceViewModel mMediaSourceViewModel;
+    private PlaybackViewModel mPlaybackViewModel;
     private ImageBinder<MediaItemMetadata.ArtworkRef> mAlbumArtBinder;
+    private ViewModel mInnerViewModel;
+
+    private PlaybackErrorViewController mPlaybackErrorViewController;
+    private PlaybackErrorsHelper mErrorsHelper;
+    private boolean mIsFatalError;
 
     @Nullable
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
             Bundle savedInstanceState) {
         FragmentActivity activity = requireActivity();
-        PlaybackViewModel playbackViewModel = PlaybackViewModel.get(activity.getApplication(),
+        mCar = Car.createCar(activity);
+        mCarPackageManager = (CarPackageManager) mCar.getCarManager(Car.PACKAGE_SERVICE);
+
+        mPlaybackViewModel = PlaybackViewModel.get(activity.getApplication(),
                 MEDIA_SOURCE_MODE_PLAYBACK);
         mMediaSourceViewModel = MediaSourceViewModel.get(activity.getApplication(),
                 MEDIA_SOURCE_MODE_PLAYBACK);
         mAppSelectorIntent = MediaSource.getSourceSelectorIntent(getContext(), true);
 
-        ViewModel innerViewModel = ViewModelProviders.of(activity).get(ViewModel.class);
-        innerViewModel.init(mMediaSourceViewModel, playbackViewModel);
+        mInnerViewModel = ViewModelProviders.of(activity).get(ViewModel.class);
+        mInnerViewModel.init(activity, mMediaSourceViewModel, mPlaybackViewModel);
 
         View view = inflater.inflate(R.layout.playback_fragment, container, false);
 
+        mPlaybackErrorViewController = new PlaybackErrorViewController(view);
+
         PlaybackControlsActionBar playbackControls = view.findViewById(R.id.playback_controls);
-        playbackControls.setModel(playbackViewModel, getViewLifecycleOwner());
-        playbackViewModel.getPlaybackStateWrapper().observe(getViewLifecycleOwner(),
-                state -> ViewUtils.setVisible(playbackControls,
-                        (state != null) && state.shouldDisplay()));
+        playbackControls.setModel(mPlaybackViewModel, getViewLifecycleOwner());
+        mPlaybackViewModel.getPlaybackStateWrapper().observe(getViewLifecycleOwner(),
+                state -> {
+                    ViewUtils.setVisible(playbackControls,
+                            (state != null) && state.shouldDisplay());
+                    if (mErrorsHelper != null) {
+                        mErrorsHelper.handlePlaybackState(TAG, state, /*ignoreSameState*/ true);
+                    }
+                });
 
         TextView appName = view.findViewById(R.id.app_name);
-        innerViewModel.getAppName().observe(getViewLifecycleOwner(), appName::setText);
+        mInnerViewModel.getAppName().observe(getViewLifecycleOwner(), appName::setText);
 
         TextView title = view.findViewById(R.id.title);
-        innerViewModel.getTitle().observe(getViewLifecycleOwner(), title::setText);
+        mInnerViewModel.getTitle().observe(getViewLifecycleOwner(), title::setText);
 
         TextView subtitle = view.findViewById(R.id.subtitle);
-        innerViewModel.getSubtitle().observe(getViewLifecycleOwner(), subtitle::setText);
+        mInnerViewModel.getSubtitle().observe(getViewLifecycleOwner(), subtitle::setText);
 
         ImageView appIcon = view.findViewById(R.id.app_icon);
-        innerViewModel.getAppIcon().observe(getViewLifecycleOwner(), appIcon::setImageBitmap);
+        mInnerViewModel.getAppIcon().observe(getViewLifecycleOwner(), appIcon::setImageBitmap);
+
+        mInnerViewModel.getBrowseTreeHasChildren().observe(getViewLifecycleOwner(),
+                this::onBrowseTreeHasChildrenChanged);
+
+        mMediaSourceViewModel.getPrimaryMediaSource().observe(getViewLifecycleOwner(),
+                this::onMediaSourceChanged);
+
+        View playbackScrim = view.findViewById(R.id.playback_scrim);
+        playbackScrim.setOnClickListener(
+                // Let the Media center trampoline figure out what to open.
+                v -> {
+                    if (!mIsFatalError) {
+                        startActivity(new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE));
+                    }
+                });
 
         CrossfadeImageView albumBackground = view.findViewById(R.id.album_background);
-        albumBackground.setOnClickListener(
-                // Let the Media center trampoline figure out what to open.
-                v -> startActivity(new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE)));
-
         int max = activity.getResources().getInteger(R.integer.media_items_bitmap_max_size_px);
         Size maxArtSize = new Size(max, max);
         mAlbumArtBinder = new ImageBinder<>(PlaceholderType.FOREGROUND, maxArtSize,
@@ -107,16 +149,59 @@
                     albumBackground.setImageBitmap(bitmap, true);
                 });
 
-        playbackViewModel.getMetadata().observe(getViewLifecycleOwner(),
+        mPlaybackViewModel.getMetadata().observe(getViewLifecycleOwner(),
                 item -> mAlbumArtBinder.setImage(PlaybackFragment.this.getContext(),
                         item != null ? item.getArtworkKey() : null));
         View appSelector = view.findViewById(R.id.app_selector_container);
         appSelector.setVisibility(mAppSelectorIntent != null ? View.VISIBLE : View.GONE);
         appSelector.setOnClickListener(e -> getContext().startActivity(mAppSelectorIntent));
 
+
+        mErrorsHelper = new PlaybackErrorsHelper(activity) {
+
+            @Override
+            public void handleNewPlaybackState(String displayedMessage, PendingIntent intent,
+                    String label) {
+                mIsFatalError = false;
+                if (!TextUtils.isEmpty(displayedMessage)) {
+                    Boolean hasChildren = mInnerViewModel.getBrowseTreeHasChildren().getValue();
+                    if (hasChildren != null && !hasChildren) {
+                        boolean isDistractionOptimized =
+                                intent != null && CarPackageManagerUtils.isDistractionOptimized(
+                                        mCarPackageManager, intent);
+                        mPlaybackErrorViewController.setError(displayedMessage, label, intent,
+                                isDistractionOptimized);
+                        mIsFatalError = true;
+                    }
+                }
+
+                if (!mIsFatalError) {
+                    mPlaybackErrorViewController.hideError();
+                }
+            }
+        };
+
         return view;
     }
 
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mCar.disconnect();
+        mErrorsHelper = null;
+    }
+
+    private void onBrowseTreeHasChildrenChanged(@Nullable Boolean hasChildren) {
+        if (hasChildren != null && mErrorsHelper != null) {
+            PlaybackStateWrapper state = mPlaybackViewModel.getPlaybackStateWrapper().getValue();
+            mErrorsHelper.handlePlaybackState(TAG, state, /*ignoreSameState*/ false);
+        }
+    }
+
+    private void onMediaSourceChanged(MediaSource source) {
+        mPlaybackErrorViewController.hideErrorNoAnim();
+    }
+
     /**
      * ViewModel for the PlaybackFragment
      */
@@ -127,15 +212,18 @@
         private LiveData<Bitmap> mAppIcon;
         private LiveData<CharSequence> mTitle;
         private LiveData<CharSequence> mSubtitle;
+        private MutableLiveData<Boolean> mBrowseTreeHasChildren = new MutableLiveData<>();
 
         private PlaybackViewModel mPlaybackViewModel;
         private MediaSourceViewModel mMediaSourceViewModel;
+        private MediaBrowserViewModel mRootMediaBrowserViewModel;
 
         public ViewModel(Application application) {
             super(application);
         }
 
-        void init(MediaSourceViewModel mediaSourceViewModel, PlaybackViewModel playbackViewModel) {
+        void init(FragmentActivity activity, MediaSourceViewModel mediaSourceViewModel,
+                PlaybackViewModel playbackViewModel) {
             if (mMediaSourceViewModel == mediaSourceViewModel
                     && mPlaybackViewModel == playbackViewModel) {
                 return;
@@ -147,6 +235,11 @@
             mAppIcon = mapNonNull(mMediaSource, MediaSource::getCroppedPackageIcon);
             mTitle = mapNonNull(playbackViewModel.getMetadata(), MediaItemMetadata::getTitle);
             mSubtitle = mapNonNull(playbackViewModel.getMetadata(), MediaItemMetadata::getArtist);
+
+            mRootMediaBrowserViewModel = MediaBrowserViewModel.Factory.getInstanceForBrowseRoot(
+                    mMediaSourceViewModel, ViewModelProviders.of(activity));
+            mRootMediaBrowserViewModel.getBrowsedMediaItems()
+                    .observe(activity, this::onItemsUpdate);
         }
 
         LiveData<CharSequence> getAppName() {
@@ -164,5 +257,22 @@
         LiveData<CharSequence> getSubtitle() {
             return mSubtitle;
         }
+
+        LiveData<Boolean> getBrowseTreeHasChildren() {
+            return mBrowseTreeHasChildren;
+        }
+
+        private void onItemsUpdate(FutureData<List<MediaItemMetadata>> futureData) {
+            if (futureData.isLoading()) {
+                mBrowseTreeHasChildren.setValue(null);
+                return;
+            }
+
+            List<MediaItemMetadata> items =
+                    BrowsedMediaItems.filterItems(/*forRoot*/ true, futureData.getData());
+
+            boolean browseTreeHasChildren = items != null && !items.isEmpty();
+            mBrowseTreeHasChildren.setValue(browseTreeHasChildren);
+        }
     }
 }
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 f93be0a..c9a5864 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
@@ -21,19 +21,21 @@
 import android.support.v4.media.MediaBrowserCompat;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.lifecycle.LiveData;
 
 import com.android.car.media.common.MediaItemMetadata;
 
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
  * A LiveData that provides access the a MediaBrowser's children
  */
 
-class BrowsedMediaItems extends LiveData<List<MediaItemMetadata>> {
+public class BrowsedMediaItems extends LiveData<List<MediaItemMetadata>> {
 
     /**
      * Number of times we will retry obtaining the list of children of a certain node
@@ -58,6 +60,19 @@
         mParentId = parentId;
     }
 
+    /**
+     * Filters the items that are valid for the root (tabs) or the current node. Returns null when
+     * the given list is null to preserve its error signal.
+     */
+    @Nullable
+    public static List<MediaItemMetadata> filterItems(boolean forRoot,
+            @Nullable List<MediaItemMetadata> items) {
+        if (items == null) return null;
+        Predicate<MediaItemMetadata> predicate = forRoot ? MediaItemMetadata::isBrowsable
+                : item -> (item.isPlayable() || item.isBrowsable());
+        return items.stream().filter(predicate).collect(Collectors.toList());
+    }
+
     @Override
     protected void onActive() {
         super.onActive();
diff --git a/car-media-common/src/com/android/car/media/common/source/MediaSourcesLiveData.java b/car-media-common/src/com/android/car/media/common/source/MediaSourcesProvider.java
similarity index 92%
rename from car-media-common/src/com/android/car/media/common/source/MediaSourcesLiveData.java
rename to car-media-common/src/com/android/car/media/common/source/MediaSourcesProvider.java
index 9f9cc26..7312589 100644
--- a/car-media-common/src/com/android/car/media/common/source/MediaSourcesLiveData.java
+++ b/car-media-common/src/com/android/car/media/common/source/MediaSourcesProvider.java
@@ -46,12 +46,11 @@
  * Singleton that provides access to the list of all possible media sources that can be selected
  * to be played.
  */
-// TODO(arnaudberry) rename to MediaSourcesProvider
-public class MediaSourcesLiveData {
+public class MediaSourcesProvider {
 
     private static final String TAG = "MediaSources";
 
-    private static MediaSourcesLiveData sInstance;
+    private static MediaSourcesProvider sInstance;
     private final Context mAppContext;
     @Nullable
     private List<MediaSource> mMediaSources;
@@ -64,17 +63,17 @@
     };
 
     /** Returns the singleton instance. */
-    public static MediaSourcesLiveData getInstance(@NonNull Context context) {
+    public static MediaSourcesProvider getInstance(@NonNull Context context) {
         if (sInstance == null) {
-            sInstance = new MediaSourcesLiveData(context);
+            sInstance = new MediaSourcesProvider(context);
         }
         return sInstance;
     }
 
     /** Returns a different instance every time (tests don't like statics) */
     @VisibleForTesting
-    public static MediaSourcesLiveData createForTesting(@NonNull Context context) {
-        return new MediaSourcesLiveData(context);
+    public static MediaSourcesProvider createForTesting(@NonNull Context context) {
+        return new MediaSourcesProvider(context);
     }
 
     @VisibleForTesting
@@ -82,7 +81,7 @@
         mMediaSources = null;
     }
 
-    private MediaSourcesLiveData(@NonNull Context context) {
+    private MediaSourcesProvider(@NonNull Context context) {
         mAppContext = context.getApplicationContext();
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
diff --git a/car-media-common/tests/robotests/src/com/android/car/media/common/playback/PlaybackViewModelTest.java b/car-media-common/tests/robotests/src/com/android/car/media/common/playback/PlaybackViewModelTest.java
index ad6cfe8..9d00628 100644
--- a/car-media-common/tests/robotests/src/com/android/car/media/common/playback/PlaybackViewModelTest.java
+++ b/car-media-common/tests/robotests/src/com/android/car/media/common/playback/PlaybackViewModelTest.java
@@ -51,6 +51,7 @@
 import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -173,7 +174,7 @@
     @Test
     public void testGetHasQueue_true() {
         List<MediaSessionCompat.QueueItem> queue =
-                Collections.singletonList(createQueueItem("title", 1));
+                Arrays.asList(createQueueItem("title1", 1), createQueueItem("title2", 2));
         CaptureObserver<Boolean> observer = new CaptureObserver<>();
         mPlaybackViewModel.hasQueue().observe(mLifecycleOwner, observer);
         observer.reset();
diff --git a/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourcesLiveDataTest.java b/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourcesProviderTest.java
similarity index 98%
rename from car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourcesLiveDataTest.java
rename to car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourcesProviderTest.java
index f97b278..79dc196 100644
--- a/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourcesLiveDataTest.java
+++ b/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourcesProviderTest.java
@@ -50,7 +50,7 @@
 import java.util.stream.Collectors;
 
 @RunWith(RobolectricTestRunner.class)
-public class MediaSourcesLiveDataTest {
+public class MediaSourcesProviderTest {
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -65,13 +65,13 @@
     private static final String TEST_SERVICE_PACKAGE_2 = "service_package2";
     private static final String TEST_SERVICE_PACKAGE_WITH_METADATA = "service_package3";
 
-    private MediaSourcesLiveData mMediaSources;
+    private MediaSourcesProvider mMediaSources;
     private Intent mActivityIntent;
     private Intent mServiceIntent;
 
     @Before
     public void setUp() {
-        mMediaSources = MediaSourcesLiveData.createForTesting(application);
+        mMediaSources = MediaSourcesProvider.createForTesting(application);
 
         mActivityIntent = new Intent(Intent.ACTION_MAIN, null);
         mActivityIntent.addCategory(Intent.CATEGORY_APP_MUSIC);
diff --git a/car-messenger-common/Android.bp b/car-messenger-common/Android.bp
index f826816..6c7a82f 100644
--- a/car-messenger-common/Android.bp
+++ b/car-messenger-common/Android.bp
@@ -23,13 +23,14 @@
         enabled: false,
     },
 
-    libs: ["android.car"],
+    sdk_version: "system_current",
+    min_sdk_version: "28",
+
+    libs: ["android.car-system-stubs",],
 
     resource_dirs: ["res"],
 
     static_libs: [
-        "android.car.userlib",
-        "androidx.legacy_legacy-support-v4",
         "car-apps-common",
         "car-messenger-protos",
         "car-telephony-common",
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 da48646..d5610a2 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
@@ -16,7 +16,6 @@
 
 package com.android.car.messenger.common;
 
-import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -24,8 +23,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
 import android.os.Bundle;
 
+import androidx.annotation.Nullable;
 import androidx.core.app.NotificationCompat;
 import androidx.core.app.NotificationCompat.Action;
 import androidx.core.app.Person;
@@ -202,9 +203,11 @@
         if (avatarIcon != null) {
             builder.setLargeIcon(avatarIcon);
         } else if (mUseLetterTile) {
-            builder.setLargeIcon(TelecomUtils.createLetterTile(mContext,
+            BitmapDrawable drawable = (BitmapDrawable) TelecomUtils.createLetterTile(mContext,
                     Utils.getInitials(lastMessage.getSenderName(), ""),
-                    lastMessage.getSenderName(), mBitmapSize, mCornerRadiusPercent).getBitmap());
+                    lastMessage.getSenderName(), mBitmapSize, mCornerRadiusPercent)
+                    .loadDrawable(mContext);
+            builder.setLargeIcon(drawable.getBitmap());
         }
         // Else, no avatar icon will be shown.
 
@@ -235,9 +238,10 @@
             }
         });
         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.
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 3baf3fd..d563d12 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,14 +18,14 @@
 
 import static com.android.car.apps.common.util.SafeLog.logw;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
-import android.os.Build;
 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;
@@ -77,7 +77,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 {
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 a2c1d60..8e016dd 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
@@ -21,12 +21,12 @@
 import static com.android.car.messenger.common.Utils.BMC_EXTRA_MESSAGE_READ_STATUS;
 import static com.android.car.messenger.common.Utils.BMC_EXTRA_MESSAGE_TIMESTAMP;
 
-import android.annotation.Nullable;
 import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
-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;
 
@@ -77,7 +77,7 @@
             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 {
@@ -103,7 +103,7 @@
      **/
     public static Message parseFromIntent(Intent intent) {
         if (!Utils.isValidMapClientIntent(intent)) {
-            if (Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
                 throw new IllegalArgumentException(
                         "BluetoothMapClient intent is missing required fields");
             } else {
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 282c1f5..c52cc56 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
@@ -23,6 +23,8 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
 import android.text.TextUtils;
 
 import androidx.annotation.Nullable;
@@ -46,6 +48,7 @@
 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 {
@@ -282,7 +285,7 @@
     /** Returns whether the BluetoothMapClient intent represents a group conversation. **/
     public static boolean isGroupConversation(Intent intent) {
         return (intent.getStringArrayExtra(Intent.EXTRA_CC) != null
-                && intent.getStringArrayExtra(Intent.EXTRA_CC).length > 0);
+                && intent.getStringArrayExtra(Intent.EXTRA_CC).length > 1);
     }
 
     /**
@@ -318,11 +321,16 @@
     /** Returns the list of recipient uris for a BluetoothMapClient intent. **/
     public static List<String> getInclusiveRecipientsUrisList(Intent intent) {
         List<String> ccUris = new ArrayList<>();
-        ccUris.add(getSenderUri(intent));
+        String uri = getSenderUri(intent);
         if (isGroupConversation(intent)) {
             ccUris.addAll(Arrays.asList(intent.getStringArrayExtra(Intent.EXTRA_CC)));
-            Collections.sort(ccUris);
         }
+        if (!ccUris.contains(uri)) {
+            ccUris.add(uri);
+        }
+        // TODO (b/169183358): remove sorting.
+        Collections.sort(ccUris);
+
         return ccUris;
     }
 
@@ -332,12 +340,96 @@
     @Nullable
     public static String getPhoneNumberFromMapClient(@Nullable String senderContactUri) {
         if (senderContactUri == null || !senderContactUri.matches(MAP_CLIENT_URI_REGEX)) {
+            logw(TAG, " contactUri is malformed! " + senderContactUri);
             return null;
         }
 
         return senderContactUri.substring(MAP_CLIENT_URI_PHONE_NUMBER_SUBSTRING_INDEX);
     }
 
+    /**
+     * Creates a Header for a group conversation, where the senderName and groupName are both shown,
+     * separated by a delimiter.
+     *
+     * @param senderName Sender's name.
+     * @param groupName  Group conversation's name.
+     * @param delimiter  delimiter that separates each element.
+     */
+    public static String constructGroupConversationHeader(String senderName, String groupName,
+            String delimiter) {
+        return constructGroupConversationHeader(senderName, groupName, delimiter,
+                BidiFormatter.getInstance());
+    }
+
+    /**
+     * Creates a Header for a group conversation, where the senderName and groupName are both shown,
+     * separated by a delimiter.
+     *
+     * @param senderName Sender's name.
+     * @param groupName  Group conversation's name.
+     * @param delimiter  delimiter that separates each element.
+     * @param bidiFormatter  formatter for the context's locale.
+     */
+    public static String constructGroupConversationHeader(String senderName, String groupName,
+            String delimiter, BidiFormatter bidiFormatter) {
+        String formattedSenderName = bidiFormatter.unicodeWrap(senderName,
+                TextDirectionHeuristics.FIRSTSTRONG_LTR, /* isolate= */ true);
+        String formattedGroupName = bidiFormatter.unicodeWrap(groupName,
+                TextDirectionHeuristics.FIRSTSTRONG_LTR, /* isolate= */ true);
+        String title = String.join(delimiter, formattedSenderName, formattedGroupName);
+        return bidiFormatter.unicodeWrap(title, TextDirectionHeuristics.LOCALE);
+    }
+
+    /**
+     * Given a name of all the participants in a group conversation (some names might be phone
+     * numbers), this function creates the conversation title by putting the names in alphabetical
+     * order first, then adding any phone numbers. This title should not exceed the
+     * conversationTitleLength, so not all participants' names are guaranteed to be
+     * in the conversation title.
+     */
+    public static String constructGroupConversationTitle(List<String> names, String delimiter,
+            int conversationTitleLength) {
+        return constructGroupConversationTitle(names, delimiter, conversationTitleLength,
+                BidiFormatter.getInstance());
+    }
+
+    /**
+     * Given a name of all the participants in a group conversation (some names might be phone
+     * numbers), this function creates the conversation title by putting the names in alphabetical
+     * order first, then adding any phone numbers. This title should not exceed the
+     * conversationTitleLength, so not all participants' names are guaranteed to be
+     * in the conversation title.
+     */
+    public static String constructGroupConversationTitle(List<String> names, String delimiter,
+            int conversationTitleLength, BidiFormatter bidiFormatter) {
+        List<String> sortedNames = getSortedSubsetNames(names, conversationTitleLength,
+                delimiter.length());
+        String formattedDelimiter = bidiFormatter.unicodeWrap(delimiter,
+                TextDirectionHeuristics.LOCALE);
+
+        String conversationName = sortedNames.stream().map(name -> bidiFormatter.unicodeWrap(name,
+                TextDirectionHeuristics.FIRSTSTRONG_LTR))
+                .collect(Collectors.joining(formattedDelimiter));
+        return bidiFormatter.unicodeWrap(conversationName, TextDirectionHeuristics.LOCALE);
+    }
+
+    /**
+     * Sorts the list, and returns the first elements whose total length is less than the given
+     * conversationTitleLength.
+     */
+    private static List<String> getSortedSubsetNames(List<String> names,
+            int conversationTitleLength,
+            int delimiterLength) {
+        Collections.sort(names, Utils.ALPHA_THEN_NUMERIC_COMPARATOR);
+        int namesCounter = 0;
+        int indexCounter = 0;
+        while (namesCounter < conversationTitleLength && indexCounter < names.size()) {
+            namesCounter = namesCounter + names.get(indexCounter).length() + delimiterLength;
+            indexCounter = indexCounter + 1;
+        }
+        return names.subList(0, indexCounter);
+    }
+
     /** Comparator that sorts names alphabetically first, then phone numbers numerically. **/
     public static final Comparator<String> ALPHA_THEN_NUMERIC_COMPARATOR =
             new Comparator<String>() {
@@ -370,4 +462,5 @@
                     }
                 }
             };
+
 }
diff --git a/car-ui-lib/tests/unit/Android.bp b/car-messenger-common/tests/unit/Android.bp
similarity index 63%
rename from car-ui-lib/tests/unit/Android.bp
rename to car-messenger-common/tests/unit/Android.bp
index da5f41d..c5d29fa 100644
--- a/car-ui-lib/tests/unit/Android.bp
+++ b/car-messenger-common/tests/unit/Android.bp
@@ -12,37 +12,28 @@
 // WITHOUT 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: "CarUILibUnitTests",
-    certificate: "platform",
+    name: "car-messenger-common-lib-unit-tests",
+
+    srcs: ["src/**/*.java"],
+
     libs: [
         "android.test.runner",
         "android.test.base",
-        "android.car"
+        "android.test.mock",
     ],
-    resource_dirs: [
-        "res",
-    ],
+
     static_libs: [
+        "android.car",
+        "androidx.test.core",
+        "androidx.test.ext.junit",
         "androidx.test.rules",
-        "androidx.test.espresso.core",
-	"androidx.test.espresso.contrib",
-        "car-ui-lib",
-        "platform-test-annotations",
-        "mockito-target-inline-minus-junit4",
+        "car-messenger-common",
+        "mockito-target-extended-minus-junit4",
         "truth-prebuilt",
     ],
 
-    jni_libs: [
-        // For mockito extended
-        "libdexmakerjvmtiagent",
-        "libstaticjvmtiagent",
-    ],
-
-    // Include all test java files.
-    srcs: ["src/**/*.java"],
-
     platform_apis: true,
-    test_suites: ["device-tests"],
-}
+}
\ 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..c64703a
--- /dev/null
+++ b/car-messenger-common/tests/unit/src/com.android.car.messenger.common/UtilsTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.messenger.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.text.BidiFormatter;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class UtilsTest {
+
+    private static final String ARABIC_NAME = "جﺗﺧ";
+    private static final List<String> NAMES = Arrays.asList("+1-650-900-1234", "Logan.", "Emily",
+            "Christopher", "!Sam", ARABIC_NAME);
+    private static final String NAME_DELIMITER = "، ";
+    private static final String TITLE_DELIMITER = " : ";
+    private static final int TITLE_LENGTH = 30;
+    private static final BidiFormatter RTL_FORMATTER = BidiFormatter.getInstance(/* rtlContext= */
+            true);
+
+    @Test
+    public void testNameWithMultipleNumbers() {
+        // Ensure that a group name with many phone numbers sorts the phone numbers correctly.
+        List<String> senderNames = Arrays.asList("+1-650-900-1234", "+1-650-900-1111",
+                "+1-100-200-1234");
+        String actual = Utils.constructGroupConversationTitle(senderNames, NAME_DELIMITER,
+                TITLE_LENGTH + 20);
+        String expected = "+1-100-200-1234، +1-650-900-1111، +1-650-900-1234";
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testNameWithInternationalNumbers() {
+        // Ensure that a group name with many phone numbers sorts the phone numbers correctly.
+        List<String> senderNames = Arrays.asList("+44-20-7183-8750", "+1-650-900-1111",
+                "+1-100-200-1234");
+        String actual = Utils.constructGroupConversationTitle(senderNames, NAME_DELIMITER,
+                TITLE_LENGTH + 20);
+        String expected = "+1-100-200-1234، +1-650-900-1111، +44-20-7183-8750";
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testNameConstructorLtr() {
+        String actual = Utils.constructGroupConversationTitle(NAMES, NAME_DELIMITER, TITLE_LENGTH);
+        assertThat(actual).isEqualTo("!Sam، Christopher، Emily، Logan.");
+    }
+
+    @Test
+    public void testNameConstructorLtr_longerTitle() {
+        String actual = Utils.constructGroupConversationTitle(NAMES, NAME_DELIMITER,
+                TITLE_LENGTH + 5);
+        assertThat(actual).isEqualTo(
+                "!Sam، Christopher، Emily، Logan.، \u200E\u202Bجﺗﺧ\u202C\u200E");
+
+    }
+
+    @Test
+    public void testTitleConstructorLtr() {
+        String actual = Utils.constructGroupConversationHeader("Christopher",
+                "!Sam، Emily، Logan.، +1-650-900-1234", TITLE_DELIMITER);
+        String expected = "Christopher : !Sam، Emily، Logan.، +1-650-900-1234";
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testTitleConstructorLtr_with_rtlName() {
+        String actual = Utils.constructGroupConversationHeader(ARABIC_NAME, "!Sam، Logan.، جﺗﺧ",
+                TITLE_DELIMITER);
+        // Note: the Group name doesn't have the RTL tag because in the function we format the
+        // entire group name string, not each name in the string.
+        String expected = "\u200E\u202Bجﺗﺧ\u202C\u200E : !Sam، Logan.، جﺗﺧ\u200E";
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testTitleConstructorLtr_with_phoneNumber() {
+        String actual = Utils.constructGroupConversationHeader("+1-650-900-1234",
+                "!Sam، Logan.، جﺗﺧ",
+                TITLE_DELIMITER);
+        // Note: the Group name doesn't have the RTL tag because in the function we format the
+        // entire group name string, not each name in the string.
+        String expected = "+1-650-900-1234 : !Sam، Logan.، جﺗﺧ\u200E";
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    /**
+     * NOTE for all the RTL tests done below: When BidiFormatter is unicode-wrapping strings, they
+     * are actually adding invisible Unicode characters to denote whether a section is RTL, LTR,
+     * etc. These invisible characters are NOT visible on the terminal output, or if you copy
+     * paste the string to most HTML pages. They ARE visible when you paste them in certain
+     * text editors like IntelliJ, or there are some online tools that provide this as well.
+     *
+     * Therefore, in most of these RTL tests (and some of the LTR tests) you will see the
+     * invisible characters in the expected strings. Here's a couple of the characters, and what
+     * they're used for:
+     * \u200F is the RTL mark
+     * \u200E is the LTR mark
+     * \u202A marks the start of LTR embedding
+     * \u202B marks the start of RTL embedding
+     * \u202C pops the directional formatting - Must be used to end an embedding
+     */
+    @Test
+    public void testNameWithInternationalNumbers_rtl() {
+        // Ensure that a group name with many phone numbers sorts the phone numbers correctly.
+        List<String> senderNames = Arrays.asList("+44-20-7183-8750", "+1-650-900-1111",
+                "+1-100-200-1234");
+        String actual = Utils.constructGroupConversationTitle(senderNames, NAME_DELIMITER,
+                TITLE_LENGTH + 20, RTL_FORMATTER);
+        String expected = "\u200F\u202A\u200F\u202A+1-100-200-1234\u202C\u200F\u200F\u202A، "
+                + "\u202C\u200F\u200F\u202A+1-650-900-1111\u202C\u200F\u200F\u202A، "
+                + "\u202C\u200F\u200F\u202A+44-20-7183-8750\u202C\u200F\u202C\u200F";
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testNameConstructorRtl() {
+        String actual = Utils.constructGroupConversationTitle(NAMES, NAME_DELIMITER, TITLE_LENGTH,
+                /* isRtl */ RTL_FORMATTER);
+
+        String expected =
+                "\u200F\u202A\u200F\u202A!Sam\u202C\u200F\u200F\u202A، \u202C\u200F"
+                        + "\u200F\u202AChristopher\u202C\u200F\u200F\u202A، \u202C\u200F"
+                        + "\u200F\u202AEmily\u202C\u200F\u200F\u202A، "
+                        + "\u202C\u200F\u200F\u202ALogan.\u202C\u200F\u202C\u200F";
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testNameConstructorRtl_longerTitle() {
+        String actual = Utils.constructGroupConversationTitle(NAMES, NAME_DELIMITER,
+                TITLE_LENGTH + 5, /* isRtl */ RTL_FORMATTER);
+
+        String expected =
+                "\u200F\u202A\u200F\u202A!Sam\u202C\u200F\u200F\u202A، "
+                        + "\u202C\u200F\u200F\u202AChristopher\u202C\u200F\u200F"
+                        + "\u202A، \u202C\u200F\u200F\u202AEmily\u202C\u200F\u200F\u202A، "
+                        + "\u202C\u200F\u200F\u202ALogan.\u202C\u200F\u200F\u202A، "
+                        + "\u202C\u200Fجﺗﺧ\u202C\u200F";
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testTitleConstructorRtl_with_rtlName() {
+        String actual = Utils.constructGroupConversationHeader(ARABIC_NAME, "!Sam، Logan.، جﺗﺧ",
+                TITLE_DELIMITER, RTL_FORMATTER);
+        // Note: the Group name doesn't have the RTL tag because in the function we format the
+        // entire group name string, not each name in the string.
+        // Also, note that the sender's name, which is RTL still has LTR embedded because we wrap
+        // it with FIRSTSTRONG_LTR.
+        String expected = "\u200F\u202Aجﺗﺧ : \u200F\u202A!Sam، Logan.، جﺗﺧ\u202C\u200F\u202C"
+                + "\u200F";
+        assertThat(actual).isEqualTo(expected);
+    }
+
+
+    @Test
+    public void testTitleConstructorRtl_with_phoneNumber() {
+        String actual = Utils.constructGroupConversationHeader("+1-650-900-1234",
+                "!Sam، Logan.، جﺗﺧ",
+                TITLE_DELIMITER, RTL_FORMATTER);
+        // Note: the Group name doesn't have the RTL tag because in the function we format the
+        // entire group name string, not each name in the string.
+        String expected = "\u200F\u202A\u200F\u202A+1-650-900-1234\u202C\u200F : "
+                + "\u200F\u202A!Sam، Logan.، جﺗﺧ\u202C\u200F\u202C\u200F";
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testTitleConstructorRtl() {
+        String actual = Utils.constructGroupConversationHeader("Christopher",
+                "+1-650-900-1234، Logan.، Emily، Christopher، !Sam", TITLE_DELIMITER, /* isRtl */
+                RTL_FORMATTER).trim();
+
+        String expected =
+                "\u200F\u202A\u200F\u202AChristopher\u202C\u200F : \u200F\u202A+1-650-900-1234، "
+                        + "Logan.، Emily، Christopher، !Sam\u202C\u200F\u202C\u200F";
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testTitleConstructorRtl_withTrailingPunctuation() {
+        String actual = Utils.constructGroupConversationHeader("Christopher",
+                "Abcd!!!", TITLE_DELIMITER, /* isRtl */
+                RTL_FORMATTER).trim();
+
+        String expected =
+                "\u200F\u202A\u200F\u202AChristopher\u202C\u200F : \u200F\u202AAbcd!!!"
+                        + "\u202C\u200F\u202C\u200F";
+
+        assertThat(actual).isEqualTo(expected);
+    }
+}
diff --git a/car-telephony-common/Android.bp b/car-telephony-common/Android.bp
index 8f9318e..1e6a9b8 100644
--- a/car-telephony-common/Android.bp
+++ b/car-telephony-common/Android.bp
@@ -25,6 +25,9 @@
         enabled: false,
     },
 
+    sdk_version: "system_current",
+    min_sdk_version: "28",
+
     static_libs: [
         "androidx.legacy_legacy-support-v4",
         "car-apps-common",
diff --git a/car-telephony-common/res/values-bg/strings.xml b/car-telephony-common/res/values-bg/strings.xml
index f308b5e..99d16bd 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="8063542005458186874">"Задържане на обаждането"</string>
     <string name="call_state_call_ended" msgid="1432127342949555464">"Обаждането завърши"</string>
     <string name="call_state_call_active" msgid="2769644783657864202">"Установена е връзка"</string>
diff --git a/car-telephony-common/res/values-et/strings.xml b/car-telephony-common/res/values-et/strings.xml
index 6707e93..9926364 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="1432127342949555464">"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 fe742e7..7450656 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="1432127342949555464">"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 aad5c48..67620e7 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="8063542005458186874">"בהמתנה"</string>
     <string name="call_state_call_ended" msgid="1432127342949555464">"השיחה הסתיימה"</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 98bcb58..038ac0c 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="1432127342949555464">"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 8c3f1ff..2283a98 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="8063542005458186874">"होल्डमा छ"</string>
     <string name="call_state_call_ended" msgid="1432127342949555464">"कल समाप्त भयो"</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 e97b569..c1f4c00 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="1432127342949555464">"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 9ac136b..f56f6ad 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="8063542005458186874">"Em espera"</string>
     <string name="call_state_call_ended" msgid="1432127342949555464">"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 6711f55..28a496b 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="8063542005458186874">"Imesitishwa"</string>
     <string name="call_state_call_ended" msgid="1432127342949555464">"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 0a8a28c..6b5a368 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="1432127342949555464">"วางสายแล้ว"</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/src/com/android/car/telephony/common/CallDetail.java b/car-telephony-common/src/com/android/car/telephony/common/CallDetail.java
index 828b7f7..04fc4bd 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/CallDetail.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/CallDetail.java
@@ -50,7 +50,7 @@
     public static CallDetail fromTelecomCallDetail(@Nullable Call.Details callDetail) {
         return new CallDetail(getNumber(callDetail), getDisconnectCause(callDetail),
                 getGatewayInfoOriginalAddress(callDetail), getConnectTimeMillis(callDetail),
-                callDetail.hasProperty(Call.Details.PROPERTY_CONFERENCE));
+                isConferenceCall(callDetail));
     }
 
     /**
@@ -126,4 +126,8 @@
             return 0;
         }
     }
+
+    private static boolean isConferenceCall(Call.Details callDetail) {
+        return callDetail != null && callDetail.hasProperty(Call.Details.PROPERTY_CONFERENCE);
+    }
 }
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 e700632..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
@@ -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 5d8e3eb..8ed7e73 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,11 +20,13 @@
 import android.database.Cursor;
 import android.provider.ContactsContract;
 import android.text.TextUtils;
+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;
 
@@ -55,6 +57,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;
 
     /**
@@ -136,12 +143,27 @@
 
     /**
      * 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.
      */
@@ -227,8 +249,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();
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 b3c1fb1..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
@@ -50,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;
 
@@ -65,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) {
@@ -83,6 +84,7 @@
     public void startQuery() {
         L.d(TAG, "startQuery");
         mAsyncQueryHandler.cancelOperation(mToken); // Cancel the query task.
+        mContentResolver.unregisterContentObserver(mContentObserver);
 
         mToken++;
         QueryParam queryParam = mQueryParamProvider.getQueryParam();
@@ -95,6 +97,7 @@
                     queryParam.mSelection,
                     queryParam.mSelectionArgs,
                     queryParam.mOrderBy);
+            mContentResolver.registerContentObserver(queryParam.mUri, false, mContentObserver);
         } else {
             mOnQueryFinishedListener.onQueryFinished(null);
         }
@@ -109,7 +112,7 @@
     public void stopQuery() {
         L.d(TAG, "stopQuery");
         mIsActive = false;
-        cleanupCursorIfNecessary();
+        mContentResolver.unregisterContentObserver(mContentObserver);
         mAsyncQueryHandler.cancelOperation(mToken); // Cancel the query task.
     }
 
@@ -118,23 +121,11 @@
             return;
         }
         L.d(TAG, "onQueryComplete");
-        cleanupCursorIfNecessary();
-        if (cursor != null) {
-            cursor.registerContentObserver(mContentObserver);
-            mCurrentCursor = cursor;
-        }
         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 815e3d5..a1e25f2 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
@@ -70,6 +70,7 @@
     private long mId;
     private String mPhoneNumberString;
     private I18nPhoneNumberWrapper mI18nPhoneNumberWrapper;
+    private String mAccountName;
     private List<Record> mCallRecords = new ArrayList<>();
 
     /**
@@ -80,6 +81,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);
@@ -88,6 +90,7 @@
                 phoneCallLog.mPhoneNumberString);
         Record record = new Record(cursor.getLong(dateColumn), cursor.getInt(callTypeColumn));
         phoneCallLog.mCallRecords.add(record);
+        phoneCallLog.mAccountName = cursor.getString(accountNameColumn);
         return phoneCallLog;
     }
 
@@ -96,6 +99,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;
@@ -163,6 +174,8 @@
         sb.append(TelecomUtils.piiLog(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/PhoneNumber.java b/car-telephony-common/src/com/android/car/telephony/common/PhoneNumber.java
index 4e3eff6..255def0 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/PhoneNumber.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/PhoneNumber.java
@@ -23,7 +23,6 @@
 import android.os.Parcelable;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -107,8 +106,8 @@
         mLabel = label;
         mIsPrimary = isPrimary;
         mId = id;
-        mAccountName = TextUtils.emptyIfNull(accountName);
-        mAccountType = TextUtils.emptyIfNull(accountType);
+        mAccountName = accountName == null ? "" : accountName;
+        mAccountType = accountType == null ? "" : accountType;
         mDataVersion = dataVersion;
     }
 
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 0ad3d57..40d93a7 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
@@ -27,8 +27,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.Icon;
-import android.location.Country;
-import android.location.CountryDetector;
 import android.net.Uri;
 import android.provider.CallLog;
 import android.provider.ContactsContract;
@@ -69,6 +67,14 @@
     private static final String TAG = "CD.TelecomUtils";
     private static final int PII_STRING_LENGTH = 4;
     private static final String COUNTRY_US = "US";
+    /**
+     * 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;
@@ -123,11 +129,10 @@
         }
 
         String countryIso = getCurrentCountryIsoFromLocale(context);
-        L.d(TAG, "PhoneNumberUtils.formatNumberToE16, number: "
-                    + piiLog(number) + ", country: " + countryIso);
+        L.d(TAG, "PhoneNumberUtils.formatNumber, number: " + piiLog(number)
+                + ", country: " + countryIso);
 
-        String e164 = PhoneNumberUtils.formatNumberToE164(number, countryIso);
-        String formattedNumber = PhoneNumberUtils.formatNumber(number, e164, countryIso);
+        String formattedNumber = PhoneNumberUtils.formatNumber(number, countryIso);
         formattedNumber = TextUtils.isEmpty(formattedNumber) ? number : formattedNumber;
         L.d(TAG, "getFormattedNumber, result: " + piiLog(formattedNumber));
 
@@ -138,22 +143,7 @@
      * @return The ISO 3166-1 two letters country code of the country the user is in.
      */
     private static String getCurrentCountryIso(Context context, Locale locale) {
-        String countryIso = null;
-        CountryDetector detector = (CountryDetector) context.getSystemService(
-                Context.COUNTRY_DETECTOR);
-        if (detector != null) {
-            Country country = detector.detectCountry();
-            if (country != null) {
-                countryIso = country.getCountryIso();
-            } else {
-                L.e(TAG, "CountryDetector.detectCountry() returned null.");
-            }
-        }
-        if (countryIso == null) {
-            countryIso = locale.getCountry();
-            L.w(TAG, "No CountryDetector; falling back to countryIso based on locale: "
-                    + countryIso);
-        }
+        String countryIso = locale.getCountry();
         if (countryIso == null || countryIso.length() != 2) {
             L.w(TAG, "Invalid locale, falling back to US");
             countryIso = COUNTRY_US;
@@ -391,26 +381,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();
 
@@ -496,6 +508,24 @@
      * valid, it will mark all new missed call log as read.
      */
     public static void markCallLogAsRead(Context context, String phoneNumberString) {
+        markCallLogAsRead(context, CallLog.Calls.NUMBER, phoneNumberString);
+    }
+
+    /**
+     * Mark missed call log matching given call log id as read. If phone number string is not
+     * valid, it will mark all new missed call log as read.
+     */
+    public static void markCallLogAsRead(Context context, long callLogId) {
+        markCallLogAsRead(context, CallLog.Calls._ID,
+                callLogId < 0 ? null : String.valueOf(callLogId));
+    }
+
+    /**
+     * Mark missed call log matching given column name and selection argument as read. If the column
+     * name or the selection argument is not valid, mark all new missed call log as read.
+     */
+    private static void markCallLogAsRead(Context context, String columnName,
+            String selectionArg) {
         if (context.checkSelfPermission(Manifest.permission.WRITE_CALL_LOG)
                 != PackageManager.PERMISSION_GRANTED) {
             L.w(TAG, "Missing WRITE_CALL_LOG permission; not marking missed calls as read.");
@@ -512,21 +542,22 @@
         where.append(CallLog.Calls.TYPE);
         where.append(" = ?");
         selectionArgs.add(Integer.toString(CallLog.Calls.MISSED_TYPE));
-        if (!TextUtils.isEmpty(phoneNumberString)) {
+        if (!TextUtils.isEmpty(columnName) && !TextUtils.isEmpty(selectionArg)) {
             where.append(" AND ");
-            where.append(CallLog.Calls.NUMBER);
+            where.append(columnName);
             where.append(" = ?");
-            selectionArgs.add(phoneNumberString);
+            selectionArgs.add(selectionArg);
         }
         String[] selectionArgsArray = new String[0];
         try {
-            context
-                    .getContentResolver()
-                    .update(
-                            CallLog.Calls.CONTENT_URI,
-                            contentValues,
-                            where.toString(),
-                            selectionArgs.toArray(selectionArgsArray));
+            ContentResolver contentResolver = context.getContentResolver();
+            contentResolver.update(
+                    CallLog.Calls.CONTENT_URI,
+                    contentValues,
+                    where.toString(),
+                    selectionArgs.toArray(selectionArgsArray));
+            // #update doesn't notify change any more. Notify change to rerun query from database.
+            contentResolver.notifyChange(CallLog.Calls.CONTENT_URI, null);
         } catch (IllegalArgumentException e) {
             L.e(TAG, "markCallLogAsRead failed", e);
         }
@@ -589,7 +620,7 @@
     private static Uri makeResourceUri(Context context, int resourceId) {
         return new Uri.Builder()
                 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
-                .encodedAuthority(context.getBasePackageName())
+                .encodedAuthority(context.getPackageName())
                 .appendEncodedPath(String.valueOf(resourceId))
                 .build();
     }
@@ -603,4 +634,12 @@
         return piiString.length() >= PII_STRING_LENGTH ? "*" + piiString.substring(
                 piiString.length() - PII_STRING_LENGTH) : piiString;
     }
+
+    /**
+     * 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/.gitignore b/car-ui-lib/.gitignore
index cedb234..a6be7ed 100644
--- a/car-ui-lib/.gitignore
+++ b/car-ui-lib/.gitignore
@@ -13,3 +13,6 @@
 
 # Python
 *.pyc
+
+# Android studio's layout inspector captures
+captures/
diff --git a/car-ui-lib/Android.bp b/car-ui-lib/Android.bp
index 3506a3e..4e59773 100644
--- a/car-ui-lib/Android.bp
+++ b/car-ui-lib/Android.bp
@@ -1,5 +1,4 @@
 
-//
 // Copyright (C) 2019 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,8 +17,9 @@
     name: "car-ui-lib",
     sdk_version: "system_current",
     min_sdk_version: "28",
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
+    srcs: ["car-ui-lib/src/main/java/**/*.java"],
+    manifest: "car-ui-lib/AndroidManifest.xml",
+    resource_dirs: ["car-ui-lib/src/main/res"],
     optimize: {
         enabled: false,
     },
@@ -35,10 +35,46 @@
     ],
 }
 
+// User this if your project includes overlayable.xml
+android_library {
+    name: "car-ui-lib-overlayable",
+    sdk_version: "system_current",
+    min_sdk_version: "28",
+    manifest: "car-ui-lib/AndroidManifest.xml",
+    resource_dirs: [
+        "car-ui-lib/src/main/res-overlayable"
+    ],
+    static_libs: [
+        "car-ui-lib",
+    ],
+    // This is also needed for `car-ui-lib` build target.
+    // But it's not in explicitly added to the build rule,
+    // Because it's hardcoded in apex.go file under Soong.
+    apex_available: [
+        "com.android.permission"
+    ],
+}
+
+android_app {
+    name: "car-ui-lib-sharedlibrary",
+    srcs: ["sharedlibrary/src/main/java/**/*.java"],
+    manifest: "sharedlibrary/AndroidManifest.xml",
+    resource_dirs: ["sharedlibrary/src/main/res"],
+    visibility: [
+        "//visibility:private",
+    ],
+    platform_apis: true,
+    aaptflags: ["--shared-lib"],
+    optimize: {
+        enabled: false,
+    },
+}
+
 android_library {
     name: "car-ui-lib-testing-support",
     sdk_version: "system_current",
     min_sdk_version: "28",
+    manifest: "car-ui-lib/AndroidManifest.xml",
     srcs: [
         "tests/baselayouttests/src/**/*.java",
     ],
@@ -50,4 +86,68 @@
         "car-ui-lib",
         "Robolectric_all-target",
     ],
-}
\ No newline at end of file
+}
+
+android_test {
+    name: "CarUILibUnitTests",
+    certificate: "platform",
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.car"
+    ],
+    manifest: "car-ui-lib/src/androidTest/AndroidManifest.xml",
+    resource_dirs: ["car-ui-lib/src/androidTest/res"],
+    // Include all test java files.
+    srcs: ["car-ui-lib/src/androidTest/java/**/*.java"],
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.espresso.core",
+        "androidx.test.espresso.contrib",
+        "androidx.test.ext.junit",
+        "car-ui-lib",
+        "platform-test-annotations",
+        "mockito-target-inline-minus-junit4",
+        "truth-prebuilt",
+    ],
+    jni_libs: [
+        // For mockito extended
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+
+    platform_apis: true,
+    test_suites: ["device-tests"],
+}
+
+android_app {
+    name: "PaintBooth",
+    srcs: [
+    	"paintbooth/src/**/*.java",
+    	"paintbooth/src/**/*.kt",
+    ],
+    required: ["privapp_whitelist_com.android.car.ui.paintbooth"],
+    manifest: "paintbooth/AndroidManifest.xml",
+    resource_dirs: ["paintbooth/src/main/res", "paintbooth/src/main/res-public"],
+    platform_apis: true,
+    certificate: "platform",
+    privileged: true,
+    static_libs: [
+        "car-ui-lib",
+        "android.car.userlib",
+	"guava",
+	"gson-prebuilt-jar",
+    ],
+    optimize: {
+        enabled: false,
+    },
+    dex_preopt: {
+        enabled: false,
+    },
+    product_variables: {
+        pdk: {
+            enabled: false,
+        },
+    },
+    export_package_resources: true,
+}
diff --git a/car-ui-lib/README.md b/car-ui-lib/README.md
index 282f8e1..3be5f68 100644
--- a/car-ui-lib/README.md
+++ b/car-ui-lib/README.md
@@ -2,14 +2,48 @@
 Components and resources designed to increase Automotive UI consistency between
 GAS (Google Automotive Services) apps, system-apps and OEM developed apps.
 
-See: go/aae-carui
+See: go/aae-chassis-site
 
 ## Content
 
 Components and resources designed to be configured by means of RRO (Runtime
 Resource Overlays) by OEMs.
 
-## Updating
+## Developing
+
+Project layout:
+* __car-ui-lib__: The main library that is statically linked into applications
+* __paintbooth__: A test application that contains demos of all the car-ui components
+* __referencedesign__: An RRO applied to internal google targets that does some minor car-ui customizations. It's used to make changes to car-ui that would cause backwards compatability issues if we were to create them directly in the library. It can also serve as an example RRO for OEMs, and should be kept reasonably functional and up-to-date because we use it actively.
+
+### Building and running PaintBooth
+
+While car-ui-lib can be developed by using aidegen, it's probably easier to use Android Studio with the gradle build files. To do this, select "Open an existing Android Studio project", then navigating to the (outer) car-ui-lib folder:
+
+![Open an existing Android Studio project](documentation/images/open_existing_android_studio_project.png)
+![Navigating to car-ui-lib](documentation/images/navigating_to_car_ui_lib.png)
+
+If this is your first time using Android Studio, it may ask you to install an SDK. Go ahead and do that.
+
+It may ask if you want to generate the gradle wrapper files, select "Yes". Wait for it to finish indexing, and then you should see car-ui-lib and PaintBooth projects in the project pane on the left. Make sure your project view is set to "Android" mode, as opposed to the regular "Project" mode:
+
+![Android project view](documentation/images/android_project_view.png)
+
+To launch paintbooth, start a car emulator or connect a device, make sure the PaintBooth module and "Virtual Device" is selected in your Android Studio toolbar, then click the green arrow:
+
+![Launching Paintbooth](documentation/images/launch_paintbooth.png)
+
+If it launches a LeakCanary activity instead of PaintBooth, either exit LeakCanary and launch PaintBooth as normal through the car's launcher, or click on the PaintBooth module > Edit configurations > Change "Launch: Default Activity" to "Specified Activity", and enter `com.android.car.ui.paintbooth.MainActivity`.
+
+### Running tests
+
+Once you've set up paintbooth as described above, just open one of the test classes in car-ui-lib > java > com.android.car.ui (androidTest) and click the green arrow next to one of the tests to run it:
+
+![Running tests](documentation/images/running_tests.png)
+
+The tests can also be run from the command line via `atest CarUILibUnitTests`, but that's much slower than running them through Android Studio.
+
+## Updating Google3
 
 This library is developed in Gerrit and copied as source to Google3 using
 Copybara (go/copybara).
@@ -20,9 +54,13 @@
 Here is the process for updating this library:
 
 1. Develop, test and upload changes to Gerrit
-2. On Google3, run './update.sh review <cl>' (with <cl> being your Gerrit CL #) and test your changes
+2. On Google3, run `./update.sh review <cl>` (with <cl> being your Gerrit CL #) and test your changes
 3. Repeat #1 and #2 until your changes look okay on both places.
 4. Back on Gerrit, submit your CL.
-5. Back on Google3, run './update.sh manual' submit
+5. Back on Google3, run `./update.sh manual` submit
 
 TODO: Automate this process using CaaS (in progress)
+
+If you're just updating the current state of car-ui-lib, and not testing a review that has yet to be submitted, the process can be simplified to:
+
+`/google/data/ro/teams/copybara/copybara /google/src/head/depot/google3/third_party/java_src/android_libs/car_chassis_lib/copy.bara.sky default`
diff --git a/car-ui-lib/build.gradle b/car-ui-lib/build.gradle
index c230345..9744622 100644
--- a/car-ui-lib/build.gradle
+++ b/car-ui-lib/build.gradle
@@ -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.
@@ -35,49 +35,8 @@
         google()
         jcenter()
     }
-    buildDir = "/tmp/car-ui-build/${rootProject.name}/${project.name}"
     tasks.withType(JavaCompile) {
         options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
     }
 }
 
-// Library-level build file
-
-apply plugin: 'com.android.library'
-
-android {
-    compileSdkVersion 29
-
-    defaultConfig {
-        minSdkVersion 28
-        targetSdkVersion 29
-        versionCode 1
-        versionName "1.0"
-    }
-
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
-    }
-
-    sourceSets {
-        main {
-            manifest.srcFile 'AndroidManifest-gradle.xml'
-            java.srcDirs = ['src']
-            res.srcDirs = ['res']
-        }
-    }
-}
-
-dependencies {
-    api 'androidx.annotation:annotation:1.1.0'
-    api 'androidx.appcompat:appcompat:1.1.0'
-    api 'androidx.constraintlayout:constraintlayout:1.1.3'
-    api 'androidx.preference:preference:1.1.0'
-    api 'androidx.recyclerview:recyclerview:1.0.0'
-    api 'androidx.core:core:1.2.0'
-    implementation 'com.android.support:support-annotations:28.0.0'
-
-    // This is the gradle equivalent of the libs: ["android.car"] in our Android.bp
-    implementation files('../../../../../out/target/common/obj/JAVA_LIBRARIES/android.car_intermediates/classes.jar')
-}
diff --git a/car-ui-lib/AndroidManifest-gradle.xml b/car-ui-lib/car-ui-lib/AndroidManifest-gradle.xml
similarity index 100%
rename from car-ui-lib/AndroidManifest-gradle.xml
rename to car-ui-lib/car-ui-lib/AndroidManifest-gradle.xml
diff --git a/car-ui-lib/AndroidManifest.xml b/car-ui-lib/car-ui-lib/AndroidManifest.xml
similarity index 100%
rename from car-ui-lib/AndroidManifest.xml
rename to car-ui-lib/car-ui-lib/AndroidManifest.xml
diff --git a/car-ui-lib/tests/unit/build.gradle b/car-ui-lib/car-ui-lib/build.gradle
similarity index 65%
rename from car-ui-lib/tests/unit/build.gradle
rename to car-ui-lib/car-ui-lib/build.gradle
index 2a80132..5c6ce32 100644
--- a/car-ui-lib/tests/unit/build.gradle
+++ b/car-ui-lib/car-ui-lib/build.gradle
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,39 +14,16 @@
  * limitations under the License.
  */
 
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
-    repositories {
-        google()
-        jcenter()
-    }
-
-    dependencies {
-        classpath 'com.android.tools.build:gradle:3.5.3'
-
-        // NOTE: Do not place your application dependencies here; they belong
-        // in the individual module build.gradle files
-    }
-}
-
-allprojects {
-    repositories {
-        google()
-        jcenter()
-    }
-}
-
 // Library-level build file
 
 apply plugin: 'com.android.library'
 
 android {
-    compileSdkVersion 29
+    compileSdkVersion 30
 
     defaultConfig {
         minSdkVersion 28
-        targetSdkVersion 29
+        targetSdkVersion 30
         versionCode 1
         versionName "1.0"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -59,12 +36,7 @@
 
     sourceSets {
         main {
-            manifest.srcFile 'AndroidManifest.xml'
-            res.srcDirs = ['res']
-        }
-
-        androidTest {
-            java.srcDirs = ['src']
+            manifest.srcFile 'AndroidManifest-gradle.xml'
         }
     }
 
@@ -73,10 +45,26 @@
             includeAndroidResources = true
         }
     }
+
+    // This is the gradle equivalent of the libs: ["android.car"] in the Android.bp
+    // Which will be in the SDK 30 R3+
+    useLibrary 'android.car'
 }
 
 dependencies {
-    implementation project(':')
+    api 'androidx.annotation:annotation:1.1.0'
+    api 'androidx.appcompat:appcompat:1.1.0'
+    api 'androidx.constraintlayout:constraintlayout:2.0.1'
+    api 'androidx.preference:preference:1.1.0'
+    api 'androidx.recyclerview:recyclerview:1.0.0'
+    api 'androidx.core:core:1.2.0'
+    implementation 'com.android.support:support-annotations:28.0.0'
+
+    testImplementation "androidx.test.ext:junit:1.1.1"
+    testImplementation 'org.robolectric:robolectric:4.4'
+    testImplementation "org.mockito:mockito-core:2.19.0"
+    testImplementation "com.google.truth:truth:0.29"
+    testImplementation "org.testng:testng:6.9.9"
 
     androidTestImplementation 'org.hamcrest:hamcrest-library:1.3'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
@@ -86,11 +74,7 @@
     androidTestImplementation "org.mockito:mockito-core:2.19.0"
     androidTestImplementation 'androidx.test:runner:1.1.0'
     androidTestImplementation 'androidx.test:rules:1.1.0'
-
     // This is needed to be able to spy certain classes with Mockito
     // It's major/minor version must match Mockito's.
-    androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.19.0'
-
-    // This is the gradle equivalent of linking to android.car in our Android.mk
-    implementation files('../../../../../../../out/target/common/obj/JAVA_LIBRARIES/android.car_intermediates/classes.jar')
+    androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito-inline:2.19.0'
 }
diff --git a/car-ui-lib/tests/unit/AndroidManifest.xml b/car-ui-lib/car-ui-lib/src/androidTest/AndroidManifest.xml
similarity index 72%
rename from car-ui-lib/tests/unit/AndroidManifest.xml
rename to car-ui-lib/car-ui-lib/src/androidTest/AndroidManifest.xml
index e499db5..73041a4 100644
--- a/car-ui-lib/tests/unit/AndroidManifest.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/AndroidManifest.xml
@@ -15,19 +15,24 @@
     limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.car.ui.tests.unit">
-    <application android:debuggable="true">
+          package="com.android.car.ui.test">
+    <application android:debuggable="true" android:theme="@style/Theme.CarUi.NoToolbar">
         <uses-library android:name="android.test.runner" />
         <activity android:name="com.android.car.ui.TestActivity" />
         <activity android:name="com.android.car.ui.recyclerview.CarUiRecyclerViewTestActivity" />
         <activity android:name="com.android.car.ui.FocusAreaTestActivity" />
         <activity android:name="com.android.car.ui.FocusParkingViewTestActivity" />
+        <activity android:name="com.android.car.ui.preference.PreferenceTestActivity" />
+        <activity
+            android:name="com.android.car.ui.preference.NonFullscreenPreferenceFragmentTest$MyActivity"
+            android:theme="@style/Theme.CarUi.WithToolbar"/>
+        <activity android:name="com.android.car.ui.utils.ViewUtilsTestActivity" />
         <activity
             android:name="com.android.car.ui.toolbar.ToolbarTestActivity"
             android:theme="@style/Theme.CarUi.WithToolbar"/>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.car.ui.tests.unit"
+        android:targetPackage="com.android.car.ui.test"
         android:label="Chassis Test Cases">
     </instrumentation>
 </manifest>
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/AlertDialogBuilderTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/AlertDialogBuilderTest.java
new file mode 100644
index 0000000..0aaa250
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/AlertDialogBuilderTest.java
@@ -0,0 +1,256 @@
+/*
+ * 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;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import android.app.AlertDialog;
+import android.database.Cursor;
+import android.view.View;
+
+import androidx.test.espresso.Root;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.recyclerview.CarUiContentListItem;
+import com.android.car.ui.recyclerview.CarUiListItemAdapter;
+import com.android.car.ui.recyclerview.CarUiRadioButtonListItem;
+import com.android.car.ui.recyclerview.CarUiRadioButtonListItemAdapter;
+import com.android.car.ui.test.R;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class AlertDialogBuilderTest {
+
+    @Rule
+    public ActivityTestRule<TestActivity> mActivityRule =
+            new ActivityTestRule<>(TestActivity.class);
+
+    @Test
+    public void test_AlertDialogBuilder_works() throws Throwable {
+        String title = "Test message from AlertDialogBuilder";
+        String subtitle = "Subtitle from AlertDialogBuilder";
+        mActivityRule.runOnUiThread(() ->
+                new AlertDialogBuilder(mActivityRule.getActivity())
+                        .setMessage(title)
+                        .setSubtitle(subtitle)
+                        .show());
+
+        AlertDialog dialog = checkDefaultButtonExists(true,
+                new AlertDialogBuilder(mActivityRule.getActivity())
+                        .setMessage(title)
+                        .setSubtitle(subtitle));
+        onView(withText(title))
+                .inRoot(new RootWithDecorMatcher(dialog.getWindow().getDecorView()))
+                .check(matches(isDisplayed()));
+        onView(withText(subtitle))
+                .inRoot(new RootWithDecorMatcher(dialog.getWindow().getDecorView()))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void test_showSingleListChoiceItem_StringArray_hidesDefaultButton() throws Throwable {
+        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+                .setAllowDismissButton(false)
+                .setSingleChoiceItems(new CharSequence[]{"Item 1", "Item 2"}, 0,
+                        ((dialog, which) -> {
+                        }));
+
+        checkDefaultButtonExists(false, builder);
+    }
+
+    @Test
+    public void test_showSingleListChoiceItem_StringArrayResource_hidesDefaultButton()
+            throws Throwable {
+        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+                .setAllowDismissButton(false)
+                .setSingleChoiceItems(R.array.test_string_array, 0, ((dialog, which) -> {
+                }));
+
+        checkDefaultButtonExists(false, builder);
+    }
+
+    @Test
+    public void test_showSingleListChoiceItem_CarUiRadioButtonListItemAdapter_forcesDefaultButton()
+            throws Throwable {
+        CarUiRadioButtonListItem item1 = new CarUiRadioButtonListItem();
+        item1.setTitle("Item 1");
+        CarUiRadioButtonListItem item2 = new CarUiRadioButtonListItem();
+        item2.setTitle("Item 2");
+        CarUiRadioButtonListItem item3 = new CarUiRadioButtonListItem();
+        item3.setTitle("Item 3");
+
+        CarUiRadioButtonListItemAdapter adapter = new CarUiRadioButtonListItemAdapter(
+                Arrays.asList(item1, item2, item3));
+        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+                .setAllowDismissButton(false)
+                .setSingleChoiceItems(adapter);
+
+        checkDefaultButtonExists(true, builder);
+    }
+
+    @Test
+    public void test_showSingleListChoiceItem_cursor_hidesDefaultButton() throws Throwable {
+        Cursor cursor = new FakeCursor(Arrays.asList("Item 1", "Item 2"), "ColumnName");
+        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+                .setTitle("Title")
+                .setAllowDismissButton(false)
+                .setSingleChoiceItems(cursor, 0, "ColumnName", ((dialog, which) -> {
+                }));
+
+        checkDefaultButtonExists(false, builder);
+    }
+
+    @Test
+    public void test_setItems_StringArrayResource_hidesDefaultButton() throws Throwable {
+        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+                .setAllowDismissButton(false)
+                .setItems(R.array.test_string_array, ((dialog, which) -> {
+                }));
+
+        checkDefaultButtonExists(false, builder);
+    }
+
+    @Test
+    public void test_setItems_StringArray_hidesDefaultButton() throws Throwable {
+        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+                .setAllowDismissButton(false)
+                .setItems(new CharSequence[]{"Item 1", "Item 2"}, ((dialog, which) -> {
+                }));
+
+        checkDefaultButtonExists(false, builder);
+    }
+
+    @Test
+    public void test_setAdapter_hidesDefaultButton()
+            throws Throwable {
+        CarUiContentListItem item1 = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item1.setTitle("Item 1");
+        CarUiContentListItem item2 = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item2.setTitle("Item 2");
+        CarUiContentListItem item3 = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item3.setTitle("Item 3");
+
+        CarUiListItemAdapter adapter = new CarUiListItemAdapter(
+                Arrays.asList(item1, item2, item3));
+        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+                .setAllowDismissButton(false)
+                .setAdapter(adapter);
+
+        checkDefaultButtonExists(false, builder);
+    }
+
+    @Test
+    public void test_multichoiceItems_StringArrayResource_forcesDefaultButton()
+            throws Throwable {
+        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+                .setAllowDismissButton(false)
+                .setMultiChoiceItems(R.array.test_string_array, null,
+                        ((dialog, which, isChecked) -> {
+                        }));
+
+        checkDefaultButtonExists(true, builder);
+    }
+
+    @Test
+    public void test_multichoiceItems_StringArray_forcesDefaultButton()
+            throws Throwable {
+        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+                .setAllowDismissButton(false)
+                .setMultiChoiceItems(new CharSequence[]{"Test 1", "Test 2"}, null,
+                        ((dialog, which, isChecked) -> {
+                        }));
+
+        checkDefaultButtonExists(true, builder);
+    }
+
+    @Test
+    public void test_multichoiceItems_Cursor_forcesDefaultButton()
+            throws Throwable {
+        Cursor cursor = new FakeCursor(Arrays.asList("Item 1", "Item 2"), "Label");
+        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+                .setAllowDismissButton(false)
+                .setMultiChoiceItems(cursor, "isChecked", "Label",
+                        ((dialog, which, isChecked) -> {
+                        }));
+
+        checkDefaultButtonExists(true, builder);
+    }
+
+    private AlertDialog checkDefaultButtonExists(boolean shouldExist, AlertDialogBuilder builder)
+            throws Throwable {
+        AtomicBoolean done = new AtomicBoolean(false);
+        AlertDialog[] result = new AlertDialog[1];
+        mActivityRule.runOnUiThread(() -> {
+            try {
+                result[0] = builder.create();
+                result[0].show();
+            } catch (RuntimeException e) {
+                assert e.getMessage() != null;
+                assert e.getMessage().contains(
+                        "must have at least one button to disable the dismiss button");
+
+                assert shouldExist;
+                done.set(true);
+            }
+        });
+
+        if (done.get()) {
+            return result[0];
+        }
+
+        if (shouldExist) {
+            onView(withText(R.string.car_ui_alert_dialog_default_button))
+                    .inRoot(new RootWithDecorMatcher(result[0].getWindow().getDecorView()))
+                    .check(matches(isDisplayed()));
+        } else {
+            onView(withText(R.string.car_ui_alert_dialog_default_button))
+                    .inRoot(new RootWithDecorMatcher(result[0].getWindow().getDecorView()))
+                    .check(doesNotExist());
+        }
+
+        return result[0];
+    }
+
+    private static class RootWithDecorMatcher extends TypeSafeMatcher<Root> {
+
+        private View mView;
+
+        RootWithDecorMatcher(View view) {
+            mView = view;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("is a root with a certain decor");
+        }
+
+        @Override
+        protected boolean matchesSafely(Root item) {
+            return item.getDecorView() == mView;
+        }
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FakeCursor.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FakeCursor.java
new file mode 100644
index 0000000..d6bc4e1
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FakeCursor.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import android.database.AbstractCursor;
+
+import java.util.List;
+
+public class FakeCursor extends AbstractCursor {
+
+    private List<String> mRows;
+    private String mColumnName;
+
+    public FakeCursor(List<String> rows, String columnName) {
+        mRows = rows;
+        mColumnName = columnName;
+    }
+
+    @Override
+    public int getCount() {
+        return mRows.size();
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        return new String[] { mColumnName };
+    }
+
+    @Override
+    public String getString(int column) {
+        return mRows.get(getPosition());
+    }
+
+    @Override
+    public short getShort(int column) {
+        return 0;
+    }
+
+    @Override
+    public int getInt(int column) {
+        return 0;
+    }
+
+    @Override
+    public long getLong(int column) {
+        return 0;
+    }
+
+    @Override
+    public float getFloat(int column) {
+        return 0;
+    }
+
+    @Override
+    public double getDouble(int column) {
+        return 0;
+    }
+
+    @Override
+    public boolean isNull(int column) {
+        return mRows.get(getPosition()) == null;
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTest.java
new file mode 100644
index 0000000..b0e7c88
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTest.java
@@ -0,0 +1,530 @@
+/*
+ * 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;
+
+import static android.view.View.FOCUS_DOWN;
+import static android.view.View.FOCUS_LEFT;
+import static android.view.View.FOCUS_RIGHT;
+import static android.view.View.FOCUS_UP;
+import static android.view.View.LAYOUT_DIRECTION_LTR;
+import static android.view.View.LAYOUT_DIRECTION_RTL;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+
+import static com.android.car.ui.RotaryCache.CACHE_TYPE_DISABLED;
+import static com.android.car.ui.RotaryCache.CACHE_TYPE_NEVER_EXPIRE;
+import static com.android.car.ui.utils.RotaryConstants.ACTION_NUDGE_SHORTCUT;
+import static com.android.car.ui.utils.RotaryConstants.ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_BOTTOM_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_LEFT_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_RIGHT_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_TOP_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.NUDGE_DIRECTION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Button;
+
+import androidx.annotation.NonNull;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/** Unit tests for {@link FocusArea} not in touch mode. */
+public class FocusAreaTest {
+    private static final long WAIT_TIME_MS = 3000;
+
+    @Rule
+    public ActivityTestRule<FocusAreaTestActivity> mActivityRule =
+            new ActivityTestRule<>(FocusAreaTestActivity.class);
+
+    private FocusAreaTestActivity mActivity;
+    private TestFocusArea mFocusArea1;
+    private TestFocusArea mFocusArea2;
+    private TestFocusArea mFocusArea3;
+    private TestFocusArea mFocusArea4;
+    private FocusParkingView mFpv;
+    private View mView1;
+    private Button mButton1;
+    private View mView2;
+    private View mDefaultFocus2;
+    private View mView3;
+    private View mNudgeShortcut3;
+    private View mView4;
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+        mFocusArea1 = mActivity.findViewById(R.id.focus_area1);
+        mFocusArea2 = mActivity.findViewById(R.id.focus_area2);
+        mFocusArea3 = mActivity.findViewById(R.id.focus_area3);
+        mFocusArea4 = mActivity.findViewById(R.id.focus_area4);
+        mFpv = mActivity.findViewById(R.id.fpv);
+        mView1 = mActivity.findViewById(R.id.view1);
+        mButton1 = mActivity.findViewById(R.id.button1);
+        mView2 = mActivity.findViewById(R.id.view2);
+        mDefaultFocus2 = mActivity.findViewById(R.id.default_focus2);
+        mView3 = mActivity.findViewById(R.id.view3);
+        mNudgeShortcut3 = mActivity.findViewById(R.id.nudge_shortcut3);
+        mView4 = mActivity.findViewById(R.id.view4);
+    }
+
+    @Test
+    public void testDrawMethodsCalled() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        mView1.post(() -> {
+            mView1.requestFocus();
+            mFocusArea1.enableForegroundHighlight();
+            mFocusArea2.enableForegroundHighlight();
+            mFocusArea1.setOnDrawCalled(false);
+            mFocusArea1.setDrawCalled(false);
+            mFocusArea2.setOnDrawCalled(false);
+            mFocusArea2.setDrawCalled(false);
+
+            mView2.requestFocus();
+            mView2.post(() -> latch.countDown());
+        });
+
+        // The methods should be called when a FocusArea gains or loses focus.
+        assertDrawMethodsCalled(mFocusArea1, latch);
+        assertDrawMethodsCalled(mFocusArea2, latch);
+    }
+
+    @Test
+    public void testPerformAccessibilityAction_actionNudgeShortcut() {
+        mFocusArea1.post(() -> {
+            // Nudge to the nudgeShortcut view.
+            mView3.requestFocus();
+            assertThat(mView3.isFocused()).isTrue();
+            Bundle arguments = new Bundle();
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_RIGHT);
+            mFocusArea3.performAccessibilityAction(ACTION_NUDGE_SHORTCUT, arguments);
+            assertThat(mNudgeShortcut3.isFocused()).isTrue();
+
+            // nudgeShortcutDirection doesn't match. The focus should stay the same.
+            mView3.requestFocus();
+            assertThat(mView3.isFocused()).isTrue();
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+            mFocusArea3.performAccessibilityAction(ACTION_NUDGE_SHORTCUT, arguments);
+            assertThat(mView3.isFocused()).isTrue();
+
+            // No nudgeShortcut view in the current FocusArea. The focus should stay the same.
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_RIGHT);
+            mFocusArea1.performAccessibilityAction(ACTION_NUDGE_SHORTCUT, arguments);
+            assertThat(mView1.isFocused()).isTrue();
+        });
+    }
+
+
+    @Test
+    public void testPerformAccessibilityAction_actionFocus() {
+        mFocusArea1.post(() -> {
+            mFocusArea1.performAccessibilityAction(ACTION_FOCUS, null);
+            assertThat(mView1.isFocused()).isTrue();
+
+            // It should focus on the default or the first view in the FocusArea.
+            mFocusArea2.performAccessibilityAction(ACTION_FOCUS, null);
+            assertThat(mDefaultFocus2.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testPerformAccessibilityAction_actionFocus_enabledFocusCache() {
+        mFocusArea1.post(() -> {
+            RotaryCache cache =
+                    new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+            mFocusArea1.setRotaryCache(cache);
+
+            mButton1.requestFocus();
+            assertThat(mButton1.isFocused()).isTrue();
+            mView2.requestFocus();
+            assertThat(mView2.isFocused()).isTrue();
+
+            // With cache, it should focus on the lastly focused view in the FocusArea.
+            mFocusArea1.performAccessibilityAction(ACTION_FOCUS, null);
+            assertThat(mButton1.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testPerformAccessibilityAction_actionFocus_disabledFocusCache() {
+        mFocusArea1.post(() -> {
+            RotaryCache cache = new RotaryCache(CACHE_TYPE_DISABLED, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+            mFocusArea1.setRotaryCache(cache);
+
+            mButton1.requestFocus();
+            assertThat(mButton1.isFocused()).isTrue();
+            mView2.requestFocus();
+            assertThat(mView2.isFocused()).isTrue();
+
+            // Without cache, it should focus on the default or the first view in the FocusArea.
+            mFocusArea1.performAccessibilityAction(ACTION_FOCUS, null);
+            assertThat(mView1.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testPerformAccessibilityAction_actionFocus_lastFocusedViewRemoved() {
+        mFocusArea1.post(() -> {
+            // Focus on mDefaultFocus2 in mFocusArea2, then mView1 in mFocusArea21.
+            mDefaultFocus2.requestFocus();
+            assertThat(mDefaultFocus2.isFocused()).isTrue();
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            // Remove mDefaultFocus2, then Perform ACTION_FOCUS on mFocusArea2.
+            mFocusArea2.removeView(mDefaultFocus2);
+            mFocusArea2.performAccessibilityAction(ACTION_FOCUS, null);
+
+            // mView2 in mFocusArea2 should get focused.
+            assertThat(mView2.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testPerformAccessibilityAction_actionNudgeToAnotherFocusArea_enabledCache() {
+        mFocusArea1.post(() -> {
+            RotaryCache cache1 =
+                    new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+            mFocusArea1.setRotaryCache(cache1);
+            RotaryCache cache2 =
+                    new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+            mFocusArea2.setRotaryCache(cache2);
+
+            // Focus on the second view in mFocusArea1, then nudge to mFocusArea2.
+            mButton1.requestFocus();
+            assertThat(mButton1.isFocused()).isTrue();
+            Bundle arguments = new Bundle();
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+            mFocusArea2.performAccessibilityAction(ACTION_FOCUS, arguments);
+            assertThat(mDefaultFocus2.isFocused()).isTrue();
+
+            // Nudge back. It should focus on the cached view (mButton1) in the cached
+            // FocusArea (mFocusArea1).
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+            mFocusArea2.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            assertThat(mButton1.isFocused()).isTrue();
+
+            // Nudge back. It should fail and the focus should stay the same because of one-way
+            // nudge history.
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+            mFocusArea1.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            assertThat(mButton1.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testPerformAccessibilityAction_actionNudgeToAnotherFocusArea_mixedCache() {
+        mFocusArea1.post(() -> {
+            // Disabled FocusCache but enabled FocusAreaCache.
+            RotaryCache cache1 =
+                    new RotaryCache(CACHE_TYPE_DISABLED, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+            mFocusArea1.setRotaryCache(cache1);
+            RotaryCache cache2 =
+                    new RotaryCache(CACHE_TYPE_DISABLED, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+            mFocusArea2.setRotaryCache(cache2);
+
+            // Focus on the second view in mFocusArea1, then nudge to mFocusArea2.
+            mButton1.requestFocus();
+            assertThat(mButton1.isFocused()).isTrue();
+            Bundle arguments = new Bundle();
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+            mFocusArea2.performAccessibilityAction(ACTION_FOCUS, arguments);
+            assertThat(mDefaultFocus2.isFocused()).isTrue();
+
+            // Nudge back. Since FocusCache is disabled, it should focus on the default or the first
+            // view (mView1) in the cached FocusArea (mFocusArea1).
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+            mFocusArea2.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            assertThat(mView1.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testPerformAccessibilityAction_actionNudgeToAnotherFocusArea_mixedCache2() {
+        mFocusArea1.post(() -> {
+            // Enabled FocusCache but disabled FocusAreaCache.
+            RotaryCache cache1 =
+                    new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_DISABLED, 0);
+            mFocusArea1.setRotaryCache(cache1);
+            RotaryCache cache2 =
+                    new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_DISABLED, 0);
+            mFocusArea2.setRotaryCache(cache2);
+
+            // Focus on the second view in mFocusArea1, then nudge to mFocusArea2.
+            mButton1.requestFocus();
+            assertThat(mButton1.isFocused()).isTrue();
+            Bundle arguments = new Bundle();
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+            mFocusArea2.performAccessibilityAction(ACTION_FOCUS, arguments);
+            assertThat(mDefaultFocus2.isFocused()).isTrue();
+
+            // Nudge back. Since FocusAreaCache is disabled, nudge should fail and the focus should
+            // stay the same.
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+            mFocusArea2.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            assertThat(mDefaultFocus2.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testPerformAccessibilityAction_actionNudgeToAnotherFocusArea_specifiedTarget() {
+        mFocusArea1.post(() -> {
+            // Nudge to specified FocusArea.
+            mView4.requestFocus();
+            assertThat(mView4.isFocused()).isTrue();
+            Bundle arguments = new Bundle();
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_LEFT);
+            mFocusArea4.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            assertThat(mDefaultFocus2.isFocused()).isTrue();
+
+            // Direction doesn't match specified FocusArea. The focus should stay the same.
+            mView4.requestFocus();
+            assertThat(mView4.isFocused()).isTrue();
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+            mFocusArea4.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            assertThat(mView4.isFocused()).isTrue();
+
+            // The FocusArea doesn't specify a target FocusArea. The focus should stay the same.
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_LEFT);
+            mFocusArea1.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            assertThat(mView1.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testDefaultFocusOverridesHistory_override() {
+        mFocusArea1.post(() -> {
+            RotaryCache cache =
+                    new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+            mFocusArea2.setRotaryCache(cache);
+            mFocusArea2.setDefaultFocusOverridesHistory(true);
+
+            mView2.requestFocus();
+            assertThat(mView2.isFocused()).isTrue();
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            // The focused view should be the default focus view rather than the cached view.
+            mFocusArea2.performAccessibilityAction(ACTION_FOCUS, null);
+            assertThat(mDefaultFocus2.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testDefaultFocusOverridesHistory_notOverride() {
+        mFocusArea1.post(() -> {
+            RotaryCache cache =
+                    new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+            mFocusArea2.setRotaryCache(cache);
+            mFocusArea2.setDefaultFocusOverridesHistory(false);
+
+            mView2.requestFocus();
+            assertThat(mView2.isFocused()).isTrue();
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            // The focused view should be the cached view rather than the default focus view.
+            mFocusArea2.performAccessibilityAction(ACTION_FOCUS, null);
+            assertThat(mView2.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testClearFocusAreaHistoryWhenRotating_clear() {
+        mFocusArea1.post(() -> {
+            RotaryCache cache1 =
+                    new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+            mFocusArea1.setRotaryCache(cache1);
+            mFocusArea1.setClearFocusAreaHistoryWhenRotating(true);
+            RotaryCache cache2 =
+                    new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+            mFocusArea2.setRotaryCache(cache2);
+            mFocusArea2.setClearFocusAreaHistoryWhenRotating(true);
+
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            // Nudging down from mFocusArea1 to mFocusArea2.
+            Bundle arguments = new Bundle();
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+            mFocusArea2.performAccessibilityAction(ACTION_FOCUS, arguments);
+            assertThat(mDefaultFocus2.isFocused()).isTrue();
+            // Rotate.
+            mView2.requestFocus();
+            assertThat(mView2.isFocused()).isTrue();
+            // Since nudge history is cleared, nudging up should fail and the focus should stay
+            // the same.
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+            mFocusArea2.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            assertThat(mView2.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testClearFocusAreaHistoryWhenRotating_notClear() {
+        mFocusArea1.post(() -> {
+            RotaryCache cache1 =
+                    new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+            mFocusArea1.setRotaryCache(cache1);
+            mFocusArea1.setClearFocusAreaHistoryWhenRotating(false);
+            RotaryCache cache2 =
+                    new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+            mFocusArea2.setRotaryCache(cache2);
+            mFocusArea2.setClearFocusAreaHistoryWhenRotating(false);
+
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            // Nudging down from mFocusArea1 to mFocusArea2.
+            Bundle arguments = new Bundle();
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+            mFocusArea2.performAccessibilityAction(ACTION_FOCUS, arguments);
+            assertThat(mDefaultFocus2.isFocused()).isTrue();
+            // Rotate.
+            mView2.requestFocus();
+            assertThat(mView2.isFocused()).isTrue();
+            // Nudging up should move focus to mFocusArea1 according to nudge history.
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+            mFocusArea2.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            assertThat(mView1.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testBoundsOffset() {
+        assertThat(mFocusArea1.getLayoutDirection()).isEqualTo(LAYOUT_DIRECTION_LTR);
+
+        // FocusArea's bounds offset specified in layout file:
+        // 10dp(start), 20dp(end), 30dp(top), 40dp(bottom).
+        int left = dp2Px(10);
+        int right = dp2Px(20);
+        int top = dp2Px(30);
+        int bottom = dp2Px(40);
+        AccessibilityNodeInfo node = mFocusArea1.createAccessibilityNodeInfo();
+        assertBoundsOffset(node, left, top, right, bottom);
+        node.recycle();
+    }
+
+    @Test
+    public void testBoundsOffsetWithRtl() {
+        mFocusArea1.post(() -> {
+            mFocusArea1.setLayoutDirection(LAYOUT_DIRECTION_RTL);
+            assertThat(mFocusArea1.getLayoutDirection()).isEqualTo(LAYOUT_DIRECTION_RTL);
+
+            // FocusArea highlight padding specified in layout file:
+            // 10dp(start), 20dp(end), 30dp(top), 40dp(bottom).
+            int left = dp2Px(20);
+            int right = dp2Px(10);
+            int top = dp2Px(30);
+            int bottom = dp2Px(40);
+            AccessibilityNodeInfo node = mFocusArea1.createAccessibilityNodeInfo();
+            assertBoundsOffset(node, left, top, right, bottom);
+            node.recycle();
+        });
+    }
+
+    @Test
+    public void testSetBoundsOffset() {
+        mFocusArea1.setBoundsOffset(50, 60, 70, 80);
+        AccessibilityNodeInfo node = mFocusArea1.createAccessibilityNodeInfo();
+        assertBoundsOffset(node, 50, 60, 70, 80);
+        node.recycle();
+    }
+
+    @Test
+    public void testHighlightPadding() {
+        assertThat(mFocusArea2.getLayoutDirection()).isEqualTo(LAYOUT_DIRECTION_LTR);
+
+        int left = dp2Px(50);
+        int right = dp2Px(10);
+        int top = dp2Px(40);
+        int bottom = dp2Px(20);
+        AccessibilityNodeInfo node = mFocusArea2.createAccessibilityNodeInfo();
+        assertBoundsOffset(node, left, top, right, bottom);
+        node.recycle();
+    }
+
+    @Test
+    public void testBug170423337() {
+        mFocusArea1.post(() -> {
+            // Focus on app bar (assume mFocusArea1 is app bar).
+            mView1.requestFocus();
+
+            // Nudge down to browse list (assume mFocusArea2 is browse list).
+            Bundle arguments = new Bundle();
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+            mFocusArea2.performAccessibilityAction(ACTION_FOCUS, arguments);
+            assertThat(mDefaultFocus2.isFocused()).isTrue();
+
+            // Nudge down to playback control bar (assume mFocusArea3 is playback control bar).
+            mFocusArea3.performAccessibilityAction(ACTION_FOCUS, arguments);
+            assertThat(mView3.isFocused()).isTrue();
+
+            // Nudge down to navigation bar (navigation bar is in system window without FocusAreas).
+            mFpv.performAccessibilityAction(ACTION_FOCUS, null);
+
+            // Nudge up to playback control bar.
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+            mFocusArea3.performAccessibilityAction(ACTION_FOCUS, arguments);
+            assertThat(mView3.isFocused()).isTrue();
+
+            // Nudge up to browse list.
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+            mFocusArea3.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            assertThat(mDefaultFocus2.isFocused()).isTrue();
+
+            // Nudge up, and it should focus on app bar.
+            mFocusArea2.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            assertThat(mView1.isFocused()).isTrue();
+        });
+    }
+
+    private void assertBoundsOffset(
+            @NonNull AccessibilityNodeInfo node, int leftPx, int topPx, int rightPx, int bottomPx) {
+        Bundle extras = node.getExtras();
+        assertThat(extras.getInt(FOCUS_AREA_LEFT_BOUND_OFFSET)).isEqualTo(leftPx);
+        assertThat(extras.getInt(FOCUS_AREA_RIGHT_BOUND_OFFSET)).isEqualTo(rightPx);
+        assertThat(extras.getInt(FOCUS_AREA_TOP_BOUND_OFFSET)).isEqualTo(topPx);
+        assertThat(extras.getInt(FOCUS_AREA_BOTTOM_BOUND_OFFSET)).isEqualTo(bottomPx);
+    }
+
+    /** Converts dp unit to equivalent pixels. */
+    private int dp2Px(int dp) {
+        return (int) (dp * mActivity.getResources().getDisplayMetrics().density + 0.5f);
+    }
+
+    private void assertDrawMethodsCalled(@NonNull TestFocusArea focusArea, CountDownLatch latch)
+            throws Exception {
+        latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        assertThat(focusArea.onDrawCalled()).isTrue();
+        assertThat(focusArea.drawCalled()).isTrue();
+    }
+}
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/FocusAreaTestActivity.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTestActivity.java
similarity index 95%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/FocusAreaTestActivity.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTestActivity.java
index f027db6..bd9716c 100644
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/FocusAreaTestActivity.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTestActivity.java
@@ -19,7 +19,7 @@
 import android.app.Activity;
 import android.os.Bundle;
 
-import com.android.car.ui.tests.unit.R;
+import com.android.car.ui.test.R;
 
 /** An activity used for testing {@link FocusArea}. */
 public class FocusAreaTestActivity extends Activity {
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTouchModeTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTouchModeTest.java
new file mode 100644
index 0000000..5c06f84
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTouchModeTest.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 com.android.car.ui;
+
+import static android.view.View.FOCUS_RIGHT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Unit tests for {@link FocusArea} in touch mode. */
+public class FocusAreaTouchModeTest {
+    @Rule
+    public ActivityTestRule<FocusAreaTestActivity> mActivityRule =
+            new ActivityTestRule<>(FocusAreaTestActivity.class, /* initialTouchMode= */ true);
+
+    private FocusAreaTestActivity mActivity;
+    private TestFocusArea mFocusArea2;
+    private View mView1;
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+        mFocusArea2 = mActivity.findViewById(R.id.focus_area2);
+        mView1 = mActivity.findViewById(R.id.view1);
+    }
+
+    @Test
+    public void testOnRequestFocusInDescendants_doesNothing() {
+        mFocusArea2.post(() -> {
+            Rect previouslyFocusedRect = new Rect();
+            previouslyFocusedRect.left = mView1.getLeft();
+            previouslyFocusedRect.top = mView1.getTop();
+            previouslyFocusedRect.right = previouslyFocusedRect.left + mView1.getWidth();
+            previouslyFocusedRect.bottom = previouslyFocusedRect.top + mView1.getHeight();
+            boolean focusTaken =
+                    mFocusArea2.onRequestFocusInDescendants(FOCUS_RIGHT, previouslyFocusedRect);
+
+            assertWithMessage("onRequestFocusInDescendants returned").that(focusTaken).isFalse();
+            assertWithMessage("No view should be focused")
+                    .that(mFocusArea2.getRootView().findFocus()).isNull();
+        });
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java
new file mode 100644
index 0000000..e706ae1
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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;
+
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+
+import static com.android.car.ui.utils.RotaryConstants.ACTION_RESTORE_DEFAULT_FOCUS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.recyclerview.TestContentLimitingAdapter;
+import com.android.car.ui.test.R;
+import com.android.car.ui.utils.CarUiUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Unit test for {@link FocusParkingView} not in touch mode. */
+public class FocusParkingViewTest {
+
+    private static final int NUM_ITEMS = 40;
+
+    @Rule
+    public ActivityTestRule<FocusParkingViewTestActivity> mActivityRule =
+            new ActivityTestRule<>(FocusParkingViewTestActivity.class);
+
+    private FocusParkingViewTestActivity mActivity;
+    private FocusParkingView mFpv;
+    private ViewGroup mParent1;
+    private View mView1;
+    private View mFocusedByDefault;
+    private RecyclerView mList;
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+        mFpv = mActivity.findViewById(R.id.fpv);
+        mParent1 = mActivity.findViewById(R.id.parent1);
+        mView1 = mActivity.findViewById(R.id.view1);
+        mFocusedByDefault = mActivity.findViewById(R.id.focused_by_default);
+        mList = mActivity.findViewById(R.id.list);
+
+        mList.post(() -> {
+            mList.setLayoutManager(new LinearLayoutManager(mActivity));
+            mList.setAdapter(new TestContentLimitingAdapter(NUM_ITEMS));
+            CarUiUtils.setRotaryScrollEnabled(mList, /* isVertical= */ true);
+        });
+    }
+
+    @Test
+    public void testGetWidthAndHeight() {
+        assertThat(mFpv.getWidth()).isEqualTo(1);
+        assertThat(mFpv.getHeight()).isEqualTo(1);
+    }
+
+    @Test
+    public void testRequestFocus_focusOnDefaultFocus() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            mFpv.requestFocus();
+            assertThat(mFocusedByDefault.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testRestoreDefaultFocus_focusOnDefaultFocus() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            mFpv.restoreDefaultFocus();
+            assertThat(mFocusedByDefault.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testOnWindowFocusChanged_loseFocus() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            mFpv.onWindowFocusChanged(false);
+            assertThat(mFpv.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testOnWindowFocusChanged_focusOnDefaultFocus() {
+        mFpv.post(() -> {
+            mFpv.performAccessibilityAction(ACTION_FOCUS, null);
+            assertThat(mFpv.isFocused()).isTrue();
+
+            mFpv.onWindowFocusChanged(true);
+            assertThat(mFocusedByDefault.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testPerformAccessibilityAction_actionRestoreDefaultFocus() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            mFpv.performAccessibilityAction(ACTION_RESTORE_DEFAULT_FOCUS, null);
+            assertThat(mFocusedByDefault.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testPerformAccessibilityAction_actionFocus() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            mFpv.performAccessibilityAction(ACTION_FOCUS, null);
+            assertThat(mFpv.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testRestoreFocusInRoot_recyclerViewItemRemoved() {
+        mList.post(() -> mList.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mList.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        View firstItem = mList.getLayoutManager().findViewByPosition(0);
+                        firstItem.requestFocus();
+                        assertThat(firstItem.isFocused()).isTrue();
+
+                        ViewGroup parent = (ViewGroup) firstItem.getParent();
+                        parent.removeView(firstItem);
+                        assertThat(mFocusedByDefault.isFocused()).isTrue();
+                    }
+                })
+        );
+    }
+
+    @Test
+    public void testRestoreFocusInRoot_recyclerViewItemScrolledOffScreen() {
+        mList.post(() -> mList.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mList.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        View firstItem = mList.getLayoutManager().findViewByPosition(0);
+                        firstItem.requestFocus();
+                        assertThat(firstItem.isFocused()).isTrue();
+
+                        mList.scrollToPosition(NUM_ITEMS - 1);
+                        mList.getViewTreeObserver().addOnGlobalLayoutListener(
+                                new ViewTreeObserver.OnGlobalLayoutListener() {
+                                    @Override
+                                    public void onGlobalLayout() {
+                                        mList.getViewTreeObserver()
+                                                .removeOnGlobalLayoutListener(this);
+                                        assertThat(mList.isFocused()).isTrue();
+                                    }
+                                });
+                    }
+                }));
+    }
+
+    @Test
+    public void testRestoreFocusInRoot_focusedViewRemoved() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            ViewGroup parent = (ViewGroup) mView1.getParent();
+            parent.removeView(mView1);
+            assertThat(mFocusedByDefault.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testRestoreFocusInRoot_focusedViewDisabled() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            mView1.setEnabled(false);
+            assertThat(mFocusedByDefault.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testRestoreFocusInRoot_focusedViewBecomesInvisible() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            mView1.setVisibility(View.INVISIBLE);
+            assertThat(mFocusedByDefault.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testRestoreFocusInRoot_focusedViewParentBecomesInvisible() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+
+            mParent1.setVisibility(View.INVISIBLE);
+            assertThat(mFocusedByDefault.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testRequestFocus_focusesFpvWhenShouldRestoreFocusIsFalse() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+            mFpv.setShouldRestoreFocus(false);
+
+            mFpv.requestFocus();
+            assertThat(mFpv.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testRestoreDefaultFocus_focusesFpvWhenShouldRestoreFocusIsFalse() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+            mFpv.setShouldRestoreFocus(false);
+
+            mFpv.restoreDefaultFocus();
+            assertThat(mFpv.isFocused()).isTrue();
+        });
+    }
+}
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/FocusParkingViewTestActivity.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTestActivity.java
similarity index 95%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/FocusParkingViewTestActivity.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTestActivity.java
index 9357ca6..9ce37cc 100644
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/FocusParkingViewTestActivity.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTestActivity.java
@@ -19,8 +19,7 @@
 import android.app.Activity;
 import android.os.Bundle;
 
-import com.android.car.ui.tests.unit.R;
-
+import com.android.car.ui.test.R;
 
 /** An Activity used for testing {@link FocusParkingView}. */
 public class FocusParkingViewTestActivity extends Activity {
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTouchModeTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTouchModeTest.java
new file mode 100644
index 0000000..d872bb6
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTouchModeTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import static com.android.car.ui.utils.RotaryConstants.ACTION_RESTORE_DEFAULT_FOCUS;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.view.View;
+
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Unit test for {@link FocusParkingView} in touch mode. */
+public class FocusParkingViewTouchModeTest {
+
+    @Rule
+    public ActivityTestRule<FocusParkingViewTestActivity> mActivityRule =
+            new ActivityTestRule<>(FocusParkingViewTestActivity.class,
+                    /* initialTouchMode= */ true);
+
+    private FocusParkingView mFpv;
+
+    @Before
+    public void setUp() {
+        FocusParkingViewTestActivity activity = mActivityRule.getActivity();
+        mFpv = activity.findViewById(R.id.fpv);
+    }
+
+    @Test
+    public void testRestoreDefaultFocus_doesNothing() {
+        mFpv.post(() -> {
+            assertThat(mFpv.getRootView().findFocus()).isNull();
+
+            boolean result = mFpv.restoreDefaultFocus();
+
+            assertWithMessage("restoreDefaultFocus returned").that(result).isFalse();
+            assertWithMessage("No view should be focused")
+                    .that(mFpv.getRootView().findFocus()).isNull();
+        });
+    }
+
+    @Test
+    public void testRequestFocus_doesNothing() {
+        mFpv.post(() -> {
+            assertThat(mFpv.getRootView().findFocus()).isNull();
+
+            boolean result = mFpv.requestFocus(View.FOCUS_DOWN, /* previouslyFocusedRect= */ null);
+
+            assertWithMessage("requestFocus returned").that(result).isFalse();
+            assertWithMessage("No view should be focused")
+                    .that(mFpv.getRootView().findFocus()).isNull();
+        });
+    }
+
+    @Test
+    public void testPerformActionRestoreDefaultFocus_exitsTouchMode() {
+        mFpv.post(() -> {
+            assertThat(mFpv.getRootView().findFocus()).isNull();
+
+            boolean result = mFpv.performAccessibilityAction(
+                    ACTION_RESTORE_DEFAULT_FOCUS, /* arguments= */ null);
+
+            assertWithMessage("performAccessibilityAction returned").that(result).isTrue();
+            assertWithMessage("A view should be focused")
+                    .that(mFpv.getRootView().findFocus()).isNotNull();
+        });
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/RotaryCacheTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/RotaryCacheTest.java
new file mode 100644
index 0000000..b1b9b59
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/RotaryCacheTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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;
+
+import static com.android.car.ui.RotaryCache.CACHE_TYPE_EXPIRED_AFTER_SOME_TIME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link RotaryCache}. */
+@RunWith(AndroidJUnit4.class)
+public class RotaryCacheTest {
+    private static final int CACHE_TIME_OUT_MS = 10000;
+
+    private RotaryCache mRotaryCache;
+    private long mValidTime;
+    private long mExpiredTime;
+    private Context mContext;
+    private FocusArea mFocusArea;
+    private View mFocusedView;
+
+    @Before
+    public void setUp() {
+        mRotaryCache = new RotaryCache(CACHE_TYPE_EXPIRED_AFTER_SOME_TIME, CACHE_TIME_OUT_MS,
+                CACHE_TYPE_EXPIRED_AFTER_SOME_TIME, CACHE_TIME_OUT_MS);
+        mValidTime = CACHE_TIME_OUT_MS - 1;
+        mExpiredTime = CACHE_TIME_OUT_MS + 1;
+        mContext = ApplicationProvider.getApplicationContext();
+        mFocusArea = new FocusArea(mContext);
+        mFocusedView = new View(mContext);
+    }
+
+    @Test
+    public void testGetFocusedView_inTheCache() {
+        mRotaryCache.saveFocusedView(mFocusedView, 0);
+        View view = mRotaryCache.getFocusedView(mValidTime);
+        assertThat(view).isEqualTo(mFocusedView);
+    }
+
+    @Test
+    public void testGetFocusedView_notInTheCache() {
+        View view = mRotaryCache.getFocusedView(mValidTime);
+        assertThat(view).isNull();
+    }
+
+    @Test
+    public void testGetFocusedView_expiredCache() {
+        mRotaryCache.saveFocusedView(mFocusedView, 0);
+        View view = mRotaryCache.getFocusedView(mExpiredTime);
+        assertThat(view).isNull();
+    }
+
+    @Test
+    public void testGetCachedFocusArea_inTheCache() {
+        int direction = View.FOCUS_LEFT;
+        mRotaryCache.saveFocusArea(direction, mFocusArea, 0);
+        FocusArea focusArea = mRotaryCache.getCachedFocusArea(direction, mValidTime);
+        assertThat(focusArea).isEqualTo(mFocusArea);
+    }
+
+    @Test
+    public void testGetCachedFocusArea_notInTheCache() {
+        int direction = View.FOCUS_LEFT;
+        mRotaryCache.saveFocusArea(direction, mFocusArea, 0);
+
+        FocusArea focusArea = mRotaryCache.getCachedFocusArea(View.FOCUS_RIGHT, mValidTime);
+        assertThat(focusArea).isNull();
+        focusArea = mRotaryCache.getCachedFocusArea(View.FOCUS_UP, mValidTime);
+        assertThat(focusArea).isNull();
+    }
+
+    @Test
+    public void testGetCachedFocusArea_expiredCache() {
+        int direction = View.FOCUS_LEFT;
+        mRotaryCache.saveFocusArea(direction, mFocusArea, 0);
+        FocusArea focusArea = mRotaryCache.getCachedFocusArea(direction, mExpiredTime);
+        assertThat(focusArea).isNull();
+    }
+
+    @Test
+    public void testClearFocusAreaHistory() {
+        mRotaryCache.saveFocusArea(View.FOCUS_UP, mFocusArea, 0);
+        assertThat(mRotaryCache.getCachedFocusArea(View.FOCUS_UP, mValidTime)).isEqualTo(
+                mFocusArea);
+
+        mRotaryCache.clearFocusAreaHistory();
+        assertThat(mRotaryCache.getCachedFocusArea(View.FOCUS_UP, mValidTime)).isNull();
+    }
+}
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/TestActivity.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/TestActivity.java
similarity index 95%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/TestActivity.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/TestActivity.java
index db8c560..a5f20d5 100644
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/TestActivity.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/TestActivity.java
@@ -19,7 +19,7 @@
 import android.app.Activity;
 import android.os.Bundle;
 
-import com.android.car.ui.tests.unit.R;
+import com.android.car.ui.test.R;
 
 /**
  * An empty activity to be used for testing.
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/TestFocusArea.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/TestFocusArea.java
similarity index 78%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/TestFocusArea.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/TestFocusArea.java
index 092624f..444cbc5 100644
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/TestFocusArea.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/TestFocusArea.java
@@ -31,6 +31,8 @@
     /** Whether {@link #draw(Canvas)} was called. */
     private boolean mDrawCalled;
 
+    private int mLayoutDirection = LAYOUT_DIRECTION_LTR;
+
     public TestFocusArea(Context context) {
         super(context);
     }
@@ -75,4 +77,20 @@
     public void setDrawCalled(boolean drawCalled) {
         mDrawCalled = drawCalled;
     }
+
+    @Override
+    public void setLayoutDirection(int layoutDirection) {
+        // The real setLayoutDirection doesn't work in the test, so let's mock it.
+        if (mLayoutDirection != layoutDirection) {
+            mLayoutDirection = layoutDirection;
+            // To trigger the highlight padding update, we need to call onLayout. Note: the
+            // parameters don't matter.
+            onLayout(false, 0, 0, 0, 0);
+        }
+    }
+
+    @Override
+    public int getLayoutDirection() {
+        return mLayoutDirection;
+    }
 }
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/actions/LowLevelActions.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/LowLevelActions.java
similarity index 100%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/actions/LowLevelActions.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/LowLevelActions.java
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/actions/ViewActions.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ViewActions.java
similarity index 100%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/actions/ViewActions.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ViewActions.java
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/actions/WaitForViewAction.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/WaitForViewAction.java
similarity index 100%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/actions/WaitForViewAction.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/WaitForViewAction.java
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/matchers/DrawableMatcher.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/DrawableMatcher.java
similarity index 100%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/matchers/DrawableMatcher.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/DrawableMatcher.java
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/IndexMatcher.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/IndexMatcher.java
new file mode 100644
index 0000000..8902b7d
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/IndexMatcher.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.matchers;
+
+import android.view.View;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * A custom matcher that allows for the specification of an index when multiple views meet the
+ * criteria of a matcher.
+ */
+public class IndexMatcher extends TypeSafeMatcher<View> {
+
+    private final Matcher<View> mMatcher;
+    private final int mIndex;
+    int mCurrentIndex = 0;
+
+    public IndexMatcher(Matcher<View> matcher, int index) {
+        mMatcher = matcher;
+        mIndex = index;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText("with index: ");
+        description.appendValue(mIndex);
+        mMatcher.describeTo(description);
+    }
+
+    @Override
+    public boolean matchesSafely(View view) {
+        return mMatcher.matches(view) && mCurrentIndex++ == mIndex;
+    }
+}
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/matchers/NthChildMatcher.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/NthChildMatcher.java
similarity index 100%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/matchers/NthChildMatcher.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/NthChildMatcher.java
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/PaddingMatcher.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/PaddingMatcher.java
new file mode 100644
index 0000000..5ab1a86
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/PaddingMatcher.java
@@ -0,0 +1,81 @@
+/*
+ * 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.matchers;
+
+import android.view.View;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+public class PaddingMatcher extends TypeSafeMatcher<View> {
+
+    public enum Side {
+        TOP,
+        BOTTOM,
+        LEFT,
+        RIGHT,
+        START,
+        END
+    }
+
+    private Side mSide;
+    private int mMin;
+    private int mMax;
+
+    public PaddingMatcher(Side side, int min, int max) {
+        mSide = side;
+        mMin = min;
+        mMax = max;
+    }
+
+    @Override
+    protected boolean matchesSafely(View item) {
+        int padding = 0;
+        switch (mSide) {
+            case TOP:
+                padding = item.getPaddingTop();
+                break;
+            case BOTTOM:
+                padding = item.getPaddingBottom();
+                break;
+            case LEFT:
+                padding = item.getPaddingLeft();
+                break;
+            case RIGHT:
+                padding = item.getPaddingRight();
+                break;
+            case START:
+                padding = item.getPaddingStart();
+                break;
+            case END:
+                padding = item.getPaddingEnd();
+                break;
+        }
+
+        if (mMin >= 0 && padding < mMin) {
+            return false;
+        }
+
+        return mMax < 0 || padding <= mMax;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description
+            .appendText("with " + mSide.toString() + " padding between " + mMin + " and " + mMax);
+    }
+}
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/matchers/ViewMatchers.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java
similarity index 68%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/matchers/ViewMatchers.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java
index 1bee2e0..cdef653 100644
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/matchers/ViewMatchers.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java
@@ -18,6 +18,8 @@
 
 import android.view.View;
 
+import com.android.car.ui.matchers.PaddingMatcher.Side;
+
 import org.hamcrest.Matcher;
 
 public class ViewMatchers {
@@ -28,4 +30,16 @@
     public static Matcher<View> nthChildOfView(Matcher<View> parentMatcher, int n) {
         return new NthChildMatcher(parentMatcher, n);
     }
+
+    public static Matcher<View> withIndex(Matcher<View> matcher, int index) {
+        return new IndexMatcher(matcher, index);
+    }
+
+    public static Matcher<View> withPadding(Side side, int exactly) {
+        return new PaddingMatcher(side, exactly, exactly);
+    }
+
+    public static Matcher<View> withPaddingAtLeast(Side side, int min) {
+        return new PaddingMatcher(side, min, -1);
+    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/NonFullscreenPreferenceFragmentTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/NonFullscreenPreferenceFragmentTest.java
new file mode 100644
index 0000000..33aaa18
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/NonFullscreenPreferenceFragmentTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.preference;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.Espresso.pressBack;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.android.car.ui.matchers.ViewMatchers.withPadding;
+import static com.android.car.ui.matchers.ViewMatchers.withPaddingAtLeast;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.ListPreference;
+import androidx.preference.MultiSelectListPreference;
+import androidx.preference.PreferenceScreen;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.ui.baselayout.Insets;
+import com.android.car.ui.baselayout.InsetsChangedListener;
+import com.android.car.ui.core.CarUi;
+import com.android.car.ui.matchers.PaddingMatcher.Side;
+import com.android.car.ui.toolbar.ToolbarController;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+public class NonFullscreenPreferenceFragmentTest {
+
+    private static final String EXTRA_FULLSCREEN = "fullscreen";
+    private static final String TOOLBAR_DEFAULT_TEXT = "Test!";
+    private static final String PREFERENCE_SCREEN_TITLE = "PreferenceScreen Title";
+    private static final String LIST_PREFERENCE_TITLE = "List Preference";
+    private static final String MULTI_SELECT_LIST_PREFERENCE_TITLE = "MultiSelect List Preference";
+    private static final String BACK_CONTENT_DESCRIPTION = "Back";
+    private static final String[] ITEMS = { "Item 1", "Item 2", "Item 3" };
+
+    @Rule
+    public ActivityScenarioRule<PreferenceTestActivity> mActivityRule =
+            new ActivityScenarioRule<>(PreferenceTestActivity.class);
+
+    @Test
+    public void test_fullscreen_changesTitle() {
+        try (ActivityScenario<MyActivity> scenario =
+                     ActivityScenario.launch(MyActivity.newIntent(true))) {
+
+            onView(withText(TOOLBAR_DEFAULT_TEXT)).check(doesNotExist());
+            onView(withText(PREFERENCE_SCREEN_TITLE)).check(matches(isDisplayed()));
+            onView(isAssignableFrom(RecyclerView.class)).check(
+                    matches(withPaddingAtLeast(Side.TOP, 1)));
+
+            onView(withText(MULTI_SELECT_LIST_PREFERENCE_TITLE)).perform(click());
+            onView(withText(MULTI_SELECT_LIST_PREFERENCE_TITLE)).check(matches(isDisplayed()));
+            onView(withText(ITEMS[0])).check(matches(isDisplayed()));
+            onView(isAssignableFrom(RecyclerView.class)).check(
+                    matches(withPaddingAtLeast(Side.TOP, 1)));
+            onView(withContentDescription(BACK_CONTENT_DESCRIPTION)).perform(click());
+
+            onView(withText(LIST_PREFERENCE_TITLE)).perform(click());
+            onView(withText(LIST_PREFERENCE_TITLE)).check(matches(isDisplayed()));
+            onView(withText(ITEMS[0])).check(matches(isDisplayed()));
+            onView(isAssignableFrom(RecyclerView.class)).check(
+                    matches(withPaddingAtLeast(Side.TOP, 1)));
+            onView(withContentDescription(BACK_CONTENT_DESCRIPTION)).perform(click());
+        }
+    }
+
+    @Test
+    public void test_nonFullscreen_doesntChangeTitle() {
+        try (ActivityScenario<MyActivity> scenario =
+                     ActivityScenario.launch(MyActivity.newIntent(false))) {
+
+            onView(withText(TOOLBAR_DEFAULT_TEXT)).check(matches(isDisplayed()));
+            onView(withText(PREFERENCE_SCREEN_TITLE)).check(doesNotExist());
+            onView(isAssignableFrom(RecyclerView.class)).check(matches(withPadding(Side.TOP, 0)));
+
+            onView(withText(MULTI_SELECT_LIST_PREFERENCE_TITLE)).perform(click());
+            onView(withText(MULTI_SELECT_LIST_PREFERENCE_TITLE)).check(doesNotExist());
+            onView(withText(TOOLBAR_DEFAULT_TEXT)).check(matches(isDisplayed()));
+            onView(withText(ITEMS[0])).check(matches(isDisplayed()));
+            onView(isAssignableFrom(RecyclerView.class)).check(matches(withPadding(Side.TOP, 0)));
+            onView(withContentDescription(BACK_CONTENT_DESCRIPTION)).check(doesNotExist());
+            pressBack();
+
+            onView(withText(LIST_PREFERENCE_TITLE)).perform(click());
+            onView(withText(LIST_PREFERENCE_TITLE)).check(doesNotExist());
+            onView(withText(TOOLBAR_DEFAULT_TEXT)).check(matches(isDisplayed()));
+            onView(withText(ITEMS[0])).check(matches(isDisplayed()));
+            onView(isAssignableFrom(RecyclerView.class)).check(matches(withPadding(Side.TOP, 0)));
+            onView(withContentDescription(BACK_CONTENT_DESCRIPTION)).check(doesNotExist());
+            pressBack();
+        }
+    }
+
+
+    public static class MyActivity extends AppCompatActivity implements InsetsChangedListener {
+
+        private boolean mIsFullScreen = false;
+
+        public static Intent newIntent(boolean isFullScreen) {
+            Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+            Intent intent = new Intent(context, MyActivity.class);
+            intent.putExtra(EXTRA_FULLSCREEN, isFullScreen);
+            return intent;
+        }
+
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            ToolbarController toolbar = CarUi.requireToolbar(this);
+            toolbar.setTitle(TOOLBAR_DEFAULT_TEXT);
+
+            mIsFullScreen = getIntent().getBooleanExtra(EXTRA_FULLSCREEN, true);
+            if (savedInstanceState == null) {
+                getSupportFragmentManager()
+                        .beginTransaction()
+                        .replace(android.R.id.content, new MyPreferenceFragment(mIsFullScreen))
+                        .commitNow();
+            }
+        }
+
+        @Override
+        public void onCarUiInsetsChanged(@NonNull Insets insets) {
+            if (!mIsFullScreen) {
+                requireViewById(android.R.id.content).setPadding(insets.getLeft(), insets.getTop(),
+                        insets.getRight(), insets.getBottom());
+            } else {
+                // No-op marker for the preference fragment to handle it
+            }
+        }
+    }
+
+    public static class MyPreferenceFragment extends PreferenceFragment {
+
+        private final boolean mIsFullScreen;
+
+        public MyPreferenceFragment(boolean isFullScreen) {
+            mIsFullScreen = isFullScreen;
+        }
+
+        @Override
+        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+            PreferenceScreen screen = getPreferenceManager()
+                    .createPreferenceScreen(requireContext());
+
+            ListPreference listPreference = new CarUiListPreference(getContext());
+            listPreference.setTitle(LIST_PREFERENCE_TITLE);
+            listPreference.setKey(LIST_PREFERENCE_TITLE);
+            listPreference.setEntries(ITEMS);
+            listPreference.setEntryValues(new CharSequence[]{"1", "2", "3"});
+
+            MultiSelectListPreference multiSelectListPreference =
+                    new CarUiMultiSelectListPreference(getContext());
+            multiSelectListPreference.setTitle(MULTI_SELECT_LIST_PREFERENCE_TITLE);
+            multiSelectListPreference.setKey(MULTI_SELECT_LIST_PREFERENCE_TITLE);
+            multiSelectListPreference.setEntries(ITEMS);
+            multiSelectListPreference.setEntryValues(new CharSequence[]{"1", "2", "3"});
+
+            screen.addPreference(listPreference);
+            screen.addPreference(multiSelectListPreference);
+
+            screen.setTitle(PREFERENCE_SCREEN_TITLE);
+            setPreferenceScreen(screen);
+        }
+
+        @Override
+        protected boolean isFullScreenFragment() {
+            return mIsFullScreen;
+        }
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTest.java
new file mode 100644
index 0000000..a955cfd
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTest.java
@@ -0,0 +1,326 @@
+/*
+ * 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.preference;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isNotChecked;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.android.car.ui.matchers.ViewMatchers.withIndex;
+
+import static org.hamcrest.Matchers.not;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+
+import androidx.preference.CheckBoxPreference;
+import androidx.preference.DropDownPreference;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/** Unit tests for {@link CarUiPreference}. */
+public class PreferenceTest {
+
+    private PreferenceTestActivity mActivity;
+    private String[] mEntries;
+    private String[] mEntriesValues;
+
+    @Rule
+    public ActivityTestRule<PreferenceTestActivity> mActivityRule =
+            new ActivityTestRule<>(PreferenceTestActivity.class);
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+        Resources resources = mActivity.getResources();
+        mEntries = resources.getStringArray(R.array.entries);
+        mEntriesValues = resources.getStringArray(R.array.entry_values);
+    }
+
+    @Test
+    public void testListPreference() {
+        // Scroll until list preference is visible
+        mActivity.runOnUiThread(() -> mActivity.scrollToPreference("list"));
+
+        // Display full screen list preference.
+        onView(withText(R.string.title_list_preference)).perform(click());
+
+        Preference.OnPreferenceChangeListener mockListener = mock(
+                Preference.OnPreferenceChangeListener.class);
+        when(mockListener.onPreferenceChange(any(), any())).thenReturn(true);
+        mActivity.setOnPreferenceChangeListener("list", mockListener);
+
+        // Check that no option is initially selected.
+        onView(withIndex(withId(R.id.radio_button_widget), 1)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 2)).check(matches(isNotChecked()));
+
+        // Select first option.
+        onView(withText(mEntries[0])).perform(click());
+        // Check that first option is selected.
+        onView(withIndex(withId(R.id.radio_button_widget), 0)).check(matches(isChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 1)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 2)).check(matches(isNotChecked()));
+
+        // Press back to save selection.
+        onView(withId(R.id.car_ui_toolbar_nav_icon)).perform(click());
+        // Verify preference value was updated.
+        verify(mockListener, times(1)).onPreferenceChange(any(), eq(mEntriesValues[0]));
+
+        onView(withText(R.string.title_list_preference)).perform(click());
+
+        // Check that first option is selected.
+        onView(withIndex(withId(R.id.radio_button_widget), 0)).check(matches(isChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 1)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 2)).check(matches(isNotChecked()));
+
+        // Select second option.
+        onView(withText(mEntries[1])).perform(click());
+        // Check that second option is selected.
+        onView(withIndex(withId(R.id.radio_button_widget), 0)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 1)).check(matches(isChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 2)).check(matches(isNotChecked()));
+
+        // Press back to save selection.
+        onView(withId(R.id.car_ui_toolbar_nav_icon)).perform(click());
+        // Verify preference value was updated.
+        verify(mockListener, times(1)).onPreferenceChange(any(), eq(mEntriesValues[1]));
+        // Return to list preference screen.
+        onView(withText(R.string.title_list_preference)).perform(click());
+
+        // Check that second option is selected.
+        onView(withIndex(withId(R.id.radio_button_widget), 0)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 1)).check(matches(isChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 2)).check(matches(isNotChecked()));
+    }
+
+    @Test
+    public void testMultiSelectListPreference() {
+        // Scroll until multi-select preference is visible
+        mActivity.runOnUiThread(() -> mActivity.scrollToPreference("multi_select_list"));
+
+        // Display full screen list preference.
+        onView(withText(R.string.title_multi_list_preference)).perform(click());
+
+        Preference.OnPreferenceChangeListener mockListener = mock(
+                Preference.OnPreferenceChangeListener.class);
+        when(mockListener.onPreferenceChange(any(), any())).thenReturn(true);
+        mActivity.setOnPreferenceChangeListener("multi_select_list", mockListener);
+
+        // Check that no option is initially selected.
+        onView(withIndex(withId(R.id.checkbox_widget), 0)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.checkbox_widget), 1)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.checkbox_widget), 2)).check(matches(isNotChecked()));
+
+        // Select options 1 and 3.
+        onView(withText(mEntries[0])).perform(click());
+        onView(withText(mEntries[2])).perform(click());
+
+        // Check that selections are correctly reflected.
+        onView(withIndex(withId(R.id.checkbox_widget), 0)).check(matches(isChecked()));
+        onView(withIndex(withId(R.id.checkbox_widget), 1)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.checkbox_widget), 2)).check(matches(isChecked()));
+
+        // Press back to save selection.
+        onView(withId(R.id.car_ui_toolbar_nav_icon)).perform(click());
+        Set<String> expectedUpdate = new HashSet<>();
+        expectedUpdate.add(mEntriesValues[0]);
+        expectedUpdate.add(mEntriesValues[2]);
+        // Verify preference value was updated.
+        verify(mockListener, times(1)).onPreferenceChange(any(), eq(expectedUpdate));
+
+        // Return to multi-select list preference screen.
+        onView(withText(R.string.title_multi_list_preference)).perform(click());
+
+        // Check that selections are correctly reflected.
+        onView(withIndex(withId(R.id.checkbox_widget), 0)).check(matches(isChecked()));
+        onView(withIndex(withId(R.id.checkbox_widget), 1)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.checkbox_widget), 2)).check(matches(isChecked()));
+    }
+
+    @Test
+    public void testCheckboxPreference() {
+        // Create checkbox preference and add it to screen.
+        CheckBoxPreference preference = new CheckBoxPreference(mActivity);
+        preference.setOrder(0);
+        preference.setKey("checkbox");
+        preference.setTitle(R.string.title_checkbox_preference);
+        preference.setSummary(R.string.summary_checkbox_preference);
+        mActivity.addPreference(preference);
+
+        // Check title and summary are displayed as expected.
+        onView(withIndex(withId(android.R.id.title), 0)).check(matches(
+                withText(mActivity.getString(R.string.title_checkbox_preference))));
+        onView(withIndex(withId(android.R.id.summary), 0)).check(matches(
+                withText(mActivity.getString(R.string.summary_checkbox_preference))));
+
+        // Ensure checkbox preference is initially not selected.
+        onView(withId(android.R.id.checkbox)).check(matches(isNotChecked()));
+
+        Preference.OnPreferenceChangeListener mockListener = mock(
+                Preference.OnPreferenceChangeListener.class);
+        when(mockListener.onPreferenceChange(any(), any())).thenReturn(true);
+        mActivity.setOnPreferenceChangeListener("checkbox", mockListener);
+
+        // Select checkbox preference.
+        onView(withText(R.string.title_checkbox_preference)).perform(click());
+
+        // Verify preference value was updated.
+        verify(mockListener, times(1)).onPreferenceChange(any(), eq(true));
+
+        // Verify checkbox preference correctly indicates preference is selected.
+        onView(withId(android.R.id.checkbox)).check(matches(isChecked()));
+
+        // Un-select checkbox preference.
+        onView(withText(R.string.title_checkbox_preference)).perform(click());
+
+        // Verify preference value was updated.
+        verify(mockListener, times(1)).onPreferenceChange(any(), eq(false));
+
+        // Verify checkbox preference correctly indicates preference is selected.
+        onView(withId(android.R.id.checkbox)).check(matches(isNotChecked()));
+    }
+
+    @Test
+    public void testSwitchPreference() {
+        // Create switch preference and add it to screen.
+        SwitchPreference preference = new SwitchPreference(mActivity);
+        preference.setOrder(0);
+        preference.setKey("switch");
+        preference.setTitle(R.string.title_switch_preference);
+        preference.setSummary(R.string.summary_switch_preference);
+        mActivity.addPreference(preference);
+
+        // Check title and summary are displayed as expected.
+        onView(withIndex(withId(android.R.id.title), 0)).check(matches(
+                withText(mActivity.getString(R.string.title_switch_preference))));
+        onView(withIndex(withId(android.R.id.summary), 0)).check(matches(
+                withText(mActivity.getString(R.string.summary_switch_preference))));
+
+        // Ensure switch preference is initially not selected.
+        onView(withId(android.R.id.switch_widget)).check(matches(isNotChecked()));
+
+        Preference.OnPreferenceChangeListener mockListener = mock(
+                Preference.OnPreferenceChangeListener.class);
+        when(mockListener.onPreferenceChange(any(), any())).thenReturn(true);
+        mActivity.setOnPreferenceChangeListener("switch", mockListener);
+
+        // Select switch preference.
+        onView(withText(R.string.title_switch_preference)).perform(click());
+
+        // Verify preference value was updated.
+        verify(mockListener, times(1)).onPreferenceChange(any(), eq(true));
+
+        // Verify switch preference correctly indicates preference is selected.
+        onView(withId(android.R.id.switch_widget)).check(matches(isChecked()));
+
+        // Un-select switch preference.
+        onView(withText(R.string.title_switch_preference)).perform(click());
+
+        // Verify preference value was updated.
+        verify(mockListener, times(1)).onPreferenceChange(any(), eq(false));
+
+        // Verify switch preference correctly indicates preference is selected.
+        onView(withId(android.R.id.switch_widget)).check(matches(isNotChecked()));
+    }
+
+    @Test
+    public void testDropDownPreference() {
+        // Create drop-down preference and add it to screen.
+        DropDownPreference preference = new CarUiDropDownPreference(mActivity);
+        preference.setKey("dropdown");
+        preference.setTitle(R.string.title_dropdown_preference);
+        preference.setEntries(mEntries);
+        preference.setEntryValues(mEntriesValues);
+        preference.setOrder(0);
+        mActivity.addPreference(preference);
+
+        // Display full screen list preference.
+        onView(withText(R.string.title_dropdown_preference)).perform(click());
+
+        Preference.OnPreferenceChangeListener mockListener = mock(
+                Preference.OnPreferenceChangeListener.class);
+        when(mockListener.onPreferenceChange(any(), any())).thenReturn(true);
+        mActivity.setOnPreferenceChangeListener("dropdown", mockListener);
+
+        // Check that first option is initially selected.
+        onView(withIndex(withId(R.id.radio_button_widget), 0)).check(matches(isChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 1)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 2)).check(matches(isNotChecked()));
+
+        // Select third option.
+        onView(withText(mEntries[2])).perform(click());
+        // Check that first option is selected.
+        onView(withIndex(withId(R.id.radio_button_widget), 0)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 1)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 2)).check(matches(isChecked()));
+
+        // Press back to save selection.
+        onView(withId(R.id.car_ui_toolbar_nav_icon)).perform(click());
+        // Verify preference value was updated.
+        verify(mockListener, times(1)).onPreferenceChange(any(), eq(mEntriesValues[2]));
+
+        onView(withText(R.string.title_dropdown_preference)).perform(click());
+
+        // Check that first option is selected.
+        onView(withIndex(withId(R.id.radio_button_widget), 0)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 1)).check(matches(isNotChecked()));
+        onView(withIndex(withId(R.id.radio_button_widget), 2)).check(matches(isChecked()));
+    }
+
+    @Test
+    public void testTwoActionPreference() {
+        // Create drop-down preference and add it to screen.
+        CarUiTwoActionPreference preference = new CarUiTwoActionPreference(mActivity);
+        preference.setKey("twoaction");
+        preference.setTitle(R.string.title_twoaction_preference);
+        preference.setSummary(R.string.summary_twoaction_preference);
+        preference.setOrder(0);
+        preference.setWidgetLayoutResource(R.layout.details_preference_widget);
+        mActivity.addPreference(preference);
+
+        // Check that widget is displayed
+        onView(withIndex(withId(com.android.car.ui.R.id.action_widget_container), 0)).check(
+                matches(isDisplayed()));
+
+        // Hide second action.
+        mActivity.runOnUiThread(() -> preference.showAction(false));
+
+        // Ensure second action isn't displayed.
+        onView(withIndex(withId(com.android.car.ui.R.id.action_widget_container), 0)).check(
+                matches(not(isDisplayed())));
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTestActivity.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTestActivity.java
new file mode 100644
index 0000000..0916f4b
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTestActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.preference;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.Preference;
+
+public class PreferenceTestActivity extends AppCompatActivity {
+
+    private PreferenceTestFragment mPreferenceFragment;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Display the fragment as the main content.
+        if (savedInstanceState == null) {
+            mPreferenceFragment = new PreferenceTestFragment();
+
+            getSupportFragmentManager()
+                    .beginTransaction()
+                    .replace(android.R.id.content, mPreferenceFragment)
+                    .commitNow();
+        }
+    }
+
+    public void setOnPreferenceChangeListener(
+            String key, Preference.OnPreferenceChangeListener listener) {
+        mPreferenceFragment.setOnPreferenceChangeListener(key, listener);
+    }
+
+    public void scrollToPreference(String key) {
+        mPreferenceFragment.scrollToPreference(key);
+    }
+
+    public void addPreference(Preference preference) {
+        mPreferenceFragment.addPreference(preference);
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTestFragment.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTestFragment.java
new file mode 100644
index 0000000..cc3897b
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTestFragment.java
@@ -0,0 +1,99 @@
+/*
+ * 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.preference;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceDataStore;
+
+import com.android.car.ui.test.R;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test Fragment to load test preferences.
+ */
+public class PreferenceTestFragment extends PreferenceFragment {
+
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        getPreferenceManager().setPreferenceDataStore(new TestDataStore());
+        // Load the preferences from an XML resource
+        setPreferencesFromResource(R.xml.test_preferences, rootKey);
+    }
+
+    public void setOnPreferenceChangeListener(
+            String key, Preference.OnPreferenceChangeListener listener) {
+        findPreference(key).setOnPreferenceChangeListener(listener);
+    }
+
+    public void addPreference(Preference preference) {
+        getPreferenceManager().getPreferenceScreen().addPreference(preference);
+    }
+
+    /**
+     * Custom data store to be used for testing as SharedPreferences for instrumentation tests
+     * are not initialized as expected.
+     */
+    public static class TestDataStore extends PreferenceDataStore {
+
+        private Map<String, String> mStringStore = new HashMap<>();
+        private Map<String, Boolean> mBooleanStore = new HashMap<>();
+        private Map<String, Set<String>> mStringSetStore = new HashMap<>();
+
+        @Override
+        public void putString(String key, @Nullable String value) {
+            mStringStore.put(key, value);
+        }
+
+        @Override
+        public void putStringSet(String key, @Nullable Set<String> values) {
+            mStringSetStore.put(key, values);
+        }
+
+        @Override
+        @Nullable
+        public String getString(String key, @Nullable String defValue) {
+            return mStringStore.getOrDefault(key, defValue);
+        }
+
+        @Override
+        @Nullable
+        public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
+            // Workaround for NullPointerException caused by null string set from custom data store.
+            if (defValues == null) {
+                defValues = new HashSet<>();
+            }
+            return mStringSetStore.getOrDefault(key, defValues);
+        }
+
+        @Override
+        public void putBoolean(String key, boolean value) {
+            mBooleanStore.put(key, value);
+        }
+
+        @Override
+        public boolean getBoolean(String key, boolean defValue) {
+            return mBooleanStore.getOrDefault(key, defValue);
+        }
+    }
+}
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiListItemTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiListItemTest.java
similarity index 83%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiListItemTest.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiListItemTest.java
index d5f5d13..bd21173 100644
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiListItemTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiListItemTest.java
@@ -26,8 +26,10 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -36,17 +38,19 @@
 
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.car.ui.tests.unit.R;
+import com.android.car.ui.R;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.mockito.ArgumentMatchers;
+import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
 import java.util.List;
 
-/** Unit tests for {@link CarUiListItem}. */
+/**
+ * Unit tests for {@link CarUiListItem}.
+ */
 public class CarUiListItemTest {
 
     private CarUiRecyclerView mCarUiRecyclerView;
@@ -233,6 +237,46 @@
     }
 
     @Test
+    public void testItem_withListenerAndSupplementalIconListener() {
+        List<CarUiListItem> items = new ArrayList<>();
+
+        CarUiContentListItem.OnClickListener clickListener = mock(
+                CarUiContentListItem.OnClickListener.class);
+        View.OnClickListener supplementalIconClickListener = mock(View.OnClickListener.class);
+
+        CarUiContentListItem item = new CarUiContentListItem(
+                CarUiContentListItem.Action.ICON);
+        item.setTitle("Test item with two listeners");
+        item.setOnItemClickedListener(clickListener);
+        item.setSupplementalIcon(
+                mActivityRule.getActivity().getDrawable(R.drawable.car_ui_icon_close),
+                supplementalIconClickListener);
+        items.add(item);
+
+        mCarUiRecyclerView.post(
+                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+
+        onView(withId(R.id.title)).check(matches(isDisplayed()));
+
+        // Clicks anywhere on the item (except supplemental icon) should trigger the item click
+        // listener.
+        onView(withId(R.id.title)).perform(click());
+        verify(clickListener, times(1)).onClick(item);
+        verify(supplementalIconClickListener, times(0)).onClick(any());
+
+        ArgumentCaptor<View> iconCaptor = ArgumentCaptor.forClass(View.class);
+        onView(withId(R.id.supplemental_icon)).perform(click());
+        // Check that icon is argument for single call to click listener.
+        verify(supplementalIconClickListener, times(1)).onClick(iconCaptor.capture());
+
+        // Verify that the standard click listener wasn't also fired.
+        verify(clickListener, times(1)).onClick(item);
+
+        View icon = mCarUiRecyclerView.findViewById(R.id.supplemental_icon);
+        assertEquals(icon, iconCaptor.getValue());
+    }
+
+    @Test
     public void testItem_withSupplementalIconAndIconOnClickListener() {
         List<CarUiListItem> items = new ArrayList<>();
 
@@ -261,8 +305,8 @@
 
         // Clicks anywhere on the icon should invoke both listeners.
         onView(withId(R.id.action_container)).perform(click());
-        verify(mockedItemOnClickListener, times(2)).onClick(item);
-        verify(mockedIconListener, times(1)).onClick(ArgumentMatchers.any(View.class));
+        verify(mockedItemOnClickListener, times(1)).onClick(item);
+        verify(mockedIconListener, times(1)).onClick(any(View.class));
     }
 
     @Test
@@ -284,8 +328,9 @@
         itemThree.setTitle(itemThreeTitle);
         items.add(itemThree);
 
+        CarUiRadioButtonListItemAdapter adapter = new CarUiRadioButtonListItemAdapter(items);
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiRadioButtonListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(adapter));
 
         onView(withText(itemOneTitle)).check(matches(isDisplayed()));
         onView(withText(itemTwoTitle)).check(matches(isDisplayed()));
@@ -295,23 +340,27 @@
         assertFalse(itemOne.isChecked());
         assertFalse(itemTwo.isChecked());
         assertFalse(itemThree.isChecked());
+        assertEquals(adapter.getSelectedItemPosition(), -1);
 
         // Select first item.
         onView(withText(itemOneTitle)).perform(click());
         assertTrue(itemOne.isChecked());
         assertFalse(itemTwo.isChecked());
         assertFalse(itemThree.isChecked());
+        assertEquals(adapter.getSelectedItemPosition(), 0);
 
         // Select second item.
         onView(withText(itemTwoTitle)).perform(click());
         assertFalse(itemOne.isChecked());
         assertTrue(itemTwo.isChecked());
         assertFalse(itemThree.isChecked());
+        assertEquals(adapter.getSelectedItemPosition(), 1);
 
         // Select third item.
         onView(withText(itemThreeTitle)).perform(click());
         assertFalse(itemOne.isChecked());
         assertFalse(itemTwo.isChecked());
         assertTrue(itemThree.isChecked());
+        assertEquals(adapter.getSelectedItemPosition(), 2);
     }
 }
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
similarity index 75%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
index 568395d..e1ec9e9 100644
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
@@ -45,8 +45,10 @@
 import static org.hamcrest.Matchers.lessThan;
 import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -66,12 +68,14 @@
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.OrientationHelper;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.core.app.ActivityScenario;
 import androidx.test.espresso.IdlingRegistry;
 import androidx.test.espresso.IdlingResource;
-import androidx.test.rule.ActivityTestRule;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
 import com.android.car.ui.TestActivity;
-import com.android.car.ui.tests.unit.R;
+import com.android.car.ui.recyclerview.decorations.grid.GridDividerItemDecoration;
+import com.android.car.ui.test.R;
 
 import org.junit.After;
 import org.junit.Before;
@@ -87,8 +91,10 @@
 public class CarUiRecyclerViewTest {
 
     @Rule
-    public ActivityTestRule<TestActivity> mActivityRule =
-            new ActivityTestRule<>(TestActivity.class);
+    public ActivityScenarioRule<TestActivity> mActivityRule =
+            new ActivityScenarioRule<>(TestActivity.class);
+
+    ActivityScenario<TestActivity> mScenario = ActivityScenario.launch(TestActivity.class);
 
     private TestActivity mActivity;
     private Context mTestableContext;
@@ -96,9 +102,12 @@
 
     @Before
     public void setUp() {
-        mActivity = mActivityRule.getActivity();
-        mTestableContext = spy(mActivity);
-        mTestableResources = spy(mActivity.getResources());
+        mScenario.onActivity(activity -> {
+            mActivity = activity;
+            mTestableContext = spy(mActivity);
+            mTestableResources = spy(mActivity.getResources());
+        });
+
         when(mTestableContext.getResources()).thenReturn(mTestableResources);
     }
 
@@ -150,6 +159,9 @@
         when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_numOfColumns), anyInt()))
                 .thenReturn(3);
 
+        // Ensure the CarUiRecyclerViewLayout constant matches the styleable attribute enum value
+        assertEquals(CarUiRecyclerView.CarUiRecyclerViewLayout.GRID, 1);
+
         CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerView(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter adapter = new TestAdapter(4);
@@ -194,6 +206,9 @@
         when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_layoutStyle), anyInt()))
                 .thenReturn(CarUiRecyclerView.CarUiRecyclerViewLayout.LINEAR);
 
+        // Ensure the CarUiRecyclerViewLayout constant matches the styleable attribute enum value
+        assertEquals(CarUiRecyclerView.CarUiRecyclerViewLayout.LINEAR, 0);
+
         CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerView(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter adapter = new TestAdapter(4);
@@ -214,26 +229,39 @@
     }
 
     @Test
-    public void testOnHeightChanged_shouldAddTheValueToInitialTopValue() {
-        mActivity.runOnUiThread(
-                () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
+    public void testSetLayoutManager_shouldUpdateItemDecorations() {
+        TypedArray typedArray = spy(mActivity.getBaseContext().obtainStyledAttributes(
+                null, R.styleable.CarUiRecyclerView));
 
-        onView(withId(R.id.list)).check(matches(isDisplayed()));
+        doReturn(typedArray).when(mTestableContext).obtainStyledAttributes(
+                any(),
+                eq(R.styleable.CarUiRecyclerView),
+                anyInt(),
+                anyInt());
+        when(typedArray.getBoolean(eq(R.styleable.CarUiRecyclerView_enableDivider), anyBoolean()))
+                .thenReturn(true);
+        when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_layoutStyle), anyInt()))
+                .thenReturn(CarUiRecyclerView.CarUiRecyclerViewLayout.GRID);
+        when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_numOfColumns), anyInt()))
+                .thenReturn(3);
 
-        CarUiRecyclerView carUiRecyclerView = mActivity.findViewById(R.id.list);
+        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerView(mTestableContext);
+        ViewGroup container = mActivity.findViewById(R.id.test_container);
+        TestAdapter adapter = new TestAdapter(4);
+        container.post(() -> {
+            container.addView(carUiRecyclerView);
+            carUiRecyclerView.setAdapter(adapter);
+        });
 
-        assertEquals(carUiRecyclerView.getPaddingBottom(), 0);
-        assertEquals(carUiRecyclerView.getPaddingTop(), 0);
-        assertEquals(carUiRecyclerView.getPaddingStart(), 0);
-        assertEquals(carUiRecyclerView.getPaddingEnd(), 0);
+        assertTrue(carUiRecyclerView.getLayoutManager() instanceof GridLayoutManager);
+        assertEquals(carUiRecyclerView.getItemDecorationCount(), 3);
+        assertTrue(carUiRecyclerView.getItemDecorationAt(0) instanceof GridDividerItemDecoration);
 
-        mActivity.runOnUiThread(() -> carUiRecyclerView.onHeightChanged(10));
-        onView(withId(R.id.list)).check(matches(isDisplayed()));
+        carUiRecyclerView.setLayoutManager(new LinearLayoutManager(mTestableContext));
 
-        assertEquals(carUiRecyclerView.getPaddingTop(), 10);
-        assertEquals(carUiRecyclerView.getPaddingBottom(), 0);
-        assertEquals(carUiRecyclerView.getPaddingStart(), 0);
-        assertEquals(carUiRecyclerView.getPaddingEnd(), 0);
+        assertTrue(carUiRecyclerView.getLayoutManager() instanceof LinearLayoutManager);
+        assertEquals(carUiRecyclerView.getItemDecorationCount(), 3);
+        assertFalse(carUiRecyclerView.getItemDecorationAt(0) instanceof GridDividerItemDecoration);
     }
 
     @Test
@@ -306,10 +334,8 @@
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
         CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
-        TestAdapter adapter = new TestAdapter(50);
-        mActivity.runOnUiThread(() -> {
-            carUiRecyclerView.setAdapter(adapter);
-        });
+        FixedSizeTestAdapter adapter = new FixedSizeTestAdapter(50, carUiRecyclerView.getHeight());
+        mActivity.runOnUiThread(() -> carUiRecyclerView.setAdapter(adapter));
 
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
         onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
@@ -405,7 +431,7 @@
 
         // scroll to the start
         onView(withId(R.id.car_ui_scrollbar_track)).perform(
-                touchDownAndUp(0f, (thumbView.getHeight() / 2f) + 1));
+                touchDownAndUp(0f, 1));
         onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
     }
 
@@ -427,10 +453,12 @@
 
         View trackView = mActivity.requireViewById(R.id.car_ui_scrollbar_track);
         View thumbView = mActivity.requireViewById(R.id.car_ui_scrollbar_thumb);
-        // drag and scroll to the middle
+        // if you drag too far in a single step you'll stop selecting the thumb view. Hence, drag
+        // 5 units at a time for 200 intervals and stop at the center of the track by limitY.
+
         onView(withId(R.id.car_ui_scrollbar_track)).perform(
                 performDrag(0f, (thumbView.getHeight() / 2f), 0,
-                        (thumbView.getHeight() / 2f) - 1, 10, Float.MAX_VALUE,
+                        5, 200, Float.MAX_VALUE,
                         trackView.getHeight() / 2f));
         onView(withText(adapter.getItemText(25))).check(matches(isDisplayed()));
     }
@@ -443,7 +471,8 @@
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
         CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
-        TestAdapter adapter = new TestAdapter(15);
+        //  50, because needs to be big enough to make sure content is scrollable.
+        TestAdapter adapter = new TestAdapter(50);
         mActivity.runOnUiThread(() -> {
             carUiRecyclerView.setAdapter(adapter);
         });
@@ -454,6 +483,7 @@
         onView(withId(R.id.car_ui_scrollbar_page_up)).check(matches(not(isEnabled())));
 
         // Moving down, should enable the up bottom.
+        onView(withId(R.id.car_ui_scrollbar_page_down)).check(matches(isEnabled()));
         onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
         onView(withId(R.id.car_ui_scrollbar_page_up)).check(matches(isEnabled()));
 
@@ -526,6 +556,51 @@
     }
 
     @Test
+    public void testPageDownScrollsOverLongItemAtTheEnd() {
+        mActivity.runOnUiThread(
+                () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
+
+        onView(withId(R.id.list)).check(matches(isDisplayed()));
+
+        int itemCount = 100;
+        // Position the long item at the end.
+        int longItemPosition = itemCount - 1;
+
+        Map<Integer, TestAdapter.ItemHeight> heightOverrides = new HashMap<>();
+        heightOverrides.put(longItemPosition, TestAdapter.ItemHeight.TALL);
+        TestAdapter adapter = new TestAdapter(itemCount, heightOverrides);
+
+        CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
+        mActivity.runOnUiThread(() -> {
+            // Setting top padding to any number greater than 0.
+            // Not having padding will make this test pass all the time.
+            // Also adding bottom padding to make sure the padding
+            // after the last content is considered in calculations.
+            carUiRecyclerView.setPadding(0, 1, 0, 1);
+            carUiRecyclerView.setAdapter(adapter);
+        });
+
+        IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+
+        OrientationHelper orientationHelper =
+                OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
+
+        // 20 is just an arbitrary number to make sure we reach the end of the recyclerview.
+        for (int i = 0; i < 20; i++) {
+            onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        }
+
+        onView(withId(R.id.car_ui_scrollbar_page_down)).check(matches(not(isEnabled())));
+
+        View longItem = getLongItem(carUiRecyclerView);
+        // Making sure we've reached end of the recyclerview, after
+        // adding bottom padding
+        assertThat(orientationHelper.getDecoratedEnd(longItem)
+                        + carUiRecyclerView.getPaddingBottom(),
+                is(equalTo(carUiRecyclerView.getHeight())));
+    }
+
+    @Test
     public void testPageUpScrollsOverLongItem() {
         mActivity.runOnUiThread(
                 () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
@@ -632,6 +707,77 @@
                 not(equalTo(carUiRecyclerView.getHeight())));
     }
 
+    @Test
+    public void testPageDownScrollsOverVeryLongItemAtTheEnd() {
+        mActivity.runOnUiThread(
+                () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
+
+        onView(withId(R.id.list)).check(matches(isDisplayed()));
+
+        int itemCount = 100;
+        // Position the long item at the end.
+        int longItemPosition = itemCount - 1;
+
+        Map<Integer, TestAdapter.ItemHeight> heightOverrides = new HashMap<>();
+        heightOverrides.put(longItemPosition, TestAdapter.ItemHeight.EXTRA_TALL);
+        TestAdapter adapter = new TestAdapter(itemCount, heightOverrides);
+
+        CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
+        mActivity.runOnUiThread(() -> {
+            // Setting top padding to any number greater than 0.
+            // Not having padding will make this test pass all the time.
+            // Also adding bottom padding to make sure the padding
+            // after the last content is considered in calculations.
+            carUiRecyclerView.setPadding(0, 1, 0, 1);
+            carUiRecyclerView.setAdapter(adapter);
+        });
+
+        IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+
+        OrientationHelper orientationHelper =
+                OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
+
+        // 20 is just an arbitrary number to make sure we reach the end of the recyclerview.
+        for (int i = 0; i < 20; i++) {
+            onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        }
+
+        onView(withId(R.id.car_ui_scrollbar_page_down)).check(matches(not(isEnabled())));
+
+        View longItem = getLongItem(carUiRecyclerView);
+        // Making sure we've reached end of the recyclerview, after
+        // adding bottom padding
+        assertThat(orientationHelper.getDecoratedEnd(longItem)
+                        + carUiRecyclerView.getPaddingBottom(),
+                is(equalTo(carUiRecyclerView.getHeight())));
+    }
+
+
+    @Test
+    public void testPageDownMaintainsMinimumScrollThumbTrackHeight() {
+        mActivity.runOnUiThread(
+                () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
+
+        onView(withId(R.id.list)).check(matches(isDisplayed()));
+
+        int itemCount = 25000;
+        TestAdapter adapter = new TestAdapter(itemCount);
+
+        CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
+        mActivity.runOnUiThread(() -> {
+            carUiRecyclerView.setAdapter(adapter);
+        });
+        IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+        mActivity.runOnUiThread(() -> carUiRecyclerView.requestLayout());
+
+        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+
+        // Check that thumb track maintains minimum height
+        int minThumbViewHeight = mActivity.getResources()
+                .getDimensionPixelOffset(R.dimen.car_ui_scrollbar_min_thumb_height);
+        View thumbView = mActivity.requireViewById(R.id.car_ui_scrollbar_thumb);
+        assertThat(thumbView.getHeight(), is(equalTo(minThumbViewHeight)));
+    }
 
     @Test
     public void testSetPaddingToRecyclerViewContainerWithScrollbar() {
@@ -709,6 +855,58 @@
         assertThat(mCarUiRecyclerView.getPaddingEnd(), is(equalTo(10)));
     }
 
+    @Test
+    public void testSetAlphaToRecyclerViewWithoutScrollbar() {
+        doReturn(false).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+
+        CarUiRecyclerView mCarUiRecyclerView = new CarUiRecyclerView(mTestableContext);
+
+        assertThat(mCarUiRecyclerView.getAlpha(), is(equalTo(1.0f)));
+
+        mCarUiRecyclerView.setAlpha(0.5f);
+
+        assertThat(mCarUiRecyclerView.getAlpha(), is(equalTo(0.5f)));
+    }
+
+    @Test
+    public void testSetAlphaToRecyclerViewWithScrollbar() {
+        mActivity.runOnUiThread(
+                () -> mActivity.setContentView(
+                        R.layout.car_ui_recycler_view_test_activity));
+
+        onView(withId(R.id.list)).check(matches(isDisplayed()));
+
+        CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
+
+        ViewGroup container = (ViewGroup) carUiRecyclerView.getParent().getParent();
+
+        assertThat(carUiRecyclerView.getAlpha(), is(equalTo(1.0f)));
+        assertThat(container.getAlpha(), is(equalTo(1.0f)));
+
+        carUiRecyclerView.setAlpha(0.5f);
+
+        assertThat(carUiRecyclerView.getAlpha(), is(equalTo(1.0f)));
+        assertThat(container.getAlpha(), is(equalTo(0.5f)));
+    }
+
+    @Test
+    public void testCallAnimateOnRecyclerViewWithScrollbar() {
+        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerView(mTestableContext);
+
+        ViewGroup container = mActivity.findViewById(R.id.test_container);
+        container.post(() -> {
+            container.addView(carUiRecyclerView);
+            carUiRecyclerView.setAdapter(new TestAdapter(100));
+        });
+
+        onView(withId(R.id.car_ui_scroll_bar)).check(matches(isDisplayed()));
+
+        ViewGroup recyclerViewContainer = (ViewGroup) carUiRecyclerView.getParent().getParent();
+
+        assertThat(carUiRecyclerView.animate(), is(equalTo(recyclerViewContainer.animate())));
+    }
+
     /**
      * Returns an item in the current list view whose height is taller than that of
      * the CarUiRecyclerView. If that item exists, then it is returned; otherwise an {@link
@@ -801,6 +999,44 @@
         }
     }
 
+    private static class FixedSizeTestAdapter extends RecyclerView.Adapter<TestViewHolder> {
+
+        private static final int ITEMS_PER_PAGE = 5;
+        private final List<String> mData;
+        private final int mItemHeight;
+
+        FixedSizeTestAdapter(int itemCount, int recyclerViewHeight) {
+            mData = new ArrayList<>(itemCount);
+            mItemHeight = recyclerViewHeight / ITEMS_PER_PAGE;
+
+            for (int i = 0; i < itemCount; i++) {
+                mData.add(getItemText(i));
+            }
+        }
+
+        String getItemText(int position) {
+            return String.format("Sample item #%d", position);
+        }
+
+        @NonNull
+        @Override
+        public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+            return new TestViewHolder(inflater, parent);
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull TestViewHolder holder, int position) {
+            holder.itemView.setMinimumHeight(mItemHeight);
+            holder.bind(mData.get(position));
+        }
+
+        @Override
+        public int getItemCount() {
+            return mData.size();
+        }
+    }
+
     private static class TestViewHolder extends RecyclerView.ViewHolder {
         private TextView mTextView;
 
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTestActivity.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTestActivity.java
similarity index 96%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTestActivity.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTestActivity.java
index 6fb5746..64dd993 100644
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTestActivity.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTestActivity.java
@@ -19,7 +19,7 @@
 import android.app.Activity;
 import android.os.Bundle;
 
-import com.android.car.ui.tests.unit.R;
+import com.android.car.ui.test.R;
 
 /**
  * An {@link Activity} that contains only an empty {@link CarUiRecyclerView}.
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterTest.java
new file mode 100644
index 0000000..288e839
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+
+import android.widget.LinearLayout;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ContentLimitingAdapterTest {
+
+    @Rule
+    public ActivityTestRule<CarUiRecyclerViewTestActivity> mActivityRule =
+            new ActivityTestRule<>(CarUiRecyclerViewTestActivity.class);
+
+    private ContentLimitingAdapter<TestViewHolder> mContentLimitingAdapter;
+
+    @Before
+    public void setUp() {
+        mContentLimitingAdapter = new TestContentLimitingAdapter(50);
+    }
+
+    @Test
+    public void setMaxItem_toLowerThanTotalItems() {
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
+        RecyclerView.ViewHolder last = getItemAtPosition(49);
+        isTestViewHolderWithText(last, "Item 49");
+
+        // Switch to limited
+        mContentLimitingAdapter.setMaxItems(20);
+
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(21);
+        RecyclerView.ViewHolder secondToLast = getItemAtPosition(19);
+        isTestViewHolderWithText(secondToLast, "Item 19");
+
+        last = getItemAtPosition(20);
+        assertThat(last).isInstanceOf(ScrollingLimitedViewHolder.class);
+
+        // Switch back to unlimited
+        mContentLimitingAdapter.setMaxItems(-1);
+
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
+        last = getItemAtPosition(49);
+        isTestViewHolderWithText(last, "Item 49");
+    }
+
+    @Test
+    public void setMaxItem_toOne() {
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
+        RecyclerView.ViewHolder last = getItemAtPosition(49);
+        isTestViewHolderWithText(last, "Item 49");
+
+        mContentLimitingAdapter.setMaxItems(1);
+
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(2);
+        RecyclerView.ViewHolder secondToLast = getItemAtPosition(0);
+        isTestViewHolderWithText(secondToLast, "Item 0");
+
+        last = getItemAtPosition(1);
+        assertThat(last).isInstanceOf(ScrollingLimitedViewHolder.class);
+
+        // Switch back to unlimited
+        mContentLimitingAdapter.setMaxItems(-1);
+
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
+        last = getItemAtPosition(49);
+        isTestViewHolderWithText(last, "Item 49");
+    }
+
+    @Test
+    public void setMaxItem_toZero() {
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
+        RecyclerView.ViewHolder last = getItemAtPosition(49);
+        isTestViewHolderWithText(last, "Item 49");
+
+        mContentLimitingAdapter.setMaxItems(0);
+
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(1);
+        last = getItemAtPosition(0);
+        assertThat(last).isInstanceOf(ScrollingLimitedViewHolder.class);
+
+        // Switch back to unlimited
+        mContentLimitingAdapter.setMaxItems(-1);
+
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
+        last = getItemAtPosition(49);
+        isTestViewHolderWithText(last, "Item 49");
+    }
+
+    @Test
+    public void setMaxItem_toHigherThanTotalItems() {
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
+        RecyclerView.ViewHolder last = getItemAtPosition(49);
+        isTestViewHolderWithText(last, "Item 49");
+
+        mContentLimitingAdapter.setMaxItems(70);
+
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
+        RecyclerView.ViewHolder secondToLast = getItemAtPosition(48);
+        isTestViewHolderWithText(secondToLast, "Item 48");
+
+        last = getItemAtPosition(49);
+        isTestViewHolderWithText(last, "Item 49");
+
+        // Switch back to unlimited
+        mContentLimitingAdapter.setMaxItems(-1);
+
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
+        last = getItemAtPosition(49);
+        isTestViewHolderWithText(last, "Item 49");
+    }
+
+    private RecyclerView.ViewHolder getItemAtPosition(int position) {
+        int viewType = mContentLimitingAdapter.getItemViewType(position);
+        RecyclerView.ViewHolder viewHolder =
+                mContentLimitingAdapter.createViewHolder(
+                        new LinearLayout(mActivityRule.getActivity().getApplicationContext()),
+                        viewType);
+        mContentLimitingAdapter.bindViewHolder(viewHolder, position);
+        return viewHolder;
+    }
+
+    private void isTestViewHolderWithText(RecyclerView.ViewHolder secondToLast, String s) {
+        assertThat(secondToLast).isInstanceOf(TestViewHolder.class);
+        TestViewHolder testViewHolder = (TestViewHolder) secondToLast;
+        assertThat(testViewHolder.getText()).isEqualTo(s);
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterUiTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterUiTest.java
new file mode 100644
index 0000000..eae42b8
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterUiTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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 androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ContentLimitingAdapterUiTest {
+
+    @Rule
+    public ActivityTestRule<CarUiRecyclerViewTestActivity> mActivityRule =
+            new ActivityTestRule<>(CarUiRecyclerViewTestActivity.class);
+
+    private ContentLimitingAdapter<TestViewHolder> mContentLimitingAdapter;
+    private RecyclerView mCarUiRecyclerView;
+
+    @Before
+    public void setUp() {
+        mContentLimitingAdapter = new TestContentLimitingAdapter(50);
+        mCarUiRecyclerView = mActivityRule.getActivity().requireViewById(R.id.list);
+        mActivityRule.getActivity().runOnUiThread(() -> {
+            mCarUiRecyclerView.setAdapter(mContentLimitingAdapter);
+        });
+    }
+
+    @Test
+    public void setMaxItem_toLowerThanTotalItems() throws Throwable {
+        onView(withId(R.id.list)).check(matches(isDisplayed()));
+
+        // Switch to limited
+        mActivityRule.runOnUiThread(() -> {
+            mContentLimitingAdapter.setMaxItems(20);
+        });
+        Thread.sleep(300);
+        onView(withText("Item 0")).check(matches(isDisplayed()));
+        onView(withId(R.id.list)).perform(scrollToPosition(20));
+        onView(withText("Item 19")).check(matches(isDisplayed()));
+        onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message))
+                .check(matches(isDisplayed()));
+
+        // Switch back to unlimited
+        mActivityRule.runOnUiThread(() -> {
+            mContentLimitingAdapter.setMaxItems(-1);
+        });
+        Thread.sleep(300);
+        onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message)).check(doesNotExist());
+    }
+
+    @Test
+    public void setMaxItem_toOne() throws Throwable {
+        onView(withId(R.id.list)).check(matches(isDisplayed()));
+
+        mActivityRule.runOnUiThread(() -> {
+            mContentLimitingAdapter.setMaxItems(1);
+        });
+        Thread.sleep(300);
+        onView(withText("Item 0")).check(matches(isDisplayed()));
+        onView(withText("Item 1")).check(doesNotExist());
+        onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message))
+                .check(matches(isDisplayed()));
+
+        // Switch back to unlimited
+        mActivityRule.runOnUiThread(() -> {
+            mContentLimitingAdapter.setMaxItems(-1);
+        });
+        Thread.sleep(300);
+        onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message)).check(doesNotExist());
+    }
+
+    @Test
+    public void setMaxItem_toZero() throws Throwable {
+        onView(withId(R.id.list)).check(matches(isDisplayed()));
+
+        mActivityRule.runOnUiThread(() -> {
+            mContentLimitingAdapter.setMaxItems(0);
+        });
+        Thread.sleep(300);
+        onView(withText("Item 0")).check(doesNotExist());
+        onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message))
+                .check(matches(isDisplayed()));
+
+        mActivityRule.runOnUiThread(() -> {
+            mContentLimitingAdapter.setMaxItems(-1);
+        });
+        Thread.sleep(300);
+        onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message)).check(doesNotExist());
+    }
+
+    @Test
+    public void setMaxItem_toHigherThanTotalItems() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mContentLimitingAdapter.setMaxItems(70);
+        });
+        Thread.sleep(300);
+        onView(withText("Item 0")).check(matches(isDisplayed()));
+        onView(withId(R.id.list)).perform(scrollToPosition(49));
+        onView(withText("Item 49")).check(matches(isDisplayed()));
+        onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message))
+                .check(doesNotExist());
+
+        // Switch back to unlimited
+        mActivityRule.runOnUiThread(() -> {
+            mContentLimitingAdapter.setMaxItems(-1);
+        });
+        Thread.sleep(300);
+        onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message)).check(doesNotExist());
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/RangeFilterImplTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/RangeFilterImplTest.java
new file mode 100644
index 0000000..ffb5c04
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/RangeFilterImplTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class RangeFilterImplTest {
+    private static final int EVEN_MAX_ITEMS = 10;
+    private static final int ODD_MAX_ITEMS = 9;
+    private static final int UNRESTRICTED_COUNT = 80;
+    private static final int UNRESTRICTED_SMALL_COUNT = 6;
+
+    @Mock
+    RecyclerView.Adapter mMockAdapter;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testRecompute_contentSizeSmallerThanRange_noLimit() {
+        RangeFilterImpl rangeFilter = new RangeFilterImpl(mMockAdapter, EVEN_MAX_ITEMS);
+        int pivotPosition = 0;
+        rangeFilter.recompute(UNRESTRICTED_SMALL_COUNT, pivotPosition);
+        verifyNoClamps(rangeFilter, UNRESTRICTED_SMALL_COUNT);
+    }
+
+    @Test
+    public void testRecompute_pivotPointInTheMiddleWithEvenMaxItems_limitContent() {
+        RangeFilterImpl rangeFilter = new RangeFilterImpl(mMockAdapter, EVEN_MAX_ITEMS);
+        int pivotPosition = 30;
+        rangeFilter.recompute(UNRESTRICTED_COUNT, pivotPosition);
+        verifyClampedBothEnds(rangeFilter, pivotPosition, EVEN_MAX_ITEMS);
+        rangeFilter.applyFilter();
+        verifyFilterBothEnds(mMockAdapter, pivotPosition, EVEN_MAX_ITEMS, UNRESTRICTED_COUNT);
+        rangeFilter.removeFilter();
+        verifyRemoveFilterBothEnds(mMockAdapter, pivotPosition, EVEN_MAX_ITEMS, UNRESTRICTED_COUNT);
+    }
+
+    @Test
+    public void testRecompute_pivotPointInTheMiddleWithOddMaxItems_limitContent() {
+        RangeFilterImpl rangeFilter = new RangeFilterImpl(mMockAdapter, ODD_MAX_ITEMS);
+        int pivotPosition = 30;
+        rangeFilter.recompute(UNRESTRICTED_COUNT, pivotPosition);
+        verifyClampedBothEnds(rangeFilter, pivotPosition, ODD_MAX_ITEMS);
+        rangeFilter.applyFilter();
+        verifyFilterBothEnds(mMockAdapter, pivotPosition, ODD_MAX_ITEMS, UNRESTRICTED_COUNT);
+        rangeFilter.removeFilter();
+        verifyRemoveFilterBothEnds(mMockAdapter, pivotPosition, ODD_MAX_ITEMS, UNRESTRICTED_COUNT);
+    }
+
+    private void verifyClampedBothEnds(
+            RangeFilterImpl rangeFilter, int pivotPoint, int maxItemCount) {
+        RangeFilterImpl.ListRange range = rangeFilter.getRange();
+        int firstHalfCount = maxItemCount / 2;
+        int secondHalfCount = maxItemCount - firstHalfCount;
+        assertThat(range.mClampedHead).isEqualTo(1);
+        assertThat(range.mClampedTail).isEqualTo(1);
+        assertThat(range.mStartIndex).isEqualTo(pivotPoint - firstHalfCount);
+        assertThat(range.mEndIndex).isEqualTo(pivotPoint + secondHalfCount);
+        assertThat(range.mEndIndex - range.mStartIndex).isEqualTo(maxItemCount);
+        assertThat(range.mLimitedCount).isEqualTo(maxItemCount + 2 /* two messages*/);
+
+        assertThat(rangeFilter.positionToIndex(0))
+                .isEqualTo(RangeFilter.INVALID_INDEX);
+        assertThat(rangeFilter.positionToIndex(1))
+                .isEqualTo(range.mStartIndex);
+
+        assertThat(rangeFilter.positionToIndex(maxItemCount))
+                .isEqualTo(range.mEndIndex - 1);
+        assertThat(rangeFilter.positionToIndex(maxItemCount + 1))
+                .isEqualTo(RangeFilter.INVALID_INDEX);
+
+        assertThat(rangeFilter.indexToPosition(range.mStartIndex))
+                .isEqualTo(1);
+
+        assertThat(rangeFilter.indexToPosition(range.mEndIndex - 1))
+                .isEqualTo(maxItemCount /* head message takes an additional position */);
+    }
+
+    private void verifyNoClamps(RangeFilterImpl rangeFilter, int unrestrictedCount) {
+        RangeFilterImpl.ListRange range = rangeFilter.getRange();
+        assertThat(range.mClampedHead).isEqualTo(0);
+        assertThat(range.mClampedTail).isEqualTo(0);
+        assertThat(range.mStartIndex).isEqualTo(0);
+        assertThat(range.mEndIndex).isEqualTo(unrestrictedCount);
+        assertThat(range.mEndIndex - range.mStartIndex).isEqualTo(unrestrictedCount);
+        assertThat(range.mLimitedCount).isEqualTo(unrestrictedCount);
+    }
+
+    private void verifyFilterBothEnds(
+            Adapter adapter,
+            int pivotPosition,
+            int maxCount,
+            int unrestrictedCount) {
+        int firstHalfRemainingItems = maxCount / 2;
+        int secondHalfRemainingItems = maxCount - firstHalfRemainingItems;
+        int clampedHeadItemCount = pivotPosition - firstHalfRemainingItems;
+        int clampedTailItemCount = unrestrictedCount - clampedHeadItemCount - maxCount;
+
+        verify(adapter).notifyItemInserted(unrestrictedCount);
+        verify(adapter).notifyItemRangeRemoved(
+                pivotPosition + secondHalfRemainingItems, clampedTailItemCount);
+        verify(adapter).notifyItemRangeRemoved(0, clampedHeadItemCount);
+        verify(adapter).notifyItemInserted(0);
+    }
+
+    private void verifyRemoveFilterBothEnds(
+            Adapter adapter,
+            int pivotPosition,
+            int maxCount,
+            int unrestrictedCount) {
+        int firstHalfRemainingItems = maxCount / 2;
+        int secondHalfRemainingItems = maxCount - firstHalfRemainingItems;
+        int clampedHeadItemCount = pivotPosition - firstHalfRemainingItems;
+        int clampedTailItemCount = unrestrictedCount - clampedHeadItemCount - maxCount;
+
+        verify(adapter).notifyItemRemoved(maxCount + 1);
+        verify(adapter).notifyItemRangeInserted(maxCount + 1, clampedTailItemCount);
+        verify(adapter).notifyItemRangeInserted(1, clampedHeadItemCount);
+        verify(adapter).notifyItemRemoved(0);
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestContentLimitingAdapter.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestContentLimitingAdapter.java
new file mode 100644
index 0000000..1af9a70
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestContentLimitingAdapter.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 androidx.annotation.NonNull;
+
+import com.android.car.ui.test.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestContentLimitingAdapter extends ContentLimitingAdapter<TestViewHolder> {
+
+    private final List<String> mItems;
+
+    public TestContentLimitingAdapter(int numItems) {
+        mItems = new ArrayList<>();
+        for (int i = 0; i < numItems; i++) {
+            mItems.add("Item " + i);
+        }
+    }
+
+    @Override
+    protected TestViewHolder onCreateViewHolderImpl(@NonNull ViewGroup parent,
+            int viewType) {
+        View layout = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.test_car_ui_recycler_view_list_item, parent, false);
+        return new TestViewHolder(layout);
+    }
+
+    @Override
+    protected void onBindViewHolderImpl(TestViewHolder holder, int position) {
+        holder.bind(mItems.get(position));
+    }
+
+    @Override
+    protected int getUnrestrictedItemCount() {
+        return mItems.size();
+    }
+
+    @Override
+    public int getConfigurationId() {
+        return 0;
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestViewHolder.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestViewHolder.java
new file mode 100644
index 0000000..d6b6899
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestViewHolder.java
@@ -0,0 +1,44 @@
+/*
+ * 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.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.test.R;
+
+public class TestViewHolder extends RecyclerView.ViewHolder {
+
+    private CharSequence mText;
+
+    TestViewHolder(@NonNull View itemView) {
+        super(itemView);
+    }
+
+    void bind(CharSequence text) {
+        mText = text;
+        TextView textView = itemView.requireViewById(R.id.textTitle);
+        textView.setText(text);
+    }
+
+    CharSequence getText() {
+        return mText;
+    }
+}
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/toolbar/ToolbarTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTest.java
similarity index 93%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/toolbar/ToolbarTest.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTest.java
index 9d5af2c..251092a 100644
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/toolbar/ToolbarTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTest.java
@@ -33,20 +33,17 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
 
 import static org.hamcrest.core.IsNot.not;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
-import android.app.Activity;
 import android.view.View;
 
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.car.ui.core.CarUi;
-import com.android.car.ui.tests.unit.R;
+import com.android.car.ui.test.R;
 
 import org.hamcrest.Matcher;
 import org.junit.Rule;
@@ -118,8 +115,7 @@
 
         onView(withId(R.id.car_ui_toolbar_nav_icon_container)).perform(click());
 
-        assertTrue(mActivityRule.getActivity().isFinishing());
-        assertEquals(mActivityRule.getActivityResult().getResultCode(), Activity.RESULT_CANCELED);
+        assertEquals(1, mActivityRule.getActivity().getTimesOnBackPressed());
     }
 
     @Test
@@ -131,8 +127,7 @@
 
         onView(withId(R.id.car_ui_toolbar_nav_icon_container)).perform(click());
 
-        assertTrue(mActivityRule.getActivity().isFinishing());
-        assertEquals(mActivityRule.getActivityResult().getResultCode(), Activity.RESULT_CANCELED);
+        assertEquals(1, mActivityRule.getActivity().getTimesOnBackPressed());
     }
 
     @Test
@@ -144,7 +139,7 @@
 
         onView(withId(R.id.car_ui_toolbar_nav_icon_container)).perform(click());
 
-        assertFalse(mActivityRule.getActivity().isFinishing());
+        assertEquals(0, mActivityRule.getActivity().getTimesOnBackPressed());
     }
 
     @Test
@@ -159,7 +154,7 @@
 
         onView(withId(R.id.car_ui_toolbar_nav_icon_container)).perform(click());
 
-        assertTrue(mActivityRule.getActivity().isFinishing());
+        assertEquals(1, mActivityRule.getActivity().getTimesOnBackPressed());
     }
 
     @Test
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/toolbar/ToolbarTestActivity.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTestActivity.java
similarity index 67%
rename from car-ui-lib/tests/unit/src/com/android/car/ui/toolbar/ToolbarTestActivity.java
rename to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTestActivity.java
index e54ac83..906305e 100644
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/toolbar/ToolbarTestActivity.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTestActivity.java
@@ -19,14 +19,30 @@
 import android.app.Activity;
 import android.os.Bundle;
 
-import com.android.car.ui.tests.unit.R;
+import com.android.car.ui.test.R;
 
 /** An Activity used for testing {@link ToolbarController}. */
 public class ToolbarTestActivity extends Activity {
 
+    private int mTimesOnBackPressed = 0;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.empty_test_activity);
     }
+
+    /**
+     * ag/12161937 changes the behavior of pressing back on the top level activity, so
+     * assert the number of times onBackPressed() is called instead of if the activity isFinishing()
+     */
+    public int getTimesOnBackPressed() {
+        return mTimesOnBackPressed;
+    }
+
+    @Override
+    public void onBackPressed() {
+        super.onBackPressed();
+        mTimesOnBackPressed++;
+    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTest.java
new file mode 100644
index 0000000..3cc9b7e
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTest.java
@@ -0,0 +1,485 @@
+/*
+ * 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.utils;
+
+import static android.view.View.GONE;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
+import static com.android.car.ui.utils.ViewUtils.DEFAULT_FOCUS;
+import static com.android.car.ui.utils.ViewUtils.FOCUSED_BY_DEFAULT;
+import static com.android.car.ui.utils.ViewUtils.IMPLICIT_DEFAULT_FOCUS;
+import static com.android.car.ui.utils.ViewUtils.NO_FOCUS;
+import static com.android.car.ui.utils.ViewUtils.REGULAR_FOCUS;
+import static com.android.car.ui.utils.ViewUtils.SCROLLABLE_CONTAINER_FOCUS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.FocusArea;
+import com.android.car.ui.FocusParkingView;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+import com.android.car.ui.recyclerview.TestContentLimitingAdapter;
+import com.android.car.ui.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Unit tests for {@link ViewUtils}. */
+public class ViewUtilsTest {
+
+    @Rule
+    public ActivityTestRule<ViewUtilsTestActivity> mActivityRule =
+            new ActivityTestRule<>(ViewUtilsTestActivity.class);
+
+    private ViewUtilsTestActivity mActivity;
+    private FocusArea mFocusArea1;
+    private FocusArea mFocusArea2;
+    private FocusArea mFocusArea3;
+    private FocusArea mFocusArea4;
+    private FocusArea mFocusArea5;
+    private FocusParkingView mFpv;
+    private View mView2;
+    private View mFocusedByDefault3;
+    private View mView4;
+    private View mDefaultFocus4;
+    private CarUiRecyclerView mList5;
+    private View mRoot;
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+        mFocusArea1 = mActivity.findViewById(R.id.focus_area1);
+        mFocusArea2 = mActivity.findViewById(R.id.focus_area2);
+        mFocusArea3 = mActivity.findViewById(R.id.focus_area3);
+        mFocusArea4 = mActivity.findViewById(R.id.focus_area4);
+        mFocusArea5 = mActivity.findViewById(R.id.focus_area5);
+        mFpv = mActivity.findViewById(R.id.fpv);
+        mView2 = mActivity.findViewById(R.id.view2);
+        mFocusedByDefault3 = mActivity.findViewById(R.id.focused_by_default3);
+        mView4 = mActivity.findViewById(R.id.view4);
+        mDefaultFocus4 = mActivity.findViewById(R.id.default_focus4);
+        mList5 = mActivity.findViewById(R.id.list5);
+        mRoot = mFocusArea1.getRootView();
+
+        mRoot.post(() -> {
+            mList5.setLayoutManager(new LinearLayoutManager(mActivity));
+            mList5.setAdapter(new TestContentLimitingAdapter(/* numItems= */ 2));
+            CarUiUtils.setRotaryScrollEnabled(mList5, /* isVertical= */ true);
+        });
+    }
+
+    @Test
+    public void testRootVisible() {
+        mRoot.post(() -> assertThat(mRoot.getVisibility()).isEqualTo(VISIBLE));
+    }
+
+    @Test
+    public void testGetAncestorFocusArea() {
+        mRoot.post(() -> assertThat(ViewUtils.getAncestorFocusArea(mView2)).isEqualTo(mFocusArea2));
+    }
+
+    @Test
+    public void testGetAncestorFocusArea_doesNotReturnItself() {
+        mRoot.post(() -> assertThat(ViewUtils.getAncestorFocusArea(mFocusArea2)).isNull());
+    }
+
+    @Test
+    public void testGetAncestorFocusArea_outsideFocusArea() {
+        mRoot.post(() -> assertThat(ViewUtils.getAncestorFocusArea(mFpv)).isNull());
+    }
+
+    @Test
+    public void testGetAncestorScrollableContainer() {
+        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+                        assertThat(ViewUtils.getAncestorScrollableContainer(firstItem))
+                                .isEqualTo(mList5);
+                    }
+                }));
+    }
+
+    @Test
+    public void testGetAncestorScrollableContainer_returnNull() {
+        mRoot.post(() -> assertThat(ViewUtils.getAncestorScrollableContainer(mView2)).isNull());
+    }
+
+    @Test
+    public void testFindFocusedByDefaultView() {
+        mRoot.post(() -> {
+            View focusedByDefault = ViewUtils.findFocusedByDefaultView(mRoot);
+            assertThat(focusedByDefault).isEqualTo(mFocusedByDefault3);
+        });
+    }
+
+    @Test
+    public void testFindFocusedByDefaultView_skipNotFocusable() {
+        mRoot.post(() -> {
+            mFocusedByDefault3.setFocusable(false);
+            View focusedByDefault = ViewUtils.findFocusedByDefaultView(mRoot);
+            assertThat(focusedByDefault).isNull();
+        });
+    }
+
+    @Test
+    public void testFindFocusedByDefaultView_skipInvisibleView() {
+        mRoot.post(() -> {
+            mFocusArea3.setVisibility(INVISIBLE);
+            assertThat(mFocusArea3.getVisibility()).isEqualTo(INVISIBLE);
+            View focusedByDefault = ViewUtils.findFocusedByDefaultView(mRoot);
+            assertThat(focusedByDefault).isNull();
+        });
+    }
+
+    @Test
+    public void testFindFocusedByDefaultView_skipInvisibleAncestor() {
+        mRoot.post(() -> {
+            mRoot.setVisibility(INVISIBLE);
+            View focusedByDefault = ViewUtils.findFocusedByDefaultView(mFocusArea3);
+            assertThat(focusedByDefault).isNull();
+        });
+    }
+
+    @Test
+    public void testFindImplicitDefaultFocusView_inRoot() {
+        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+                        View implicitDefaultFocus = ViewUtils.findImplicitDefaultFocusView(mRoot);
+                        assertThat(implicitDefaultFocus).isEqualTo(firstItem);
+                    }
+                }));
+    }
+
+    @Test
+    public void testFindImplicitDefaultFocusView_inFocusArea() {
+        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+                        View implicitDefaultFocus =
+                                ViewUtils.findImplicitDefaultFocusView(mFocusArea5);
+                        assertThat(implicitDefaultFocus).isEqualTo(firstItem);
+                    }
+                }));
+    }
+
+    @Test
+    public void testFindImplicitDefaultFocusView_skipInvisibleAncestor() {
+        mRoot.post(() -> {
+            mRoot.setVisibility(INVISIBLE);
+            View implicitDefaultFocus = ViewUtils.findImplicitDefaultFocusView(mFocusArea5);
+            assertThat(implicitDefaultFocus).isNull();
+        });
+    }
+
+    @Test
+    public void testFindFirstFocusableDescendant() {
+        mRoot.post(() -> {
+            mFocusArea2.setFocusable(true);
+            View firstFocusable = ViewUtils.findFirstFocusableDescendant(mRoot);
+            assertThat(firstFocusable).isEqualTo(mFocusArea2);
+        });
+    }
+
+    @Test
+    public void testFindFirstFocusableDescendant_skipItself() {
+        mRoot.post(() -> {
+            mFocusArea2.setFocusable(true);
+            View firstFocusable = ViewUtils.findFirstFocusableDescendant(mFocusArea2);
+            assertThat(firstFocusable).isEqualTo(mView2);
+        });
+    }
+
+    @Test
+    public void testFindFirstFocusableDescendant_skipInvisibleAndGoneView() {
+        mRoot.post(() -> {
+            mFocusArea2.setVisibility(INVISIBLE);
+            mFocusArea3.setVisibility(GONE);
+            View firstFocusable = ViewUtils.findFirstFocusableDescendant(mRoot);
+            assertThat(firstFocusable).isEqualTo(mView4);
+        });
+    }
+
+    @Test
+    public void testFindFirstFocusableDescendant_skipInvisibleAncestor() {
+        mRoot.post(() -> {
+            mRoot.setVisibility(INVISIBLE);
+            View firstFocusable = ViewUtils.findFirstFocusableDescendant(mFocusArea2);
+            assertThat(firstFocusable).isNull();
+        });
+    }
+
+    @Test
+    public void testIsImplicitDefaultFocusView_firstItem() {
+        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+                        assertThat(ViewUtils.isImplicitDefaultFocusView(firstItem)).isTrue();
+                    }
+                }));
+    }
+
+    @Test
+    public void testIsImplicitDefaultFocusView_secondItem() {
+        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        View secondItem = mList5.getLayoutManager().findViewByPosition(1);
+                        assertThat(ViewUtils.isImplicitDefaultFocusView(secondItem)).isFalse();
+                    }
+                }));
+    }
+
+    @Test
+    public void testIsImplicitDefaultFocusView_normalView() {
+        mRoot.post(() -> assertThat(ViewUtils.isImplicitDefaultFocusView(mView2)).isFalse());
+    }
+
+    @Test
+    public void testIsImplicitDefaultFocusView_skipInvisibleAncestor() {
+        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        mFocusArea5.setVisibility(INVISIBLE);
+                        View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+                        assertThat(ViewUtils.isImplicitDefaultFocusView(firstItem)).isFalse();
+                    }
+                }));
+    }
+
+    @Test
+    public void testRequestFocus() {
+        mRoot.post(() -> assertRequestFocus(mView2, true));
+    }
+
+    @Test
+    public void testRequestFocus_nullView() {
+        mRoot.post(() -> assertRequestFocus(null, false));
+    }
+
+    @Test
+    public void testRequestFocus_alreadyFocused() {
+        mRoot.post(() -> {
+            assertRequestFocus(mView2, true);
+            // mView2 is already focused before requesting focus.
+            assertRequestFocus(mView2, true);
+        });
+    }
+
+    @Test
+    public void testRequestFocus_notFocusable() {
+        mRoot.post(() -> {
+            mView2.setFocusable(false);
+            assertRequestFocus(mView2, false);
+        });
+    }
+
+    @Test
+    public void testRequestFocus_disabled() {
+        mRoot.post(() -> {
+            mView2.setEnabled(false);
+            assertRequestFocus(mView2, false);
+        });
+    }
+
+    @Test
+    public void testRequestFocus_notVisible() {
+        mRoot.post(() -> {
+            mView2.setVisibility(View.INVISIBLE);
+            assertRequestFocus(mView2, false);
+        });
+    }
+
+    @Test
+    public void testRequestFocus_skipInvisibleAncestor() {
+        mRoot.post(() -> {
+            mFocusArea2.setVisibility(View.INVISIBLE);
+            assertRequestFocus(mView2, false);
+        });
+    }
+
+    @Test
+    public void testRequestFocus_zeroWidth() {
+        mRoot.post(() -> {
+            mView2.setRight(mView2.getLeft());
+            assertThat(mView2.getWidth()).isEqualTo(0);
+            assertRequestFocus(mView2, false);
+        });
+    }
+
+    @Test
+    public void testRequestFocus_detachedFromWindow() {
+        mRoot.post(() -> {
+            mFocusArea2.removeView(mView2);
+            assertRequestFocus(mView2, false);
+        });
+    }
+
+    @Test
+    public void testRequestFocus_FocusParkingView() {
+        mRoot.post(() -> {
+            assertRequestFocus(mView2, true);
+            assertRequestFocus(mFpv, false);
+        });
+    }
+
+    @Test
+    public void testRequestFocus_rotaryContainer() {
+        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        assertRequestFocus(mList5, false);
+                    }
+                }));
+    }
+
+    @Test
+    public void testRequestFocus_scrollableContainer() {
+        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        assertRequestFocus(mList5, false);
+                    }
+                }));
+    }
+
+    @Test
+    public void testAdjustFocus_inRoot() {
+        mRoot.post(() -> {
+            assertRequestFocus(mView2, true);
+            ViewUtils.adjustFocus(mRoot, null);
+            assertThat(mFocusedByDefault3.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testAdjustFocus_inFocusAreaWithDefaultFocus() {
+        mRoot.post(() -> {
+            assertRequestFocus(mView2, true);
+            ViewUtils.adjustFocus(mFocusArea3, null);
+            assertThat(mFocusedByDefault3.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testAdjustFocus_inFocusAreaWithoutDefaultFocus() {
+        mRoot.post(() -> {
+            assertRequestFocus(mView4, true);
+            ViewUtils.adjustFocus(mFocusArea2, null);
+            assertThat(mView2.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testAdjustFocus_inFocusAreaWithoutFocusableDescendant() {
+        mRoot.post(() -> {
+            assertRequestFocus(mView2, true);
+            boolean success = ViewUtils.adjustFocus(mFocusArea1, null);
+            assertThat(mFocusArea1.hasFocus()).isFalse();
+            assertThat(success).isFalse();
+        });
+    }
+
+    @Test
+    public void testAdjustFocus_differentFocusLevels() {
+        mRoot.post(() -> {
+            assertThat(ViewUtils.adjustFocus(mFocusArea2, SCROLLABLE_CONTAINER_FOCUS)).isTrue();
+            assertThat(ViewUtils.adjustFocus(mFocusArea2, REGULAR_FOCUS)).isFalse();
+
+            assertThat(ViewUtils.adjustFocus(mFocusArea5, REGULAR_FOCUS)).isTrue();
+            assertThat(ViewUtils.adjustFocus(mFocusArea5, IMPLICIT_DEFAULT_FOCUS)).isFalse();
+
+            assertThat(ViewUtils.adjustFocus(mFocusArea4, IMPLICIT_DEFAULT_FOCUS)).isTrue();
+            assertThat(ViewUtils.adjustFocus(mFocusArea4, DEFAULT_FOCUS)).isFalse();
+
+            assertThat(ViewUtils.adjustFocus(mFocusArea3, DEFAULT_FOCUS)).isTrue();
+            assertThat(ViewUtils.adjustFocus(mFocusArea3, FOCUSED_BY_DEFAULT)).isFalse();
+
+            View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+            firstItem.setFocusable(false);
+            View secondItem = mList5.getLayoutManager().findViewByPosition(1);
+            secondItem.setFocusable(false);
+            assertThat(ViewUtils.adjustFocus(mFocusArea5, NO_FOCUS)).isTrue();
+            assertThat(ViewUtils.adjustFocus(mFocusArea5, SCROLLABLE_CONTAINER_FOCUS)).isFalse();
+        });
+    }
+
+    @Test
+    public void testGetFocusLevel() {
+        mRoot.post(() -> {
+            assertThat(ViewUtils.getFocusLevel(null)).isEqualTo(NO_FOCUS);
+            assertThat(ViewUtils.getFocusLevel(mFpv)).isEqualTo(NO_FOCUS);
+            mFocusArea2.setVisibility(INVISIBLE);
+            assertThat(ViewUtils.getFocusLevel(mView2)).isEqualTo(NO_FOCUS);
+
+            assertThat(ViewUtils.getFocusLevel(mList5)).isEqualTo(SCROLLABLE_CONTAINER_FOCUS);
+
+            assertThat(ViewUtils.getFocusLevel(mView4)).isEqualTo(REGULAR_FOCUS);
+
+            mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+                    new ViewTreeObserver.OnGlobalLayoutListener() {
+                        @Override
+                        public void onGlobalLayout() {
+                            mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                            View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+                            assertThat(ViewUtils.getFocusLevel(firstItem))
+                                    .isEqualTo(IMPLICIT_DEFAULT_FOCUS);
+                        }
+                    }));
+
+            assertThat(ViewUtils.getFocusLevel(mDefaultFocus4)).isEqualTo(DEFAULT_FOCUS);
+
+            assertThat(ViewUtils.getFocusLevel(mFocusedByDefault3)).isEqualTo(FOCUSED_BY_DEFAULT);
+        });
+    }
+
+    private static void assertRequestFocus(@Nullable View view, boolean focused) {
+        boolean result = ViewUtils.requestFocus(view);
+        assertThat(result).isEqualTo(focused);
+        if (view != null) {
+            assertThat(view.isFocused()).isEqualTo(focused);
+        }
+    }
+}
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/FocusAreaTestActivity.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTestActivity.java
similarity index 76%
copy from car-ui-lib/tests/unit/src/com/android/car/ui/FocusAreaTestActivity.java
copy to car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTestActivity.java
index f027db6..87434d4 100644
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/FocusAreaTestActivity.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTestActivity.java
@@ -14,19 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.car.ui;
+package com.android.car.ui.utils;
 
 import android.app.Activity;
 import android.os.Bundle;
 
-import com.android.car.ui.tests.unit.R;
+import com.android.car.ui.test.R;
 
-/** An activity used for testing {@link FocusArea}. */
-public class FocusAreaTestActivity extends Activity {
+/** An activity used for testing {@link ViewUtils}. */
+public class ViewUtilsTestActivity extends Activity {
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.focus_area_test_activity);
+        setContentView(R.layout.view_utils_test_activity);
     }
 }
diff --git a/car-ui-lib/tests/paintbooth/res/drawable/ic_launcher.png b/car-ui-lib/car-ui-lib/src/androidTest/res/drawable/ic_launcher.png
similarity index 100%
copy from car-ui-lib/tests/paintbooth/res/drawable/ic_launcher.png
copy to car-ui-lib/car-ui-lib/src/androidTest/res/drawable/ic_launcher.png
Binary files differ
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/drawable/ic_settings_gear.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/drawable/ic_settings_gear.xml
new file mode 100644
index 0000000..2b7fa2f
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/drawable/ic_settings_gear.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+-->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportHeight="48.0"
+    android:viewportWidth="48.0">
+    <path
+        android:fillColor="#fff"
+        android:pathData="M38.86,25.95c0.08,-0.64 0.14,-1.29 0.14,-1.95s-0.06,-1.31 -0.14,-1.95l4.23,-3.31c0.38,-0.3 0.49,-0.84 0.24,-1.28l-4,-6.93c-0.25,-0.43 -0.77,-0.61 -1.22,-0.43l-4.98,2.01c-1.03,-0.79 -2.16,-1.46 -3.38,-1.97L29,4.84c-0.09,-0.47 -0.5,-0.84 -1,-0.84h-8c-0.5,0 -0.91,0.37 -0.99,0.84l-0.75,5.3c-1.22,0.51 -2.35,1.17 -3.38,1.97L9.9,10.1c-0.45,-0.17 -0.97,0 -1.22,0.43l-4,6.93c-0.25,0.43 -0.14,0.97 0.24,1.28l4.22,3.31C9.06,22.69 9,23.34 9,24s0.06,1.31 0.14,1.95l-4.22,3.31c-0.38,0.3 -0.49,0.84 -0.24,1.28l4,6.93c0.25,0.43 0.77,0.61 1.22,0.43l4.98,-2.01c1.03,0.79 2.16,1.46 3.38,1.97l0.75,5.3c0.08,0.47 0.49,0.84 0.99,0.84h8c0.5,0 0.91,-0.37 0.99,-0.84l0.75,-5.3c1.22,-0.51 2.35,-1.17 3.38,-1.97l4.98,2.01c0.45,0.17 0.97,0 1.22,-0.43l4,-6.93c0.25,-0.43 0.14,-0.97 -0.24,-1.28l-4.22,-3.31zM24,31c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
+</vector>
diff --git a/car-ui-lib/tests/unit/res/layout/car_ui_recycler_view_gone_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/car_ui_recycler_view_gone_test_activity.xml
similarity index 100%
rename from car-ui-lib/tests/unit/res/layout/car_ui_recycler_view_gone_test_activity.xml
rename to car-ui-lib/car-ui-lib/src/androidTest/res/layout/car_ui_recycler_view_gone_test_activity.xml
diff --git a/car-ui-lib/tests/unit/res/layout/car_ui_recycler_view_invisible_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/car_ui_recycler_view_invisible_test_activity.xml
similarity index 100%
rename from car-ui-lib/tests/unit/res/layout/car_ui_recycler_view_invisible_test_activity.xml
rename to car-ui-lib/car-ui-lib/src/androidTest/res/layout/car_ui_recycler_view_invisible_test_activity.xml
diff --git a/car-ui-lib/tests/unit/res/layout/car_ui_recycler_view_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/car_ui_recycler_view_test_activity.xml
similarity index 100%
rename from car-ui-lib/tests/unit/res/layout/car_ui_recycler_view_test_activity.xml
rename to car-ui-lib/car-ui-lib/src/androidTest/res/layout/car_ui_recycler_view_test_activity.xml
diff --git a/car-ui-lib/tests/robotests/AndroidManifest.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/details_preference_widget.xml
similarity index 67%
copy from car-ui-lib/tests/robotests/AndroidManifest.xml
copy to car-ui-lib/car-ui-lib/src/androidTest/res/layout/details_preference_widget.xml
index b62579f..3912de1 100644
--- a/car-ui-lib/tests/robotests/AndroidManifest.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/details_preference_widget.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2019 Google Inc.
+    Copyright 2020 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -14,6 +14,10 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.car.ui">
-</manifest>
+
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:src="@drawable/ic_settings_gear"/>
diff --git a/car-ui-lib/tests/unit/res/layout/empty_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/empty_test_activity.xml
similarity index 100%
rename from car-ui-lib/tests/unit/res/layout/empty_test_activity.xml
rename to car-ui-lib/car-ui-lib/src/androidTest/res/layout/empty_test_activity.xml
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_area_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_area_test_activity.xml
new file mode 100644
index 0000000..da1255d
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_area_test_activity.xml
@@ -0,0 +1,98 @@
+<?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.
+  -->
+<LinearLayout
+    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:orientation="vertical">
+    <com.android.car.ui.FocusParkingView
+        android:id="@+id/fpv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+    <com.android.car.ui.TestFocusArea
+        android:id="@+id/focus_area1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:startBoundOffset="10dp"
+        app:endBoundOffset="20dp"
+        app:topBoundOffset="30dp"
+        app:bottomBoundOffset="40dp">
+        <View
+            android:id="@+id/view1"
+            android:focusable="true"
+            android:layout_width="100dp"
+            android:layout_height="100dp"/>
+        <Button
+            android:id="@+id/button1"
+            android:layout_width="100dp"
+            android:layout_height="100dp"/>
+    </com.android.car.ui.TestFocusArea>
+    <com.android.car.ui.TestFocusArea
+        android:id="@+id/focus_area2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:defaultFocus="@+id/default_focus2"
+        app:highlightPaddingHorizontal="10dp"
+        app:highlightPaddingVertical="20dp"
+        app:highlightPaddingStart="30dp"
+        app:highlightPaddingTop="40dp"
+        app:startBoundOffset="50dp">
+        <View
+            android:id="@+id/view2"
+            android:focusable="true"
+            android:layout_width="100dp"
+            android:layout_height="100dp"/>
+        <View
+            android:id="@+id/default_focus2"
+            android:focusable="true"
+            android:layout_width="100dp"
+            android:layout_height="100dp"/>
+    </com.android.car.ui.TestFocusArea>
+    <com.android.car.ui.TestFocusArea
+        android:id="@+id/focus_area3"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:nudgeShortcut="@+id/nudge_shortcut3"
+        app:nudgeShortcutDirection="right">
+        <View
+            android:id="@+id/view3"
+            android:focusable="true"
+            android:layout_width="100dp"
+            android:layout_height="100dp"/>
+        <View
+            android:focusable="true"
+            android:layout_width="100dp"
+            android:layout_height="100dp"/>
+        <View
+            android:id="@+id/nudge_shortcut3"
+            android:focusable="true"
+            android:layout_width="100dp"
+            android:layout_height="100dp"/>
+    </com.android.car.ui.TestFocusArea>
+    <com.android.car.ui.TestFocusArea
+        android:id="@+id/focus_area4"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:nudgeLeft="@+id/focus_area2">
+        <View
+            android:id="@+id/view4"
+            android:focusable="true"
+            android:layout_width="100dp"
+            android:layout_height="100dp"/>
+    </com.android.car.ui.TestFocusArea>
+</LinearLayout>
diff --git a/car-ui-lib/tests/unit/res/layout/focus_area_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml
similarity index 62%
rename from car-ui-lib/tests/unit/res/layout/focus_area_test_activity.xml
rename to car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml
index 405bbfe..02d7326 100644
--- a/car-ui-lib/tests/unit/res/layout/focus_area_test_activity.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml
@@ -18,19 +18,28 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
-    <com.android.car.ui.TestFocusArea
-        android:id="@+id/focus_area"
+    <com.android.car.ui.FocusParkingView
+        android:id="@+id/fpv"
+        android:layout_width="10dp"
+        android:layout_height="10dp"/>
+    <LinearLayout
+        android:id="@+id/parent1"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content">
         <View
-            android:id="@+id/child"
-            android:focusable="true"
-            android:layout_width="10dp"
-            android:layout_height="10dp"/>
-    </com.android.car.ui.TestFocusArea>
+            android:id="@+id/view1"
+            android:layout_width="100dp"
+            android:layout_height="40dp"
+            android:focusable="true"/>
+    </LinearLayout>
     <View
-        android:id="@+id/non_child"
+        android:id="@+id/focused_by_default"
+        android:layout_width="100dp"
+        android:layout_height="40dp"
         android:focusable="true"
-        android:layout_width="10dp"
-        android:layout_height="10dp"/>
+        android:focusedByDefault="true"/>
+    <com.android.car.ui.recyclerview.CarUiRecyclerView
+        android:id="@+id/list"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
 </LinearLayout>
diff --git a/car-ui-lib/tests/unit/res/layout/test_list_item.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_car_ui_recycler_view_list_item.xml
similarity index 79%
copy from car-ui-lib/tests/unit/res/layout/test_list_item.xml
copy to car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_car_ui_recycler_view_list_item.xml
index e789145..8297f6b 100644
--- a/car-ui-lib/tests/unit/res/layout/test_list_item.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_car_ui_recycler_view_list_item.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2020 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.
@@ -12,20 +12,23 @@
   ~ distributed under the License is distributed on an "AS IS" BASIS,
   ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+  ~ limitations under the License
   -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="horizontal">
+    android:orientation="horizontal"
+    android:focusable="true">
 
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="10dp"
         android:layout_marginBottom="10dp"
-        android:textAppearance="@style/TextAppearance.CarUi.ListItem.Body"
-        android:id="@+id/text" />
+        android:id="@+id/textTitle"
+        android:text="TESTING"/>
 
 </LinearLayout>
+
+
diff --git a/car-ui-lib/tests/unit/res/layout/test_list_item.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_list_item.xml
similarity index 94%
rename from car-ui-lib/tests/unit/res/layout/test_list_item.xml
rename to car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_list_item.xml
index e789145..1fab70e 100644
--- a/car-ui-lib/tests/unit/res/layout/test_list_item.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_list_item.xml
@@ -18,7 +18,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="horizontal">
+    android:orientation="horizontal"
+    android:focusable="true">
 
     <TextView
         android:layout_width="wrap_content"
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/view_utils_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/view_utils_test_activity.xml
new file mode 100644
index 0000000..72c2366
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/view_utils_test_activity.xml
@@ -0,0 +1,92 @@
+<?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.
+  -->
+<LinearLayout
+    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">
+    <com.android.car.ui.FocusParkingView
+        android:id="@+id/fpv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+    <com.android.car.ui.FocusArea
+        android:id="@+id/focus_area1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <View
+            android:layout_width="100dp"
+            android:layout_height="100dp"/>
+    </com.android.car.ui.FocusArea>
+    <com.android.car.ui.FocusArea
+        android:id="@+id/focus_area2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <View
+            android:layout_width="100dp"
+            android:layout_height="100dp"/>
+        <View
+            android:id="@+id/view2"
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:focusable="true"/>
+    </com.android.car.ui.FocusArea>
+    <com.android.car.ui.FocusArea
+        android:id="@+id/focus_area3"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <View
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:focusable="true"/>
+        <View
+            android:id="@+id/focused_by_default3"
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:focusable="true"
+            android:focusedByDefault="true"/>
+    </com.android.car.ui.FocusArea>
+    <com.android.car.ui.FocusArea
+        android:id="@+id/focus_area4"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:defaultFocus="@+id/default_focus4">
+        <View
+            android:id="@+id/view4"
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:focusable="true"/>
+        <View
+            android:id="@+id/default_focus4"
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:focusable="true"/>
+    </com.android.car.ui.FocusArea>
+    <com.android.car.ui.FocusArea
+        android:id="@+id/focus_area5"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <com.android.car.ui.recyclerview.CarUiRecyclerView
+            android:id="@+id/list5"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+    </com.android.car.ui.FocusArea>
+</LinearLayout>
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/values/arrays.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/values/arrays.xml
new file mode 100644
index 0000000..9606ddd
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/values/arrays.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<resources>
+
+    <!-- Used for the entries in a list preference [CHAR_LIMIT=NONE]-->
+    <string-array name="entries">
+        <!-- The first sample choice [CHAR_LIMIT=NONE]-->
+        <item>Choose me!</item>
+        <!-- The second sample choice [CHAR_LIMIT=NONE]-->
+        <item>No, me!</item>
+        <!-- The third sample choice [CHAR_LIMIT=NONE]-->
+        <item>What about me?!</item>
+    </string-array>
+
+    <!-- Used for the values in a list preference -->
+    <string-array name="entry_values" translatable="false">
+        <!-- The first sample value -->
+        <item>alpha</item>
+        <!-- The second sample value -->
+        <item>beta</item>
+        <!-- The third sample value -->
+        <item>charlie</item>
+    </string-array>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/values/strings.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/values/strings.xml
new file mode 100644
index 0000000..7926d9c
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/values/strings.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <string name="preferences_screen_title">Preferences</string>
+    <string name="preference_dialog_category">Dialog preferences</string>
+    <string name="title_list_preference">List preference</string>
+    <string name="dialog_title_list_preference">Choose one option!</string>
+    <string name="title_multi_list_preference">Multi-select list preference</string>
+    <string name="summary_multi_list_preference">Shows a dialog with multiple choice options</string>
+    <string name="dialog_title_multi_list_preference">Choose some options!</string>
+    <string name="preference_widgets_category">Widgets</string>
+    <string name="title_checkbox_preference">Checkbox preference</string>
+    <string name="summary_checkbox_preference">Tap anywhere in this preference to toggle state</string>
+    <string name="title_switch_preference">Switch preference</string>
+    <string name="summary_switch_preference">Tap anywhere in this preference to toggle state</string>
+    <string name="title_dropdown_preference">Dropdown preference</string>
+    <string name="title_twoaction_preference">TwoAction preference</string>
+    <string name="summary_twoaction_preference">A widget should be visible on the right</string>
+
+    <string-array name="test_string_array">
+        <item>Item 1</item>
+        <item>Item 2</item>
+        <item>Item 3</item>
+    </string-array>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/xml/test_preferences.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/xml/test_preferences.xml
new file mode 100644
index 0000000..042bbb0
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/xml/test_preferences.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    app:title="@string/preferences_screen_title">
+
+    <PreferenceCategory
+        android:title="@string/preference_dialog_category"
+        android:order="1">
+
+        <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"/>
+
+        <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"/>
+    </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/AlertDialogBuilder.java
similarity index 93%
rename from car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/AlertDialogBuilder.java
index a025e9e..803bb29 100644
--- a/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/AlertDialogBuilder.java
@@ -60,6 +60,8 @@
     private CharSequence mSubtitle;
     private Drawable mIcon;
     private boolean mIconTinted;
+    private boolean mAllowDismissButton = true;
+    private boolean mHasSingleChoiceBodyButton = false;
 
     public AlertDialogBuilder(Context context) {
         // Resource id specified as 0 uses the parent contexts resolved value for alertDialogTheme.
@@ -329,6 +331,7 @@
     public AlertDialogBuilder setItems(@ArrayRes int itemsId,
             final DialogInterface.OnClickListener listener) {
         mBuilder.setItems(itemsId, listener);
+        mHasSingleChoiceBodyButton = true;
         return this;
     }
 
@@ -341,6 +344,7 @@
     public AlertDialogBuilder setItems(CharSequence[] items,
             final DialogInterface.OnClickListener listener) {
         mBuilder.setItems(items, listener);
+        mHasSingleChoiceBodyButton = true;
         return this;
     }
 
@@ -353,6 +357,7 @@
     public AlertDialogBuilder setAdapter(final ListAdapter adapter,
             final DialogInterface.OnClickListener listener) {
         mBuilder.setAdapter(adapter, listener);
+        mHasSingleChoiceBodyButton = true;
         return this;
     }
 
@@ -363,6 +368,7 @@
      */
     public AlertDialogBuilder setAdapter(final CarUiListItemAdapter adapter) {
         setCustomList(adapter);
+        mHasSingleChoiceBodyButton = true;
         return this;
     }
 
@@ -390,6 +396,7 @@
             final DialogInterface.OnClickListener listener,
             String labelColumn) {
         mBuilder.setCursor(cursor, listener, labelColumn);
+        mHasSingleChoiceBodyButton = true;
         return this;
     }
 
@@ -413,6 +420,7 @@
     public AlertDialogBuilder setMultiChoiceItems(@ArrayRes int itemsId, boolean[] checkedItems,
             final DialogInterface.OnMultiChoiceClickListener listener) {
         mBuilder.setMultiChoiceItems(itemsId, checkedItems, listener);
+        mHasSingleChoiceBodyButton = false;
         return this;
     }
 
@@ -435,6 +443,7 @@
     public AlertDialogBuilder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems,
             final DialogInterface.OnMultiChoiceClickListener listener) {
         mBuilder.setMultiChoiceItems(items, checkedItems, listener);
+        mHasSingleChoiceBodyButton = false;
         return this;
     }
 
@@ -460,6 +469,7 @@
             String labelColumn,
             final DialogInterface.OnMultiChoiceClickListener listener) {
         mBuilder.setMultiChoiceItems(cursor, isCheckedColumn, labelColumn, listener);
+        mHasSingleChoiceBodyButton = true;
         return this;
     }
 
@@ -480,6 +490,7 @@
     public AlertDialogBuilder setSingleChoiceItems(@ArrayRes int itemsId, int checkedItem,
             final DialogInterface.OnClickListener listener) {
         mBuilder.setSingleChoiceItems(itemsId, checkedItem, listener);
+        mHasSingleChoiceBodyButton = true;
         return this;
     }
 
@@ -502,6 +513,7 @@
             String labelColumn,
             final DialogInterface.OnClickListener listener) {
         mBuilder.setSingleChoiceItems(cursor, checkedItem, labelColumn, listener);
+        mHasSingleChoiceBodyButton = true;
         return this;
     }
 
@@ -521,6 +533,7 @@
     public AlertDialogBuilder setSingleChoiceItems(CharSequence[] items, int checkedItem,
             final DialogInterface.OnClickListener listener) {
         mBuilder.setSingleChoiceItems(items, checkedItem, listener);
+        mHasSingleChoiceBodyButton = true;
         return this;
     }
 
@@ -534,6 +547,7 @@
     public AlertDialogBuilder setSingleChoiceItems(ListAdapter adapter, int checkedItem,
             final DialogInterface.OnClickListener listener) {
         mBuilder.setSingleChoiceItems(adapter, checkedItem, listener);
+        mHasSingleChoiceBodyButton = true;
         return this;
     }
 
@@ -555,6 +569,7 @@
     public AlertDialogBuilder setSingleChoiceItems(CarUiRadioButtonListItemAdapter adapter,
             final DialogInterface.OnClickListener listener) {
         setCustomList(adapter);
+        mHasSingleChoiceBodyButton = false;
         return this;
     }
 
@@ -570,6 +585,7 @@
      */
     public AlertDialogBuilder setSingleChoiceItems(CarUiRadioButtonListItemAdapter adapter) {
         setCustomList(adapter);
+        mHasSingleChoiceBodyButton = false;
         return this;
     }
 
@@ -583,6 +599,7 @@
     public AlertDialogBuilder setOnItemSelectedListener(
             final AdapterView.OnItemSelectedListener listener) {
         mBuilder.setOnItemSelectedListener(listener);
+        mHasSingleChoiceBodyButton = true;
         return this;
     }
 
@@ -635,6 +652,22 @@
         return setEditBox(prompt, textChangedListener, inputFilters, 0);
     }
 
+    /**
+     * By default, the AlertDialogBuilder may add a "Dismiss" button if you don't provide
+     * a positive/negative/neutral button. This is so that the dialog is still dismissible
+     * using the rotary controller. If however, you add buttons that can close the dialog via
+     * {@link #setAdapter(CarUiListItemAdapter)} or a similar method, then you may wish to
+     * suppress the addition of the dismiss button, which this method allows for.
+     *
+     * @param allowDismissButton If true, a "Dismiss" button may be added to the dialog.
+     *                           If false, it will never be added.
+     * @return this Builder object to allow for chaining of calls to set methods
+     */
+    public AlertDialogBuilder setAllowDismissButton(boolean allowDismissButton) {
+        mAllowDismissButton = allowDismissButton;
+        return this;
+    }
+
 
     /** Final steps common to both {@link #create()} and {@link #show()} */
     private void prepareDialog() {
@@ -659,7 +692,14 @@
         }
         mBuilder.setCustomTitle(customTitle);
 
-        if (!mNeutralButtonSet && !mNegativeButtonSet && !mPositiveButtonSet) {
+        if (!mAllowDismissButton && !mHasSingleChoiceBodyButton
+                && !mNeutralButtonSet && !mNegativeButtonSet && !mPositiveButtonSet) {
+            throw new RuntimeException(
+                    "The dialog must have at least one button to disable the dismiss button");
+        }
+        if (mContext.getResources().getBoolean(R.bool.car_ui_alert_dialog_force_dismiss_button)
+                && !mNeutralButtonSet && !mNegativeButtonSet && !mPositiveButtonSet
+                && mAllowDismissButton) {
             String mDefaultButtonText = mContext.getString(
                     R.string.car_ui_alert_dialog_default_button);
             mBuilder.setNegativeButton(mDefaultButtonText, (dialog, which) -> {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusArea.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusArea.java
new file mode 100644
index 0000000..0488d28
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusArea.java
@@ -0,0 +1,747 @@
+/*
+ * 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;
+
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+
+import static com.android.car.ui.utils.RotaryConstants.ACTION_NUDGE_SHORTCUT;
+import static com.android.car.ui.utils.RotaryConstants.ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_BOTTOM_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_LEFT_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_RIGHT_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_TOP_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.NUDGE_DIRECTION;
+import static com.android.car.ui.utils.ViewUtils.NO_FOCUS;
+import static com.android.car.ui.utils.ViewUtils.REGULAR_FOCUS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.ui.utils.CarUiUtils;
+import com.android.car.ui.utils.ViewUtils;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@link LinearLayout} used as a navigation block for the rotary controller.
+ * <p>
+ * The {@link com.android.car.rotary.RotaryService} looks for instances of {@link FocusArea} in the
+ * view hierarchy when handling rotate and nudge actions. When receiving a rotation event ({@link
+ * android.car.input.RotaryEvent}), RotaryService will move the focus to another {@link View} that
+ * can take focus within the same FocusArea. When receiving a nudge event ({@link
+ * KeyEvent#KEYCODE_SYSTEM_NAVIGATION_UP}, {@link KeyEvent#KEYCODE_SYSTEM_NAVIGATION_DOWN}, {@link
+ * KeyEvent#KEYCODE_SYSTEM_NAVIGATION_LEFT}, or {@link KeyEvent#KEYCODE_SYSTEM_NAVIGATION_RIGHT}),
+ * RotaryService will move the focus to another view that can take focus in another (typically
+ * adjacent) FocusArea.
+ * <p>
+ * If enabled, FocusArea can draw highlights when one of its descendants has focus and it's not in
+ * touch mode.
+ * <p>
+ * When creating a navigation block in the layout file, if you intend to use a LinearLayout as a
+ * container for that block, just use a FocusArea instead; otherwise wrap the block in a FocusArea.
+ * <p>
+ * DO NOT nest a FocusArea inside another FocusArea because it will result in undefined navigation
+ * behavior.
+ */
+public class FocusArea extends LinearLayout {
+
+    private static final String TAG = "FocusArea";
+
+    private static final int INVALID_DIMEN = -1;
+
+    private static final int INVALID_DIRECTION = -1;
+
+    private static final List<Integer> NUDGE_DIRECTIONS =
+            Arrays.asList(FOCUS_LEFT, FOCUS_RIGHT, FOCUS_UP, FOCUS_DOWN);
+
+    /** Whether the FocusArea's descendant has focus (the FocusArea itself is not focusable). */
+    private boolean mHasFocus;
+
+    /**
+     * Whether to draw {@link #mForegroundHighlight} when one of the FocusArea's descendants has
+     * focus and it's not in touch mode.
+     */
+    private boolean mEnableForegroundHighlight;
+
+    /**
+     * Whether to draw {@link #mBackgroundHighlight} when one of the FocusArea's descendants has
+     * focus and it's not in touch mode.
+     */
+    private boolean mEnableBackgroundHighlight;
+
+    /**
+     * Highlight (typically outline of the FocusArea) drawn on top of the FocusArea and its
+     * descendants.
+     */
+    private Drawable mForegroundHighlight;
+
+    /**
+     * Highlight (typically a solid or gradient shape) drawn on top of the FocusArea but behind its
+     * descendants.
+     */
+    private Drawable mBackgroundHighlight;
+
+    /** The padding (in pixels) of the FocusArea highlight. */
+    private int mPaddingLeft;
+    private int mPaddingRight;
+    private int mPaddingTop;
+    private int mPaddingBottom;
+
+    /** The offset (in pixels) of the FocusArea's bounds. */
+    private int mLeftOffset;
+    private int mRightOffset;
+    private int mTopOffset;
+    private int mBottomOffset;
+
+    /** Whether the layout direction is {@link View#LAYOUT_DIRECTION_RTL}. */
+    private boolean mRtl;
+
+    /** The ID of the view specified in {@code app:defaultFocus}. */
+    private int mDefaultFocusId;
+    /** The view specified in {@code app:defaultFocus}. */
+    @Nullable
+    private View mDefaultFocusView;
+
+    /**
+     * Whether to focus on the {@code app:defaultFocus} view when nudging to the FocusArea, even if
+     * there was another view in the FocusArea focused before.
+     */
+    private boolean mDefaultFocusOverridesHistory;
+
+    /** The ID of the view specified in {@code app:nudgeShortcut}. */
+    private int mNudgeShortcutId;
+    /** The view specified in {@code app:nudgeShortcut}. */
+    @Nullable
+    private View mNudgeShortcutView;
+
+    /** The direction specified in {@code app:nudgeShortcutDirection}. */
+    private int mNudgeShortcutDirection;
+
+    /**
+     * Map of nudge target FocusArea IDs specified in {@code app:nudgeLeft}, {@code app:nudgRight},
+     * {@code app:nudgeUp}, or {@code app:nudgeDown}.
+     */
+    private Map<Integer, Integer> mSpecifiedNudgeIdMap;
+
+    /** Map of specified nudge target FocusAreas. */
+    private Map<Integer, FocusArea> mSpecifiedNudgeFocusAreaMap;
+
+    /**
+     * Cache of focus history and nudge history of the rotary controller.
+     * <p>
+     * For focus history, the previously focused view and a timestamp will be saved when the
+     * focused view has changed.
+     * <p>
+     * For nudge history, the target FocusArea, direction, and a timestamp will be saved when the
+     * focus has moved from another FocusArea to this FocusArea. There are 2 cases:
+     * <ul>
+     *     <li>The focus is moved to another FocusArea because this FocusArea has called {@link
+     *         #nudgeToAnotherFocusArea}. In this case, the target FocusArea and direction are
+     *         trivial to this FocusArea.
+     *     <li>The focus is moved to this FocusArea because RotaryService has performed {@link
+     *         AccessibilityNodeInfo#ACTION_FOCUS} on this FocusArea. In this case, this FocusArea
+     *         can get the source FocusArea through the {@link
+     *         android.view.ViewTreeObserver.OnGlobalFocusChangeListener} registered, and can get
+     *         the direction when handling the action. Since the listener is triggered before
+     *         {@link #requestFocus} returns (which is called when handling the action), the
+     *         source FocusArea is revealed earlier than the direction, so the nudge history should
+     *         be saved when the direction is revealed.
+     * </ul>
+     */
+    private RotaryCache mRotaryCache;
+
+    /** Whether to clear focus area history when the user rotates the rotary controller. */
+    private boolean mClearFocusAreaHistoryWhenRotating;
+
+    /** The FocusArea that had focus before this FocusArea, if any. */
+    private FocusArea mPreviousFocusArea;
+
+    /** The focused view in this FocusArea, if any. */
+    private View mFocusedView;
+
+    public FocusArea(Context context) {
+        super(context);
+        init(context, null);
+    }
+
+    public FocusArea(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        init(context, attrs);
+    }
+
+    public FocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context, attrs);
+    }
+
+    public FocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(context, attrs);
+    }
+
+    private void init(Context context, @Nullable AttributeSet attrs) {
+        Resources resources = getContext().getResources();
+        mEnableForegroundHighlight = resources.getBoolean(
+                R.bool.car_ui_enable_focus_area_foreground_highlight);
+        mEnableBackgroundHighlight = resources.getBoolean(
+                R.bool.car_ui_enable_focus_area_background_highlight);
+        mForegroundHighlight = resources.getDrawable(
+                R.drawable.car_ui_focus_area_foreground_highlight, getContext().getTheme());
+        mBackgroundHighlight = resources.getDrawable(
+                R.drawable.car_ui_focus_area_background_highlight, getContext().getTheme());
+
+        mDefaultFocusOverridesHistory = resources.getBoolean(
+                R.bool.car_ui_focus_area_default_focus_overrides_history);
+        mClearFocusAreaHistoryWhenRotating = resources.getBoolean(
+                R.bool.car_ui_clear_focus_area_history_when_rotating);
+
+        @RotaryCache.CacheType
+        int focusHistoryCacheType = resources.getInteger(R.integer.car_ui_focus_history_cache_type);
+        int focusHistoryExpirationPeriodMs =
+                resources.getInteger(R.integer.car_ui_focus_history_expiration_period_ms);
+        @RotaryCache.CacheType
+        int focusAreaHistoryCacheType = resources.getInteger(
+                R.integer.car_ui_focus_area_history_cache_type);
+        int focusAreaHistoryExpirationPeriodMs =
+                resources.getInteger(R.integer.car_ui_focus_area_history_expiration_period_ms);
+        mRotaryCache = new RotaryCache(focusHistoryCacheType, focusHistoryExpirationPeriodMs,
+                focusAreaHistoryCacheType, focusAreaHistoryExpirationPeriodMs);
+
+        // Ensure that an AccessibilityNodeInfo is created for this view.
+        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+
+        // By default all ViewGroup subclasses do not call their draw() and onDraw() methods. We
+        // should enable it since we override these methods.
+        setWillNotDraw(false);
+
+        registerFocusChangeListener();
+
+        initAttrs(context, attrs);
+    }
+
+    private void registerFocusChangeListener() {
+        getViewTreeObserver().addOnGlobalFocusChangeListener(
+                (oldFocus, newFocus) -> {
+                    boolean hasFocus = hasFocus();
+                    saveFocusHistory(hasFocus);
+                    maybeUpdatePreviousFocusArea(hasFocus, oldFocus);
+                    maybeClearFocusAreaHistory(hasFocus, oldFocus);
+                    maybeUpdateFocusAreaHighlight(hasFocus);
+                    mHasFocus = hasFocus;
+                });
+    }
+
+    private void saveFocusHistory(boolean hasFocus) {
+        if (!hasFocus) {
+            mRotaryCache.saveFocusedView(mFocusedView, SystemClock.uptimeMillis());
+            mFocusedView = null;
+            return;
+        }
+        View v = getFocusedChild();
+        while (v != null) {
+            if (v.isFocused()) {
+                break;
+            }
+            v = v instanceof ViewGroup ? ((ViewGroup) v).getFocusedChild() : null;
+        }
+        mFocusedView = v;
+    }
+
+    /**
+     * Updates {@link #mPreviousFocusArea} when the focus has moved from another FocusArea to this
+     * FocusArea, and sets it to {@code null} in any other cases.
+     */
+    private void maybeUpdatePreviousFocusArea(boolean hasFocus, View oldFocus) {
+        if (mHasFocus || !hasFocus || oldFocus == null || oldFocus instanceof FocusParkingView) {
+            mPreviousFocusArea = null;
+            return;
+        }
+        mPreviousFocusArea = ViewUtils.getAncestorFocusArea(oldFocus);
+        if (mPreviousFocusArea == null) {
+            Log.w(TAG, "No parent FocusArea for " + oldFocus);
+        }
+    }
+
+    /**
+     * Clears FocusArea nudge history when the user rotates the controller to move focus within this
+     * FocusArea.
+     */
+    private void maybeClearFocusAreaHistory(boolean hasFocus, View oldFocus) {
+        if (!mClearFocusAreaHistoryWhenRotating) {
+            return;
+        }
+        if (!hasFocus || oldFocus == null) {
+            return;
+        }
+        FocusArea oldFocusArea = ViewUtils.getAncestorFocusArea(oldFocus);
+        if (oldFocusArea != this) {
+            return;
+        }
+        mRotaryCache.clearFocusAreaHistory();
+    }
+
+    /** Updates highlight of the FocusArea if this FocusArea has gained or lost focus. */
+    private void maybeUpdateFocusAreaHighlight(boolean hasFocus) {
+        if (!mEnableBackgroundHighlight && !mEnableForegroundHighlight) {
+            return;
+        }
+        if (mHasFocus != hasFocus) {
+            invalidate();
+        }
+    }
+
+    private void initAttrs(Context context, @Nullable AttributeSet attrs) {
+        if (attrs == null) {
+            return;
+        }
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FocusArea);
+        try {
+            mDefaultFocusId = a.getResourceId(R.styleable.FocusArea_defaultFocus, View.NO_ID);
+
+            // Initialize the highlight padding. The padding, for example, left padding, is set in
+            // the following order:
+            // 1. if highlightPaddingStart (or highlightPaddingEnd in RTL layout) specified, use it
+            // 2. otherwise, if highlightPaddingHorizontal is specified, use it
+            // 3. otherwise use 0
+
+            int paddingStart = a.getDimensionPixelSize(
+                    R.styleable.FocusArea_highlightPaddingStart, INVALID_DIMEN);
+            if (paddingStart == INVALID_DIMEN) {
+                paddingStart = a.getDimensionPixelSize(
+                        R.styleable.FocusArea_highlightPaddingHorizontal, 0);
+            }
+
+            int paddingEnd = a.getDimensionPixelSize(
+                    R.styleable.FocusArea_highlightPaddingEnd, INVALID_DIMEN);
+            if (paddingEnd == INVALID_DIMEN) {
+                paddingEnd = a.getDimensionPixelSize(
+                        R.styleable.FocusArea_highlightPaddingHorizontal, 0);
+            }
+
+            mRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+            mPaddingLeft = mRtl ? paddingEnd : paddingStart;
+            mPaddingRight = mRtl ? paddingStart : paddingEnd;
+
+            mPaddingTop = a.getDimensionPixelSize(
+                    R.styleable.FocusArea_highlightPaddingTop, INVALID_DIMEN);
+            if (mPaddingTop == INVALID_DIMEN) {
+                mPaddingTop = a.getDimensionPixelSize(
+                        R.styleable.FocusArea_highlightPaddingVertical, 0);
+            }
+
+            mPaddingBottom = a.getDimensionPixelSize(
+                    R.styleable.FocusArea_highlightPaddingBottom, INVALID_DIMEN);
+            if (mPaddingBottom == INVALID_DIMEN) {
+                mPaddingBottom = a.getDimensionPixelSize(
+                        R.styleable.FocusArea_highlightPaddingVertical, 0);
+            }
+
+            // Initialize the offset of the FocusArea's bounds. The offset, for example, left
+            // offset, is set in the following order:
+            // 1. if startBoundOffset (or endBoundOffset in RTL layout) specified, use it
+            // 2. otherwise, if horizontalBoundOffset is specified, use it
+            // 3. otherwise use mPaddingLeft
+
+            int startOffset = a.getDimensionPixelSize(
+                    R.styleable.FocusArea_startBoundOffset, INVALID_DIMEN);
+            if (startOffset == INVALID_DIMEN) {
+                startOffset = a.getDimensionPixelSize(
+                        R.styleable.FocusArea_horizontalBoundOffset, paddingStart);
+            }
+
+            int endOffset = a.getDimensionPixelSize(
+                    R.styleable.FocusArea_endBoundOffset, INVALID_DIMEN);
+            if (endOffset == INVALID_DIMEN) {
+                endOffset = a.getDimensionPixelSize(
+                        R.styleable.FocusArea_horizontalBoundOffset, paddingEnd);
+            }
+
+            mLeftOffset = mRtl ? endOffset : startOffset;
+            mRightOffset = mRtl ? startOffset : endOffset;
+
+            mTopOffset = a.getDimensionPixelSize(
+                    R.styleable.FocusArea_topBoundOffset, INVALID_DIMEN);
+            if (mTopOffset == INVALID_DIMEN) {
+                mTopOffset = a.getDimensionPixelSize(
+                        R.styleable.FocusArea_verticalBoundOffset, mPaddingTop);
+            }
+
+            mBottomOffset = a.getDimensionPixelSize(
+                    R.styleable.FocusArea_bottomBoundOffset, INVALID_DIMEN);
+            if (mBottomOffset == INVALID_DIMEN) {
+                mBottomOffset = a.getDimensionPixelSize(
+                        R.styleable.FocusArea_verticalBoundOffset, mPaddingBottom);
+            }
+
+            mNudgeShortcutId = a.getResourceId(R.styleable.FocusArea_nudgeShortcut, View.NO_ID);
+            mNudgeShortcutDirection = a.getInt(
+                    R.styleable.FocusArea_nudgeShortcutDirection, INVALID_DIRECTION);
+            if ((mNudgeShortcutId == View.NO_ID) ^ (mNudgeShortcutDirection == INVALID_DIRECTION)) {
+                throw new IllegalStateException("nudgeShortcut and nudgeShortcutDirection must "
+                        + "be specified together");
+            }
+
+            mSpecifiedNudgeIdMap = new HashMap<>();
+            mSpecifiedNudgeIdMap.put(FOCUS_LEFT,
+                    a.getResourceId(R.styleable.FocusArea_nudgeLeft, View.NO_ID));
+            mSpecifiedNudgeIdMap.put(FOCUS_RIGHT,
+                    a.getResourceId(R.styleable.FocusArea_nudgeRight, View.NO_ID));
+            mSpecifiedNudgeIdMap.put(FOCUS_UP,
+                    a.getResourceId(R.styleable.FocusArea_nudgeUp, View.NO_ID));
+            mSpecifiedNudgeIdMap.put(FOCUS_DOWN,
+                    a.getResourceId(R.styleable.FocusArea_nudgeDown, View.NO_ID));
+        } finally {
+            a.recycle();
+        }
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        if (mDefaultFocusId != View.NO_ID) {
+            mDefaultFocusView = CarUiUtils.requireViewByRefId(this, mDefaultFocusId);
+        }
+        if (mNudgeShortcutId != View.NO_ID) {
+            mNudgeShortcutView = CarUiUtils.requireViewByRefId(this, mNudgeShortcutId);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+        if (mRtl != rtl) {
+            mRtl = rtl;
+
+            int temp = mPaddingLeft;
+            mPaddingLeft = mPaddingRight;
+            mPaddingRight = temp;
+
+            temp = mLeftOffset;
+            mLeftOffset = mRightOffset;
+            mRightOffset = temp;
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        // To ensure the focus is initialized properly in rotary mode when there is a window focus
+        // change, this FocusArea will grab the focus from the currently focused view if one of this
+        // FocusArea's descendants is a better focus candidate than the currently focused view.
+        if (hasWindowFocus && !isInTouchMode()) {
+            maybeAdjustFocus();
+        }
+        super.onWindowFocusChanged(hasWindowFocus);
+    }
+
+    /**
+     * Focuses on another view in this FocusArea if the view is a better focus candidate than the
+     * currently focused view.
+     */
+    private boolean maybeAdjustFocus() {
+        View root = getRootView();
+        View focus = root.findFocus();
+        return ViewUtils.adjustFocus(root, focus);
+    }
+
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        switch (action) {
+            case ACTION_FOCUS:
+                // Repurpose ACTION_FOCUS to focus on its descendant. We can do this because
+                // FocusArea is not focusable and it didn't consume ACTION_FOCUS previously.
+                boolean success = focusOnDescendant();
+                if (success && mPreviousFocusArea != null) {
+                    int direction = getNudgeDirection(arguments);
+                    if (direction != INVALID_DIRECTION) {
+                        saveFocusAreaHistory(direction, mPreviousFocusArea, this,
+                                SystemClock.uptimeMillis());
+                    }
+                }
+                return success;
+            case ACTION_NUDGE_SHORTCUT:
+                return nudgeToShortcutView(arguments);
+            case ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA:
+                return nudgeToAnotherFocusArea(arguments);
+            default:
+                return super.performAccessibilityAction(action, arguments);
+        }
+    }
+
+    private boolean focusOnDescendant() {
+        if (mDefaultFocusOverridesHistory) {
+            // Check mDefaultFocus before last focused view.
+            if (focusDefaultFocusView() || focusOnLastFocusedView()) {
+                return true;
+            }
+        } else {
+            // Check last focused view before mDefaultFocus.
+            if (focusOnLastFocusedView() || focusDefaultFocusView()) {
+                return true;
+            }
+        }
+        return focusOnFirstFocusableView();
+    }
+
+    private boolean focusDefaultFocusView() {
+        return ViewUtils.adjustFocus(this, /* currentLevel= */ REGULAR_FOCUS);
+    }
+
+    /**
+     * Gets the {@code app:defaultFocus} view.
+     *
+     * @hidden
+     */
+    public View getDefaultFocusView() {
+        return mDefaultFocusView;
+    }
+
+    private boolean focusOnLastFocusedView() {
+        View lastFocusedView = mRotaryCache.getFocusedView(SystemClock.uptimeMillis());
+        return ViewUtils.requestFocus(lastFocusedView);
+    }
+
+    private boolean focusOnFirstFocusableView() {
+        return ViewUtils.adjustFocus(this, /* currentLevel= */ NO_FOCUS);
+    }
+
+    private boolean nudgeToShortcutView(Bundle arguments) {
+        if (mNudgeShortcutDirection == INVALID_DIRECTION) {
+            // No nudge shortcut configured for this FocusArea.
+            return false;
+        }
+        if (arguments == null
+                || arguments.getInt(NUDGE_DIRECTION, INVALID_DIRECTION)
+                    != mNudgeShortcutDirection) {
+            // The user is not nudging in the nudge shortcut direction.
+            return false;
+        }
+        if (mNudgeShortcutView.isFocused()) {
+            // The nudge shortcut view is already focused; return false so that the user can
+            // nudge to another FocusArea.
+            return false;
+        }
+        return ViewUtils.requestFocus(mNudgeShortcutView);
+    }
+
+    private boolean nudgeToAnotherFocusArea(Bundle arguments) {
+        int direction = getNudgeDirection(arguments);
+        long elapsedRealtime = SystemClock.uptimeMillis();
+
+        // Try to nudge to specified FocusArea, if any.
+        FocusArea targetFocusArea = getSpecifiedFocusArea(direction);
+        boolean success = targetFocusArea != null && targetFocusArea.focusOnDescendant();
+
+        // If failed, try to nudge to cached FocusArea, if any.
+        if (!success) {
+            targetFocusArea = mRotaryCache.getCachedFocusArea(direction, elapsedRealtime);
+            success = targetFocusArea != null && targetFocusArea.focusOnDescendant();
+        }
+
+        return success;
+    }
+
+    private static int getNudgeDirection(Bundle arguments) {
+        return arguments == null
+                ? INVALID_DIRECTION
+                : arguments.getInt(NUDGE_DIRECTION, INVALID_DIRECTION);
+    }
+
+    private void saveFocusAreaHistory(int direction, @NonNull FocusArea sourceFocusArea,
+            @NonNull FocusArea targetFocusArea, long elapsedRealtime) {
+        // Save one-way rather than two-way nudge history to avoid infinite nudge loop.
+        if (sourceFocusArea.mRotaryCache.getCachedFocusArea(direction, elapsedRealtime) == null) {
+            // Save reversed nudge history so that the users can nudge back to where they were.
+            int oppositeDirection = getOppositeDirection(direction);
+            targetFocusArea.mRotaryCache.saveFocusArea(oppositeDirection, sourceFocusArea,
+                    elapsedRealtime);
+        }
+    }
+
+    /** Returns the direction opposite the given {@code direction} */
+    @VisibleForTesting
+    private static int getOppositeDirection(int direction) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return View.FOCUS_RIGHT;
+            case View.FOCUS_RIGHT:
+                return View.FOCUS_LEFT;
+            case View.FOCUS_UP:
+                return View.FOCUS_DOWN;
+            case View.FOCUS_DOWN:
+                return View.FOCUS_UP;
+        }
+        throw new IllegalArgumentException("direction must be "
+                + "FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, or FOCUS_RIGHT.");
+    }
+
+    @Nullable
+    private FocusArea getSpecifiedFocusArea(int direction) {
+        maybeInitializeSpecifiedFocusAreas();
+        return mSpecifiedNudgeFocusAreaMap.get(direction);
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        // Draw highlight on top of this FocusArea (including its background and content) but
+        // behind its children.
+        if (mEnableBackgroundHighlight && mHasFocus && !isInTouchMode()) {
+            mBackgroundHighlight.setBounds(
+                    mPaddingLeft + getScrollX(),
+                    mPaddingTop + getScrollY(),
+                    getScrollX() + getWidth() - mPaddingRight,
+                    getScrollY() + getHeight() - mPaddingBottom);
+            mBackgroundHighlight.draw(canvas);
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+
+        // Draw highlight on top of this FocusArea (including its background and content) and its
+        // children (including background, content, focus highlight, etc).
+        if (mEnableForegroundHighlight && mHasFocus && !isInTouchMode()) {
+            mForegroundHighlight.setBounds(
+                    mPaddingLeft + getScrollX(),
+                    mPaddingTop + getScrollY(),
+                    getScrollX() + getWidth() - mPaddingRight,
+                    getScrollY() + getHeight() - mPaddingBottom);
+            mForegroundHighlight.draw(canvas);
+        }
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return FocusArea.class.getName();
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        Bundle bundle = info.getExtras();
+        bundle.putInt(FOCUS_AREA_LEFT_BOUND_OFFSET, mLeftOffset);
+        bundle.putInt(FOCUS_AREA_RIGHT_BOUND_OFFSET, mRightOffset);
+        bundle.putInt(FOCUS_AREA_TOP_BOUND_OFFSET, mTopOffset);
+        bundle.putInt(FOCUS_AREA_BOTTOM_BOUND_OFFSET, mBottomOffset);
+    }
+
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        if (isInTouchMode()) {
+            return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+        }
+        return maybeAdjustFocus();
+    }
+
+    @Override
+    public boolean restoreDefaultFocus() {
+        return maybeAdjustFocus();
+    }
+
+    private void maybeInitializeSpecifiedFocusAreas() {
+        if (mSpecifiedNudgeFocusAreaMap != null) {
+            return;
+        }
+        View root = getRootView();
+        mSpecifiedNudgeFocusAreaMap = new HashMap<>();
+        for (Integer direction : NUDGE_DIRECTIONS) {
+            int id = mSpecifiedNudgeIdMap.get(direction);
+            mSpecifiedNudgeFocusAreaMap.put(direction, root.findViewById(id));
+        }
+    }
+
+    /**
+     * Sets the padding (in pixels) of the FocusArea highlight.
+     * <p>
+     * It doesn't affect other values, such as the paddings on its child views.
+     */
+    public void setHighlightPadding(int left, int top, int right, int bottom) {
+        if (mPaddingLeft == left && mPaddingTop == top && mPaddingRight == right
+                && mPaddingBottom == bottom) {
+            return;
+        }
+        mPaddingLeft = left;
+        mPaddingTop = top;
+        mPaddingRight = right;
+        mPaddingBottom = bottom;
+        invalidate();
+    }
+
+    /**
+     * Sets the offset (in pixels) of the FocusArea's bounds.
+     * <p>
+     * It only affects the perceived bounds for the purposes of finding the nudge target. It doesn't
+     * affect the FocusArea's view bounds or highlight bounds. The offset should only be used when
+     * FocusAreas are overlapping and nudge interaction is ambiguous.
+     */
+    public void setBoundsOffset(int left, int top, int right, int bottom) {
+        mLeftOffset = left;
+        mTopOffset = top;
+        mRightOffset = right;
+        mBottomOffset = bottom;
+    }
+
+    /** Sets the default focus view in this FocusArea. */
+    public void setDefaultFocus(@NonNull View defaultFocus) {
+        mDefaultFocusView = defaultFocus;
+    }
+
+    @VisibleForTesting
+    void enableForegroundHighlight() {
+        mEnableForegroundHighlight = true;
+    }
+
+    @VisibleForTesting
+    void setDefaultFocusOverridesHistory(boolean override) {
+        mDefaultFocusOverridesHistory = override;
+    }
+
+    @VisibleForTesting
+    void setRotaryCache(@NonNull RotaryCache rotaryCache) {
+        mRotaryCache = rotaryCache;
+    }
+
+    @VisibleForTesting
+    void setClearFocusAreaHistoryWhenRotating(boolean clear) {
+        mClearFocusAreaHistoryWhenRotating = clear;
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingView.java
new file mode 100644
index 0000000..9d5cfeb
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingView.java
@@ -0,0 +1,274 @@
+/*
+ * 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;
+
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+
+import static com.android.car.ui.utils.RotaryConstants.ACTION_HIDE_IME;
+import static com.android.car.ui.utils.RotaryConstants.ACTION_RESTORE_DEFAULT_FOCUS;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.utils.ViewUtils;
+
+/**
+ * A transparent {@link View} that can take focus. It's used by {@link
+ * com.android.car.rotary.RotaryService} to support rotary controller navigation. It's also used to
+ * initialize the focus when in rotary mode.
+ * <p>
+ * To support the rotary controller, each {@link android.view.Window} must have a FocusParkingView
+ * as the first focusable view in the view tree, and outside of all {@link FocusArea}s.
+ * <p>
+ * Android doesn't clear focus automatically when focus is set in another window. If we try to clear
+ * focus in the previous window, Android will re-focus a view in that window, resulting in two
+ * windows being focused simultaneously. Adding this view to each window can fix this issue. This
+ * view is transparent and its default focus highlight is disabled, so it's invisible to the user no
+ * matter whether it's focused or not. It can take focus so that RotaryService can "park" the focus
+ * on it to remove the focus highlight.
+ * <p>
+ * If there is only one focus area in the current window, rotating the controller within the focus
+ * area will cause RotaryService to move the focus around from the view on the right to the view on
+ * the left or vice versa. Adding this view to each window can fix this issue. When RotaryService
+ * finds out the focus target is a FocusParkingView, it will know a wrap-around is going to happen.
+ * Then it will avoid the wrap-around by not moving focus.
+ * <p>
+ * To ensure the focus is initialized properly when there is a window change, the FocusParkingView
+ * will not get focused when the framework wants to focus on it. Instead, it will try to find a
+ * better focus target in the window and focus on the target. That said, the FocusParkingView can
+ * still be focused in order to clear focus highlight in the window, such as when RotaryService
+ * performs {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_FOCUS} on the
+ * FocusParkingView, or the window has lost focus.
+ */
+public class FocusParkingView extends View {
+
+    /**
+     * The focused view in the window containing this FocusParkingView. It's null if no view is
+     * focused, or the focused view is a FocusParkingView.
+     */
+    @Nullable
+    private View mFocusedView;
+
+    /** The scrollable container that contains the {@link #mFocusedView}, if any. */
+    @Nullable
+    ViewGroup mScrollableContainer;
+
+    /**
+     * Whether to restore focus when the frameworks wants to focus this view. When false, this view
+     * allows itself to be focused instead. This should be false for the {@code FocusParkingView} in
+     * an {@code ActivityView}. The default value is true.
+     */
+    private boolean mShouldRestoreFocus;
+
+    public FocusParkingView(Context context) {
+        super(context);
+        init(context, /* attrs= */ null);
+    }
+
+    public FocusParkingView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        init(context, attrs);
+    }
+
+    public FocusParkingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context, attrs);
+    }
+
+    public FocusParkingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(context, attrs);
+    }
+
+    private void init(Context context, @Nullable AttributeSet attrs) {
+        if (attrs != null) {
+            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FocusParkingView);
+            mShouldRestoreFocus = a.getBoolean(R.styleable.FocusParkingView_shouldRestoreFocus,
+                    /* defValue= */ true);
+        }
+
+        // This view is focusable, visible and enabled so it can take focus.
+        setFocusable(View.FOCUSABLE);
+        setVisibility(VISIBLE);
+        setEnabled(true);
+
+        // This view is not clickable so it won't affect the app's behavior when the user clicks on
+        // it by accident.
+        setClickable(false);
+
+        // This view is always transparent.
+        setAlpha(0f);
+
+        // Prevent Android from drawing the default focus highlight for this view when it's focused.
+        setDefaultFocusHighlightEnabled(false);
+
+        // Keep track of the focused view so that we can recover focus when it's removed.
+        getViewTreeObserver().addOnGlobalFocusChangeListener((oldFocus, newFocus) -> {
+            mFocusedView = newFocus instanceof FocusParkingView ? null : newFocus;
+            mScrollableContainer = ViewUtils.getAncestorScrollableContainer(mFocusedView);
+        });
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // This size of the view is always 1 x 1 pixel, no matter what value is set in the layout
+        // file (match_parent, wrap_content, 100dp, 0dp, etc). Small size is to ensure it has little
+        // impact on the layout, non-zero size is to ensure it can take focus.
+        setMeasuredDimension(1, 1);
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        if (!hasWindowFocus) {
+            // We need to clear the focus highlight(by parking the focus on the FocusParkingView)
+            // once the current window goes to background. This can't be done by RotaryService
+            // because RotaryService sees the window as removed, thus can't perform any action
+            // (such as focus, clear focus) on the nodes in the window. So FocusParkingView has to
+            // grab the focus proactively.
+            super.requestFocus(FOCUS_DOWN, null);
+
+            // OnGlobalFocusChangeListener won't be triggered when the window lost focus, so reset
+            // the focused view here.
+            mFocusedView = null;
+            mScrollableContainer = null;
+        } else if (isFocused()) {
+            // When FocusParkingView is focused and the window just gets focused, transfer the view
+            // focus to a non-FocusParkingView in the window.
+            restoreFocusInRoot(/* checkForTouchMode= */ true);
+        }
+        super.onWindowFocusChanged(hasWindowFocus);
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return FocusParkingView.class.getName();
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        switch (action) {
+            case ACTION_RESTORE_DEFAULT_FOCUS:
+                return restoreFocusInRoot(/* checkForTouchMode= */ false);
+            case ACTION_HIDE_IME:
+                InputMethodManager inputMethodManager =
+                        getContext().getSystemService(InputMethodManager.class);
+                return inputMethodManager.hideSoftInputFromWindow(getWindowToken(),
+                        /* flags= */ 0);
+            case ACTION_FOCUS:
+                // Don't leave this to View to handle as it will exit touch mode.
+                if (!hasFocus()) {
+                    return super.requestFocus(FOCUS_DOWN, null);
+                }
+                return false;
+        }
+        return super.performAccessibilityAction(action, arguments);
+    }
+
+    @Override
+    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+        if (!mShouldRestoreFocus) {
+            return super.requestFocus(direction, previouslyFocusedRect);
+        }
+        // Find a better target to focus instead of focusing this FocusParkingView when the
+        // framework wants to focus it.
+        return restoreFocusInRoot(/* checkForTouchMode= */ true);
+    }
+
+    @Override
+    public boolean restoreDefaultFocus() {
+        if (!mShouldRestoreFocus) {
+            return super.restoreDefaultFocus();
+        }
+        // Find a better target to focus instead of focusing this FocusParkingView when the
+        // framework wants to focus it.
+        return restoreFocusInRoot(/* checkForTouchMode= */ true);
+    }
+
+    /**
+     * Sets whether this view should restore focus when the framework wants to focus this view. When
+     * set to false, this view allows itself to be focused instead. This should be set to false for
+     * the {@code FocusParkingView} in an {@code ActivityView}.  The default value is true.
+     */
+    public void setShouldRestoreFocus(boolean shouldRestoreFocus) {
+        mShouldRestoreFocus = shouldRestoreFocus;
+    }
+
+    private boolean restoreFocusInRoot(boolean checkForTouchMode) {
+        // Don't do anything in touch mode if checkForTouchMode is true.
+        if (checkForTouchMode && isInTouchMode()) {
+            return false;
+        }
+        // The focused view was in a scrollable container and the Framework unfocused it because it
+        // was scrolled off the screen. In this case focus on the scrollable container so that the
+        // rotary controller can scroll the scrollable container.
+        if (maybeFocusOnScrollableContainer()) {
+            return true;
+        }
+        // Otherwise try to find the best target view to focus.
+        if (ViewUtils.adjustFocus(getRootView(), /* currentFocus= */ null)) {
+            return true;
+        }
+        // It failed to find a target view (e.g., all the views are not shown), so focus on this
+        // FocusParkingView as fallback.
+        return super.requestFocus(FOCUS_DOWN, /* previouslyFocusedRect= */ null);
+    }
+
+    private boolean maybeFocusOnScrollableContainer() {
+        // If the focused view was in a scrollable container and it was scrolled off the screen,
+        // focus on the scrollable container. When a view is scrolled off the screen, it is no
+        // longer attached to window and its parent is not null. When a view is removed, its parent
+        // is null. There is no need to focus on the scrollable container when its focused element
+        // is removed.
+        if (mFocusedView != null && !mFocusedView.isAttachedToWindow()
+                && mFocusedView.getParent() != null && mScrollableContainer != null
+                && mScrollableContainer.isAttachedToWindow() && mScrollableContainer.isShown()) {
+            RecyclerView recyclerView = mScrollableContainer instanceof RecyclerView
+                    ? (RecyclerView) mScrollableContainer
+                    : null;
+            if (mScrollableContainer.requestFocus()) {
+                if (recyclerView != null && recyclerView.isComputingLayout()) {
+                    // When a RecyclerView gains focus, it won't dispatch AccessibilityEvent if its
+                    // layout is not ready. So wait until its layout is ready then dispatch the
+                    // event.
+                    getViewTreeObserver().addOnGlobalLayoutListener(
+                            new ViewTreeObserver.OnGlobalLayoutListener() {
+                                @Override
+                                public void onGlobalLayout() {
+                                    // At this point the layout is complete and the dimensions of
+                                    // recyclerView and any child views are known.
+                                    recyclerView.sendAccessibilityEvent(TYPE_VIEW_FOCUSED);
+                                    getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                                }
+                            });
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/RotaryCache.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/RotaryCache.java
new file mode 100644
index 0000000..18f9fb0
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/RotaryCache.java
@@ -0,0 +1,218 @@
+/*
+ * 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;
+
+import android.os.SystemClock;
+import android.view.View;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+
+/**
+ * Cache used by {@link FocusArea} to save focus history and nudge history of the rotary controller.
+ */
+class RotaryCache {
+    /** The cache is disabled. */
+    @VisibleForTesting
+    static final int CACHE_TYPE_DISABLED = 1;
+    /** Entries in the cache will expire after a period of time. */
+    @VisibleForTesting
+    static final int CACHE_TYPE_EXPIRED_AFTER_SOME_TIME = 2;
+    /** Entries in the cache will never expire. */
+    @VisibleForTesting
+    static final int CACHE_TYPE_NEVER_EXPIRE = 3;
+
+    @IntDef(flag = true, value = {
+            CACHE_TYPE_DISABLED, CACHE_TYPE_EXPIRED_AFTER_SOME_TIME, CACHE_TYPE_NEVER_EXPIRE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CacheType {
+    }
+
+    /** Cache of focused view. */
+    @NonNull
+    private final FocusCache mFocusCache;
+
+    /** Cache of FocusAreas that were nudged to. */
+    @NonNull
+    private final FocusAreaCache mFocusAreaCache;
+
+    /** A record of when a View was focused. */
+    private static class FocusHistory {
+        /** The focused view. */
+        final View mFocusedView;
+        /** The {@link SystemClock#uptimeMillis} when this history was recorded. */
+        final long mTimestamp;
+
+        FocusHistory(@NonNull View focusedView, long timestamp) {
+            mFocusedView = focusedView;
+            mTimestamp = timestamp;
+        }
+    }
+
+    /** Cache of focused view. */
+    private static class FocusCache {
+        /** The cache type. */
+        @CacheType
+        final int mCacheType;
+        /** How many milliseconds before the entry in the cache expires. */
+        long mExpirationPeriodMs;
+        /** The record of focused view. */
+        @NonNull
+        FocusHistory mFocusHistory;
+
+        FocusCache(@CacheType int cacheType, final long expirationPeriodMs) {
+            mCacheType = cacheType;
+            mExpirationPeriodMs = expirationPeriodMs;
+            if (mCacheType == CACHE_TYPE_EXPIRED_AFTER_SOME_TIME && mExpirationPeriodMs <= 0) {
+                throw new IllegalArgumentException(
+                        "Expiration time must be positive if CacheType is "
+                                + "CACHE_TYPE_EXPIRED_AFTER_SOME_TIME");
+            }
+        }
+
+        View getFocusedView(long elapsedRealtime) {
+            return isValidHistory(elapsedRealtime) ? mFocusHistory.mFocusedView : null;
+        }
+
+        void setFocusedView(@NonNull View focusedView, long elapsedRealtime) {
+            if (mCacheType == CACHE_TYPE_DISABLED) {
+                return;
+            }
+            mFocusHistory = new FocusHistory(focusedView, elapsedRealtime);
+        }
+
+        boolean isValidHistory(long elapsedRealtime) {
+            if (mFocusHistory == null) {
+                return false;
+            }
+            switch (mCacheType) {
+                case CACHE_TYPE_NEVER_EXPIRE:
+                    return true;
+                case CACHE_TYPE_EXPIRED_AFTER_SOME_TIME:
+                    return elapsedRealtime < mFocusHistory.mTimestamp + mExpirationPeriodMs;
+                default:
+                    return false;
+            }
+        }
+    }
+
+    /** A record of a FocusArea that was nudged to. */
+    private static class FocusAreaHistory {
+        /** The FocusArea that was nudged to. */
+        @NonNull
+        final FocusArea mFocusArea;
+        /** The {@link SystemClock#uptimeMillis} when this history was recorded. */
+        final long mTimestamp;
+
+        FocusAreaHistory(@NonNull FocusArea focusArea, long timestamp) {
+            mFocusArea = focusArea;
+            mTimestamp = timestamp;
+        }
+    }
+
+    /** Cache of FocusAreas that were nudged to. */
+    private static class FocusAreaCache extends HashMap<Integer, FocusAreaHistory> {
+        /** Type of the cache. */
+        @CacheType
+        private final int mCacheType;
+        /** How many milliseconds before an entry in the cache expires. */
+        private final int mExpirationPeriodMs;
+
+        FocusAreaCache(@CacheType int cacheType, int expirationPeriodMs) {
+            mCacheType = cacheType;
+            mExpirationPeriodMs = expirationPeriodMs;
+            if (mCacheType == CACHE_TYPE_EXPIRED_AFTER_SOME_TIME && mExpirationPeriodMs <= 0) {
+                throw new IllegalArgumentException(
+                        "Expiration time must be positive if CacheType is "
+                                + "CACHE_TYPE_EXPIRED_AFTER_SOME_TIME");
+            }
+        }
+
+        void put(int direction, @NonNull FocusArea targetFocusArea, long elapsedRealtime) {
+            if (mCacheType == CACHE_TYPE_DISABLED) {
+                return;
+            }
+            put(direction, new FocusAreaHistory(targetFocusArea, elapsedRealtime));
+        }
+
+        FocusArea get(int direction, long elapsedRealtime) {
+            FocusAreaHistory history = get(direction);
+            return isValidHistory(history, elapsedRealtime) ? history.mFocusArea : null;
+        }
+
+        boolean isValidHistory(@Nullable FocusAreaHistory history, long elapsedRealtime) {
+            if (history == null) {
+                return false;
+            }
+            switch (mCacheType) {
+                case CACHE_TYPE_NEVER_EXPIRE:
+                    return true;
+                case CACHE_TYPE_EXPIRED_AFTER_SOME_TIME:
+                    return elapsedRealtime - history.mTimestamp < mExpirationPeriodMs;
+                default:
+                    return false;
+            }
+        }
+    }
+
+    RotaryCache(@CacheType int focusHistoryCacheType,
+            int focusHistoryExpirationPeriodMs,
+            @CacheType int focusAreaHistoryCacheType,
+            int focusAreaHistoryExpirationPeriodMs) {
+        mFocusCache = new FocusCache(focusHistoryCacheType, focusHistoryExpirationPeriodMs);
+        mFocusAreaCache = new FocusAreaCache(
+                focusAreaHistoryCacheType, focusAreaHistoryExpirationPeriodMs);
+    }
+
+    /**
+     * Searches the cache to find the last focused view of the FocusArea. Returns the view, or null
+     * if there is nothing in the cache, the cache is stale.
+     */
+    @Nullable
+    View getFocusedView(long elapsedRealtime) {
+        return mFocusCache.getFocusedView(elapsedRealtime);
+    }
+
+    /** Saves the focused view. */
+    void saveFocusedView(@NonNull View view, long elapsedRealtime) {
+        mFocusCache.setFocusedView(view, elapsedRealtime);
+    }
+
+    /**
+     * Searches the cache to find the target FocusArea for a nudge in a given {@code direction}.
+     * Returns the target FocusArea, or null if there is nothing in the cache, the cache is stale.
+     */
+    @Nullable
+    FocusArea getCachedFocusArea(int direction, long elapsedRealtime) {
+        return mFocusAreaCache.get(direction, elapsedRealtime);
+    }
+
+    /** Saves the FocusArea nudge history. */
+    void saveFocusArea(int direction, @NonNull FocusArea targetFocusArea, long elapsedRealtime) {
+        mFocusAreaCache.put(direction, targetFocusArea, elapsedRealtime);
+    }
+
+    /** Clears the FocusArea nudge history cache. */
+    void clearFocusAreaHistory() {
+        mFocusAreaCache.clear();
+    }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/baselayout/ClickBlockingView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/ClickBlockingView.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/baselayout/ClickBlockingView.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/ClickBlockingView.java
diff --git a/car-ui-lib/src/com/android/car/ui/baselayout/Insets.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/Insets.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/baselayout/Insets.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/Insets.java
diff --git a/car-ui-lib/src/com/android/car/ui/baselayout/InsetsChangedListener.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/InsetsChangedListener.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/baselayout/InsetsChangedListener.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/InsetsChangedListener.java
diff --git a/car-ui-lib/src/com/android/car/ui/core/BaseLayoutController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/BaseLayoutController.java
similarity index 70%
rename from car-ui-lib/src/com/android/car/ui/core/BaseLayoutController.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/BaseLayoutController.java
index 153b4e6..1e5a46c 100644
--- a/car-ui-lib/src/com/android/car/ui/core/BaseLayoutController.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/BaseLayoutController.java
@@ -15,18 +15,20 @@
  */
 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;
-import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 
 import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.util.Pair;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
 
@@ -35,7 +37,6 @@
 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.Map;
 import java.util.WeakHashMap;
@@ -45,7 +46,7 @@
  * It also exposes a {@link ToolbarController} to access the toolbar. This may be null if
  * used with a base layout without a Toolbar.
  */
-class BaseLayoutController {
+final class BaseLayoutController {
 
     private static final Map<Activity, BaseLayoutController> sBaseLayoutMap = new WeakHashMap<>();
 
@@ -117,6 +118,30 @@
      */
     private void installBaseLayout(Activity activity) {
         boolean toolbarEnabled = getThemeBoolean(activity, R.attr.carUiToolbar);
+        Pair<ToolbarController, InsetsUpdater> results = installBaseLayoutAround(
+                activity,
+                requireViewByRefId(activity.getWindow().getDecorView(), android.R.id.content),
+                toolbarEnabled);
+
+        mToolbarController = results.first;
+        mInsetsUpdater = results.second;
+    }
+
+    /**
+     * Installs a base layout *around* the provided contentView.
+     *
+     * @param activity May be null. Used to dispatch inset changes to, if it implements
+     *                 {@link InsetsChangedListener}
+     * @param contentView The view to install the base layout around.
+     * @param toolbarEnabled If there should be a toolbar in the base layout.
+     * @return Both the {@link ToolbarController} and {@link InsetsUpdater} for the base layout.
+     *         The InsetsUpdater will never be null. The ToolbarController will be null if
+     *         {@code toolbarEnabled} was false.
+     */
+    public static Pair<ToolbarController, InsetsUpdater> installBaseLayoutAround(
+            @Nullable Activity activity,
+            @NonNull View contentView,
+            boolean toolbarEnabled) {
         boolean legacyToolbar = Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q;
         @LayoutRes final int baseLayoutRes;
 
@@ -128,33 +153,34 @@
             baseLayoutRes = R.layout.car_ui_base_layout;
         }
 
-        View baseLayout = LayoutInflater.from(activity)
+        View baseLayout = LayoutInflater.from(contentView.getContext())
                 .inflate(baseLayoutRes, null, false);
 
-        // Replace windowContentView with baseLayout
-        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());
+        // Replace the app's content view with a base layout
+        ViewGroup contentViewParent = (ViewGroup) contentView.getParent();
+        int contentIndex = contentViewParent.indexOfChild(contentView);
+        contentViewParent.removeView(contentView);
+        contentViewParent.addView(baseLayout, contentIndex, contentView.getLayoutParams());
 
-        // Add windowContentView to the baseLayout's content view
-        FrameLayout contentView = CarUiUtils.requireViewByRefId(baseLayout, R.id.content);
-        contentView.addView(windowContentView, new FrameLayout.LayoutParams(
+        // Add the app's content view to the baseLayout's content view container
+        FrameLayout contentViewContainer = requireViewByRefId(baseLayout,
+                R.id.car_ui_base_layout_content_container);
+        contentViewContainer.addView(contentView, new FrameLayout.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT));
 
+        ToolbarController toolbarController = null;
         if (toolbarEnabled) {
             if (legacyToolbar) {
-                mToolbarController = CarUiUtils.requireViewByRefId(baseLayout, R.id.car_ui_toolbar);
+                toolbarController = requireViewByRefId(baseLayout, R.id.car_ui_toolbar);
             } else {
-                mToolbarController = new ToolbarControllerImpl(baseLayout);
+                toolbarController = new ToolbarControllerImpl(baseLayout);
             }
         }
 
-        mInsetsUpdater = new InsetsUpdater(activity, baseLayout, windowContentView);
-        mInsetsUpdater.installListeners();
+        InsetsUpdater insetsUpdater = new InsetsUpdater(activity, baseLayout, contentView);
+
+        return Pair.create(toolbarController, insetsUpdater);
     }
 
     /**
@@ -180,7 +206,7 @@
      * none of the Activity/Fragments implement {@link InsetsChangedListener}, it will set
      * padding on the content view equal to the insets.
      */
-    private static class InsetsUpdater implements ViewTreeObserver.OnGlobalLayoutListener {
+    static final class InsetsUpdater {
         // These tags mark views that should overlay the content view in the base layout.
         // OEMs should add them to views in their base layout, ie: android:tag="car_ui_left_inset"
         // Apps will then be able to draw under these views, but will be encouraged to not put
@@ -190,26 +216,35 @@
         private static final String TOP_INSET_TAG = "car_ui_top_inset";
         private static final String BOTTOM_INSET_TAG = "car_ui_bottom_inset";
 
+        @Nullable
         private final Activity mActivity;
+        private final View mContentView;
+        private final View mContentViewContainer; // Equivalent to mContentView except in Media
         private final View mLeftInsetView;
         private final View mRightInsetView;
         private final View mTopInsetView;
         private final View mBottomInsetView;
         private InsetsChangedListener mInsetsChangedListenerDelegate;
 
-        private boolean mInsetsDirty = true;
         @NonNull
         private Insets mInsets = new Insets();
 
         /**
          * Constructs an InsetsUpdater that calculates and dispatches insets to an {@link Activity}.
          *
-         * @param activity    The activity that is using base layouts
+         * @param activity    The activity that is using base layouts. Used to dispatch insets to if
+         *                    it implements {@link InsetsChangedListener}
          * @param baseLayout  The root view of the base layout
          * @param contentView The android.R.id.content View
          */
-        InsetsUpdater(Activity activity, View baseLayout, View contentView) {
+        InsetsUpdater(
+                @Nullable Activity activity,
+                @NonNull View baseLayout,
+                @NonNull View contentView) {
             mActivity = activity;
+            mContentView = contentView;
+            mContentViewContainer = requireViewByRefId(baseLayout,
+                    R.id.car_ui_base_layout_content_container);
 
             mLeftInsetView = baseLayout.findViewWithTag(LEFT_INSET_TAG);
             mRightInsetView = baseLayout.findViewWithTag(RIGHT_INSET_TAG);
@@ -221,7 +256,7 @@
                             int oldLeft, int oldTop, int oldRight, int oldBottom) -> {
                         if (left != oldLeft || top != oldTop
                                 || right != oldRight || bottom != oldBottom) {
-                            mInsetsDirty = true;
+                            recalcInsets();
                         }
                     };
 
@@ -238,17 +273,7 @@
                 mBottomInsetView.addOnLayoutChangeListener(layoutChangeListener);
             }
             contentView.addOnLayoutChangeListener(layoutChangeListener);
-        }
-
-        /**
-         * Install a global layout listener, during which the insets will be recalculated and
-         * dispatched.
-         */
-        void installListeners() {
-            // The global layout listener will run after all the individual layout change listeners
-            // so that we only updateInsets once per layout, even if multiple inset views changed
-            mActivity.getWindow().getDecorView().getViewTreeObserver()
-                    .addOnGlobalLayoutListener(this);
+            mContentViewContainer.addOnLayoutChangeListener(layoutChangeListener);
         }
 
         @NonNull
@@ -261,37 +286,41 @@
         }
 
         /**
-         * onGlobalLayout() should recalculate the amount of insets we need, and then dispatch them.
+         * Recalculate the amount of insets we need, and then dispatch them.
          */
-        @Override
-        public void onGlobalLayout() {
-            if (!mInsetsDirty) {
-                return;
-            }
-
-            View content = CarUiUtils.requireViewByRefId(mActivity.getWindow().getDecorView(),
-                    android.R.id.content);
+        public void recalcInsets() {
 
             // Calculate how much each inset view overlays the content view
-            int top = 0;
-            int left = 0;
-            int right = 0;
-            int bottom = 0;
+
+            // These initial values are for Media Center's implementation of base layouts.
+            // They should evaluate to 0 in all other apps, because the content view and content
+            // view container have the same size and position there.
+            int top = Math.max(0,
+                    getTopOfView(mContentViewContainer) - getTopOfView(mContentView));
+            int left = Math.max(0,
+                    getLeftOfView(mContentViewContainer) - getLeftOfView(mContentView));
+            int right = Math.max(0,
+                    getRightOfView(mContentView) - getRightOfView(mContentViewContainer));
+            int bottom = Math.max(0,
+                    getBottomOfView(mContentView) - getBottomOfView(mContentViewContainer));
             if (mTopInsetView != null) {
-                top = Math.max(0, getBottomOfView(mTopInsetView) - getTopOfView(content));
+                top += Math.max(0,
+                        getBottomOfView(mTopInsetView) - getTopOfView(mContentViewContainer));
             }
             if (mBottomInsetView != null) {
-                bottom = Math.max(0, getBottomOfView(content) - getTopOfView(mBottomInsetView));
+                bottom += Math.max(0,
+                        getBottomOfView(mContentViewContainer) - getTopOfView(mBottomInsetView));
             }
             if (mLeftInsetView != null) {
-                left = Math.max(0, getRightOfView(mLeftInsetView) - getLeftOfView(content));
+                left += Math.max(0,
+                        getRightOfView(mLeftInsetView) - getLeftOfView(mContentViewContainer));
             }
             if (mRightInsetView != null) {
-                right = Math.max(0, getRightOfView(content) - getLeftOfView(mRightInsetView));
+                right += Math.max(0,
+                        getRightOfView(mContentViewContainer) - getLeftOfView(mRightInsetView));
             }
             Insets insets = new Insets(left, top, right, bottom);
 
-            mInsetsDirty = false;
             if (!insets.equals(mInsets)) {
                 mInsets = insets;
                 dispatchNewInsets(insets);
@@ -299,7 +328,8 @@
         }
 
         /**
-         * Dispatch the new {@link Insets} to the {@link Activity} and all of its
+         * Dispatch the new {@link Insets} to the {@link InsetsChangedListener} IIF there is one,
+         * otherwise dispatch the new {@link Insets} to the {@link Activity} and all of its
          * {@link Fragment Fragments}. If none of those implement {@link InsetsChangedListener},
          * we will set the value of the insets as padding on the content view.
          *
@@ -333,8 +363,7 @@
             }
 
             if (!handled) {
-                CarUiUtils.requireViewByRefId(mActivity.getWindow().getDecorView(),
-                        android.R.id.content).setPadding(insets.getLeft(), insets.getTop(),
+                mContentView.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/car-ui-lib/src/main/java/com/android/car/ui/core/CarUi.java
similarity index 78%
rename from car-ui-lib/src/com/android/car/ui/core/CarUi.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUi.java
index ad67121..a645b98 100644
--- a/car-ui-lib/src/com/android/car/ui/core/CarUi.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUi.java
@@ -16,15 +16,19 @@
 package com.android.car.ui.core;
 
 import android.app.Activity;
+import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.util.Pair;
 
 import com.android.car.ui.baselayout.Insets;
 import com.android.car.ui.baselayout.InsetsChangedListener;
+import com.android.car.ui.core.BaseLayoutController.InsetsUpdater;
 import com.android.car.ui.toolbar.ToolbarController;
 
 import java.lang.reflect.Method;
+import java.util.Objects;
 
 /**
  * Public interface for general CarUi static functions.
@@ -119,6 +123,34 @@
         return result;
     }
 
+    /**
+     * Most apps should not use this method, but instead rely on CarUi automatically
+     * installing the base layout into their activities. See {@link #requireToolbar(Activity)}.
+     *
+     * This method installs the base layout *around* the provided view. As a result, this view
+     * must have a parent ViewGroup.
+     *
+     * When using this method, you can't use the other activity-based methods.
+     * ({@link #requireToolbar(Activity)}, {@link #requireInsets(Activity)}, ect.)
+     *
+     * @param view The view to wrap inside a base layout.
+     * @param hasToolbar if there should be a toolbar in the base layout.
+     * @return The {@link ToolbarController}, which will be null if hasToolbar is false.
+     */
+    @Nullable
+    public static ToolbarController installBaseLayoutAround(
+            View view,
+            InsetsChangedListener insetsChangedListener,
+            boolean hasToolbar) {
+        Pair<ToolbarController, InsetsUpdater> results =
+                BaseLayoutController.installBaseLayoutAround(null, view, hasToolbar);
+
+        Objects.requireNonNull(results.second)
+                .replaceInsetsChangedListenerWith(insetsChangedListener);
+
+        return results.first;
+    }
+
     /* package */ static BaseLayoutController getBaseLayoutController(Activity activity) {
         if (activity.getClassLoader().equals(CarUi.class.getClassLoader())) {
             return BaseLayoutController.getBaseLayout(activity);
@@ -127,7 +159,7 @@
             // The usage of the alternate classloader is to accommodate GMSCore.
             // Some activities are loaded dynamically from external modules.
             try {
-                Class baseLayoutControllerClass = activity.getClassLoader()
+                Class<?> baseLayoutControllerClass = activity.getClassLoader()
                         .loadClass(BaseLayoutController.class.getName());
                 Method method = baseLayoutControllerClass
                         .getDeclaredMethod("getBaseLayout", Activity.class);
diff --git a/car-ui-lib/src/com/android/car/ui/core/CarUiInstaller.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUiInstaller.java
similarity index 99%
rename from car-ui-lib/src/com/android/car/ui/core/CarUiInstaller.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUiInstaller.java
index e845979..4693b18 100644
--- a/car-ui-lib/src/com/android/car/ui/core/CarUiInstaller.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUiInstaller.java
@@ -183,7 +183,7 @@
         // The usage of the alternate classloader is to accommodate GMSCore.
         // Some activities are loaded dynamically from external modules.
         try {
-            Class baseLayoutControllerClass =
+            Class<?> baseLayoutControllerClass =
                     activity.getClassLoader()
                             .loadClass(BaseLayoutController.class.getName());
             Method method = baseLayoutControllerClass
diff --git a/car-ui-lib/src/com/android/car/ui/core/CheckCarUiComponents.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CheckCarUiComponents.java
similarity index 70%
rename from car-ui-lib/src/com/android/car/ui/core/CheckCarUiComponents.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CheckCarUiComponents.java
index b4fd358..bd8462d 100644
--- a/car-ui-lib/src/com/android/car/ui/core/CheckCarUiComponents.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CheckCarUiComponents.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Bundle;
+import android.os.Handler;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -30,6 +31,7 @@
 
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.car.ui.R;
 import com.android.car.ui.utils.CarUiUtils;
 
 /**
@@ -39,45 +41,29 @@
  * To check if the activity is using the CarUI components properly, navigate to the activity and
  * run: adb shell am broadcast -a com.android.car.ui.intent.CHECK_CAR_UI_COMPONENTS. Filter
  * the logs with "CheckCarUiComponents". This is ONLY available for debug and eng builds.
+ *
+ * Other than using the adb command you can also set a boolean resource
+ * "car_ui_escrow_check_components_automatically" to true. This will generate the logs 2 seconds
+ * after launching any activity.
  */
 class CheckCarUiComponents implements Application.ActivityLifecycleCallbacks {
     private static final String TAG = CheckCarUiComponents.class.getSimpleName();
     private static final String INTENT_FILTER = "com.android.car.ui.intent.CHECK_CAR_UI_COMPONENTS";
+    private static final String NO_CAR_UI_RV = "CarUiRecyclerView not used:";
+    private static final String NO_CAR_UI_TOOLBAR = "CarUiToolbar is not used: ";
+    private static final String NO_CAR_UI_TOOLBAR_BL = "CarUiBaseLayoutToolbar is not used: ";
+    private static final String NO_CAR_UI_PREFERENCE = "CarUiPreference is not used: ";
+    private static final String NO_LIST_ITEM =
+            "CarUiListItem are not used within CarUiRecyclerView: ";
     private View mRootView;
     private boolean mIsScreenVisible;
 
+    private Handler mHandler = new Handler();
+
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (!mIsScreenVisible) {
-                return;
-            }
-
-            CarUiComponents carUiComponents = new CarUiComponents();
-            checkForCarUiComponents(mRootView, carUiComponents);
-            if (carUiComponents.mIsUsingCarUiRecyclerView
-                    && !carUiComponents.mIsCarUiRecyclerViewUsingListItem) {
-                Log.e(TAG, "CarUiListItem are not used within CarUiRecyclerView: ");
-                showToast(context, "CarUiListItem are not used within CarUiRecyclerView");
-            }
-            if (carUiComponents.mIsUsingAndroidXRecyclerView) {
-                Log.e(TAG, "CarUiRecyclerView not used: ");
-                showToast(context, "CarUiRecycler is not used");
-            }
-            if (!carUiComponents.mIsUsingCarUiToolbar) {
-                Log.e(TAG, "CarUiToolbar is not used: ");
-                showToast(context, "CarUiToolbar is not used");
-            }
-            if (!carUiComponents.mIsUsingCarUiBaseLayoutToolbar
-                    && carUiComponents.mIsUsingCarUiToolbar) {
-                Log.e(TAG, "CarUiBaseLayoutToolbar is not used: ");
-                showToast(context, "CarUiBaseLayoutToolbar is not used");
-            }
-            if (carUiComponents.mIsUsingCarUiRecyclerViewForPreference
-                    && !carUiComponents.mIsUsingCarUiPreference) {
-                Log.e(TAG, "CarUiPreference is not used: ");
-                showToast(context, "CarUiPreference is not used");
-            }
+            checkForComponents(context, true);
         }
     };
 
@@ -97,8 +83,17 @@
 
     @Override
     public void onActivityResumed(Activity activity) {
+    }
+
+    @Override
+    public void onActivityPostResumed(Activity activity) {
         mRootView = activity.getWindow().getDecorView().getRootView();
         mIsScreenVisible = true;
+        if (checkComponentsForAllActivities(activity)) {
+            // post a message after 2 seconds delay. This is an arbitrary number so that activity
+            // is ready with all its views rendered by this time.
+            mHandler.postDelayed(() -> checkForComponents(activity, false), 2000);
+        }
     }
 
     @Override
@@ -108,6 +103,7 @@
 
     @Override
     public void onActivityStopped(Activity activity) {
+        mHandler.removeCallbacksAndMessages(null);
     }
 
     @Override
@@ -122,6 +118,43 @@
         }
     }
 
+    private boolean checkComponentsForAllActivities(Context context) {
+        return context.getResources().getBoolean(
+                R.bool.car_ui_escrow_check_components_automatically);
+    }
+
+    private void checkForComponents(Context context, boolean showToast) {
+        if (!mIsScreenVisible) {
+            return;
+        }
+
+        CarUiComponents carUiComponents = new CarUiComponents();
+        checkForCarUiComponents(mRootView, carUiComponents);
+        if (carUiComponents.mIsUsingCarUiRecyclerView
+                && !carUiComponents.mIsCarUiRecyclerViewUsingListItem) {
+            Log.d(TAG, NO_LIST_ITEM);
+            mayShowToast(context, NO_LIST_ITEM, showToast);
+        }
+        if (carUiComponents.mIsUsingAndroidXRecyclerView) {
+            Log.d(TAG, NO_CAR_UI_RV);
+            mayShowToast(context, NO_CAR_UI_RV, showToast);
+        }
+        if (!carUiComponents.mIsUsingCarUiToolbar) {
+            Log.d(TAG, NO_CAR_UI_TOOLBAR);
+            mayShowToast(context, NO_CAR_UI_TOOLBAR, showToast);
+        }
+        if (!carUiComponents.mIsUsingCarUiBaseLayoutToolbar
+                && carUiComponents.mIsUsingCarUiToolbar) {
+            Log.d(TAG, NO_CAR_UI_TOOLBAR_BL);
+            mayShowToast(context, NO_CAR_UI_TOOLBAR_BL, showToast);
+        }
+        if (carUiComponents.mIsUsingCarUiRecyclerViewForPreference
+                && !carUiComponents.mIsUsingCarUiPreference) {
+            Log.d(TAG, NO_CAR_UI_PREFERENCE);
+            mayShowToast(context, NO_CAR_UI_PREFERENCE, showToast);
+        }
+    }
+
     private void checkForCarUiComponents(View v, CarUiComponents carUiComponents) {
         viewHasChildMatching(v, view -> {
             if (isCarUiRecyclerView(view)) {
@@ -194,7 +227,10 @@
         return view.getClass() == RecyclerView.class;
     }
 
-    private static void showToast(Context context, String message) {
+    private static void mayShowToast(Context context, String message, boolean show) {
+        if (!show) {
+            return;
+        }
         Toast.makeText(context, message, Toast.LENGTH_LONG).show();
     }
 
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiDialogFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiDialogFragment.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/CarUiDialogFragment.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiDialogFragment.java
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiDropDownPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiDropDownPreference.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/CarUiDropDownPreference.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiDropDownPreference.java
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiEditTextPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiEditTextPreference.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/CarUiEditTextPreference.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiEditTextPreference.java
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiListPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiListPreference.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/CarUiListPreference.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiListPreference.java
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiMultiSelectListPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiMultiSelectListPreference.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/CarUiMultiSelectListPreference.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiMultiSelectListPreference.java
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiPreference.java
similarity index 93%
rename from car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiPreference.java
index 111fbf5..8db5733 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiPreference.java
@@ -106,6 +106,16 @@
     }
 
     /**
+     * An exact copy of {@link androidx.preference.Preference#performClick(View)}
+     * This method was added here because super.performClick(View) is not open
+     * for app usage.
+     */
+    @SuppressWarnings("RestrictTo")
+    void performClickUnrestricted(View v) {
+        performClick();
+    }
+
+    /**
      * This is similar to {@link Preference#performClick()} with the only difference that we do not
      * return when view is not enabled.
      */
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiRadioButtonPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiRadioButtonPreference.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/CarUiRadioButtonPreference.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiRadioButtonPreference.java
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiSeekBarDialogPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiSeekBarDialogPreference.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/CarUiSeekBarDialogPreference.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiSeekBarDialogPreference.java
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiSwitchPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiSwitchPreference.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/CarUiSwitchPreference.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiSwitchPreference.java
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiTwoActionPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionPreference.java
similarity index 85%
rename from car-ui-lib/src/com/android/car/ui/preference/CarUiTwoActionPreference.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionPreference.java
index 3b9a583..3b9693a 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/CarUiTwoActionPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionPreference.java
@@ -88,14 +88,20 @@
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
+        View containerWithoutWidget = CarUiUtils.findViewByRefId(holder.itemView,
+                R.id.car_ui_preference_container_without_widget);
         View actionContainer = CarUiUtils.findViewByRefId(holder.itemView,
                 R.id.action_widget_container);
         View widgetFrame = CarUiUtils.findViewByRefId(holder.itemView, android.R.id.widget_frame);
+        holder.itemView.setFocusable(!mIsActionShown);
+        containerWithoutWidget.setOnClickListener(
+                mIsActionShown ? this::performClickUnrestricted : null);
+        containerWithoutWidget.setClickable(mIsActionShown);
+        containerWithoutWidget.setFocusable(mIsActionShown);
+        actionContainer.setVisibility(mIsActionShown ? View.VISIBLE : View.GONE);
+        widgetFrame.setFocusable(mIsActionShown);
         if (mIsActionShown) {
-            actionContainer.setVisibility(View.VISIBLE);
             onBindWidgetFrame(widgetFrame);
-        } else {
-            actionContainer.setVisibility(View.GONE);
         }
     }
 
diff --git a/car-ui-lib/src/com/android/car/ui/preference/DialogFragmentCallbacks.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/DialogFragmentCallbacks.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/DialogFragmentCallbacks.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/DialogFragmentCallbacks.java
diff --git a/car-ui-lib/src/com/android/car/ui/preference/DisabledPreferenceCallback.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/DisabledPreferenceCallback.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/DisabledPreferenceCallback.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/DisabledPreferenceCallback.java
diff --git a/car-ui-lib/src/com/android/car/ui/preference/EditTextPreferenceDialogFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/EditTextPreferenceDialogFragment.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/EditTextPreferenceDialogFragment.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/EditTextPreferenceDialogFragment.java
diff --git a/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ListPreferenceFragment.java
similarity index 82%
rename from car-ui-lib/src/com/android/car/ui/preference/ListPreferenceFragment.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ListPreferenceFragment.java
index 348ec54..2f63fbf 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceFragment.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ListPreferenceFragment.java
@@ -51,31 +51,23 @@
  */
 public class ListPreferenceFragment extends Fragment implements InsetsChangedListener {
 
-    private ToolbarController mToolbar;
+    private static final String ARG_FULLSCREEN = "fullscreen";
+
     private ListPreference mPreference;
     private CarUiContentListItem mSelectedItem;
     private int mSelectedIndex = -1;
-    private final Toolbar.OnBackListener mOnBackListener = () -> {
-        if (mSelectedIndex >= 0 && mPreference != null) {
-            String entryValue = mPreference.getEntryValues()[mSelectedIndex].toString();
-
-            if (mPreference.callChangeListener(entryValue)) {
-                mPreference.setValue(entryValue);
-            }
-        }
-
-        return false;
-    };
+    private boolean mFullScreen;
 
     /**
      * Returns a new instance of {@link ListPreferenceFragment} for the {@link ListPreference} with
      * the given {@code key}.
      */
     @NonNull
-    static ListPreferenceFragment newInstance(String key) {
+    static ListPreferenceFragment newInstance(String key, boolean fullScreen) {
         ListPreferenceFragment fragment = new ListPreferenceFragment();
         Bundle b = new Bundle(/* capacity= */ 1);
         b.putString(ARG_KEY, key);
+        b.putBoolean(ARG_FULLSCREEN, fullScreen);
         fragment.setArguments(b);
         return fragment;
     }
@@ -96,29 +88,34 @@
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         final CarUiRecyclerView carUiRecyclerView = CarUiUtils.requireViewByRefId(view, R.id.list);
-        mToolbar = CarUi.getToolbar(getActivity());
+        mFullScreen = requireArguments().getBoolean(ARG_FULLSCREEN, true);
+        ToolbarController toolbar = mFullScreen ? CarUi.getToolbar(getActivity()) : null;
 
         // TODO(b/150230923) remove the code for the old toolbar height change when apps are ready
-        if (mToolbar == null) {
-            Toolbar toolbarView = CarUiUtils.requireViewByRefId(view, R.id.toolbar);
-            mToolbar = toolbarView;
+        if (toolbar == null) {
+            Toolbar toolbarView = CarUiUtils.findViewByRefId(view, R.id.toolbar);
+            toolbar = toolbarView;
 
-            carUiRecyclerView.setPadding(0, toolbarView.getHeight(), 0, 0);
-            toolbarView.registerToolbarHeightChangeListener(newHeight -> {
-                if (carUiRecyclerView.getPaddingTop() == newHeight) {
-                    return;
-                }
+            if (toolbarView != null) {
+                carUiRecyclerView.setPadding(0, toolbarView.getHeight(), 0, 0);
+                toolbarView.registerToolbarHeightChangeListener(newHeight -> {
+                    if (carUiRecyclerView.getPaddingTop() == newHeight) {
+                        return;
+                    }
 
-                int oldHeight = carUiRecyclerView.getPaddingTop();
-                carUiRecyclerView.setPadding(0, newHeight, 0, 0);
-                carUiRecyclerView.scrollBy(0, oldHeight - newHeight);
-            });
+                    int oldHeight = carUiRecyclerView.getPaddingTop();
+                    carUiRecyclerView.setPadding(0, newHeight, 0, 0);
+                    carUiRecyclerView.scrollBy(0, oldHeight - newHeight);
+                });
+            }
         }
 
         carUiRecyclerView.setClipToPadding(false);
         mPreference = getListPreference();
-        mToolbar.setTitle(mPreference.getTitle());
-        mToolbar.setState(Toolbar.State.SUBPAGE);
+        if (toolbar != null) {
+            toolbar.setTitle(mPreference.getTitle());
+            toolbar.setState(Toolbar.State.SUBPAGE);
+        }
 
         CharSequence[] entries = mPreference.getEntries();
         CharSequence[] entryValues = mPreference.getEntryValues();
@@ -166,7 +163,6 @@
     @Override
     public void onStart() {
         super.onStart();
-        mToolbar.registerOnBackListener(mOnBackListener);
         Insets insets = CarUi.getInsets(getActivity());
         if (insets != null) {
             onCarUiInsetsChanged(insets);
@@ -176,15 +172,21 @@
     @Override
     public void onStop() {
         super.onStop();
-        mToolbar.unregisterOnBackListener(mOnBackListener);
+        updatePreference();
+    }
+
+    private void updatePreference() {
+        if (mSelectedIndex >= 0 && mPreference != null) {
+            String entryValue = mPreference.getEntryValues()[mSelectedIndex].toString();
+
+            if (mPreference.callChangeListener(entryValue)) {
+                mPreference.setValue(entryValue);
+            }
+        }
     }
 
     private ListPreference getListPreference() {
-        if (getArguments() == null) {
-            throw new IllegalStateException("Preference arguments cannot be null");
-        }
-
-        String key = getArguments().getString(ARG_KEY);
+        String key = requireArguments().getString(ARG_KEY);
         DialogPreference.TargetFragment fragment =
                 (DialogPreference.TargetFragment) getTargetFragment();
 
@@ -212,6 +214,9 @@
 
     @Override
     public void onCarUiInsetsChanged(@NonNull Insets insets) {
+        if (!mFullScreen) {
+            return;
+        }
         View view = requireView();
         CarUiUtils.requireViewByRefId(view, R.id.list)
                 .setPadding(0, insets.getTop(), 0, insets.getBottom());
diff --git a/car-ui-lib/src/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
similarity index 84%
rename from car-ui-lib/src/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
index 1d4a3a2..b1b8a28 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
@@ -52,26 +52,23 @@
  */
 public class MultiSelectListPreferenceFragment extends Fragment implements InsetsChangedListener {
 
+    private static final String ARG_FULLSCREEN = "fullscreen";
+
     private CarUiMultiSelectListPreference mPreference;
     private Set<String> mNewValues;
     private ToolbarController mToolbar;
-    private final Toolbar.OnBackListener mOnBackListener = () -> {
-        if (mPreference.callChangeListener(mNewValues)) {
-            mPreference.setValues(mNewValues);
-        }
-
-        return false;
-    };
+    private boolean mFullScreen;
 
     /**
      * Returns a new instance of {@link MultiSelectListPreferenceFragment} for the {@link
      * CarUiMultiSelectListPreference} with the given {@code key}.
      */
     @NonNull
-    static MultiSelectListPreferenceFragment newInstance(String key) {
+    static MultiSelectListPreferenceFragment newInstance(String key, boolean fullScreen) {
         MultiSelectListPreferenceFragment fragment = new MultiSelectListPreferenceFragment();
         Bundle b = new Bundle(/* capacity= */ 1);
         b.putString(ARG_KEY, key);
+        b.putBoolean(ARG_FULLSCREEN, fullScreen);
         fragment.setArguments(b);
         return fragment;
     }
@@ -92,30 +89,35 @@
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         final CarUiRecyclerView recyclerView = CarUiUtils.requireViewByRefId(view, R.id.list);
-        mToolbar = CarUi.getToolbar(requireActivity());
+        mFullScreen = requireArguments().getBoolean(ARG_FULLSCREEN, true);
+        mToolbar = mFullScreen ? CarUi.getToolbar(requireActivity()) : null;
 
         // TODO(b/150230923) remove the code for the old toolbar height change when apps are ready
         if (mToolbar == null) {
-            Toolbar toolbarView = CarUiUtils.requireViewByRefId(view, R.id.toolbar);
+            Toolbar toolbarView = CarUiUtils.findViewByRefId(view, R.id.toolbar);
             mToolbar = toolbarView;
 
-            recyclerView.setPadding(0, toolbarView.getHeight(), 0, 0);
-            toolbarView.registerToolbarHeightChangeListener(newHeight -> {
-                if (recyclerView.getPaddingTop() == newHeight) {
-                    return;
-                }
+            if (toolbarView != null) {
+                recyclerView.setPadding(0, toolbarView.getHeight(), 0, 0);
+                toolbarView.registerToolbarHeightChangeListener(newHeight -> {
+                    if (recyclerView.getPaddingTop() == newHeight) {
+                        return;
+                    }
 
-                int oldHeight = recyclerView.getPaddingTop();
-                recyclerView.setPadding(0, newHeight, 0, 0);
-                recyclerView.scrollBy(0, oldHeight - newHeight);
-            });
+                    int oldHeight = recyclerView.getPaddingTop();
+                    recyclerView.setPadding(0, newHeight, 0, 0);
+                    recyclerView.scrollBy(0, oldHeight - newHeight);
+                });
+            }
         }
 
         mPreference = getPreference();
 
         recyclerView.setClipToPadding(false);
-        mToolbar.setTitle(mPreference.getTitle());
-        mToolbar.setState(Toolbar.State.SUBPAGE);
+        if (mToolbar != null) {
+            mToolbar.setTitle(mPreference.getTitle());
+            mToolbar.setState(Toolbar.State.SUBPAGE);
+        }
 
         mNewValues = new HashSet<>(mPreference.getValues());
         CharSequence[] entries = mPreference.getEntries();
@@ -161,7 +163,6 @@
     @Override
     public void onStart() {
         super.onStart();
-        mToolbar.registerOnBackListener(mOnBackListener);
         Insets insets = CarUi.getInsets(getActivity());
         if (insets != null) {
             onCarUiInsetsChanged(insets);
@@ -171,7 +172,13 @@
     @Override
     public void onStop() {
         super.onStop();
-        mToolbar.unregisterOnBackListener(mOnBackListener);
+        updatePreference();
+    }
+
+    private void updatePreference() {
+        if (mPreference.callChangeListener(mNewValues)) {
+            mPreference.setValues(mNewValues);
+        }
     }
 
     private CarUiMultiSelectListPreference getPreference() {
@@ -207,6 +214,9 @@
 
     @Override
     public void onCarUiInsetsChanged(@NonNull Insets insets) {
+        if (!mFullScreen) {
+            return;
+        }
         View view = requireView();
         CarUiUtils.requireViewByRefId(view, R.id.list)
                 .setPadding(0, insets.getTop(), 0, insets.getBottom());
diff --git a/car-ui-lib/src/com/android/car/ui/preference/PreferenceDialogFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceDialogFragment.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/PreferenceDialogFragment.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceDialogFragment.java
diff --git a/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceFragment.java
similarity index 86%
rename from car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceFragment.java
index 8b66b8e..3f5b5cb 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceFragment.java
@@ -37,8 +37,10 @@
 import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceScreen;
 import androidx.preference.SwitchPreference;
+import androidx.preference.TwoStatePreference;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.car.ui.FocusArea;
 import com.android.car.ui.R;
 import com.android.car.ui.baselayout.Insets;
 import com.android.car.ui.baselayout.InsetsChangedListener;
@@ -70,15 +72,29 @@
     private static final String DIALOG_FRAGMENT_TAG =
             "com.android.car.ui.PreferenceFragment.DIALOG";
 
+    /**
+     * This method can be overridden to indicate whether or not this fragment covers the
+     * whole screen. When it returns false, the preference fragment will not attempt to change
+     * the CarUi base layout toolbar (but will still have its own toolbar and change it when using
+     * non-baselayout toolbars), and will also not take into account CarUi insets.
+     *
+     * @return Whether to PreferenceFragment takes up the whole app's space. Defaults to true.
+     */
+    protected boolean isFullScreenFragment() {
+        return true;
+    }
+
     @Override
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
 
-        ToolbarController baseLayoutToolbar = CarUi.getToolbar(getActivity());
-        if (baseLayoutToolbar != null) {
-            baseLayoutToolbar.setState(Toolbar.State.SUBPAGE);
-            if (getPreferenceScreen() != null) {
-                baseLayoutToolbar.setTitle(getPreferenceScreen().getTitle());
+        if (isFullScreenFragment()) {
+            ToolbarController baseLayoutToolbar = CarUi.getToolbar(getActivity());
+            if (baseLayoutToolbar != null) {
+                baseLayoutToolbar.setState(Toolbar.State.SUBPAGE);
+                if (getPreferenceScreen() != null) {
+                    baseLayoutToolbar.setTitle(getPreferenceScreen().getTitle());
+                }
             }
         }
 
@@ -98,6 +114,10 @@
             int oldHeight = recyclerView.getPaddingTop();
             recyclerView.setPadding(0, newHeight, 0, 0);
             recyclerView.scrollBy(0, oldHeight - newHeight);
+
+            FocusArea focusArea = CarUiUtils.requireViewByRefId(view, R.id.car_ui_focus_area);
+            focusArea.setHighlightPadding(0, newHeight, 0, 0);
+            focusArea.setBoundsOffset(0, newHeight, 0, 0);
         });
 
         recyclerView.setClipToPadding(false);
@@ -117,7 +137,14 @@
 
     @Override
     public void onCarUiInsetsChanged(@NonNull Insets insets) {
+        if (!isFullScreenFragment()) {
+            return;
+        }
+
         View view = requireView();
+        FocusArea focusArea = CarUiUtils.requireViewByRefId(view, R.id.car_ui_focus_area);
+        focusArea.setHighlightPadding(0, insets.getTop(), 0, insets.getBottom());
+        focusArea.setBoundsOffset(0, insets.getTop(), 0, insets.getBottom());
         CarUiUtils.requireViewByRefId(view, R.id.recycler_view)
                 .setPadding(0, insets.getTop(), 0, insets.getBottom());
         view.setPadding(insets.getLeft(), 0, insets.getRight(), 0);
@@ -151,9 +178,10 @@
         if (preference instanceof EditTextPreference) {
             f = EditTextPreferenceDialogFragment.newInstance(preference.getKey());
         } else if (preference instanceof ListPreference) {
-            f = ListPreferenceFragment.newInstance(preference.getKey());
+            f = ListPreferenceFragment.newInstance(preference.getKey(), isFullScreenFragment());
         } else if (preference instanceof MultiSelectListPreference) {
-            f = MultiSelectListPreferenceFragment.newInstance(preference.getKey());
+            f = MultiSelectListPreferenceFragment
+                    .newInstance(preference.getKey(), isFullScreenFragment());
         } else if (preference instanceof CarUiSeekBarDialogPreference) {
             f = SeekbarPreferenceDialogFragment.newInstance(preference.getKey());
         } else {
@@ -360,6 +388,18 @@
             toMulti.setEntries(fromMulti.getEntries());
             toMulti.setEntryValues(fromMulti.getEntryValues());
             toMulti.setValues(fromMulti.getValues());
+        } else if (from instanceof TwoStatePreference) {
+            TwoStatePreference fromTwoState = (TwoStatePreference) from;
+            TwoStatePreference toTwoState = (TwoStatePreference) to;
+            toTwoState.setSummaryOn(fromTwoState.getSummaryOn());
+            toTwoState.setSummaryOff(fromTwoState.getSummaryOff());
+
+            if (from instanceof SwitchPreference) {
+                SwitchPreference fromSwitch = (SwitchPreference) from;
+                SwitchPreference toSwitch = (SwitchPreference) to;
+                toSwitch.setSwitchTextOn(fromSwitch.getSwitchTextOn());
+                toSwitch.setSwitchTextOff(fromSwitch.getSwitchTextOff());
+            }
         }
 
         // We don't need to add checks for things that we will never replace,
diff --git a/car-ui-lib/src/com/android/car/ui/preference/SeekbarPreferenceDialogFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/SeekbarPreferenceDialogFragment.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/preference/SeekbarPreferenceDialogFragment.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/SeekbarPreferenceDialogFragment.java
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiCheckBoxListItem.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiCheckBoxListItem.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/CarUiCheckBoxListItem.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiCheckBoxListItem.java
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiContentListItem.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiContentListItem.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/CarUiContentListItem.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiContentListItem.java
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiHeaderListItem.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiHeaderListItem.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/CarUiHeaderListItem.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiHeaderListItem.java
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItem.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItem.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItem.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItem.java
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
similarity index 98%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
index eac856a..8bc39eb 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
@@ -309,10 +309,8 @@
                         mActionContainerTouchInterceptor.setOnClickListener(
                                 (container) -> {
                                     if (item.getSupplementalIconOnClickListener() != null) {
-                                        item.getSupplementalIconOnClickListener().onClick(mIcon);
-                                    }
-                                    if (itemOnClickListener != null) {
-                                        itemOnClickListener.onClick(item);
+                                        item.getSupplementalIconOnClickListener().onClick(
+                                                mSupplementalIcon);
                                     }
                                 });
                         mTouchInterceptor.setVisibility(View.GONE);
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRadioButtonListItem.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRadioButtonListItem.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRadioButtonListItem.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRadioButtonListItem.java
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRadioButtonListItemAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRadioButtonListItemAdapter.java
similarity index 96%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRadioButtonListItemAdapter.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRadioButtonListItemAdapter.java
index 9485678..80d763e 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRadioButtonListItemAdapter.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRadioButtonListItemAdapter.java
@@ -106,6 +106,13 @@
         }
     }
 
+    /*
+     * @return the position of the currently selected item, -1 if no item is selected.
+     */
+    public int getSelectedItemPosition() {
+        return mSelectedIndex;
+    }
+
     static class RadioButtonListItemViewHolder extends ListItemViewHolder {
         /**
          * Callback to be invoked when the checked state of a {@link RadioButtonListItemViewHolder}
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
similarity index 68%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
index 8f2cb7d..e9e5b3c 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
@@ -16,6 +16,7 @@
 package com.android.car.ui.recyclerview;
 
 import static com.android.car.ui.utils.CarUiUtils.findViewByRefId;
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_CONTAINER;
 import static com.android.car.ui.utils.RotaryConstants.ROTARY_HORIZONTALLY_SCROLLABLE;
 import static com.android.car.ui.utils.RotaryConstants.ROTARY_VERTICALLY_SCROLLABLE;
 
@@ -25,8 +26,6 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -36,6 +35,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
@@ -52,19 +52,17 @@
 import com.android.car.ui.recyclerview.decorations.linear.LinearDividerItemDecoration;
 import com.android.car.ui.recyclerview.decorations.linear.LinearOffsetItemDecoration;
 import com.android.car.ui.recyclerview.decorations.linear.LinearOffsetItemDecoration.OffsetPosition;
-import com.android.car.ui.toolbar.Toolbar;
 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 {
+public final class CarUiRecyclerView extends RecyclerView {
 
     private static final String TAG = "CarUiRecyclerView";
 
@@ -78,17 +76,21 @@
     private String mScrollBarClass;
     private int mScrollBarPaddingTop;
     private int mScrollBarPaddingBottom;
-    private boolean mHasScrolledToTop = false;
 
     @Nullable
     private ScrollBar mScrollBar;
-    private int mInitialTopPadding;
 
     @Nullable
-    private GridOffsetItemDecoration mOffsetItemDecoration;
-    @NonNull
+    private GridOffsetItemDecoration mTopOffsetItemDecorationGrid;
+    @Nullable
+    private GridOffsetItemDecoration mBottomOffsetItemDecorationGrid;
+    @Nullable
+    private RecyclerView.ItemDecoration mTopOffsetItemDecorationLinear;
+    @Nullable
+    private RecyclerView.ItemDecoration mBottomOffsetItemDecorationLinear;
+    @Nullable
     private GridDividerItemDecoration mDividerItemDecorationGrid;
-    @NonNull
+    @Nullable
     private RecyclerView.ItemDecoration mDividerItemDecorationLinear;
     private int mNumOfColumns;
     private boolean mInstallingExtScrollBar = false;
@@ -100,6 +102,23 @@
     @Nullable
     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 boolean mHasScrolled = false;
+
+    private OnScrollListener mOnScrollListener = new OnScrollListener() {
+        @Override
+        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+            if (dx > 0 || dy > 0) {
+                mHasScrolled = true;
+                removeOnScrollListener(this);
+            }
+        }
+    };
 
     /**
      * The possible values for setScrollBarPosition. The default value is actually {@link
@@ -112,13 +131,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;
     }
 
     /**
@@ -145,8 +166,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);
     }
@@ -167,7 +187,7 @@
     }
 
     private void init(Context context, AttributeSet attrs, int defStyleAttr) {
-        initRotaryScroll(context, attrs, defStyleAttr);
+        initRotaryScroll();
         setClipToPadding(false);
         TypedArray a = context.obtainStyledAttributes(
                 attrs,
@@ -185,7 +205,7 @@
         @CarUiRecyclerViewLayout int carUiRecyclerViewLayout =
                 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);
 
         mDividerItemDecorationLinear = new LinearDividerItemDecoration(
@@ -197,92 +217,95 @@
                         context.getDrawable(R.drawable.car_ui_divider),
                         mNumOfColumns);
 
-        int topOffset = a.getInteger(R.styleable.CarUiRecyclerView_topOffset, /* defValue= */0);
-        int bottomOffset = a.getInteger(
+        mTopOffset = a.getInteger(R.styleable.CarUiRecyclerView_topOffset, /* defValue= */0);
+        mBottomOffset = a.getInteger(
                 R.styleable.CarUiRecyclerView_bottomOffset, /* defValue= */0);
-        if (carUiRecyclerViewLayout == CarUiRecyclerViewLayout.LINEAR) {
+        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);
 
-            if (enableDivider) {
-                addItemDecoration(mDividerItemDecorationLinear);
-            }
-            RecyclerView.ItemDecoration topOffsetItemDecoration =
-                    new LinearOffsetItemDecoration(topOffset, OffsetPosition.START);
+        mIsInitialized = true;
 
-            RecyclerView.ItemDecoration bottomOffsetItemDecoration =
-                    new LinearOffsetItemDecoration(bottomOffset, OffsetPosition.END);
-
-            addItemDecoration(topOffsetItemDecoration);
-            addItemDecoration(bottomOffsetItemDecoration);
+        // Check if a layout manager has already been set via XML
+        boolean isLayoutMangerSet = getLayoutManager() != null;
+        if (!isLayoutMangerSet && carUiRecyclerViewLayout == CarUiRecyclerViewLayout.LINEAR) {
             setLayoutManager(new LinearLayoutManager(getContext()));
-        } else {
-
-            if (enableDivider) {
-                addItemDecoration(mDividerItemDecorationGrid);
-            }
-
-            mOffsetItemDecoration =
-                    new GridOffsetItemDecoration(topOffset, mNumOfColumns,
-                            OffsetPosition.START);
-
-            GridOffsetItemDecoration bottomOffsetItemDecoration =
-                    new GridOffsetItemDecoration(bottomOffset, mNumOfColumns,
-                            OffsetPosition.END);
-
-            addItemDecoration(mOffsetItemDecoration);
-            addItemDecoration(bottomOffsetItemDecoration);
+        } else if (!isLayoutMangerSet && carUiRecyclerViewLayout == CarUiRecyclerViewLayout.GRID) {
             setLayoutManager(new GridLayoutManager(getContext(), mNumOfColumns));
-            setNumOfColumns(mNumOfColumns);
         }
+        addOnScrollListener(mOnScrollListener);
 
         a.recycle();
+
         if (!mScrollBarEnabled) {
             return;
         }
 
+        mContainer = new LinearLayout(getContext());
+
         setVerticalScrollBarEnabled(false);
         setHorizontalScrollBarEnabled(false);
 
         mScrollBarClass = context.getResources().getString(R.string.car_ui_scrollbar_component);
-        this.getViewTreeObserver()
-                .addOnGlobalLayoutListener(() -> {
-                    if (!mHasScrolledToTop && getLayoutManager() != null) {
-                        // Scroll to the top after the first global layout, so that
-                        // we can set padding for the insets and still have the
-                        // recyclerview start at the top.
-                        new Handler(Objects.requireNonNull(Looper.myLooper())).post(() ->
-                                getLayoutManager().scrollToPosition(0));
-                        mHasScrolledToTop = true;
-                    }
+    }
 
-                    if (mInitialTopPadding == 0) {
-                        mInitialTopPadding = getPaddingTop();
-                    }
-                });
+    @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));
+        }
     }
 
     /**
-     * If this view's content description isn't set to opt out of scrolling via the rotary
-     * controller, initialize it accordingly.
+     * If this view's content description is set to opt into scrolling via the rotary controller,
+     * initialize it accordingly.
      */
-    private void initRotaryScroll(Context context, AttributeSet attrs, int defStyleAttr) {
+    private void initRotaryScroll() {
         CharSequence contentDescription = getContentDescription();
-        if (contentDescription == null) {
-            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
-                    defStyleAttr, /* defStyleRes= */ 0);
-            int orientation = a.getInt(R.styleable.RecyclerView_android_orientation,
-                    LinearLayout.VERTICAL);
-            setContentDescription(
-                    orientation == LinearLayout.HORIZONTAL
-                            ? ROTARY_HORIZONTALLY_SCROLLABLE
-                            : ROTARY_VERTICALLY_SCROLLABLE);
-        } else if (!ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription)
-                && !ROTARY_VERTICALLY_SCROLLABLE.contentEquals(contentDescription)) {
-            return;
-        }
+        boolean rotaryScrollEnabled = contentDescription != null
+                && (ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription)
+                || ROTARY_VERTICALLY_SCROLLABLE.contentEquals(contentDescription));
 
-        // Convert SOURCE_ROTARY_ENCODER scroll events into SOURCE_MOUSE scroll events that
-        // RecyclerView knows how to handle.
-        setOnGenericMotionListener((v, event) -> {
+        // If rotary scrolling is enabled, set a generic motion event listener to convert
+        // SOURCE_ROTARY_ENCODER scroll events into SOURCE_MOUSE scroll events that RecyclerView
+        // knows how to handle.
+        setOnGenericMotionListener(rotaryScrollEnabled ? (v, event) -> {
             if (event.getAction() == MotionEvent.ACTION_SCROLL) {
                 if (event.getSource() == InputDevice.SOURCE_ROTARY_ENCODER) {
                     MotionEvent mouseEvent = MotionEvent.obtain(event);
@@ -292,11 +315,11 @@
                 }
             }
             return false;
-        });
+        } : null);
 
-        // Mark this view as focusable. This view will be focused when no focusable elements are
-        // visible.
-        setFocusable(true);
+        // If rotary scrolling is enabled, mark this view as focusable. This view will be focused
+        // when no focusable elements are visible.
+        setFocusable(rotaryScrollEnabled);
 
         // Focus this view before descendants so that the RotaryService can focus this view when it
         // wants to.
@@ -305,15 +328,20 @@
         // Disable the default focus highlight. No highlight should appear when this view is
         // focused.
         setDefaultFocusHighlightEnabled(false);
+
+        // This view is a rotary container if it's not a scrollable container.
+        if (!rotaryScrollEnabled) {
+            super.setContentDescription(ROTARY_CONTAINER);
+        }
     }
 
     @Override
     protected void onRestoreInstanceState(Parcelable state) {
         super.onRestoreInstanceState(state);
 
-        // If we're restoring an existing RecyclerView, we don't want
-        // to do the initial scroll to top
-        mHasScrolledToTop = true;
+        // If we're restoring an existing RecyclerView, consider
+        // it as having already scrolled some.
+        mHasScrolled = true;
     }
 
     @Override
@@ -324,19 +352,13 @@
         }
     }
 
-    @Override
-    public void onHeightChanged(int height) {
-        setPaddingRelative(getPaddingStart(), mInitialTopPadding + height,
-                getPaddingEnd(), getPaddingBottom());
-    }
-
     /**
      * Sets the number of columns in which grid needs to be divided.
      */
     public void setNumOfColumns(int numberOfColumns) {
         mNumOfColumns = numberOfColumns;
-        if (mOffsetItemDecoration != null) {
-            mOffsetItemDecoration.setNumOfColumns(mNumOfColumns);
+        if (mTopOffsetItemDecorationGrid != null) {
+            mTopOffsetItemDecorationGrid.setNumOfColumns(mNumOfColumns);
         }
         if (mDividerItemDecorationGrid != null) {
             mDividerItemDecorationGrid.setNumOfColumns(mNumOfColumns);
@@ -371,11 +393,10 @@
 
     /**
      * 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() {
-        mContainer = new LinearLayout(getContext());
         LayoutInflater inflater = LayoutInflater.from(getContext());
         inflater.inflate(R.layout.car_ui_recycler_view, mContainer, true);
         mContainer.setVisibility(mContainerVisibility);
@@ -430,6 +451,20 @@
     }
 
     @Override
+    public void setAlpha(float value) {
+        if (mScrollBarEnabled) {
+            mContainer.setAlpha(value);
+        } else {
+            super.setAlpha(value);
+        }
+    }
+
+    @Override
+    public ViewPropertyAnimator animate() {
+        return mScrollBarEnabled ? mContainer.animate() : super.animate();
+    }
+
+    @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mCarUxRestrictionsUtil.unregister(mListener);
@@ -440,6 +475,13 @@
         mContainerPaddingRelative = null;
         if (mScrollBarEnabled) {
             super.setPadding(0, top, 0, bottom);
+            if (!mHasScrolled) {
+                // If we haven't scrolled, and thus are still at the top of the screen,
+                // we should stay scrolled to the top after applying padding. Without this
+                // scroll, the padding will start scrolled offscreen. We need the padding
+                // to be onscreen to shift the content into a good visible range.
+                scrollToPosition(0);
+            }
             mContainerPadding = new Rect(left, 0, right, 0);
             if (mContainer != null) {
                 mContainer.setPadding(left, 0, right, 0);
@@ -455,6 +497,13 @@
         mContainerPadding = null;
         if (mScrollBarEnabled) {
             super.setPaddingRelative(0, top, 0, bottom);
+            if (!mHasScrolled) {
+                // If we haven't scrolled, and thus are still at the top of the screen,
+                // we should stay scrolled to the top after applying padding. Without this
+                // scroll, the padding will start scrolled offscreen. We need the padding
+                // to be onscreen to shift the content into a good visible range.
+                scrollToPosition(0);
+            }
             mContainerPaddingRelative = new Rect(start, 0, end, 0);
             if (mContainer != null) {
                 mContainer.setPaddingRelative(start, 0, end, 0);
@@ -466,8 +515,8 @@
     }
 
     /**
-     * Sets the scrollbar's padding top and bottom.
-     * This padding is applied in addition to the padding of the 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 paddingTop, int paddingBottom) {
         if (mScrollBarEnabled) {
@@ -503,6 +552,12 @@
         removeItemDecoration(mDividerItemDecorationGrid);
     }
 
+    @Override
+    public void setContentDescription(CharSequence contentDescription) {
+        super.setContentDescription(contentDescription);
+        initRotaryScroll();
+    }
+
     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/CarUiRecyclerViewAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewAdapter.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerViewAdapter.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewAdapter.java
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerViewContainer.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewContainer.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerViewContainer.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewContainer.java
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiSmoothScroller.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiSmoothScroller.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/CarUiSmoothScroller.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiSmoothScroller.java
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiSnapHelper.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiSnapHelper.java
similarity index 86%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/CarUiSnapHelper.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiSnapHelper.java
index 136dc6e..fd532b5 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiSnapHelper.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiSnapHelper.java
@@ -246,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 static float getPercentageVisible(View view, OrientationHelper helper) {
+    static float getPercentageVisible(View view, OrientationHelper helper) {
         int start = helper.getStartAfterPadding();
         int end = helper.getEndAfterPadding();
 
@@ -340,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.
+     */
+    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) {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ContentLimiting.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ContentLimiting.java
new file mode 100644
index 0000000..29fb159
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/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/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ContentLimitingAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ContentLimitingAdapter.java
new file mode 100644
index 0000000..a3114ab
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/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/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java
similarity index 77%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/DefaultScrollBar.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java
index a643f6d..d44274c 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/DefaultScrollBar.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java
@@ -35,11 +35,12 @@
 /**
  * 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 {
 
+
     private float mButtonDisabledAlpha;
     private CarUiSnapHelper mSnapHelper;
 
@@ -48,6 +49,7 @@
     private View mScrollThumb;
     private View mUpButton;
     private View mDownButton;
+    private int mScrollbarThumbMinHeight;
 
     private RecyclerView mRecyclerView;
 
@@ -57,6 +59,9 @@
 
     private OrientationHelper mOrientationHelper;
 
+    private OnContinuousScrollListener mPageUpOnContinuousScrollListener;
+    private OnContinuousScrollListener mPageDownOnContinuousScrollListener;
+
     @Override
     public void initialize(RecyclerView rv, View scrollView) {
         mRecyclerView = rv;
@@ -66,6 +71,8 @@
         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);
@@ -73,14 +80,17 @@
         mUpButton = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_page_up);
         View.OnClickListener paginateUpButtonOnClickListener = v -> pageUp();
         mUpButton.setOnClickListener(paginateUpButtonOnClickListener);
-        mUpButton.setOnTouchListener(
-                new OnContinuousScrollListener(rv.getContext(), paginateUpButtonOnClickListener));
+        mPageUpOnContinuousScrollListener = new OnContinuousScrollListener(rv.getContext(),
+                paginateUpButtonOnClickListener);
+        mUpButton.setOnTouchListener(mPageUpOnContinuousScrollListener);
+
 
         mDownButton = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_page_down);
         View.OnClickListener paginateDownButtonOnClickListener = v -> pageDown();
         mDownButton.setOnClickListener(paginateDownButtonOnClickListener);
-        mDownButton.setOnTouchListener(
-                new OnContinuousScrollListener(rv.getContext(), paginateDownButtonOnClickListener));
+        mPageDownOnContinuousScrollListener = new OnContinuousScrollListener(rv.getContext(),
+                paginateDownButtonOnClickListener);
+        mDownButton.setOnTouchListener(mPageDownOnContinuousScrollListener);
 
         mScrollTrack = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_track);
         mScrollThumb = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_thumb);
@@ -127,6 +137,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);
     }
@@ -137,6 +154,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);
     }
@@ -156,8 +180,7 @@
      * 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
@@ -201,18 +224,17 @@
      */
     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) * mScrollTrack.getHeight());
+        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.
+     *                    the given range.
      * @param thumbLength The current length of the thumb in pixels.
      * @return The amount the thumb should be offset in pixels.
      */
@@ -223,11 +245,13 @@
         // the top of scrollbar track is.
         return mScrollTrack.getTop()
                 + (isDownEnabled()
-                ? Math.round(((float) offset / range) * mScrollTrack.getHeight())
+                ? 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) {
         view.animate()
                 .y(newPosition)
@@ -257,32 +281,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.
@@ -293,13 +315,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.
-        mRecyclerView.smoothScrollBy(0, -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;
     }
 
     /**
@@ -311,21 +352,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
@@ -337,16 +378,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
@@ -365,12 +408,9 @@
     /**
      * 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.
      */
     private void updatePaginationButtons() {
 
@@ -381,6 +421,7 @@
         // enable/disable the button before the view is shown. So there is no flicker.
         setUpEnabled(!isAtStart);
         setDownEnabled(!isAtEnd);
+
         if ((isAtStart && isAtEnd) || layoutManager == null || layoutManager.getItemCount() == 0) {
             mScrollView.setVisibility(View.INVISIBLE);
         } else {
@@ -406,12 +447,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/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapter.java
new file mode 100644
index 0000000..c865b75
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/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/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/FastScroller.java
similarity index 88%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/FastScroller.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/FastScroller.java
index 9a44110..15c0ff2 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/FastScroller.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/FastScroller.java
@@ -20,6 +20,7 @@
 
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
@@ -38,15 +39,17 @@
     private float mTouchDownY = -1;
 
     private View mScrollTrackView;
-    private boolean mIsDragging;
     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() {
@@ -60,10 +63,8 @@
         switch (me.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 mTouchDownY = me.getY();
-                mIsDragging = false;
                 break;
             case MotionEvent.ACTION_MOVE:
-                mIsDragging = true;
                 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.
@@ -85,17 +86,21 @@
                 break;
             case MotionEvent.ACTION_UP:
             default:
-                mTouchDownY = -1;
-                // if not dragged then it's a click. When a click is detected on the track and
-                // within the range we want to move the center of the thumb to the Y coordinate
-                // of the clicked position.
-                if (!mIsDragging) {
+                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) {
@@ -116,12 +121,7 @@
         float percentage = ((newDragPos - thumbCenter) / (float) scrollbarLength);
         int totalPossibleOffset =
                 mRecyclerView.computeVerticalScrollRange() - mRecyclerView.getHeight();
-        int scrollingBy = (int) (percentage * totalPossibleOffset);
-        int absoluteOffset = mRecyclerView.computeVerticalScrollOffset() + scrollingBy;
-        if (absoluteOffset < 0) {
-            return 0;
-        }
-        return scrollingBy;
+        return (int) (percentage * totalPossibleOffset);
     }
 
     /**
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/OnContinuousScrollListener.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
similarity index 81%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
index 4fafc8d..48570c6 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
@@ -28,16 +28,15 @@
 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.
+ * 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 Handler mHandler = new Handler();
-
-    private int mInitialDelay;
-    private int mRepeatInterval;
+    private final Handler mHandler = new Handler();
+    private final int mInitialDelay;
+    private final int mRepeatInterval;
     private final OnClickListener mOnClickListener;
     private View mTouchedView;
     private boolean mIsLongPressed;
@@ -45,7 +44,7 @@
     /**
      * Notifies listener and self schedules to be re-run at next callback interval.
      */
-    private Runnable mPeriodicRunnable = new Runnable() {
+    private final Runnable mPeriodicRunnable = new Runnable() {
         @Override
         public void run() {
             if (mTouchedView.isEnabled()) {
@@ -59,14 +58,12 @@
     };
 
     /**
-     * @param clickListener The OnClickListener, that will be called
-     *                      periodically
+     * @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);
 
@@ -76,6 +73,15 @@
         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;
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/PassThroughFilter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/PassThroughFilter.java
new file mode 100644
index 0000000..4a02a94
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/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/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/RangeFilter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/RangeFilter.java
new file mode 100644
index 0000000..c9ea953
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/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/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/RangeFilterImpl.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/RangeFilterImpl.java
new file mode 100644
index 0000000..ecba8da
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/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/ScrollBar.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ScrollBar.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/ScrollBar.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ScrollBar.java
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ScrollingLimitedViewHolder.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ScrollingLimitedViewHolder.java
new file mode 100644
index 0000000..63b8692
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/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/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java
similarity index 90%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java
index abb90ca..5eb090a 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java
@@ -25,6 +25,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 {
 
@@ -89,7 +91,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);
@@ -102,8 +106,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(
@@ -130,7 +135,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++) {
@@ -144,8 +151,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/grid/GridOffsetItemDecoration.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/grid/GridOffsetItemDecoration.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/decorations/grid/GridOffsetItemDecoration.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/grid/GridOffsetItemDecoration.java
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java
similarity index 93%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java
index 4d5e6bd..3ab24aa 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java
@@ -104,9 +104,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();
 
@@ -133,9 +134,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/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
similarity index 95%
rename from car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
index 33e14db..62d361e 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
@@ -169,7 +169,8 @@
         if (mOffsetPosition == OffsetPosition.START) {
             parentLeft = parent.getPaddingLeft();
         } 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;
@@ -190,7 +191,8 @@
         if (mOffsetPosition == OffsetPosition.START) {
             parentTop = parent.getPaddingTop();
         } 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/MenuItem.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItem.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/toolbar/MenuItem.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItem.java
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/MenuItemRenderer.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemRenderer.java
similarity index 88%
rename from car-ui-lib/src/com/android/car/ui/toolbar/MenuItemRenderer.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemRenderer.java
index 7bf9025..85141f1 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/MenuItemRenderer.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemRenderer.java
@@ -63,9 +63,13 @@
     private View mIconContainer;
     private ImageView mIconView;
     private Switch mSwitch;
+    private View mTextContainer;
     private TextView mTextView;
     private TextView mTextWithIconView;
 
+    /** Whether the layout file supports rotary mode. */
+    private boolean mIsRotaryEnabledLayout;
+
     MenuItemRenderer(MenuItem item, ViewGroup parentView) {
         mMenuItem = item;
         mParentView = parentView;
@@ -102,6 +106,10 @@
                     requireViewByRefId(mView, R.id.car_ui_toolbar_menu_item_icon_container);
             mIconView = requireViewByRefId(mView, R.id.car_ui_toolbar_menu_item_icon);
             mSwitch = requireViewByRefId(mView, R.id.car_ui_toolbar_menu_item_switch);
+            // mTextContainer is only available in rotary enabled layout.
+            mTextContainer =
+                    CarUiUtils.findViewByRefId(mView, R.id.car_ui_toolbar_menu_item_text_container);
+            mIsRotaryEnabledLayout = mTextContainer != null;
             mTextView = requireViewByRefId(mView, R.id.car_ui_toolbar_menu_item_text);
             mTextWithIconView =
                     requireViewByRefId(mView, R.id.car_ui_toolbar_menu_item_text_with_icon);
@@ -132,24 +140,36 @@
         mView.setVisibility(View.VISIBLE);
         mView.setContentDescription(mMenuItem.getTitle());
 
-        mIconContainer.setVisibility(View.GONE);
+        int iconContainerVisibility = View.GONE;
+        int textContainerVisibility = View.GONE;
         mTextView.setVisibility(View.GONE);
         mTextWithIconView.setVisibility(View.GONE);
         mSwitch.setVisibility(View.GONE);
         if (checkable) {
             mSwitch.setChecked(mMenuItem.isChecked());
             mSwitch.setVisibility(View.VISIBLE);
+            if (mIsRotaryEnabledLayout) {
+                iconContainerVisibility = View.VISIBLE;
+            }
         } else if (hasText && hasIcon && textAndIcon) {
             mMenuItem.getIcon().setBounds(0, 0, mMenuItemIconSize, mMenuItemIconSize);
             mTextWithIconView.setCompoundDrawables(mMenuItem.getIcon(), null, null, null);
             mTextWithIconView.setText(mMenuItem.getTitle());
             mTextWithIconView.setVisibility(View.VISIBLE);
+            textContainerVisibility = View.VISIBLE;
         } else if (hasIcon) {
             mIconView.setImageDrawable(mMenuItem.getIcon());
-            mIconContainer.setVisibility(View.VISIBLE);
+            iconContainerVisibility = View.VISIBLE;
         } else { // hasText will be true
             mTextView.setText(mMenuItem.getTitle());
             mTextView.setVisibility(View.VISIBLE);
+            textContainerVisibility = View.VISIBLE;
+        }
+        // Unlike other views, we should only update the visibility of mIconContainer and
+        // mTextContainer once, otherwise rotary focus might break.
+        mIconContainer.setVisibility(iconContainerVisibility);
+        if (mTextContainer != null) {
+            mTextContainer.setVisibility(textContainerVisibility);
         }
 
         if (!mMenuItem.isTinted() && hasIcon) {
@@ -159,13 +179,19 @@
         recursiveSetEnabledAndDrawableState(mView);
         mView.setActivated(mMenuItem.isActivated());
 
+        View clickTarget = null;
+        if (mIsRotaryEnabledLayout) {
+            clickTarget = iconContainerVisibility == View.VISIBLE ? mIconContainer : mTextContainer;
+        } else {
+            clickTarget = mView;
+        }
         if (mMenuItem.getOnClickListener() != null
                 || mMenuItem.isCheckable()
                 || mMenuItem.isActivatable()) {
-            mView.setOnClickListener(v -> mMenuItem.performClick());
+            clickTarget.setOnClickListener(v -> mMenuItem.performClick());
         } else {
-            mView.setOnClickListener(null);
-            mView.setClickable(false);
+            clickTarget.setOnClickListener(null);
+            clickTarget.setClickable(false);
         }
     }
 
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/ProgressBarController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ProgressBarController.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/toolbar/ProgressBarController.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ProgressBarController.java
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/ProgressBarControllerImpl.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ProgressBarControllerImpl.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/toolbar/ProgressBarControllerImpl.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ProgressBarControllerImpl.java
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java
similarity index 88%
rename from car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java
index 7f7eb80..9506fe1 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java
@@ -23,6 +23,7 @@
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.inputmethod.EditorInfo;
@@ -116,15 +117,37 @@
         mSearchText.setOnEditorActionListener((v, actionId, event) -> {
             if (actionId == EditorInfo.IME_ACTION_DONE
                     || actionId == EditorInfo.IME_ACTION_SEARCH) {
-                mSearchText.clearFocus();
-                for (Toolbar.OnSearchCompletedListener listener : mSearchCompletedListeners) {
-                    listener.onSearchCompleted();
+                notifyQuerySubmit();
+            } else if (isEnter(event)) {
+                if (event.getAction() == KeyEvent.ACTION_UP) {
+                    // Note that we want to trigger search only on ACTION_UP, but want to return
+                    // true for all actions for the relevant key event.
+                    notifyQuerySubmit();
                 }
+                return true;
             }
             return false;
         });
     }
 
+    private boolean isEnter(KeyEvent event) {
+        boolean result = false;
+        if (event != null) {
+            int keyCode = event.getKeyCode();
+            result = keyCode == KeyEvent.KEYCODE_ENTER
+                    || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER
+                    || keyCode == KeyEvent.KEYCODE_SEARCH;
+        }
+        return result;
+    }
+
+    private void notifyQuerySubmit() {
+        mSearchText.clearFocus();
+        for (Toolbar.OnSearchCompletedListener listener : mSearchCompletedListeners) {
+            listener.onSearchCompleted();
+        }
+    }
+
     private boolean mWasShown = false;
 
     @Override
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/TabLayout.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TabLayout.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/toolbar/TabLayout.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TabLayout.java
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Toolbar.java
similarity index 96%
rename from car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Toolbar.java
index b069fb0..45e7dce 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Toolbar.java
@@ -42,8 +42,12 @@
  * {@link android.app.Activity#setActionBar(android.widget.Toolbar)} with it)
  *
  * <p>The toolbar supports a navigation button, title, tabs, search, and {@link MenuItem MenuItems}
+ *
+ * @deprecated Instead of creating this class, use Theme.CarUi.WithToolbar, and get access to it
+ *             via {@link com.android.car.ui.core.CarUi#requireToolbar(android.app.Activity)}
  */
-public class Toolbar extends FrameLayout implements ToolbarController {
+@Deprecated
+public final class Toolbar extends FrameLayout implements ToolbarController {
 
     /** Callback that will be issued whenever the height of toolbar is changed. */
     public interface OnHeightChangedListener {
@@ -268,13 +272,32 @@
 
     /**
      * Gets the {@link TabLayout} for this toolbar.
+     * @deprecated Use other tab-related functions in the ToolbarController interface.
      */
+    @Deprecated
     @Override
     public TabLayout getTabLayout() {
         return mController.getTabLayout();
     }
 
     /**
+     * Gets the number of tabs in the toolbar. The tabs can be retrieved using
+     * {@link #getTab(int)}.
+     */
+    @Override
+    public int getTabCount() {
+        return mController.getTabCount();
+    }
+
+    /**
+     * Gets the index of the tab.
+     */
+    @Override
+    public int getTabPosition(TabLayout.Tab tab) {
+        return mController.getTabPosition(tab);
+    }
+
+    /**
      * Adds a tab to this toolbar. You can listen for when it is selected via
      * {@link #registerOnTabSelectedListener(OnTabSelectedListener)}.
      */
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/ToolbarController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java
similarity index 96%
rename from car-ui-lib/src/com/android/car/ui/toolbar/ToolbarController.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java
index 4eb009d..d3e3910 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/ToolbarController.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java
@@ -77,10 +77,23 @@
 
     /**
      * Gets the {@link TabLayout} for this toolbar.
+     * @deprecated Use other tab-related functions in this interface.
      */
+    @Deprecated
     TabLayout getTabLayout();
 
     /**
+     * Gets the number of tabs in the toolbar. The tabs can be retrieved using
+     * {@link #getTab(int)}.
+     */
+    int getTabCount();
+
+    /**
+     * Gets the index of the tab.
+     */
+    int getTabPosition(TabLayout.Tab tab);
+
+    /**
      * Adds a tab to this toolbar. You can listen for when it is selected via
      * {@link #registerOnTabSelectedListener(Toolbar.OnTabSelectedListener)}.
      */
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/ToolbarControllerImpl.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java
similarity index 95%
rename from car-ui-lib/src/com/android/car/ui/toolbar/ToolbarControllerImpl.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java
index 1ef9f4a..c3ce075 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/ToolbarControllerImpl.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java
@@ -20,6 +20,7 @@
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
+import static com.android.car.ui.utils.CarUiUtils.findViewByRefId;
 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
 
 import android.app.Activity;
@@ -60,9 +61,10 @@
  * The implementation of {@link ToolbarController}. This class takes a ViewGroup, and looks
  * in the ViewGroup to find all the toolbar-related views to control.
  */
-public class ToolbarControllerImpl implements ToolbarController {
+public final class ToolbarControllerImpl implements ToolbarController {
     private static final String TAG = "CarUiToolbarController";
 
+    @Nullable
     private View mBackground;
     private ImageView mNavIcon;
     private ImageView mLogoInNavIconSpace;
@@ -152,7 +154,7 @@
                 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);
+        mBackground = findViewByRefId(view, R.id.car_ui_toolbar_background);
         mTabLayout = requireViewByRefId(view, R.id.car_ui_toolbar_tabs);
         mNavIcon = requireViewByRefId(view, R.id.car_ui_toolbar_nav_icon);
         mLogoInNavIconSpace = requireViewByRefId(view, R.id.car_ui_toolbar_logo);
@@ -176,14 +178,16 @@
             }
         });
 
-        mBackground.addOnLayoutChangeListener((v, left, top, right, bottom,
-                oldLeft, oldTop, oldRight, oldBottom) -> {
-            if (oldBottom - oldTop != bottom - top) {
-                for (Toolbar.OnHeightChangedListener listener : mOnHeightChangedListeners) {
-                    listener.onHeightChanged(mBackground.getHeight());
+        if (mBackground != null) {
+            mBackground.addOnLayoutChangeListener((v, left, top, right, bottom,
+                    oldLeft, oldTop, oldRight, oldBottom) -> {
+                if (oldBottom - oldTop != bottom - top) {
+                    for (Toolbar.OnHeightChangedListener listener : mOnHeightChangedListeners) {
+                        listener.onHeightChanged(mBackground.getHeight());
+                    }
                 }
-            }
-        });
+            });
+        }
 
         setBackgroundShown(true);
 
@@ -262,13 +266,32 @@
 
     /**
      * Gets the {@link TabLayout} for this toolbar.
+     * @deprecated Use other tab-related functions in the ToolbarController interface.
      */
+    @Deprecated
     @Override
     public TabLayout getTabLayout() {
         return mTabLayout;
     }
 
     /**
+     * Gets the number of tabs in the toolbar. The tabs can be retrieved using
+     * {@link #getTab(int)}.
+     */
+    @Override
+    public int getTabCount() {
+        return mTabLayout.getTabCount();
+    }
+
+    /**
+     * Gets the index of the tab.
+     */
+    @Override
+    public int getTabPosition(TabLayout.Tab tab) {
+        return mTabLayout.getTabPosition(tab);
+    }
+
+    /**
      * Adds a tab to this toolbar. You can listen for when it is selected via
      * {@link #registerOnTabSelectedListener(Toolbar.OnTabSelectedListener)}.
      */
@@ -417,6 +440,10 @@
     /** Show/hide the background. When hidden, the toolbar is completely transparent. */
     @Override
     public void setBackgroundShown(boolean shown) {
+        if (mBackground == null) {
+            return;
+        }
+
         if (shown) {
             mBackground.setBackground(
                     getContext().getDrawable(R.drawable.car_ui_toolbar_background));
@@ -428,6 +455,10 @@
     /** Returns true is the toolbar background is shown */
     @Override
     public boolean getBackgroundShown() {
+        if (mBackground == null) {
+            return true;
+        }
+
         return mBackground.getBackground() != null;
     }
 
diff --git a/car-ui-lib/src/com/android/car/ui/utils/CarUiUtils.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java
similarity index 81%
rename from car-ui-lib/src/com/android/car/ui/utils/CarUiUtils.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java
index 93cf868..ce4d8a5 100644
--- a/car-ui-lib/src/com/android/car/ui/utils/CarUiUtils.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java
@@ -15,12 +15,16 @@
  */
 package com.android.car.ui.utils;
 
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_HORIZONTALLY_SCROLLABLE;
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_VERTICALLY_SCROLLABLE;
+
 import android.app.Activity;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
@@ -152,7 +156,23 @@
     }
 
     /**
-     * It behaves similar to @see View#findViewById, except it resolves the ID reference first.
+     * Enables rotary scrolling for {@code view}, either vertically (if {@code isVertical} is true)
+     * or horizontally (if {@code isVertical} is false). With rotary scrolling enabled, rotating the
+     * rotary controller will scroll rather than moving the focus when moving the focus would cause
+     * a lot of scrolling. Rotary scrolling should be enabled for scrolling views which contain
+     * content which the user may want to see but can't interact with, either alone or along with
+     * interactive (focusable) content.
+     */
+    public static void setRotaryScrollEnabled(@NonNull View view, boolean isVertical) {
+        view.setContentDescription(
+                isVertical ? ROTARY_VERTICALLY_SCROLLABLE : ROTARY_HORIZONTALLY_SCROLLABLE);
+    }
+
+    /**
+     * It behaves similarly to {@link View#findViewById(int)}, except that on Q and below,
+     * it will first resolve the id to whatever it references.
+     *
+     * This is to support layout RROs before the new RRO features in R.
      *
      * @param id the ID to search for
      * @return a view with given ID if found, or {@code null} otherwise
@@ -161,6 +181,10 @@
     @Nullable
     @UiThread
     public static <T extends View> T findViewByRefId(@NonNull View root, @IdRes int id) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            return root.findViewById(id);
+        }
+
         if (id == View.NO_ID) {
             return null;
         }
@@ -171,7 +195,10 @@
     }
 
     /**
-     * It behaves similar to @see View#requireViewById, except it resolves the ID reference first.
+     * It behaves similarly to {@link View#requireViewById(int)}, except that on Q and below,
+     * it will first resolve the id to whatever it references.
+     *
+     * This is to support layout RROs before the new RRO features in R.
      *
      * @param id the ID to search for
      * @return a view with given ID
diff --git a/car-ui-lib/src/com/android/car/ui/utils/CarUxRestrictionsUtil.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUxRestrictionsUtil.java
similarity index 74%
rename from car-ui-lib/src/com/android/car/ui/utils/CarUxRestrictionsUtil.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUxRestrictionsUtil.java
index e5c7fc9..b4baf31 100644
--- a/car-ui-lib/src/com/android/car/ui/utils/CarUxRestrictionsUtil.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUxRestrictionsUtil.java
@@ -22,6 +22,7 @@
 import android.car.drivingstate.CarUxRestrictions.CarUxRestrictionsInfo;
 import android.car.drivingstate.CarUxRestrictionsManager;
 import android.content.Context;
+import android.os.Build;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -34,10 +35,6 @@
 import java.util.Set;
 import java.util.WeakHashMap;
 
-// This can't be in the middle of the rest of the imports on gerrit or it will
-// fail our style checks
-// copybara:insert import android.car.CarNotConnectedException;
-
 /**
  * Utility class to access Car Restriction Manager.
  *
@@ -48,10 +45,6 @@
 public class CarUxRestrictionsUtil {
     private static final String TAG = "CarUxRestrictionsUtil";
 
-    /* copybara:insert
-    private final Car mCarApi;
-    private CarUxRestrictionsManager mCarUxRestrictionsManager;
-    */
     @NonNull
     private CarUxRestrictions mCarUxRestrictions = getDefaultRestrictions();
 
@@ -73,36 +66,36 @@
                     }
                 };
 
-        // copybara:strip_begin
-        Car.createCar(context.getApplicationContext(), null, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
-                (Car car, boolean ready) -> {
-                    if (ready) {
-                        CarUxRestrictionsManager carUxRestrictionsManager =
-                                (CarUxRestrictionsManager) car.getCarManager(
-                                        Car.CAR_UX_RESTRICTION_SERVICE);
-                        carUxRestrictionsManager.registerListener(listener);
-                        listener.onUxRestrictionsChanged(
-                                carUxRestrictionsManager.getCurrentCarUxRestrictions());
-                    } else {
-                        Log.w(TAG, "Car service disconnected, assuming fully restricted uxr");
-                        listener.onUxRestrictionsChanged(null);
-                    }
-                });
-        /* copybara:strip_end_and_replace
-        mCarApi = Car.createCar(context.getApplicationContext());
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            Car.createCar(context.getApplicationContext(), null, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+                    (Car car, boolean ready) -> {
+                        if (ready) {
+                            CarUxRestrictionsManager carUxRestrictionsManager =
+                                    (CarUxRestrictionsManager) car.getCarManager(
+                                            Car.CAR_UX_RESTRICTION_SERVICE);
+                            carUxRestrictionsManager.registerListener(listener);
+                            listener.onUxRestrictionsChanged(
+                                    carUxRestrictionsManager.getCurrentCarUxRestrictions());
+                        } else {
+                            Log.w(TAG, "Car service disconnected, assuming fully restricted uxr");
+                            listener.onUxRestrictionsChanged(null);
+                        }
+                    });
+        } else {
+            Car carApi = Car.createCar(context.getApplicationContext());
 
-        try {
-            mCarUxRestrictionsManager =
-                    (CarUxRestrictionsManager) mCarApi.getCarManager(
-                            Car.CAR_UX_RESTRICTION_SERVICE);
-            mCarUxRestrictionsManager.registerListener(listener);
-            listener.onUxRestrictionsChanged(
-                    mCarUxRestrictionsManager.getCurrentCarUxRestrictions());
-        } catch (CarNotConnectedException | NullPointerException e) {
-            Log.e(TAG, "Car not connected", e);
-            // mCarUxRestrictions will be the default
+            try {
+                CarUxRestrictionsManager carUxRestrictionsManager =
+                        (CarUxRestrictionsManager) carApi.getCarManager(
+                                Car.CAR_UX_RESTRICTION_SERVICE);
+                carUxRestrictionsManager.registerListener(listener);
+                listener.onUxRestrictionsChanged(
+                        carUxRestrictionsManager.getCurrentCarUxRestrictions());
+            } catch (NullPointerException e) {
+                Log.e(TAG, "Car not connected", e);
+                // mCarUxRestrictions will be the default
+            }
         }
-        */
     }
 
     @NonNull
diff --git a/car-ui-lib/src/com/android/car/ui/utils/DirectManipulationHelper.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/DirectManipulationHelper.java
similarity index 65%
rename from car-ui-lib/src/com/android/car/ui/utils/DirectManipulationHelper.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/DirectManipulationHelper.java
index 24a32d1..184e522 100644
--- a/car-ui-lib/src/com/android/car/ui/utils/DirectManipulationHelper.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/DirectManipulationHelper.java
@@ -15,6 +15,8 @@
  */
 package com.android.car.ui.utils;
 
+import static android.os.Build.VERSION_CODES.R;
+
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.text.TextUtils;
@@ -25,8 +27,6 @@
 
 import androidx.annotation.NonNull;
 
-import java.lang.reflect.Method;
-
 /** Helper class to toggle direct manipulation mode. */
 public final class DirectManipulationHelper {
 
@@ -74,31 +74,50 @@
         return TextUtils.equals(DIRECT_MANIPULATION, event.getClassName());
     }
 
-    /** Returns whether the given {@code node} supports direct manipulation. */
-    @TargetApi(30)
-    public static boolean supportDirectManipulation(@NonNull AccessibilityNodeInfo node) {
-        try {
-            // TODO(b/156115044): remove the reflection once Android R sdk is publicly released.
-            Method getStateDescription =
-                    AccessibilityNodeInfo.class.getMethod("getStateDescription");
-            CharSequence stateDescription = (CharSequence) getStateDescription.invoke(node);
-            return TextUtils.equals(DIRECT_MANIPULATION, stateDescription);
-        } catch (ReflectiveOperationException e) {
-            throw new RuntimeException(e);
-        }
+    /** Returns whether the given {@code node} supports rotate directly. */
+    @TargetApi(R)
+    public static boolean supportRotateDirectly(@NonNull AccessibilityNodeInfo node) {
+        return TextUtils.equals(DIRECT_MANIPULATION, node.getStateDescription());
     }
 
-    /** Sets whether the given {@code view} supports direct manipulation. */
-    @TargetApi(30)
+    /**
+     * Sets whether the given {@code view} supports rotate directly.
+     * <p>
+     * If the view supports rotate directly, when it's focused but not in direct manipulation mode,
+     * clicking the center button of the rotary controller will make RotaryService enter direct
+     * manipulation mode. In this mode, the view's selected state is toggled, and only controller
+     * rotation and Back button press are supported.
+     * <ul>
+     *   <li>When the controller is rotated, the view will be asked to perform ACTION_SCROLL_FORWARD
+     *       or ACTION_SCROLL_BACKWARD.
+     *   <li>When Back button is pressed, RotaryService will toggle off the view's selected state
+     *       and exit this mode.
+     * </ul>
+     * To support controller nudges as well in direct manipulation mode, use {@link
+     * #enableDirectManipulationMode} instead.
+     */
+    @TargetApi(R)
+    public static void setSupportsRotateDirectly(@NonNull View view, boolean enable) {
+        view.setStateDescription(enable ? DIRECT_MANIPULATION : null);
+    }
+
+    /**
+     * Returns whether the given {@code node} supports rotate directly.
+     *
+     * @deprecated use {@link #supportRotateDirectly} instead
+     */
+    @Deprecated
+    public static boolean supportDirectManipulation(@NonNull AccessibilityNodeInfo node) {
+        return supportRotateDirectly(node);
+    }
+
+    /**
+     * Sets whether the given {@code view} supports rotate directly.
+     *
+     * @deprecated use {@link #setSupportsRotateDirectly} instead
+     */
+    @Deprecated
     public static void setSupportsDirectManipulation(@NonNull View view, boolean enable) {
-        try {
-            // TODO(b/156115044): remove the reflection once Android R sdk is publicly released.
-            Method setStateDescription =
-                    View.class.getMethod("setStateDescription", CharSequence.class);
-            CharSequence stateDescription = enable ? DIRECT_MANIPULATION : null;
-            setStateDescription.invoke(view, stateDescription);
-        } catch (ReflectiveOperationException e) {
-            throw new RuntimeException(e);
-        }
+        setSupportsRotateDirectly(view, enable);
     }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/RotaryConstants.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/RotaryConstants.java
new file mode 100644
index 0000000..e6c62c9
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/RotaryConstants.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.utils;
+
+/**
+ * Constants for the rotary controller.
+ *
+ * @hide
+ */
+public final class RotaryConstants {
+    /**
+     * Content description indicating that the view is a rotary container.
+     * <p>
+     * A rotary container contains focusable elements. When initializing focus, the first element
+     * in the rotary container is prioritized to take focus. When searching for nudge target, the
+     * bounds of the rotary container is the minimum bounds containing its descendants.
+     * <p>
+     * A rotary container shouldn't be focusable unless it's a scrollable container. Though it
+     * can't be focused, it can be scrolled as a side-effect of moving the focus within it.
+     */
+    public static final String ROTARY_CONTAINER =
+            "com.android.car.ui.utils.ROTARY_CONTAINER";
+
+    /**
+     * Content description indicating that the view is a scrollable container and can be scrolled
+     * horizontally by the rotary controller.
+     * <p>
+     * A scrollable container is a focusable rotary container. When it's focused, it can be scrolled
+     * when the rotary controller rotates. A scrollable container is often used to show long text.
+     */
+    public static final String ROTARY_HORIZONTALLY_SCROLLABLE =
+            "com.android.car.ui.utils.HORIZONTALLY_SCROLLABLE";
+
+    /**
+     * Content description indicating that the view is a scrollable container and can be scrolled
+     * vertically by the rotary controller.
+     * <p>
+     * A scrollable container is a focusable rotary container. When it's focused, it can be scrolled
+     * when the rotary controller rotates. A scrollable container is often used to show long text.
+     */
+    public static final String ROTARY_VERTICALLY_SCROLLABLE =
+            "com.android.car.ui.utils.VERTICALLY_SCROLLABLE";
+
+    /**
+     * Content description indicating that the view is a focus delegating container. When
+     * restoring focus, FocusParkingView and FocusArea will skip non-focusable views unless it's
+     * a focus delegating container. The focus delegating container can delegate focus to one of
+     * its descendants.
+     */
+    public static final String ROTARY_FOCUS_DELEGATING_CONTAINER =
+            "com.android.car.ui.utils.FOCUS_DELEGATING_CONTAINER";
+
+    /** The key to store the offset of the FocusArea's left bound in the node's extras. */
+    public static final String FOCUS_AREA_LEFT_BOUND_OFFSET =
+            "com.android.car.ui.utils.FOCUS_AREA_LEFT_BOUND_OFFSET";
+
+    /** The key to store the offset of the FocusArea's right bound in the node's extras. */
+    public static final String FOCUS_AREA_RIGHT_BOUND_OFFSET =
+            "com.android.car.ui.utils.FOCUS_AREA_RIGHT_BOUND_OFFSET";
+
+    /** The key to store the offset of the FocusArea's top bound in the node's extras. */
+    public static final String FOCUS_AREA_TOP_BOUND_OFFSET =
+            "com.android.car.ui.utils.FOCUS_AREA_TOP_BOUND_OFFSET";
+
+    /** The key to store the offset of the FocusArea's bottom bound in the node's extras. */
+    public static final String FOCUS_AREA_BOTTOM_BOUND_OFFSET =
+            "com.android.car.ui.utils.FOCUS_AREA_BOTTOM_BOUND_OFFSET";
+
+    /** The key to store nudge direction in the Bundle. */
+    public static final String NUDGE_DIRECTION =
+            "com.android.car.ui.utils.NUDGE_DIRECTION";
+
+    /**
+     * Action performed on a FocusArea to move focus to the nudge shortcut within the same
+     * FocusArea.
+     * <p>
+     * This action and the actions below only use the most significant 8 bits to avoid
+     * conflicting with legacy standard actions (which don't use the most significant 8 bits),
+     * e.g. ACTION_FOCUS. The actions only use one bit to avoid conflicting with IDs defined in
+     * framework (which start with 0x0102), e.g. R.id.accessibilityActionScrollUp.
+     */
+    public static final int ACTION_NUDGE_SHORTCUT = 0x01000000;
+
+    /** Action performed on a FocusArea to move focus to another FocusArea. */
+    public static final int ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA = 0x02000000;
+
+    /** Action performed on a FocusParkingView to restore the focus in the window. */
+    public static final int ACTION_RESTORE_DEFAULT_FOCUS = 0x04000000;
+
+    /** Action performed on a FocusParkingView to hide the IME. */
+    public static final int ACTION_HIDE_IME = 0x08000000;
+
+    /** Prevent instantiation. */
+    private RotaryConstants() {
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/ViewUtils.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/ViewUtils.java
new file mode 100644
index 0000000..62c189f
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/ViewUtils.java
@@ -0,0 +1,434 @@
+/*
+ * 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.utils;
+
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_CONTAINER;
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_FOCUS_DELEGATING_CONTAINER;
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_HORIZONTALLY_SCROLLABLE;
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_VERTICALLY_SCROLLABLE;
+
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.ui.FocusArea;
+import com.android.car.ui.FocusParkingView;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Utility class used by {@link com.android.car.ui.FocusArea} and {@link
+ * com.android.car.ui.FocusParkingView}.
+ *
+ * @hide
+ */
+public final class ViewUtils {
+
+    /**
+     * No view is focused, the focused view is not shown, or the focused view is a FocusParkingView.
+     */
+    public static final int NO_FOCUS = 1;
+
+    /** A scrollable container is focused. */
+    public static final int SCROLLABLE_CONTAINER_FOCUS = 2;
+
+    /**
+     * A regular view is focused. A regular View is a View that is neither a FocusParkingView nor a
+     * scrollable container.
+     */
+    public static final int REGULAR_FOCUS = 3;
+
+    /**
+     * An implicit default focus view (i.e., the first focusable item in a scrollable container) is
+     * focused.
+     */
+    public static final int IMPLICIT_DEFAULT_FOCUS = 4;
+
+    /** The {@code app:defaultFocus} view is focused. */
+    public static final int DEFAULT_FOCUS = 5;
+
+    /** The {@code android:focusedByDefault} view is focused. */
+    public static final int FOCUSED_BY_DEFAULT = 6;
+
+    /**
+     * Focus level of a view. When adjusting the focus, the view with the highest focus level will
+     * be focused.
+     */
+    @IntDef(flag = true, value = {NO_FOCUS, SCROLLABLE_CONTAINER_FOCUS, REGULAR_FOCUS,
+            IMPLICIT_DEFAULT_FOCUS, DEFAULT_FOCUS, FOCUSED_BY_DEFAULT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FocusLevel {
+    }
+
+    /** This is a utility class. */
+    private ViewUtils() {
+    }
+
+    /**
+     * This is a functional interface and can therefore be used as the assignment target for a
+     * lambda expression or method reference.
+     *
+     * @param <T> the type of the input to the predicate
+     */
+    private interface Predicate<T> {
+        /** Evaluates this predicate on the given argument. */
+        boolean test(@NonNull T t);
+    }
+
+    /** Gets the ancestor FocusArea of the {@code view}, if any. Returns null if not found. */
+    @Nullable
+    public static FocusArea getAncestorFocusArea(@NonNull View view) {
+        ViewParent parent = view.getParent();
+        while (parent != null) {
+            if (parent instanceof FocusArea) {
+                return (FocusArea) parent;
+            }
+            parent = parent.getParent();
+        }
+        return null;
+    }
+
+    /**
+     * Gets the ancestor scrollable container of the {@code view}, if any. Returns null if not
+     * found.
+     */
+    @Nullable
+    public static ViewGroup getAncestorScrollableContainer(@Nullable View view) {
+        if (view == null) {
+            return null;
+        }
+        ViewParent parent = view.getParent();
+        // A scrollable container can't contain a FocusArea, so let's return earlier if we found
+        // a FocusArea.
+        while (parent != null && parent instanceof ViewGroup && !(parent instanceof FocusArea)) {
+            ViewGroup viewGroup = (ViewGroup) parent;
+            if (isScrollableContainer(viewGroup)) {
+                return viewGroup;
+            }
+            parent = parent.getParent();
+        }
+        return null;
+    }
+
+    /**
+     * Focuses on the {@code view} if it can be focused.
+     *
+     * @return whether it was successfully focused or already focused
+     */
+    public static boolean requestFocus(@Nullable View view) {
+        if (view == null || !canTakeFocus(view)) {
+            return false;
+        }
+        if (view.isFocused()) {
+            return true;
+        }
+        // Exit touch mode and focus the view. The view may not be focusable in touch mode, so we
+        // need to exit touch mode before focusing it.
+        return view.performAccessibilityAction(ACTION_FOCUS, /* arguments= */ null);
+    }
+
+    /**
+     * Searches the {@code root}'s descendants for a view with the highest {@link FocusLevel}. If
+     * the view's FocusLevel is higher than the {@code currentFocus}'s FocusLevel, focuses on the
+     * view.
+     *
+     * @return whether the view is focused
+     */
+    public static boolean adjustFocus(@NonNull View root, @Nullable View currentFocus) {
+        @FocusLevel int level = getFocusLevel(currentFocus);
+        return adjustFocus(root, level);
+    }
+
+    /**
+     * Searches the {@code root}'s descendants for a view with the highest {@link FocusLevel}. If
+     * the view's FocusLevel is higher than {@code currentLevel}, focuses on the view.
+     *
+     * @return whether the view is focused
+     */
+    public static boolean adjustFocus(@NonNull View root, @FocusLevel int currentLevel) {
+        if (currentLevel < FOCUSED_BY_DEFAULT && focusOnFocusedByDefaultView(root)) {
+            return true;
+        }
+        if (currentLevel < DEFAULT_FOCUS && focusOnDefaultFocusView(root)) {
+            return true;
+        }
+        if (currentLevel < IMPLICIT_DEFAULT_FOCUS && focusOnImplicitDefaultFocusView(root)) {
+            return true;
+        }
+        if (currentLevel < REGULAR_FOCUS && focusOnFirstRegularView(root)) {
+            return true;
+        }
+        if (currentLevel < SCROLLABLE_CONTAINER_FOCUS) {
+            return focusOnScrollableContainer(root);
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    @FocusLevel
+    static int getFocusLevel(@Nullable View view) {
+        if (view == null || view instanceof FocusParkingView || !view.isShown()) {
+            return NO_FOCUS;
+        }
+        if (view.isFocusedByDefault()) {
+            return FOCUSED_BY_DEFAULT;
+        }
+        if (isDefaultFocus(view)) {
+            return DEFAULT_FOCUS;
+        }
+        if (isImplicitDefaultFocusView(view)) {
+            return IMPLICIT_DEFAULT_FOCUS;
+        }
+        if (isScrollableContainer(view)) {
+            return SCROLLABLE_CONTAINER_FOCUS;
+        }
+        return REGULAR_FOCUS;
+    }
+
+    /** Returns whether the {@code view} is a {@code app:defaultFocus} view. */
+    private static boolean isDefaultFocus(@NonNull View view) {
+        FocusArea parent = getAncestorFocusArea(view);
+        return parent != null && view == parent.getDefaultFocusView();
+    }
+
+    /**
+     * Returns whether the {@code view} is an implicit default focus view, i.e., the first focusable
+     * item in a rotary container.
+     */
+    @VisibleForTesting
+    static boolean isImplicitDefaultFocusView(@NonNull View view) {
+        ViewGroup rotaryContainer = null;
+        ViewParent parent = view.getParent();
+        while (parent != null && parent instanceof ViewGroup) {
+            ViewGroup viewGroup = (ViewGroup) parent;
+            if (isRotaryContainer(viewGroup)) {
+                rotaryContainer = viewGroup;
+                break;
+            }
+            parent = parent.getParent();
+        }
+        if (rotaryContainer == null) {
+            return false;
+        }
+        return findFirstFocusableDescendant(rotaryContainer) == view;
+    }
+
+    private static boolean isRotaryContainer(@NonNull View view) {
+        CharSequence contentDescription = view.getContentDescription();
+        return TextUtils.equals(contentDescription, ROTARY_CONTAINER)
+                || TextUtils.equals(contentDescription, ROTARY_VERTICALLY_SCROLLABLE)
+                || TextUtils.equals(contentDescription, ROTARY_HORIZONTALLY_SCROLLABLE);
+    }
+
+    private static boolean isScrollableContainer(@NonNull View view) {
+        CharSequence contentDescription = view.getContentDescription();
+        return TextUtils.equals(contentDescription, ROTARY_VERTICALLY_SCROLLABLE)
+                || TextUtils.equals(contentDescription, ROTARY_HORIZONTALLY_SCROLLABLE);
+    }
+
+    private static boolean isFocusDelegatingContainer(@NonNull View view) {
+        CharSequence contentDescription = view.getContentDescription();
+        return TextUtils.equals(contentDescription, ROTARY_FOCUS_DELEGATING_CONTAINER);
+    }
+
+    /**
+     * Focuses on the first {@code app:defaultFocus} view in the view tree, if any.
+     *
+     * @param root the root of the view tree
+     * @return whether succeeded
+     */
+    private static boolean focusOnDefaultFocusView(@NonNull View root) {
+        View defaultFocus = findDefaultFocusView(root);
+        return requestFocus(defaultFocus);
+    }
+
+    /**
+     * Focuses on the first {@code android:focusedByDefault} view in the view tree, if any.
+     *
+     * @param root the root of the view tree
+     * @return whether succeeded
+     */
+    private static boolean focusOnFocusedByDefaultView(@NonNull View root) {
+        View focusedByDefault = findFocusedByDefaultView(root);
+        return requestFocus(focusedByDefault);
+    }
+
+    /**
+     * Focuses on the first implicit default focus view in the view tree, if any.
+     *
+     * @param root the root of the view tree
+     * @return whether succeeded
+     */
+    private static boolean focusOnImplicitDefaultFocusView(@NonNull View root) {
+        View implicitDefaultFocus = findImplicitDefaultFocusView(root);
+        return requestFocus(implicitDefaultFocus);
+    }
+
+    /**
+     * Tries to focus on the first focusable view in the view tree in depth first order, excluding
+     * the FocusParkingView and scrollable containers. If focusing on the first such view fails,
+     * keeps trying other views in depth first order until succeeds or there are no more such views.
+     *
+     * @param root the root of the view tree
+     * @return whether succeeded
+     */
+    private static boolean focusOnFirstRegularView(@NonNull View root) {
+        View focusedView = ViewUtils.depthFirstSearch(root,
+                /* targetPredicate= */
+                v -> !isScrollableContainer(v) && canTakeFocus(v) && requestFocus(v),
+                /* skipPredicate= */ v -> !v.isShown());
+        return focusedView != null;
+    }
+
+    /**
+     * Focuses on the first scrollable container in the view tree, if any.
+     *
+     * @param root the root of the view tree
+     * @return whether succeeded
+     */
+    private static boolean focusOnScrollableContainer(@NonNull View root) {
+        View focusedView = ViewUtils.depthFirstSearch(root,
+                /* targetPredicate= */ v -> isScrollableContainer(v) && canTakeFocus(v),
+                /* skipPredicate= */ v -> !v.isShown());
+        return requestFocus(focusedView);
+    }
+
+    /**
+     * Searches the {@code root}'s descendants in depth first order, and returns the first
+     * {@code app:defaultFocus} view that can take focus. Returns null if not found.
+     */
+    @Nullable
+    private static View findDefaultFocusView(@NonNull View view) {
+        if (!view.isShown()) {
+            return null;
+        }
+        if (view instanceof FocusArea) {
+            FocusArea focusArea = (FocusArea) view;
+            View defaultFocus = focusArea.getDefaultFocusView();
+            if (defaultFocus != null && canTakeFocus(defaultFocus)) {
+                return defaultFocus;
+            }
+        } else if (view instanceof ViewGroup) {
+            ViewGroup parent = (ViewGroup) view;
+            for (int i = 0; i < parent.getChildCount(); i++) {
+                View child = parent.getChildAt(i);
+                View defaultFocus = findDefaultFocusView(child);
+                if (defaultFocus != null) {
+                    return defaultFocus;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Searches the {@code view} and its descendants in depth first order, and returns the first
+     * {@code android:focusedByDefault} view that can take focus. Returns null if not found.
+     */
+    @VisibleForTesting
+    @Nullable
+    static View findFocusedByDefaultView(@NonNull View view) {
+        return depthFirstSearch(view,
+                /* targetPredicate= */ v -> v.isFocusedByDefault() && canTakeFocus(v),
+                /* skipPredicate= */ v -> !v.isShown());
+    }
+
+    /**
+     * Searches the {@code view} and its descendants in depth first order, and returns the first
+     * implicit default focus view, i.e., the first focusable item in the first rotary container.
+     * Returns null if not found.
+     */
+    @VisibleForTesting
+    @Nullable
+    static View findImplicitDefaultFocusView(@NonNull View view) {
+        View rotaryContainer = findRotaryContainer(view);
+        return rotaryContainer == null
+                ? null
+                : findFirstFocusableDescendant(rotaryContainer);
+    }
+
+    /**
+     * Searches the {@code view}'s descendants in depth first order, and returns the first view
+     * that can take focus, or null if not found.
+     */
+    @VisibleForTesting
+    @Nullable
+    static View findFirstFocusableDescendant(@NonNull View view) {
+        return depthFirstSearch(view,
+                /* targetPredicate= */ v -> v != view && canTakeFocus(v),
+                /* skipPredicate= */ v -> !v.isShown());
+    }
+
+    /**
+     * Searches the {@code view} and its descendants in depth first order, and returns the first
+     * rotary container shown on the screen. Returns null if not found.
+     */
+    @Nullable
+    private static View findRotaryContainer(@NonNull View view) {
+        return depthFirstSearch(view,
+                /* targetPredicate= */ v -> isRotaryContainer(v),
+                /* skipPredicate= */ v -> !v.isShown());
+    }
+
+    /**
+     * Searches the {@code view} and its descendants in depth first order, skips the views that
+     * match {@code skipPredicate} and their descendants, and returns the first view that matches
+     * {@code targetPredicate}. Returns null if not found.
+     */
+    @Nullable
+    private static View depthFirstSearch(@NonNull View view,
+            @NonNull Predicate<View> targetPredicate,
+            @Nullable Predicate<View> skipPredicate) {
+        if (skipPredicate != null && skipPredicate.test(view)) {
+            return null;
+        }
+        if (targetPredicate.test(view)) {
+            return view;
+        }
+        if (view instanceof ViewGroup) {
+            ViewGroup parent = (ViewGroup) view;
+            for (int i = 0; i < parent.getChildCount(); i++) {
+                View child = parent.getChildAt(i);
+                View target = depthFirstSearch(child, targetPredicate, skipPredicate);
+                if (target != null) {
+                    return target;
+                }
+            }
+        }
+        return null;
+    }
+
+    /** Returns whether {@code view} can be focused. */
+    private static boolean canTakeFocus(@NonNull View view) {
+        boolean focusable = view.isFocusable() || isFocusDelegatingContainer(view);
+        return focusable && view.isEnabled() && view.isShown()
+                && view.getWidth() > 0 && view.getHeight() > 0 && view.isAttachedToWindow()
+                && !(view instanceof FocusParkingView)
+                // If it's a scrollable container, it can be focused only when it has no focusable
+                // descendants. We focus on it so that the rotary controller can scroll it.
+                && (!isScrollableContainer(view) || findFirstFocusableDescendant(view) == null);
+    }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/uxr/DrawableStateButton.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateButton.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/uxr/DrawableStateButton.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateButton.java
diff --git a/car-ui-lib/src/com/android/car/ui/uxr/DrawableStateSwitch.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateSwitch.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/uxr/DrawableStateSwitch.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateSwitch.java
diff --git a/car-ui-lib/src/com/android/car/ui/uxr/DrawableStateView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateView.java
similarity index 100%
rename from car-ui-lib/src/com/android/car/ui/uxr/DrawableStateView.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateView.java
diff --git a/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml b/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml
new file mode 100644
index 0000000..453db47
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml
@@ -0,0 +1,485 @@
+<?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_alert_dialog_force_dismiss_button"/>
+      <item type="bool" name="car_ui_clear_focus_area_history_when_rotating"/>
+      <item type="bool" name="car_ui_enable_focus_area_background_highlight"/>
+      <item type="bool" name="car_ui_enable_focus_area_foreground_highlight"/>
+      <item type="bool" name="car_ui_escrow_check_components_automatically"/>
+      <item type="bool" name="car_ui_focus_area_default_focus_overrides_history"/>
+      <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_dialog_icon_color"/>
+      <item type="color" name="car_ui_list_item_divider"/>
+      <item type="color" name="car_ui_preference_icon_color"/>
+      <item type="color" name="car_ui_preference_two_action_divider_color"/>
+      <item type="color" name="car_ui_recyclerview_divider_color"/>
+      <item type="color" name="car_ui_ripple_color"/>
+      <item type="color" name="car_ui_rotary_focus_fill_color"/>
+      <item type="color" name="car_ui_rotary_focus_fill_secondary_color"/>
+      <item type="color" name="car_ui_rotary_focus_pressed_fill_color"/>
+      <item type="color" name="car_ui_rotary_focus_pressed_stroke_color"/>
+      <item type="color" name="car_ui_rotary_focus_stroke_color"/>
+      <item type="color" name="car_ui_rotary_focus_stroke_secondary_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_divider_width"/>
+      <item type="dimen" name="car_ui_header_list_item_text_start_margin"/>
+      <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_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_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_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_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_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_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_rotary_focus_pressed_stroke_width"/>
+      <item type="dimen" name="car_ui_rotary_focus_stroke_width"/>
+      <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_size"/>
+      <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_focus_area_background_highlight"/>
+      <item type="drawable" name="car_ui_focus_area_foreground_highlight"/>
+      <item type="drawable" name="car_ui_icon_add"/>
+      <item type="drawable" name="car_ui_icon_arrow_back"/>
+      <item type="drawable" name="car_ui_icon_close"/>
+      <item type="drawable" name="car_ui_icon_delete"/>
+      <item type="drawable" name="car_ui_icon_down"/>
+      <item type="drawable" name="car_ui_icon_edit"/>
+      <item type="drawable" name="car_ui_icon_lock"/>
+      <item type="drawable" name="car_ui_icon_overflow_menu"/>
+      <item type="drawable" name="car_ui_icon_save"/>
+      <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="action_widget_container"/>
+      <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_focus_area"/>
+      <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_container_without_widget"/>
+      <item type="id" name="car_ui_preference_fragment_container"/>
+      <item type="id" name="car_ui_recycler_view"/>
+      <item type="id" name="car_ui_scroll_bar"/>
+      <item type="id" name="car_ui_scrollbar_page_down"/>
+      <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_container"/>
+      <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="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"/>
+      <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="seek_bar"/>
+      <item type="id" name="seek_bar_text_left"/>
+      <item type="id" name="seek_bar_text_right"/>
+      <item type="id" name="seek_bar_text_top"/>
+      <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_container"/>
+      <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_focus_area_history_cache_type"/>
+      <item type="integer" name="car_ui_focus_area_history_expiration_period_ms"/>
+      <item type="integer" name="car_ui_focus_history_cache_type"/>
+      <item type="integer" name="car_ui_focus_history_expiration_period_ms"/>
+      <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_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_list_preference_with_toolbar"/>
+      <item type="layout" name="car_ui_preference"/>
+      <item type="layout" name="car_ui_preference_category"/>
+      <item type="layout" name="car_ui_preference_chevron"/>
+      <item type="layout" name="car_ui_preference_dialog_edittext"/>
+      <item type="layout" name="car_ui_preference_dropdown"/>
+      <item type="layout" name="car_ui_preference_fragment"/>
+      <item type="layout" name="car_ui_preference_fragment_with_toolbar"/>
+      <item type="layout" name="car_ui_preference_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_radio_button_preference_widget"/>
+      <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_seekbar_dialog"/>
+      <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="layout" name="car_ui_two_action_preference"/>
+      <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_installer_process_name"/>
+      <item type="string" name="car_ui_preference_switch_off"/>
+      <item type="string" name="car_ui_preference_switch_on"/>
+      <item type="string" name="car_ui_restricted_while_driving"/>
+      <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.DialogSeekBarPreference"/>
+      <item type="style" name="Preference.CarUi.DialogSeekBarPreference.LeftText"/>
+      <item type="style" name="Preference.CarUi.DialogSeekBarPreference.RightText"/>
+      <item type="style" name="Preference.CarUi.DialogSeekBarPreference.Seekbar"/>
+      <item type="style" name="Preference.CarUi.DialogSeekBarPreference.TopText"/>
+      <item type="style" name="Preference.CarUi.Divider"/>
+      <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/color/car_ui_color_accent.xml b/car-ui-lib/car-ui-lib/src/main/res/color/car_ui_color_accent.xml
similarity index 100%
rename from car-ui-lib/res/color/car_ui_color_accent.xml
rename to car-ui-lib/car-ui-lib/src/main/res/color/car_ui_color_accent.xml
diff --git a/car-ui-lib/res/color/car_ui_dialog_icon_color.xml b/car-ui-lib/car-ui-lib/src/main/res/color/car_ui_dialog_icon_color.xml
similarity index 100%
rename from car-ui-lib/res/color/car_ui_dialog_icon_color.xml
rename to car-ui-lib/car-ui-lib/src/main/res/color/car_ui_dialog_icon_color.xml
diff --git a/car-ui-lib/res/color/car_ui_list_item_divider.xml b/car-ui-lib/car-ui-lib/src/main/res/color/car_ui_list_item_divider.xml
similarity index 100%
rename from car-ui-lib/res/color/car_ui_list_item_divider.xml
rename to car-ui-lib/car-ui-lib/src/main/res/color/car_ui_list_item_divider.xml
diff --git a/car-ui-lib/res/color/car_ui_text_color_hint.xml b/car-ui-lib/car-ui-lib/src/main/res/color/car_ui_text_color_hint.xml
similarity index 100%
rename from car-ui-lib/res/color/car_ui_text_color_hint.xml
rename to car-ui-lib/car-ui-lib/src/main/res/color/car_ui_text_color_hint.xml
diff --git a/car-ui-lib/res/color/car_ui_text_color_primary.xml b/car-ui-lib/car-ui-lib/src/main/res/color/car_ui_text_color_primary.xml
similarity index 100%
rename from car-ui-lib/res/color/car_ui_text_color_primary.xml
rename to car-ui-lib/car-ui-lib/src/main/res/color/car_ui_text_color_primary.xml
diff --git a/car-ui-lib/res/color/car_ui_text_color_secondary.xml b/car-ui-lib/car-ui-lib/src/main/res/color/car_ui_text_color_secondary.xml
similarity index 100%
copy from car-ui-lib/res/color/car_ui_text_color_secondary.xml
copy to car-ui-lib/car-ui-lib/src/main/res/color/car_ui_text_color_secondary.xml
diff --git a/car-ui-lib/res/color/car_ui_toolbar_menu_item_icon_background_color.xml b/car-ui-lib/car-ui-lib/src/main/res/color/car_ui_toolbar_menu_item_icon_background_color.xml
similarity index 100%
rename from car-ui-lib/res/color/car_ui_toolbar_menu_item_icon_background_color.xml
rename to car-ui-lib/car-ui-lib/src/main/res/color/car_ui_toolbar_menu_item_icon_background_color.xml
diff --git a/car-ui-lib/res/color/car_ui_toolbar_menu_item_icon_color.xml b/car-ui-lib/car-ui-lib/src/main/res/color/car_ui_toolbar_menu_item_icon_color.xml
similarity index 100%
rename from car-ui-lib/res/color/car_ui_toolbar_menu_item_icon_color.xml
rename to car-ui-lib/car-ui-lib/src/main/res/color/car_ui_toolbar_menu_item_icon_color.xml
diff --git a/car-ui-lib/res/color/car_ui_toolbar_tab_item_selector.xml b/car-ui-lib/car-ui-lib/src/main/res/color/car_ui_toolbar_tab_item_selector.xml
similarity index 100%
rename from car-ui-lib/res/color/car_ui_toolbar_tab_item_selector.xml
rename to car-ui-lib/car-ui-lib/src/main/res/color/car_ui_toolbar_tab_item_selector.xml
diff --git a/car-ui-lib/res/drawable/car_ui_divider.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_divider.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_divider.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_divider.xml
diff --git a/car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_focus_area_background_highlight.xml
similarity index 94%
rename from car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_focus_area_background_highlight.xml
index 74b8d13..7dd6341 100644
--- a/car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_focus_area_background_highlight.xml
@@ -16,5 +16,5 @@
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
-    <solid android:color="#00FF00"/>
+    <solid android:color="#2994CBFF"/>
 </shape>
diff --git a/car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_focus_area_foreground_highlight.xml
similarity index 87%
copy from car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml
copy to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_focus_area_foreground_highlight.xml
index 74b8d13..94de514 100644
--- a/car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_focus_area_foreground_highlight.xml
@@ -16,5 +16,6 @@
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
-    <solid android:color="#00FF00"/>
+    <stroke android:width="1dp"
+            android:color="@color/car_ui_rotary_focus_stroke_color"/>
 </shape>
diff --git a/car-ui-lib/res/drawable/car_ui_icon_add.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_add.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_icon_add.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_add.xml
diff --git a/car-ui-lib/res/drawable/car_ui_icon_arrow_back.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_arrow_back.xml
similarity index 100%
copy from car-ui-lib/res/drawable/car_ui_icon_arrow_back.xml
copy to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_arrow_back.xml
diff --git a/car-ui-lib/res/drawable/car_ui_icon_close.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_close.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_icon_close.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_close.xml
diff --git a/car-ui-lib/res/drawable/car_ui_icon_delete.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_delete.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_icon_delete.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_delete.xml
diff --git a/car-ui-lib/res/drawable/car_ui_icon_down.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_down.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_icon_down.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_down.xml
diff --git a/car-ui-lib/res/drawable/car_ui_icon_edit.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_edit.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_icon_edit.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_edit.xml
diff --git a/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_lock.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_lock.xml
new file mode 100644
index 0000000..d74b14a
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/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_icon_overflow_menu.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_overflow_menu.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_icon_overflow_menu.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_overflow_menu.xml
diff --git a/car-ui-lib/res/drawable/car_ui_icon_save.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_save.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_icon_save.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_save.xml
diff --git a/car-ui-lib/res/drawable/car_ui_icon_search.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_search.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_icon_search.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_search.xml
diff --git a/car-ui-lib/res/drawable/car_ui_icon_settings.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_settings.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_icon_settings.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_settings.xml
diff --git a/car-ui-lib/res/drawable/car_ui_list_header_background.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_header_background.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_list_header_background.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_header_background.xml
diff --git a/car-ui-lib/res/drawable/car_ui_list_item_avatar_icon_outline.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_item_avatar_icon_outline.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_list_item_avatar_icon_outline.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_item_avatar_icon_outline.xml
diff --git a/car-ui-lib/res/drawable/car_ui_list_item_background.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_item_background.xml
similarity index 62%
rename from car-ui-lib/res/drawable/car_ui_list_item_background.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_item_background.xml
index 15585b2..4d33475 100644
--- a/car-ui-lib/res/drawable/car_ui_list_item_background.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_item_background.xml
@@ -13,16 +13,20 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
+            <stroke android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
+                    android:color="@color/car_ui_rotary_focus_pressed_stroke_color"/>
+        </shape>
+    </item>
     <item android:state_focused="true">
-        <ripple android:color="@color/car_ui_rotary_focus_color">
-            <item android:id="@android:id/mask">
-                <shape android:shape="rectangle">
-                    <solid android:color="?android:colorAccent"/>
-                </shape>
-            </item>
-        </ripple>
+        <shape android:shape="rectangle">
+            <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
+            <stroke android:width="@dimen/car_ui_rotary_focus_stroke_width"
+                    android:color="@color/car_ui_rotary_focus_stroke_color"/>
+        </shape>
     </item>
     <item>
         <ripple android:color="?android:attr/colorControlHighlight">
diff --git a/car-ui-lib/res/drawable/car_ui_list_item_divider.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_item_divider.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_list_item_divider.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_item_divider.xml
diff --git a/car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_limiting_message_background.xml
similarity index 83%
copy from car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml
copy to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_limiting_message_background.xml
index 74b8d13..02a8065 100644
--- a/car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_limiting_message_background.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2020 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,5 +15,6 @@
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
-    <solid android:color="#00FF00"/>
+    <solid android:color="#282A2D"/>
+    <corners android:radius="96dp"/>
 </shape>
diff --git a/car-ui-lib/res/drawable/car_ui_preference_icon_chevron.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_preference_icon_chevron.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_preference_icon_chevron.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_preference_icon_chevron.xml
diff --git a/car-ui-lib/res/drawable/car_ui_recyclerview_button_ripple_background.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recyclerview_button_ripple_background.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_recyclerview_button_ripple_background.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recyclerview_button_ripple_background.xml
diff --git a/car-ui-lib/res/drawable/car_ui_recyclerview_divider.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recyclerview_divider.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_recyclerview_divider.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recyclerview_divider.xml
diff --git a/car-ui-lib/res/drawable/car_ui_recyclerview_ic_down.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recyclerview_ic_down.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_recyclerview_ic_down.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recyclerview_ic_down.xml
diff --git a/car-ui-lib/res/drawable/car_ui_recyclerview_ic_up.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recyclerview_ic_up.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_recyclerview_ic_up.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recyclerview_ic_up.xml
diff --git a/car-ui-lib/res/drawable/car_ui_recyclerview_scrollbar_thumb.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recyclerview_scrollbar_thumb.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_recyclerview_scrollbar_thumb.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recyclerview_scrollbar_thumb.xml
diff --git a/car-ui-lib/res/drawable/car_ui_toolbar_menu_item_divider.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_toolbar_menu_item_divider.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_toolbar_menu_item_divider.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_toolbar_menu_item_divider.xml
diff --git a/car-ui-lib/res/drawable/car_ui_toolbar_menu_item_icon_background.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
similarity index 99%
rename from car-ui-lib/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
index 33594c2..46171fd 100644
--- a/car-ui-lib/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
@@ -23,5 +23,3 @@
         android:height="@dimen/car_ui_toolbar_menu_item_icon_background_size"/>
     <solid android:color="@color/car_ui_toolbar_menu_item_icon_background_color"/>
 </shape>
-
-
diff --git a/car-ui-lib/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
rename to car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
diff --git a/car-ui-lib/res/layout/car_ui_alert_dialog_edit_text.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_alert_dialog_edit_text.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_alert_dialog_edit_text.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_alert_dialog_edit_text.xml
diff --git a/car-ui-lib/res/layout/car_ui_alert_dialog_list.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_alert_dialog_list.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_alert_dialog_list.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_alert_dialog_list.xml
diff --git a/car-ui-lib/res/layout/car_ui_alert_dialog_title_with_subtitle.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_alert_dialog_title_with_subtitle.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_alert_dialog_title_with_subtitle.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_alert_dialog_title_with_subtitle.xml
diff --git a/car-ui-lib/res/layout/car_ui_base_layout.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_base_layout.xml
similarity index 80%
rename from car-ui-lib/res/layout/car_ui_base_layout.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_base_layout.xml
index 14dfb75..cd73725 100644
--- a/car-ui-lib/res/layout/car_ui_base_layout.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_base_layout.xml
@@ -17,14 +17,18 @@
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:id="@+id/content">
+    android:layout_height="match_parent">
     <!-- When not in touch mode, if we clear focus in current window, Android will re-focus the
          first focusable view in the window automatically. Adding a FocusParkingView to the window
          can fix this issue, because it can take focus, and it is transparent and its default focus
          highlight is disabled, so it's invisible to the user no matter whether it's focused or not.
          -->
     <com.android.car.ui.FocusParkingView
-        android:layout_width="1dp"
-        android:layout_height="1dp"/>
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <FrameLayout
+        android:id="@+id/car_ui_base_layout_content_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
 </FrameLayout>
diff --git a/car-ui-lib/res/layout/car_ui_base_layout_toolbar.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_base_layout_toolbar.xml
similarity index 97%
rename from car-ui-lib/res/layout/car_ui_base_layout_toolbar.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_base_layout_toolbar.xml
index 2d0cde8..410261c 100644
--- a/car-ui-lib/res/layout/car_ui_base_layout_toolbar.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_base_layout_toolbar.xml
@@ -27,11 +27,11 @@
          highlight is disabled, so it's invisible to the user no matter whether it's focused or not.
          -->
     <com.android.car.ui.FocusParkingView
-        android:layout_width="1dp"
-        android:layout_height="1dp"/>
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
 
     <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"
diff --git a/car-ui-lib/res/layout/car_ui_base_layout_toolbar_legacy.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_base_layout_toolbar_legacy.xml
similarity index 92%
rename from car-ui-lib/res/layout/car_ui_base_layout_toolbar_legacy.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_base_layout_toolbar_legacy.xml
index 0a55746..191fb3e 100644
--- a/car-ui-lib/res/layout/car_ui_base_layout_toolbar_legacy.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_base_layout_toolbar_legacy.xml
@@ -28,11 +28,11 @@
          highlight is disabled, so it's invisible to the user no matter whether it's focused or not.
          -->
     <com.android.car.ui.FocusParkingView
-        android:layout_width="1dp"
-        android:layout_height="1dp"/>
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
 
     <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"
diff --git a/car-ui-lib/res/layout/car_ui_header_list_item.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_header_list_item.xml
similarity index 62%
rename from car-ui-lib/res/layout/car_ui_header_list_item.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_header_list_item.xml
index d91512e..7ac4732 100644
--- a/car-ui-lib/res/layout/car_ui_header_list_item.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_header_list_item.xml
@@ -29,27 +29,33 @@
         android:orientation="vertical"
         app:layout_constraintGuide_begin="@dimen/car_ui_list_item_header_start_inset" />
 
-    <TextView
-        android:id="@+id/title"
+    <!-- Use nested layout as a workaround for a regression in constraintlayout where chains not
+    are not rendered correctly when the tail is gone (b/168627311).-->
+    <LinearLayout
+        android:id="@+id/text_container"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        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"
-        app:layout_constraintStart_toStartOf="@id/car_ui_list_item_start_guideline"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintVertical_chainStyle="packed" />
-
-    <TextView
-        android:id="@+id/body"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/car_ui_list_item_text_no_icon_start_margin"
-        android:textAppearance="@style/TextAppearance.CarUi.ListItem.Body"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="@id/car_ui_list_item_start_guideline"
-        app:layout_constraintTop_toBottomOf="@+id/title" />
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/car_ui_header_list_item_text_start_margin"
+            android:textDirection="locale"
+            android:textAppearance="@style/TextAppearance.CarUi.ListItem.Header" />
+
+        <TextView
+            android:id="@+id/body"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/car_ui_list_item_text_no_icon_start_margin"
+            android:textDirection="locale"
+            android:textAppearance="@style/TextAppearance.CarUi.ListItem.Body" />
+    </LinearLayout>
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-ui-lib/res/layout/car_ui_list_item.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item.xml
similarity index 86%
rename from car-ui-lib/res/layout/car_ui_list_item.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item.xml
index cc5c39a..d0f179e 100644
--- a/car-ui-lib/res/layout/car_ui_list_item.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item.xml
@@ -21,8 +21,7 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:tag="carUiListItem"
-    android:minHeight="@dimen/car_ui_list_item_height"
-    app:layout_optimizationLevel="none">
+    android:minHeight="@dimen/car_ui_list_item_height">
 
     <!-- The following touch interceptor views are sized to encompass the specific sub-sections of
     the list item view to easily control the bounds of a background ripple effects. -->
@@ -91,31 +90,35 @@
             android:scaleType="fitXY" />
     </FrameLayout>
 
-    <TextView
-        android:id="@+id/title"
+    <!-- Use nested layout as a workaround for a regression in constraintlayout where chains not
+    are not rendered correctly when the tail is gone (b/168627311).-->
+    <LinearLayout
+        android:id="@+id/text_container"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/car_ui_list_item_text_start_margin"
-        android:singleLine="@bool/car_ui_list_item_single_line_title"
-        android:textAppearance="@style/TextAppearance.CarUi.ListItem"
-        app:layout_constraintBottom_toTopOf="@+id/body"
-        app:layout_constraintEnd_toStartOf="@+id/action_container"
-        app:layout_constraintStart_toEndOf="@+id/icon_container"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintVertical_chainStyle="packed"
-        app:layout_goneMarginStart="@dimen/car_ui_list_item_text_no_icon_start_margin" />
-
-    <TextView
-        android:id="@+id/body"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/car_ui_list_item_text_start_margin"
-        android:textAppearance="@style/TextAppearance.CarUi.ListItem.Body"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/action_container"
-        app:layout_constraintStart_toEndOf="@+id/icon_container"
-        app:layout_constraintTop_toBottomOf="@+id/title"
-        app:layout_goneMarginStart="@dimen/car_ui_list_item_text_no_icon_start_margin" />
+        app:layout_constraintEnd_toStartOf="@id/action_container"
+        app:layout_constraintStart_toEndOf="@id/icon_container"
+        android:layout_marginStart="@dimen/car_ui_list_item_text_start_margin"
+        app:layout_goneMarginStart="@dimen/car_ui_list_item_text_no_icon_start_margin"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="@bool/car_ui_list_item_single_line_title"
+            android:textDirection="locale"
+            android:textAppearance="@style/TextAppearance.CarUi.ListItem" />
+
+        <TextView
+            android:id="@+id/body"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textDirection="locale"
+            android:textAppearance="@style/TextAppearance.CarUi.ListItem.Body" />
+    </LinearLayout>
 
     <!-- This touch interceptor is sized and positioned to encompass the action container   -->
     <View
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_limiting_message.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_limiting_message.xml
new file mode 100644
index 0000000..a9f9b9b
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/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>
diff --git a/car-ui-lib/res/layout/car_ui_list_preference.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_preference.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_list_preference.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_preference.xml
diff --git a/car-ui-lib/res/layout/car_ui_list_preference_with_toolbar.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_preference_with_toolbar.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_list_preference_with_toolbar.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_preference_with_toolbar.xml
diff --git a/car-ui-lib/res/layout/car_ui_preference.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_preference.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference.xml
diff --git a/car-ui-lib/res/layout/car_ui_preference_category.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_category.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_preference_category.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_category.xml
diff --git a/car-ui-lib/res/layout/car_ui_preference_chevron.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_chevron.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_preference_chevron.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_chevron.xml
diff --git a/car-ui-lib/res/layout/car_ui_preference_dialog_edittext.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_dialog_edittext.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_preference_dialog_edittext.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_dialog_edittext.xml
diff --git a/car-ui-lib/res/layout/car_ui_preference_dropdown.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_dropdown.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_preference_dropdown.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_dropdown.xml
diff --git a/car-ui-lib/res/layout/car_ui_preference_fragment.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_fragment.xml
similarity index 70%
rename from car-ui-lib/res/layout/car_ui_preference_fragment.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_fragment.xml
index e17664a..53fc089 100644
--- a/car-ui-lib/res/layout/car_ui_preference_fragment.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_fragment.xml
@@ -26,11 +26,16 @@
         android:id="@android:id/list_container"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
-        <com.android.car.ui.recyclerview.CarUiRecyclerView
-            android:id="@+id/recycler_view"
+        <com.android.car.ui.FocusArea
+            android:id="@+id/car_ui_focus_area"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:tag="carUiPreferenceRecyclerView"
-            app:enableDivider="true"/>
+            android:layout_height="match_parent">
+            <com.android.car.ui.recyclerview.CarUiRecyclerView
+                android:id="@+id/recycler_view"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:tag="carUiPreferenceRecyclerView"
+                app:enableDivider="true"/>
+        </com.android.car.ui.FocusArea>
     </FrameLayout>
 </FrameLayout>
diff --git a/car-ui-lib/res/layout/car_ui_preference_fragment_with_toolbar.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_fragment_with_toolbar.xml
similarity index 74%
rename from car-ui-lib/res/layout/car_ui_preference_fragment_with_toolbar.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_fragment_with_toolbar.xml
index cf55067..b3c6ea9 100644
--- a/car-ui-lib/res/layout/car_ui_preference_fragment_with_toolbar.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_fragment_with_toolbar.xml
@@ -27,12 +27,17 @@
         android:id="@android:id/list_container"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
-        <com.android.car.ui.recyclerview.CarUiRecyclerView
-            android:id="@+id/recycler_view"
+        <com.android.car.ui.FocusArea
+            android:id="@+id/car_ui_focus_area"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:tag="carUiPreferenceRecyclerView"
-            app:enableDivider="true"/>
+            android:layout_height="match_parent">
+            <com.android.car.ui.recyclerview.CarUiRecyclerView
+                android:id="@+id/recycler_view"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:tag="carUiPreferenceRecyclerView"
+                app:enableDivider="true"/>
+        </com.android.car.ui.FocusArea>
     </FrameLayout>
 
     <com.android.car.ui.toolbar.Toolbar
diff --git a/car-ui-lib/res/layout/car_ui_preference_widget_checkbox.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_checkbox.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_preference_widget_checkbox.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_checkbox.xml
diff --git a/car-ui-lib/res/layout/car_ui_preference_widget_seekbar.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_seekbar.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_preference_widget_seekbar.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_seekbar.xml
diff --git a/car-ui-lib/res/layout/car_ui_preference_widget_switch.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_switch.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_preference_widget_switch.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_switch.xml
diff --git a/car-ui-lib/res/layout/car_ui_radio_button_preference_widget.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_radio_button_preference_widget.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_radio_button_preference_widget.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_radio_button_preference_widget.xml
diff --git a/car-ui-lib/res/layout/car_ui_recycler_view.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view.xml
similarity index 95%
rename from car-ui-lib/res/layout/car_ui_recycler_view.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view.xml
index 1aa70fa..a0c955f 100644
--- a/car-ui-lib/res/layout/car_ui_recycler_view.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view.xml
@@ -20,7 +20,7 @@
 
     <com.android.car.ui.recyclerview.CarUiRecyclerViewContainer
         android:id="@+id/car_ui_recycler_view"
-        android:layout_width="0dp"
+        android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_marginEnd="@dimen/car_ui_scrollbar_margin"
         android:tag="carUiRecyclerView"
diff --git a/car-ui-lib/res/layout/car_ui_recycler_view_item.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_recycler_view_item.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
diff --git a/car-ui-lib/res/layout/car_ui_recyclerview_scrollbar.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recyclerview_scrollbar.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_recyclerview_scrollbar.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recyclerview_scrollbar.xml
diff --git a/car-ui-lib/res/layout/car_ui_seekbar_dialog.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_seekbar_dialog.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_seekbar_dialog.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_seekbar_dialog.xml
diff --git a/car-ui-lib/res/layout/car_ui_toolbar.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_toolbar.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar.xml
diff --git a/car-ui-lib/res/layout/car_ui_toolbar_menu_item.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_menu_item.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_toolbar_menu_item.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_menu_item.xml
diff --git a/car-ui-lib/res/layout/car_ui_toolbar_search_view.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_search_view.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_toolbar_search_view.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_search_view.xml
diff --git a/car-ui-lib/res/layout/car_ui_toolbar_tab_item.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_tab_item.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_toolbar_tab_item.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_tab_item.xml
diff --git a/car-ui-lib/res/layout/car_ui_toolbar_tab_item_flexible.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_tab_item_flexible.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_toolbar_tab_item_flexible.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_tab_item_flexible.xml
diff --git a/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_two_row.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_toolbar_two_row.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_two_row.xml
diff --git a/car-ui-lib/res/layout/car_ui_two_action_preference.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_two_action_preference.xml
similarity index 97%
rename from car-ui-lib/res/layout/car_ui_two_action_preference.xml
rename to car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_two_action_preference.xml
index 4ad6ce4..7c633e0 100644
--- a/car-ui-lib/res/layout/car_ui_two_action_preference.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_two_action_preference.xml
@@ -23,6 +23,7 @@
     android:gravity="center_vertical"
     android:minHeight="?android:attr/listPreferredItemHeightSmall">
     <LinearLayout
+        android:id="@+id/car_ui_preference_container_without_widget"
         android:layout_width="0dp"
         android:layout_height="match_parent"
         android:layout_weight="1"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-af/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-af/strings.xml
new file mode 100644
index 0000000..2c00927
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-af/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Soek …"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Rollees af"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Rollees op"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Terug"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Soek"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Instellings"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Oorloop"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Aan"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Af"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Maak toe"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Rollees word beperk terwyl jy bestuur"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Kenmerk is nie beskikbaar terwyl jy bestuur nie"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-am/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-am/strings.xml
new file mode 100644
index 0000000..0558e81
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-am/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"ይፈልጉ…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"ወደ ታች ይሸብልሉ"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"ወደ ላይ ይሸብልሉ"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"ተመለስ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"ፈልግ"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"ቅንብሮች"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ትርፍ ፍሰት"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"አብራ"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"ቅናሽ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"ዝጋ"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"እየነዱ ማሸብለል ተገድቧል"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"እየነዱ ሳለ ባህሪው አይገኝም"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ar/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..4879cb5
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ar/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"بحث…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"التمرير للأسفل"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"التمرير للأعلى"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"رجوع"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"بحث"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"إعدادات"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"القائمة الكاملة"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"مفعّل"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"غير مفعَّل"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"إغلاق"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"هناك حد أقصى للتمرير على الشاشة أثناء القيادة."</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"لا تتوفَّر هذه الميزة أثناء القيادة."</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-as/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-as/strings.xml
new file mode 100644
index 0000000..060f64e
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-as/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"সন্ধান কৰক…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"তললৈ স্ক্ৰ’ল কৰক"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"ওপৰলৈ স্ক্ৰ’ল কৰক"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"উভতি যাওক"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"সন্ধান কৰক"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"ছেটিংসমূহ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"অভাৰফ্ল’"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"অন আছে"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"অফ আছে"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"বন্ধ কৰক"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"গাড়ী চলাই থকা সময়ত স্ক্ৰ’ল কৰাটো সীমিত কৰা হৈছে"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"গাড়ী চলাই থকা সময়ত এই সুবিধাটো উপলব্ধ নহয়"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-az/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-az/strings.xml
new file mode 100644
index 0000000..9f0472e
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-az/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Axtarış…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Aşağı sürüşdürün"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Yuxarı sürüşdürün"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Geri"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Axtarış"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Ayarlar"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Kənara çıxma"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Aktiv"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Deaktiv"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Qapadın"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Sürüşdürmə avtomobil idarə edərkən məhdudlaşdırılır"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funksiya avtomobil idarə edərkən əlçatan deyil"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-b+sr+Latn/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..2584a57
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Pretražite…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Pomerite nadole"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Pomerite nagore"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Nazad"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Pretraži"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Podešavanja"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Preklopni meni"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Uključeno"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Isključeno"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Zatvori"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Pomeranje je ograničeno tokom vožnje"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funkcija nije dostupna tokom vožnje"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-be/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-be/strings.xml
new file mode 100644
index 0000000..c66577d
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-be/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Пошук…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Прагартаць уніз"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Прагартаць уверх"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Назад"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Пошук"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Налады"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Дадатковае меню"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Уключана"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Выключана"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Закрыць"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Функцыя прагортвання абмежаваная, калі аўтамабіль рухаецца"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Функцыя недаступная, калі аўтамабіль рухаецца"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-bg/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000..64d1e0a
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-bg/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Търсете…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Превъртане надолу"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Превъртане нагоре"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Назад"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Търсене"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Настройки"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Препълване"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Вкл."</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Изкл."</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Затваряне"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Превъртането е ограничено при шофиране"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Функцията не е налице по време на шофиране"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-bn/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-bn/strings.xml
new file mode 100644
index 0000000..7f09486
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-bn/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"সার্চ করুন…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"নিচের দিকে স্ক্রল করুন"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"উপরের দিকে স্ক্রল করুন"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"ফিরুন"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"সার্চ মেনু"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"সেটিংস"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ওভারফ্লো মেনু"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"চালু"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"বন্ধ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"বন্ধ করুন"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"গাড়ি চালানোর সময় স্ক্রলিং ফিচার সীমিতভাবে ব্যবহার করা যাবে"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"গাড়ি চালানোর সময় এই ফিচার কাজ করবে না"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-bs/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-bs/strings.xml
new file mode 100644
index 0000000..7295dac
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-bs/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Pretražite…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Klizanje prema dolje"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Klizanje prema gore"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Nazad"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Pretraživanje"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Postavke"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Preklopni meni"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Uključeno"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Isključeno"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Zatvori"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Klizanje je ograničeno tokom vožnje"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funkcija nije dostupna tokom vožnje"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ca/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ca/strings.xml
new file mode 100644
index 0000000..a19a663
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ca/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Cerca…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Desplaça cap avall"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Desplaça cap amunt"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Enrere"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Cerca"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Configuració"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Menú addicional"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Activat"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Desactivat"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Tanca"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"El desplaçament està limitat mentre condueixes"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Aquesta funció no està disponible mentre condueixes"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-cs/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000..16ad755
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-cs/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Vyhledat…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Posunout dolů"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Posunout nahoru"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Zpět"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Hledat"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Nastavení"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Rozbalovací nabídka"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Zap"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Vyp"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Zavřít"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Posouvání zobrazení je při řízení omezeno"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funkce při řízení není dostupná"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-da/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000..80d4a64
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-da/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Søg…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Rul ned"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Rul op"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Tilbage"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Søg"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Indstillinger"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Overløb"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Til"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Fra"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Luk"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Rullefunktionen er begrænset under kørsel"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funktionen er ikke tilgængelig, mens du kører"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-de/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..8a76bd3
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-de/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Suchen…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Nach unten scrollen"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Nach oben scrollen"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Zurück"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Suchen"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Einstellungen"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Dreipunkt-Menü"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"An"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Aus"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Schließen"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Scrollen während der Fahrt eingeschränkt"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funktion während der Fahrt nicht verfügbar"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-el/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000..1819ec2
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-el/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Αναζήτηση…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Κύλιση προς τα κάτω"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Κύλιση προς τα επάνω"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Πίσω"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Αναζήτηση"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Ρυθμίσεις"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Υπερχείλιση"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Ενεργό"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Ανενεργή"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Κλείσιμο"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Η κύλιση είναι περιορισμένη κατά τη διάρκεια της οδήγησης."</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Η λειτουργία δεν διατίθεται κατά τη διάρκεια της οδήγησης."</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-en-rAU/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..fb25c85
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-en-rAU/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Search…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Scroll down"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Scroll up"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Back"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Settings"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Overflow"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"On"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Off"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Close"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Scrolling limited while driving"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Feature not available while driving"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-en-rCA/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..fb25c85
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-en-rCA/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Search…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Scroll down"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Scroll up"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Back"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Settings"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Overflow"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"On"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Off"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Close"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Scrolling limited while driving"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Feature not available while driving"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-en-rGB/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..fb25c85
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Search…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Scroll down"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Scroll up"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Back"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Settings"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Overflow"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"On"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Off"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Close"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Scrolling limited while driving"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Feature not available while driving"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-en-rIN/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..fb25c85
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-en-rIN/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Search…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Scroll down"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Scroll up"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Back"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Settings"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Overflow"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"On"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Off"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Close"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Scrolling limited while driving"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Feature not available while driving"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-en-rXC/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..82c6ca1
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-en-rXC/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‎‏‏‎‏‎‎‎Search…‎‏‎‎‏‎"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‏‎‏‏‎Scroll down‎‏‎‎‏‎"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‏‎‎‏‎‏‏‏‏‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‏‎‏‎‏‎‎‏‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‏‏‎‎‏‎Scroll up‎‏‎‎‏‎"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‎‏‎‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‎Back‎‏‎‎‏‎"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‎‏‎‎‎‏‎‏‎‏‏‎Search‎‏‎‎‏‎"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‎‎‎‎‎‏‎‏‎Settings‎‏‎‎‏‎"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‎‏‏‎‏‎‎Overflow‎‏‎‎‏‎"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‎‏‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‎‏‎On‎‏‎‎‏‎"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‎‎‏‎Off‎‏‎‎‏‎"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‏‎‎‏‎‎‎‏‎‎‎‏‏‏‎‏‎‏‎‎‏‏‎Close‎‏‎‎‏‎"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‎‎Scrolling limited while driving‎‏‎‎‏‎"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‎‏‎‏‏‎‎‎Feature not available while driving‎‏‎‎‏‎"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-es-rUS/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..4904ad4
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-es-rUS/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Buscar…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Desplazamiento hacia abajo"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Desplazamiento hacia arriba"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Atrás"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Buscar"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Configuración"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Ampliado"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Sí"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"No"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Cerrar"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"La función de desplazamiento se limita al conducir"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Esta función no está disponible mientras conduces"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-es/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..171134a
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-es/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Realiza una búsqueda…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Desplazarse hacia abajo"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Desplazarse hacia arriba"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Atrás"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Buscar"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Ajustes"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Menú adicional"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Activado"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Desactivado"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Cerrar"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Desplazamiento limitado mientras conduces"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Función no disponible mientras conduces"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-et/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000..bb5092c
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-et/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Otsing …"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Keri alla"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Keri üles"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Tagasi"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Otsi"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Seaded"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Ületäide"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Sees"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Väljas"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Sule"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Kerimine on sõitmise ajal piiratud"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funktsioon pole sõidu ajal saadaval"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-eu/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-eu/strings.xml
new file mode 100644
index 0000000..af4e757
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-eu/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Bilatu…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Egin behera"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Egin gora"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Atzera"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Bilaketa"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Ezarpenak"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Luzapena"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Aktibatuta"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Desaktibatuta"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Itxi"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Gora edo behera egiteko eginbidea mugatuta dago gidatu bitartean"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Eginbide hau ezin da erabili gidatu bitartean"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-fa/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-fa/strings.xml
new file mode 100644
index 0000000..1ddc1df
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-fa/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"جستجو…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"پیمایش به پایین"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"پیمایش به بالا"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"برگشت"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"جستجو"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"تنظیمات"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"لبریزشده"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"روشن"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"خاموش"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"بستن"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"پیمایش درحین رانندگی محدود شده است"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"این ویژگی هنگام رانندگی در دسترس نیست"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-fi/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000..31c1e62
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-fi/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Hae…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Vieritä alas"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Vieritä ylös"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Takaisin"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Haku"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Asetukset"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Ylivuoto"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Päällä"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Pois"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Sulje"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Vierittämistä rajoitettu ajon aikana"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Ominaisuus ei ole käytettävissä ajon aikana"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-fr-rCA/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..a894032
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-fr-rCA/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Rechercher…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Faire défiler vers le bas"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Faire défiler vers le haut"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Retour"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Rechercher"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Paramètres"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Menu déroulant"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Activé"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Désactivé"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Fermer"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Le défilement de l\'écran est limité durant la conduite"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Cette fonctionnalité n\'est pas accessible durant la conduite"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-fr/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..8776859
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-fr/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Rechercher…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Défiler vers le bas"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Défiler vers le haut"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Retour"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Rechercher"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Paramètres"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Menu à développer"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Activé"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Désactivé"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Fermer"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Défilement limité lorsque vous conduisez"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Fonctionnalité non disponible lorsque vous conduisez"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-gl/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-gl/strings.xml
new file mode 100644
index 0000000..7882edf
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-gl/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Busca…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Desprazarse cara abaixo"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Desprazarse cara arriba"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Atrás"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Buscar"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Configuración"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Elemento do menú adicional"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Si"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Non"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Pechar"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"O desprazamento está limitado mentres conduces"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Esta función non está dispoñible mentres conduces"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-gu/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-gu/strings.xml
new file mode 100644
index 0000000..25d6fbc
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-gu/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"શોધો…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"નીચે સ્ક્રોલ કરો"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"ઉપર સ્ક્રોલ કરો"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"પાછળ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"શોધો"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"સેટિંગ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ઓવરફ્લો"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"ચાલુ"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"બંધ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"બંધ કરો"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"ડ્રાઇવિંગ કરતી વખતે સ્ક્રોલ કરવું મર્યાદિત છે"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"ડ્રાઇવિંગ કરતી વખતે આ સુવિધા ઉપલબ્ધ રહેશે નહીં"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-hi/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-hi/strings.xml
new file mode 100644
index 0000000..21aaf3c
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-hi/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"खोजें…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"नीचे की ओर स्क्रोल करें"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"ऊपर की ओर स्क्रोल करें"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"वापस जाएं"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"खोजें"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"सेटिंग"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ओवरफ़्लो मेन्यू"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"चालू है"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"बंद है"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"बंद करें"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"गाड़ी चलाते समय इससे ज़्यादा स्क्रोल नहीं कर सकते"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"गाड़ी चलाते समय इस सुविधा का इस्तेमाल नहीं किया जा सकता"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-hr/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000..1bc74b5
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-hr/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Pretražite…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Pomak prema dolje"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Pomak prema gore"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Natrag"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Pretraži"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Postavke"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Dodatno"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Uključeno"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Isključeno"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Zatvori"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Pomicanje je ograničeno tijekom vožnje"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Značajka nije dostupna tijekom vožnje"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-hu/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000..912809b
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-hu/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Keresés…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Görgetés lefelé"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Görgetés felfelé"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Vissza"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Keresés"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Beállítások"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"További elemeket tartalmazó menü"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Be"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Ki"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Bezárás"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Vezetés közben a görgetés korlátozott"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Vezetés közben nem áll rendelkezésre a funkció"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-hy/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-hy/strings.xml
new file mode 100644
index 0000000..bed2d2c
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-hy/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Որոնում…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Ոլորել վար"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Ոլորել վեր"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Հետ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Որոնել"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Կարգավորումներ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Լրացուցիչ ընտրացանկ"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Միացված է"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Անջատված է"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Փակել"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Վարելու ընթացքում ոլորումը սահմանափակված է"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Վարելու ընթացքում գործառույթը հասանելի չէ"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-in/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-in/strings.xml
new file mode 100644
index 0000000..b3d7063
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-in/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Telusuri…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Scroll ke bawah"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Scroll ke atas"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Kembali"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Penelusuran"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Setelan"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Tambahan"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Aktif"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Nonaktif"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Tutup"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Men-scroll dibatasi saat mengemudi"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Fitur tidak tersedia saat mengemudi"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-is/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-is/strings.xml
new file mode 100644
index 0000000..61d2891
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-is/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Leita…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Fletta niður"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Fletta upp"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Til baka"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Leit"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Stillingar"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Yfirflæði"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Kveikt"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Slökkt"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Loka"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Fletting er takmörkuð meðan á akstri stendur"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Þessi eiginleiki er ekki í boði meðan á akstri stendur"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-it/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000..2cb178a
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-it/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Cerca…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Scorri verso il basso"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Scorri verso l\'alto"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Indietro"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Cerca"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Impostazioni"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Extra"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"On"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Off"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Chiudi"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Scorrimento limitato durante la guida"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funzionalità non disponibile durante la guida"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-iw/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-iw/strings.xml
new file mode 100644
index 0000000..746dfbc
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-iw/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"חיפוש…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"גלילה למטה"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"גלילה למעלה"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"חזרה"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"חיפוש"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"הגדרות"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"אפשרויות נוספות"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"פועל"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"כבוי"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"סגירה"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"הגלילה מוגבלת בזמן הנהיגה"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"התכונה לא זמינה בזמן הנהיגה"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ja/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000..7d8eac1
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ja/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"検索…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"下にスクロール"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"上にスクロール"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"戻る"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"検索"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"設定"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"オーバーフロー"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"ON"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"OFF"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"閉じる"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"運転中のスクロール操作は制限されています"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"この機能は運転中は利用できません"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ka/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ka/strings.xml
new file mode 100644
index 0000000..1350fe8
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ka/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"ძიება…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"ქვემოთ გადაადგილება"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"ზემოთ გადაადგილება"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"უკან"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"ძიება"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"პარამეტრები"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"გადავსება"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"ჩართულია"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"გამორთულია"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"დახურვა"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"გადაადგილება შეზღუდულია მანქანის მართვისას"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"ფუნქცია მიუწვდომელია მანქანის მართვისას"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-kk/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-kk/strings.xml
new file mode 100644
index 0000000..ab8a259
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-kk/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Іздеу…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Төмен айналдыру"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Жоғары айналдыру"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Артқа"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Іздеу"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Параметрлер"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Қосымша мәзір"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Қосулы"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Өшірулі"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Жабу"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Көлік жүргізу кезінде айналдыру мүмкіндігі шектеледі."</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Көлік жүргізу кезінде бұл функция жұмыс істемейді."</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-km/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-km/strings.xml
new file mode 100644
index 0000000..17d3293
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-km/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"ស្វែងរក…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"រំកិលចុះក្រោម"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"រំកិល​​ឡើង​លើ"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"ថយក្រោយ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"ស្វែងរក"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"ការកំណត់"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ម៉ឺនុយបន្ថែម"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"បើក"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"បិទ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"បិទ"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"ការរំកិលបានកំណត់ ខណៈពេលកំពុង​បើកបរ"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"មិនអាច​ប្រើមុខងារ​នេះបានទេ ខណៈពេល​កំពុង​បើកបរ"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-kn/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-kn/strings.xml
new file mode 100644
index 0000000..3d312d1
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-kn/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"ಹುಡುಕಿ…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"ಕೆಳಗೆ ಸ್ಕ್ರಾಲ್ ಮಾಡಿ"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"ಮೇಲೆ ಸ್ಕ್ರಾಲ್ ಮಾಡಿ"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"ಹಿಂದಕ್ಕೆ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"ಹುಡುಕಾಟ"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ಓವರ್‌ಫ್ಲೋ"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"ಆನ್ ಆಗಿದೆ"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"ಆಫ್ ಆಗಿದೆ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"ಮುಚ್ಚಿ"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"ಡ್ರೈವ್ ಮಾಡುವಾಗ ಸ್ಕ್ರಾಲ್ ಮಾಡುವಿಕೆ ವೈಶಿಷ್ಟ್ಯವನ್ನು ಸೀಮಿತಗೊಳಿಸಲಾಗಿದೆ"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"ಡ್ರೈವ್ ಮಾಡುವಾಗ ಈ ವೈಶಿಷ್ಟ್ಯ ಲಭ್ಯವಿಲ್ಲ"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ko/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000..d992a45
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ko/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"검색…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"아래로 스크롤"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"위로 스크롤"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"뒤로"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"검색"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"설정"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"더보기"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"사용 설정됨"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"사용 중지됨"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"닫기"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"운전 중에는 스크롤이 제한됨"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"운전 중에 사용할 수 없는 기능입니다."</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ky/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ky/strings.xml
new file mode 100644
index 0000000..7340674
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ky/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Издөө…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Төмөн сыдыруу"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Жогору сыдыруу"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Артка"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Издөө"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Жөндөөлөр"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Кошумча меню"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Күйүк"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Өчүк"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Жабуу"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Унаа айдаганда сыдыруу чектелет"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Унаа айдаганда бул функция жеткиликтүү эмес"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-lo/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-lo/strings.xml
new file mode 100644
index 0000000..0422da3
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-lo/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"ຊອກຫາ…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"ເລື່ອນລົງ"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"ເລື່ອນຂຶ້ນ"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"ກັບຄືນ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"ຊອກຫາ"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"ການຕັ້ງຄ່າ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ລົ້ນ"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"ເປີດ"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"ປິດ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"ປິດ"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"ຈຳກັດການເລື່ອນໃນຂະນະທີ່ຂັບລົດ"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"ຄຸນສົມບັດບໍ່ສາມາດໃຊ້ໄດ້ໃນເວລາຂັບລົດ"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-lt/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-lt/strings.xml
new file mode 100644
index 0000000..09539cc
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-lt/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Ieškoti…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Slinkti žemyn"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Slinkti aukštyn"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Atgal"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Paieška"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Nustatymai"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Perpildymas"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Įjungta"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Išjungta"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Uždaryti"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Slinkimas apribotas vairuojant"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funkcija nepasiekiama vairuojant"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-lv/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-lv/strings.xml
new file mode 100644
index 0000000..1df9318
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-lv/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Meklēt…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Ritināt uz leju"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Ritināt uz augšu"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Atpakaļ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Meklēšana"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Iestatījumi"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Pārpilde"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Ieslēgta"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Izslēgta"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Aizvērt"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Ritināšana ir ierobežota braukšanas laikā."</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funkcija nav pieejama braukšanas laikā."</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-mk/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-mk/strings.xml
new file mode 100644
index 0000000..413e340
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-mk/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Пребарувајте…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Оди надолу"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Оди нагоре"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Назад"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Пребарување"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Поставки"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Прелевање"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Вклучено"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Исклучено"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Затвори"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Лизгањето е ограничено при возење"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Функцијата не е достапна при возење"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ml/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ml/strings.xml
new file mode 100644
index 0000000..f588c60
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ml/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"തിരയുക…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"താഴോട്ട് സ്‌ക്രോൾ ചെയ്യുക"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"മുകളിലോട്ട് സ്‌ക്രോൾ ചെയ്യുക"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"മടങ്ങുക"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"തിരയുക"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"ക്രമീകരണം"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ഓവർഫ്ലോ"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"ഓണാണ്"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"ഓഫാണ്"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"അടയ്‌ക്കുക"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"ഡ്രൈവ് ചെയ്യുമ്പോൾ സ്ക്രോൾ ചെയ്യൽ പരിമിതപ്പെടുത്തിയിരിക്കുന്നു"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"ഡ്രൈവ് ചെയ്യുമ്പോൾ ഫീച്ചർ ലഭ്യമല്ല"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-mn/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-mn/strings.xml
new file mode 100644
index 0000000..db34cd7
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-mn/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Хайх..."</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Доош гүйлгэх"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Дээш гүйлгэх"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Буцах"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Хайлт"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Тохиргоо"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Халих"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Асаалттай"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Унтраалттай"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Хаах"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Жолоо барьж байх үед гүйлгэхийг хязгаарласан"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Жолоо барьж байх үед онцлог боломжгүй"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-mr/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-mr/strings.xml
new file mode 100644
index 0000000..c8f7a42
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-mr/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"शोधा…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"खाली स्क्रोल करा"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"वर स्क्रोल करा"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"मागे जा"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Search"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"सेटिंग्ज"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ओव्हरफ्लो"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"सुरू आहे"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"बंद आहे"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"बंद करा"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"ड्राइव्ह करताना स्क्रोलिंग मर्यादित केली आहे"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"ड्राइव्ह करताना वैशिष्ट्य उपलब्ध नाही"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ms/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ms/strings.xml
new file mode 100644
index 0000000..7ea4fbb
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ms/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Cari…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Tatal ke bawah"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Tatal ke atas"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Kembali"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Cari"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Tetapan"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Limpahan"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Hidup"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Mati"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Tutup"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Penatalan terhad semasa memandu"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Ciri tidak tersedia semasa anda memandu"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-my/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-my/strings.xml
new file mode 100644
index 0000000..55f37b1
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-my/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"ရှာဖွေရန်…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"အောက်သို့ လှိမ့်ရန်"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"အပေါ်သို့ လှိမ့်ရန်"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"နောက်သို့"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"ရှာဖွေခြင်း"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"ဆက်တင်များ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"အပို"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"ဖွင့်ထားသည်"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"ပိတ်ထားသည်"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"ပိတ်ရန်"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"ယာဉ်မောင်းနေစဉ် အပေါ်အောက်ရွှေ့ခြင်းကို ကန့်သတ်ထားသည်"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"ကားမောင်းနေစဉ် ဝန်ဆောင်မှု မရနိုင်ပါ"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-nb/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-nb/strings.xml
new file mode 100644
index 0000000..d490393
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-nb/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Søk"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Rull ned"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Rull opp"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Tilbake"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Søk"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Innstillinger"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Overflyt"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"På"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Av"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Lukk"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Rullefunksjonen er begrenset mens du kjører"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funksjonen er ikke tilgjengelig når du kjører"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ne/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ne/strings.xml
new file mode 100644
index 0000000..90124de
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ne/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"खोज्नुहोस्…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"तलतिर स्क्रोल गर्नुहोस्"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"माथितिर स्क्रोल गर्नु…"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"पछाडि"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"खोज्नुहोस्"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"सेटिङ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ओभरफ्लो"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"सुचारू छ"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"बन्द छ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"बन्द गर्नुहोस्"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"सवारी साधन चलाइरहेका बेला योभन्दा बढी स्क्रोल गर्न पाइँदैन"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"सवारी साधन चलाइरहेका बेला यो सुविधा उपलब्ध हुँदैन"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-nl/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000..fe36ab0
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-nl/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Zoeken…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Omlaag scrollen"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Omhoog scrollen"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Terug"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Zoeken"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Instellingen"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Overloop"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Aan"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Uit"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Sluiten"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Scrollen is beperkt tijdens het rijden"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Functie niet beschikbaar tijdens het rijden"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-or/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-or/strings.xml
new file mode 100644
index 0000000..d84ae51
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-or/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"ସନ୍ଧାନ କରନ୍ତୁ…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"ତଳକୁ ସ୍କ୍ରୋଲ୍ କରନ୍ତୁ"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"ଉପରକୁ ସ୍କ୍ରୋଲ୍ କରନ୍ତୁ"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"ପଛକୁ ଫେରନ୍ତୁ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"ସେଟିଂସ୍"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ଓଭରଫ୍ଲୋ"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"ଚାଲୁ ଅଛି"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"ବନ୍ଦ ଅଛି"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"ଗାଡ଼ି ଚଲାଇବା ସମୟରେ ସ୍କ୍ରୋଲିଂ ସୀମିତ ଅଟେ"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"ଗାଡ଼ି ଚଲାଇବା ସମୟରେ ଫିଚର୍ ଉପଲବ୍ଧ ହେବ ନାହିଁ"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-pa/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-pa/strings.xml
new file mode 100644
index 0000000..576c6d4
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-pa/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"ਖੋਜ…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"ਹੇਠਾਂ ਵੱਲ ਸਕ੍ਰੋਲ ਕਰੋ"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"ਉੱਪਰ ਵੱਲ ਸਕ੍ਰੋਲ ਕਰੋ"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"ਪਿੱਛੇ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"ਖੋਜ"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"ਸੈਟਿੰਗਾਂ"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ਓਵਰਫ਼ਲੋ"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"ਚਾਲੂ"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"ਬੰਦ"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"ਬੰਦ ਕਰੋ"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"ਚਲਦੀ ਗੱਡੀ ਵਿੱਚ ਸਕ੍ਰੋਲ ਕਰਨ ਨੂੰ ਸੀਮਤ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"ਚੱਲਦੀ ਗੱਡੀ ਵਿੱਚ ਇਹ ਵਿਸ਼ੇਸ਼ਤਾ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-pl/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000..85df36d
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-pl/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Szukaj…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Przewiń w dół"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Przewiń w górę"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Wstecz"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Szukaj"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Ustawienia"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Rozszerzone menu"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Wł."</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Wył."</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Zamknij"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Podczas jazdy można przewijać tylko w ograniczonym zakresie"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funkcja niedostępna podczas jazdy"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-port/bools.xml b/car-ui-lib/car-ui-lib/src/main/res/values-port/bools.xml
new file mode 100644
index 0000000..136b3e9
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-port/bools.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+   <!-- 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>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-pt-rPT/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..ad969e8
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Pesquisar…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Deslocar para baixo"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Deslocar para cima"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Anterior"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Pesquisar"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Definições"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Menu adicional"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Ativado"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Desativado"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Fechar"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Deslocamento limitado durante a condução"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funcionalidade não disponível durante a condução."</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-pt/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-pt/strings.xml
new file mode 100644
index 0000000..98a3746
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-pt/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Pesquisar…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Rolar para baixo"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Rolar para cima"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Voltar"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Pesquisa"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Configurações"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Menu flutuante"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Ativada"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Desativada"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Fechar"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"O recurso de rolagem fica limitado enquanto você dirige"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Recurso indisponível enquanto você dirige"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ro/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000..3433253
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ro/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Căutați…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Derulați în jos"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Derulați în sus"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Înapoi"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Căutați"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Setări"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Suplimentar"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Activat"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Dezactivat"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Închideți"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Derularea este restricționată în timp ce conduceți"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funcția nu este disponibilă când conduceți"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ru/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..50c7efd
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ru/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Поиск…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Прокрутить вниз"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Прокрутить вверх"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Назад"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Поиск"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Настройки"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Дополнительное меню"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Включено"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Выключено"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Закрыть"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Функция прокручивания ограничена во время вождения."</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Функция недоступна во время вождения."</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-si/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-si/strings.xml
new file mode 100644
index 0000000..3693869
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-si/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"සොයන්න…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"පහළට අනුචලනය කරන්න"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"ඉහළට අනුචලනය කරන්න"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"ආපසු"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"සෙවීම"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"සැකසීම්"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ඉතිරියනය"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"ක්‍රියාත්මකයි"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"ක්‍රියාවිරහිතයි"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"වසන්න"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"රිය පදවන අතරතුර සීමිත අනුචලනය"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"රිය පදවන අතරේ විශේෂාංගය නොමැත"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-sk/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000..a9c0565
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-sk/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Vyhľadať…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Posunúť nadol"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Posunúť nahor"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Späť"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Hľadať"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Nastavenia"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Rozšírená ponuka"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Zap."</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Vyp."</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Zavrieť"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Posúvanie zobrazenia je počas jazdy obmedzené"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funkcia nie je k dispozícii počas jazdy"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-sl/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000..ac0940f
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-sl/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Iskanje …"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Pomik navzdol"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Pomik navzgor"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Nazaj"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Išči"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Nastavitve"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Element menija z dodatnimi elementi"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Vklopljeno"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Izklopljeno"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Zapri"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Pomikanje je med vožnjo omejeno"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funkcija med vožnjo ni na voljo"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-sq/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-sq/strings.xml
new file mode 100644
index 0000000..9208197
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-sq/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Kërko…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Lëviz poshtë"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Lëviz lart"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Pas"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Kërko"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Cilësimet"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Tejkalo"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Aktiv"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Joaktiv"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Mbyll"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Lëvizja është e kufizuar gjatë drejtimit të makinës"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Veçoria nuk ofrohet gjatë drejtimit të makinës"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-sr/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000..6a6fb9b
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-sr/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Претражите…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Померите надоле"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Померите нагоре"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Назад"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Претражи"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Подешавања"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Преклопни мени"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Укључено"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Искључено"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Затвори"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Померање је ограничено током вожње"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Функција није доступна током вожње"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-sv/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-sv/strings.xml
new file mode 100644
index 0000000..e14b299
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-sv/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Sök …"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Scrolla nedåt"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Scrolla uppåt"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Tillbaka"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Sök"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Inställningar"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Fler menyalternativ"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"På"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Av"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Stäng"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Scrollning begränsas när du kör"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Funktionen är inte tillgänglig när du kör"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-sw/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-sw/strings.xml
new file mode 100644
index 0000000..3d6ed8a
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-sw/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Tafuta…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Sogeza chini"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Sogeza juu"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Nyuma"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Tafuta"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Mipangilio"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Vipengee vya ziada"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Imewashwa"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Imezimwa"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Funga"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Umedhibitiwa kusogeza unapoendesha gari"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Kipengele hakipatikani unapoendesha gari"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ta/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ta/strings.xml
new file mode 100644
index 0000000..abd4b98
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ta/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"தேடுக…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"கீழே செல்லும்"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"மேலே செல்லும்"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"பின்செல்வதற்கான பட்டன்"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"தேடு"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"அமைப்புகள்"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"ஓவர்ஃப்லோ"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"ஆன்"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"ஆஃப்"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"மூடுக"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"வாகனம் ஓட்டிக் கொண்டிருப்பதனால் இதற்குமேல் ஸ்க்ரோல் செய்ய முடியாது"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"வாகனம் ஓட்டும்போது இந்த அம்சத்தைப் பயன்படுத்த இயலாது"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-te/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-te/strings.xml
new file mode 100644
index 0000000..e3422b8
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-te/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"వెతకండి…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"కిందికి స్క్రోల్ చేయండి"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"పైకి స్క్రోల్ చేయండి"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"వెనుకకు"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"శోధన"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"సెట్టింగ్‌లు"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"అతివ్యాప్తి అంశాలు"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"ఆన్‌లో ఉంది"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"ఆఫ్‌లో ఉంది"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"మూసివేయండి"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"డ్రైవింగ్ చేస్తున్నప్పుడు స్క్రోలింగ్ పరిమితంగా ఉంటుంది"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"డ్రైవింగ్ చేస్తున్నప్పుడు ఈ ఫీచర్ అందుబాటులో ఉండదు"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-th/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-th/strings.xml
new file mode 100644
index 0000000..837ff9c
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-th/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"ค้นหา…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"เลื่อนลง"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"เลื่อนขึ้น"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"กลับ"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"ค้นหา"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"การตั้งค่า"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"รายการเพิ่มเติม"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"เปิด"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"ปิด"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"ปิด"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"การเลื่อนถูกจำกัดไม่ให้ใช้งานขณะขับรถ"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"ฟีเจอร์ไม่พร้อมใช้งานขณะขับรถ"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-tl/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-tl/strings.xml
new file mode 100644
index 0000000..3a8e981
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-tl/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Maghanap…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Mag-scroll pababa"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Mag-scroll pataas"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Bumalik"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Paghahanap"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Mga Setting"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Overflow"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"I-on"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"I-off"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Isara"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Limitado ang pag-scroll habang nagmamaneho"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Hindi available ang feature habang nagmamaneho"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-tr/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..f9e923f
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-tr/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Ara…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Aşağı kaydır"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Yukarı kaydır"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Geri"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Ara"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Ayarlar"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Taşma"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Açık"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Kapalı"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Kapat"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"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="7490306547860308268">"Sürüş sırasında bu özellik kullanılamaz"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-uk/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000..0ecec7e
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-uk/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Пошук…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Прокрутити вниз"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Прокрутити вгору"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Назад"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Пошук"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Налаштування"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Додаткове меню"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Увімкнено"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Вимкнено"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Закрити"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Прокручування обмежено під час водіння"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Функція недоступна під час руху автомобіля"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-ur/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-ur/strings.xml
new file mode 100644
index 0000000..23b9033
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-ur/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"تلاش کریں…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"نیچے اسکرول کریں"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"اوپر اسکرول کریں"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"پیچھے"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"تلاش کریں"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"ترتیبات"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"اوورفلو"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"آن ہے"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"آف ہے"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"بند کریں"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"ڈرائیونگ کے دوران اسکرولنگ محدود ہے"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"ڈرائیونگ کے دوران یہ خصوصیت دستیاب نہیں ہے"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-uz/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-uz/strings.xml
new file mode 100644
index 0000000..9735f60
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-uz/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Qidirish…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Pastga surish"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Tepaga surish"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Orqaga"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Qidiruv"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Sozlamalar"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Kengaytirilgan"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Yoniq"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Yoqilmagan"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Yopish"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Avtomobilda harakatlanayotganda aylantirish funksiyasi cheklangan"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Avtomobilda harakatlanayotganda bu funksiya ishlamaydi"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-vi/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-vi/strings.xml
new file mode 100644
index 0000000..d0ebb04
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-vi/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Tìm kiếm…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Cuộn xuống"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Cuộn lên"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Quay lại"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Tìm kiếm"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Cài đặt"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Trình đơn mục bổ sung"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Đang bật"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Đang tắt"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Đóng"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Tính năng cuộn bị hạn chế khi đang lái xe"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"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/car-ui-lib/src/main/res/values-zh-rCN/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..7c5a5b0
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"搜索…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"向下滚动"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"向上滚动"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"返回"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"搜索"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"设置"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"溢出菜单"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"开启"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"关闭"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"关闭"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"驾车时滚动操作受限"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"驾车时无法使用此功能"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-zh-rHK/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..2bd14e7
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-zh-rHK/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"搜尋…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"向下捲動"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"向上捲動"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"返回"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"搜尋"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"設定"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"展開式選單"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"已開啟"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"已關閉"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"關閉"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"捲動功能在駕駛時受限制"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"無法在駕駛時使用此功能"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-zh-rTW/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..1fc26f4
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"搜尋…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"向下捲動"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"向上捲動"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"返回"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"搜尋"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"設定"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"溢位"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"開啟"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"關閉"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"關閉"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"系統會限制開車時的捲動操作"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"開車時無法使用這項功能"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values-zu/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values-zu/strings.xml
new file mode 100644
index 0000000..8955377
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values-zu/strings.xml
@@ -0,0 +1,31 @@
+<?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="8339474149462104372">"Sesha…"</string>
+    <string name="car_ui_scrollbar_page_down_button" msgid="746830252244551947">"Skrolela phansi"</string>
+    <string name="car_ui_scrollbar_page_up_button" msgid="965431866383176249">"Skrolela phezulu"</string>
+    <string name="car_ui_toolbar_nav_icon_content_description" msgid="6116610935599234725">"Emuva"</string>
+    <string name="car_ui_toolbar_menu_item_search_title" msgid="2988075382498202155">"Sesha"</string>
+    <string name="car_ui_toolbar_menu_item_settings_title" msgid="5206858558664840197">"Izilungiselelo"</string>
+    <string name="car_ui_toolbar_menu_item_overflow_title" msgid="9132512843297732890">"Ukuphuphuma"</string>
+    <string name="car_ui_preference_switch_on" msgid="2091385466752081649">"Vuliwe"</string>
+    <string name="car_ui_preference_switch_off" msgid="511096411523593697">"Valiwe"</string>
+    <string name="car_ui_alert_dialog_default_button" msgid="399078418913970003">"Vala"</string>
+    <string name="car_ui_scrolling_limited_message" msgid="5978003166159186378">"Ukuskrola kukhawulelwe uma ushayela"</string>
+    <string name="car_ui_restricted_while_driving" msgid="7490306547860308268">"Isici asitholakali ngenkathi ushayela"</string>
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml b/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..f38522f
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml
@@ -0,0 +1,247 @@
+<?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>
+    <!-- Global theme options for CarUi -->
+    <declare-styleable name="CarUi">
+        <!-- When set to true, the window decor will contain an OEM-customizable layout -->
+        <attr name="carUiBaseLayout" format="boolean"/>
+        <!-- When set to true, a CarUi Toolbar will be provided in the window decor -->
+        <attr name="carUiToolbar" format="boolean"/>
+    </declare-styleable>
+
+    <declare-styleable name="CarUiToolbar">
+        <!-- Title of the toolbar, only displayed in certain conditions -->
+        <attr name="title" format="string"/>
+        <!-- Logo drawable for the toolbar. Appears when there's no back/close button shown -->
+        <attr name="logo" format="reference"/>
+        <!-- Hint for the search bar in the toolbar -->
+        <attr name="searchHint" format="string"/>
+        <!-- Whether or not to show the MenuItems while searching. Default false. -->
+        <attr name="showMenuItemsWhileSearching" format="boolean"/>
+        <!-- Initial state of the toolbar. See the Toolbar.State enum for more information -->
+        <attr name="car_ui_state" format="enum">
+            <enum name="home" value="0"/>
+            <enum name="subpage" value="1"/>
+            <enum name="search" value="2"/>
+        </attr>
+        <!-- Whether or not the toolbar should have a background. Default true. -->
+        <attr name="showBackground" format="boolean"/>
+        <!-- Mode of the navigation button See the Toolbar.NavButtonMode enum for more information -->
+        <attr name="car_ui_navButtonMode" format="enum">
+            <enum name="back" value="0"/>
+            <enum name="close" value="1"/>
+            <enum name="down" value="2"/>
+        </attr>
+        <!-- XML resource of MenuItems. See Toolbar.setMenuItems(int) for more information. -->
+        <attr name="menuItems" format="reference"/>
+        <!-- Whether or not to show tabs in the SUBPAGE state. Default false -->
+        <attr name="showTabsInSubpage" format="boolean"/>
+    </declare-styleable>
+
+    <declare-styleable name="CarUiToolbarMenuItem">
+        <!-- Id of MenuItem, used to differentiate them -->
+        <attr name="id" format="reference"/>
+        <!-- Show/hide the MenuItem -->
+        <attr name="visible" format="boolean"/>
+        <!-- Set this to true to make a search MenuItem. This will override every other property except id, visible, and onclick. -->
+        <attr name="search" format="boolean"/>
+        <!-- Set this to true to make a settings MenuItem. This will override every other property except id, visible, and onclick. -->
+        <attr name="settings" format="boolean"/>
+        <!-- Title -->
+        <attr name="title"/>
+        <!-- Icon -->
+        <attr name="icon" format="reference"/>
+        <!-- True to tint the icon to a consistent color. Default true, all the other booleans default to false -->
+        <attr name="tinted" format="boolean"/>
+        <!-- Show both the icon and title at the same time -->
+        <attr name="showIconAndTitle" format="boolean"/>
+        <!-- True if this MenuItem should be a switch -->
+        <attr name="checkable" format="boolean"/>
+        <!-- Whether the switch should be checked or not. Setting this implies checkable=true -->
+        <attr name="checked" format="boolean"/>
+        <!-- True if this MenuItem should be activatable, in which case it will visually toggle states when clicked -->
+        <attr name="activatable" format="boolean"/>
+        <!-- Whether the MenuItem starts activated. Setting this implies activatable=true -->
+        <attr name="activated" format="boolean"/>
+        <!-- How to display the MenuItem. "always" means always show it on the toolbar, "never" means never show it on the toolbar and instead show it in the overflow menu -->
+        <attr name="displayBehavior" format="enum">
+            <enum name="always" value="0"/>
+            <enum name="never" value="1"/>
+        </attr>
+        <!-- Ux restrictions required to interact with this MenuItem -->
+        <attr name="uxRestrictions">
+            <!-- Values are copied from android.car.drivingstate.CarUxRestrictions. Note:
+            UX_RESTRICTIONS_BASELINE is not allowed here because it's useless and confusing. -->
+            <flag name="UX_RESTRICTIONS_NO_DIALPAD" value="1"/>
+            <flag name="UX_RESTRICTIONS_NO_FILTERING" value="2"/>
+            <flag name="UX_RESTRICTIONS_LIMIT_STRING_LENGTH" value="4"/>
+            <flag name="UX_RESTRICTIONS_NO_KEYBOARD" value="8"/>
+            <flag name="UX_RESTRICTIONS_NO_VIDEO" value="16"/>
+            <flag name="UX_RESTRICTIONS_LIMIT_CONTENT" value="32"/>
+            <flag name="UX_RESTRICTIONS_NO_SETUP" value="64"/>
+            <flag name="UX_RESTRICTIONS_NO_TEXT_MESSAGE" value="128"/>
+            <flag name="UX_RESTRICTIONS_NO_VOICE_TRANSCRIPTION" value="256"/>
+            <flag name="UX_RESTRICTIONS_FULLY_RESTRICTED" value="511"/>
+        </attr>
+        <!-- The name of a method that takes a MenuItem as an argument in you'r toolbar's Activity. Will be called when the MenuItem is clicked -->
+        <attr name="onClick" format="string"/>
+    </declare-styleable>
+
+    <!-- Theme attribute to specifying a default style for all CarUiToolbars -->
+    <attr name="CarUiToolbarStyle" format="reference"/>
+
+    <declare-styleable name="CarUiRecyclerView">
+        <!-- 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="topOffset" format="integer" />
+        <!-- Bottom offset for car ui recycler view for linear layout. -->
+        <attr name="bottomOffset" format="integer" />
+
+        <!-- Number of columns in a grid layout. -->
+        <attr name="numOfColumns" format="integer" />
+
+        <!-- car ui recycler view layout. -->
+        <attr name="layoutStyle" format="enum">
+            <!-- linear layout -->
+            <enum name="linear" value="0" />
+            <!-- grid layout -->
+            <enum name="grid" value="1" />
+        </attr>
+    </declare-styleable>
+
+    <declare-styleable name="CarUiPreference">
+        <!-- Toggle for showing chevron -->
+        <attr name="showChevron" format="boolean" />
+        <!-- Show ripple when disabled preference is clicked -->
+        <attr name="showRippleOnDisabledPreference" format="boolean" />
+    </declare-styleable>
+
+    <declare-styleable name="CarUiTwoActionPreference">
+        <!-- Determines if the secondary action is initially shown -->
+        <attr name="actionShown" format="boolean"/>
+    </declare-styleable>
+
+    <!-- Theme attribute to specify a default style for all CarUiPreferences -->
+    <attr name="carUiPreferenceStyle" format="reference" />
+
+    <!-- Theme attribute to specify a default style for all CarUiRecyclerViews -->
+    <attr name="carUiRecyclerViewStyle" format="reference" />
+
+    <attr name="state_ux_restricted" format="boolean" />
+
+    <!-- Attributes for FocusArea. -->
+    <declare-styleable name="FocusArea">
+        <!-- The ID of the default focus view. The view will be prioritized when searching for a
+             focus target.
+             (1) When the user nudges the rotary controller, it will search for a target FocusArea,
+                 then search for a target view within the target FocusArea, and focus on the target
+                 view. The target view is chosen in the following order:
+                   1. the "android:focusedByDefault" view, if any
+                   2. the "app:defaultFocus" view, if any
+                   3. the first focusable item in a scrollable container, if any
+                   4. previously focused view, if any and the cache is not stale
+                   5. the first focusable view, if any
+                 Note that 4 will be prioritized over 1&2&3 when
+                 car_ui_focus_area_default_focus_overrides_history is false.
+             (2) When it needs to initialize the focus (such as when a window is opened), it will
+                 search for a view in the window and focus on it. The view is chosen in the
+                 following order:
+                   1. the first "android:focusedByDefault" view, if any
+                   2. the first "app:defaultFocus" view, if any
+                   3. the first focusable item in a scrollable container, if any
+                   4. the first focusable view that is not a FocusParkingView, if any
+             If there is only one FocusArea that needs to set default focus, you can use either
+             "app:defaultFocus" or "android:focusedByDefault". If there are more than one, you
+             should use "android:focusedByDefault" in the primary FocusArea, and use
+             "app:defaultFocus" in other FocusAreas. -->
+
+        <attr name="defaultFocus" format="reference"/>
+
+        <!-- The paddings of FocusArea highlight. It does't impact the paddings on its child views,
+             or vice versa. -->
+        <!-- The start padding of the FocusArea highlight. -->
+        <attr name="highlightPaddingStart" format="dimension"/>
+        <!-- The end padding of the FocusArea highlight. -->
+        <attr name="highlightPaddingEnd" format="dimension"/>
+        <!-- The top padding of the FocusArea highlight. -->
+        <attr name="highlightPaddingTop" format="dimension"/>
+        <!-- The bottom padding of the FocusArea highlight. -->
+        <attr name="highlightPaddingBottom" format="dimension"/>
+        <!-- The horizontal padding of the FocusArea highlight. It can be overridden by
+             highlightPaddingStart or highlightPaddingEnd. -->
+        <attr name="highlightPaddingHorizontal" format="dimension"/>
+        <!-- The vertical padding of the FocusArea highlight.  It can be overridden by
+             highlightPaddingTop or highlightPaddingBottom. -->
+        <attr name="highlightPaddingVertical" format="dimension"/>
+
+        <!-- The offset of the FocusArea's bounds. It only affects the perceived bounds for the
+             purposes of finding the nudge target. It doesn't affect the FocusArea's view bounds or
+             highlight bounds. The offset should only be used when FocusAreas are overlapping and
+             nudge interaction is ambiguous. -->
+        <!-- The offset of the FocusArea's start bound. -->
+        <attr name="startBoundOffset" format="dimension"/>
+        <!-- The offset of the FocusArea's end bound. -->
+        <attr name="endBoundOffset" format="dimension"/>
+        <!-- The offset of the FocusArea's top bound. -->
+        <attr name="topBoundOffset" format="dimension"/>
+        <!-- The offset of the FocusArea's bottom bound. -->
+        <attr name="bottomBoundOffset" format="dimension"/>
+        <!-- The offset of the FocusArea's horizontal bounds. It can be overridden by
+             startBoundOffset or endBoundOffset. -->
+        <attr name="horizontalBoundOffset" format="dimension"/>
+        <!-- The offset of the FocusArea's vertical bounds. It can be overridden by topBoundOffset
+             or bottomBoundOffset. -->
+        <attr name="verticalBoundOffset" format="dimension"/>
+
+        <!-- Attributes for nudge shortcut. Usually nudge is used to navigate to another FocusArea,
+             but when a nudge shortcut is specified, it's used to navigate to the given view within
+             the same FocusArea. The 2 attributes must be specified together. -->
+        <!-- The ID of the nudge shortcut view. -->
+        <attr name="nudgeShortcut" format="reference"/>
+        <!-- The direction of the nudge shortcut. -->
+        <attr name="nudgeShortcutDirection">
+            <!-- View.FOCUS_LEFT -->
+            <flag name="left" value="0x11" />
+            <!-- View.FOCUS_RIGHT -->
+            <flag name="right" value="0x42" />
+            <!-- View.FOCUS_UP -->
+            <flag name="up" value="0x21" />
+            <!-- View.FOCUS_DOWN -->
+            <flag name="down" value="0x82" />
+        </attr>
+
+        <!-- Attributes to specify the target FocusArea for a nudge. -->
+        <!-- The ID of the target FocusArea when nudging to the left. -->
+        <attr name="nudgeLeft" format="reference"/>
+        <!-- The ID of the target FocusArea when nudging to the right. -->
+        <attr name="nudgeRight" format="reference"/>
+        <!-- The ID of the target FocusArea when nudging up. -->
+        <attr name="nudgeUp" format="reference"/>
+        <!-- The ID of the target FocusArea when nudging down. -->
+        <attr name="nudgeDown" format="reference"/>
+    </declare-styleable>
+
+    <!-- Attributes for FocusParkingView. -->
+    <declare-styleable name="FocusParkingView">
+        <!-- Whether to restore focus when the frameworks wants to focus the FocusParkingView. When
+             false, the FocusParkingView allows itself to be focused instead. This should be false
+             for the FocusParkingView in an ActivityView. The default value is true. -->
+        <attr name="shouldRestoreFocus" format="boolean"/>
+    </declare-styleable>
+</resources>
diff --git a/car-ui-lib/res/values/bools.xml b/car-ui-lib/car-ui-lib/src/main/res/values/bools.xml
similarity index 78%
rename from car-ui-lib/res/values/bools.xml
rename to car-ui-lib/car-ui-lib/src/main/res/values/bools.xml
index d85e840..f53ee3f 100644
--- a/car-ui-lib/res/values/bools.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/bools.xml
@@ -55,4 +55,15 @@
     <!-- Whether to draw highlight (car_ui_focus_area_background_highlight) on top of the FocusArea
          but behind its children when the FocusArea's descendant gets focused. -->
     <bool name="car_ui_enable_focus_area_background_highlight">false</bool>
+    <!-- Whether to focus on the app:defaultFocus view when nudging to the FocusArea, even if there
+         was another view focused in the FocusArea. -->
+    <bool name="car_ui_focus_area_default_focus_overrides_history">true</bool>
+    <!-- Whether to clear FocusArea history when the user rotates the rotary controller. -->
+    <bool name="car_ui_clear_focus_area_history_when_rotating">true</bool>
+
+    <!--  Whether to log the escrow components check automatically for all the activities or not.  -->
+    <bool name="car_ui_escrow_check_components_automatically">false</bool>
+
+    <!-- If there is no positive/negative/neutral button, should we add one that says "dismiss"? -->
+    <bool name="car_ui_alert_dialog_force_dismiss_button">true</bool>
 </resources>
diff --git a/car-ui-lib/res/values/colors.xml b/car-ui-lib/car-ui-lib/src/main/res/values/colors.xml
similarity index 82%
rename from car-ui-lib/res/values/colors.xml
rename to car-ui-lib/car-ui-lib/src/main/res/values/colors.xml
index db62ad2..326dcfb 100644
--- a/car-ui-lib/res/values/colors.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/colors.xml
@@ -45,5 +45,11 @@
     <color name="car_ui_preference_two_action_divider_color">#1fffffff</color>
 
     <!-- Rotary focus -->
-    <color name="car_ui_rotary_focus_color">#4b9eff</color>
+
+    <color name="car_ui_rotary_focus_stroke_color">#94CBFF</color>
+    <color name="car_ui_rotary_focus_fill_color">#3D94CBFF</color>
+    <color name="car_ui_rotary_focus_pressed_stroke_color">#94CBFF</color>
+    <color name="car_ui_rotary_focus_pressed_fill_color">#8A94CBFF</color>
+    <color name="car_ui_rotary_focus_stroke_secondary_color">#0059B3</color>
+    <color name="car_ui_rotary_focus_fill_secondary_color">#3D0059B3</color>
 </resources>
diff --git a/car-ui-lib/res/values/dimens.xml b/car-ui-lib/car-ui-lib/src/main/res/values/dimens.xml
similarity index 97%
rename from car-ui-lib/res/values/dimens.xml
rename to car-ui-lib/car-ui-lib/src/main/res/values/dimens.xml
index 3910c60..b2eaadc 100644
--- a/car-ui-lib/res/values/dimens.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/dimens.xml
@@ -127,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>
@@ -206,4 +207,9 @@
     <dimen name="car_ui_list_item_check_box_end_inset">@dimen/car_ui_list_item_end_inset</dimen>
     <dimen name="car_ui_list_item_check_box_icon_container_width">@dimen/car_ui_list_item_icon_container_width</dimen>
 
+    <!-- Rotary focus highlight  -->
+
+    <dimen name="car_ui_rotary_focus_stroke_width">8dp</dimen>
+    <dimen name="car_ui_rotary_focus_pressed_stroke_width">4dp</dimen>
+
 </resources>
diff --git a/car-ui-lib/res/values/drawables.xml b/car-ui-lib/car-ui-lib/src/main/res/values/drawables.xml
similarity index 100%
rename from car-ui-lib/res/values/drawables.xml
rename to car-ui-lib/car-ui-lib/src/main/res/values/drawables.xml
diff --git a/car-ui-lib/res/values/ids.xml b/car-ui-lib/car-ui-lib/src/main/res/values/ids.xml
similarity index 85%
rename from car-ui-lib/res/values/ids.xml
rename to car-ui-lib/car-ui-lib/src/main/res/values/ids.xml
index 2c23ac3..3f0c9b1 100644
--- a/car-ui-lib/res/values/ids.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/ids.xml
@@ -16,4 +16,7 @@
 <resources>
     <!-- Id used for the search button when using Toolbar.createSearch() method -->
     <item name="search" type="id"/>
+
+    <!-- Id used for in car_ui_toolbar_menu_item.xml -->
+    <item name="car_ui_toolbar_menu_item_text_container" type="id"/>
 </resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/integers.xml b/car-ui-lib/car-ui-lib/src/main/res/values/integers.xml
new file mode 100644
index 0000000..976a634
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/integers.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+    <!-- Default max string length -->
+    <integer name="car_ui_default_max_string_length">120</integer>
+    <integer name="car_ui_scrollbar_longpress_initial_delay">1000</integer>
+    <integer name="car_ui_scrollbar_longpress_repeat_interval">100</integer>
+
+    <!-- Type of FocusHistoryCache. The values are defined in RotaryCache. 1 means the cache
+    is disabled, 2 means entries in the cache will expire after a period of time, and 3 means
+    elements in the cache will never expire. -->
+    <integer name="car_ui_focus_history_cache_type">2</integer>
+    <!-- How many milliseconds before the entry in FocusHistoryCache expires. Must be positive value
+     when car_ui_focus_history_cache_type is 2. -->
+    <integer name="car_ui_focus_history_expiration_period_ms">300000</integer>
+
+    <!-- Type of FocusAreaHistoryCache. The values are defined in RotaryCache. 1 means the
+    cache is disabled, 2 means entries in the cache will expire after a period of time, and 3 means
+    elements in the cache will never expire. -->
+    <integer name="car_ui_focus_area_history_cache_type">2</integer>
+    <!-- How many milliseconds before an entry in FocusAreaHistoryCache expires. Must be positive
+    value when car_ui_focus_area_history_cache_type is 2. -->
+    <integer name="car_ui_focus_area_history_expiration_period_ms">3000</integer>
+</resources>
diff --git a/car-ui-lib/res/values/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml
similarity index 92%
rename from car-ui-lib/res/values/strings.xml
rename to car-ui-lib/car-ui-lib/src/main/res/values/strings.xml
index ff101f7..46a29fe 100644
--- a/car-ui-lib/res/values/strings.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml
@@ -48,7 +48,10 @@
     <string name="car_ui_preference_switch_off">Off</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/car-ui-lib/src/main/res/values/styles.xml
similarity index 100%
rename from car-ui-lib/res/values/styles.xml
rename to car-ui-lib/car-ui-lib/src/main/res/values/styles.xml
diff --git a/car-ui-lib/res/values/themes.xml b/car-ui-lib/car-ui-lib/src/main/res/values/themes.xml
similarity index 98%
rename from car-ui-lib/res/values/themes.xml
rename to car-ui-lib/car-ui-lib/src/main/res/values/themes.xml
index 34bbf3c..a599fa0 100644
--- a/car-ui-lib/res/values/themes.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/themes.xml
@@ -155,6 +155,7 @@
         <item name="seekBarStyle">?android:attr/seekBarStyle</item>
 
         <!-- Button styles -->
+        <item name="android:buttonStyle">@style/Widget.CarUi.Button</item>
         <item name="buttonStyle">?android:attr/buttonStyle</item>
         <item name="buttonStyleSmall">?android:attr/buttonStyleSmall</item>
 
@@ -204,6 +205,9 @@
 
         <!-- Used by CarUiRecyclerView -->
         <item name="carUiRecyclerViewStyle">@style/Widget.CarUi.CarUiRecyclerView</item>
+
+        <!-- textAppearance -->
+        <item name="android:textAppearance">@style/TextAppearance.CarUi</item>
     </style>
 
     <!-- TODO(b/150230923) remove this when other apps are ready -->
diff --git a/car-ui-lib/res/values/values.xml b/car-ui-lib/car-ui-lib/src/main/res/values/values.xml
similarity index 100%
rename from car-ui-lib/res/values/values.xml
rename to car-ui-lib/car-ui-lib/src/main/res/values/values.xml
diff --git a/car-ui-lib/sharedlibrary/Android.bp b/car-ui-lib/car-ui-lib/src/test/Android.bp
similarity index 69%
copy from car-ui-lib/sharedlibrary/Android.bp
copy to car-ui-lib/car-ui-lib/src/test/Android.bp
index 1abaa30..5c48329 100644
--- a/car-ui-lib/sharedlibrary/Android.bp
+++ b/car-ui-lib/car-ui-lib/src/test/Android.bp
@@ -1,4 +1,3 @@
-
 //
 // Copyright (C) 2020 The Android Open Source Project
 //
@@ -14,18 +13,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//###########################################################
+// CarUi lib just for Robolectric test target.     #
+//###########################################################
 android_app {
-    name: "car-ui-lib-sharedlibrary",
-    visibility: [
-        "//packages/apps/Car/libs/car-ui-lib/tests/PaintBooth:__pkg__",
-    ],
-    platform_apis: true,
+    name: "CarUiRobolectricTestApp",
     resource_dirs: ["res"],
-    aaptflags: ["--shared-lib"],
-    optimize: {
-        enabled: false,
-    },
-    static_libs: [
-        "car-ui-lib",
-    ],
+    platform_apis: true,
+    privileged: true,
+    libs: ["android.car"],
+    static_libs: ["car-ui-lib"],
 }
+
diff --git a/car-ui-lib/tests/robotests/AndroidManifest.xml b/car-ui-lib/car-ui-lib/src/test/AndroidManifest.xml
similarity index 95%
rename from car-ui-lib/tests/robotests/AndroidManifest.xml
rename to car-ui-lib/car-ui-lib/src/test/AndroidManifest.xml
index b62579f..eb83765 100644
--- a/car-ui-lib/tests/robotests/AndroidManifest.xml
+++ b/car-ui-lib/car-ui-lib/src/test/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2019 Google Inc.
+    Copyright (C) 2020 Google Inc.
 
     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/car-ui-lib/sharedlibrary/Android.bp b/car-ui-lib/car-ui-lib/src/test/java/Android.bp
similarity index 65%
copy from car-ui-lib/sharedlibrary/Android.bp
copy to car-ui-lib/car-ui-lib/src/test/java/Android.bp
index 1abaa30..551d985 100644
--- a/car-ui-lib/sharedlibrary/Android.bp
+++ b/car-ui-lib/car-ui-lib/src/test/java/Android.bp
@@ -1,4 +1,3 @@
-
 //
 // Copyright (C) 2020 The Android Open Source Project
 //
@@ -14,18 +13,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-android_app {
-    name: "car-ui-lib-sharedlibrary",
-    visibility: [
-        "//packages/apps/Car/libs/car-ui-lib/tests/PaintBooth:__pkg__",
+
+//###############################################
+// Car Ui Robolectric test target. #
+//###############################################
+android_robolectric_test {
+    name: "CarUiRoboTests",
+    srcs: ["**/*.java"],
+    libs: [
+        "android.car",
+        "testng",
     ],
-    platform_apis: true,
-    resource_dirs: ["res"],
-    aaptflags: ["--shared-lib"],
-    optimize: {
-        enabled: false,
-    },
-    static_libs: [
-        "car-ui-lib",
-    ],
+    instrumentation_for: "CarUiRobolectricTestApp",
 }
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiListItemTest.java b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/CarUiListItemTest.java
similarity index 100%
rename from car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiListItemTest.java
rename to car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/CarUiListItemTest.java
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiRecyclerViewAdapterTest.java b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/CarUiRecyclerViewAdapterTest.java
similarity index 100%
rename from car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiRecyclerViewAdapterTest.java
rename to car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/CarUiRecyclerViewAdapterTest.java
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
similarity index 65%
rename from car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
rename to car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
index c0b87de..648a3a4 100644
--- a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
+++ b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.view.LayoutInflater;
 import android.view.View;
 
@@ -41,40 +42,23 @@
     private Context mContext;
     private View mView;
     private CarUiRecyclerView mCarUiRecyclerView;
+    private Resources mResources;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-
         mContext = RuntimeEnvironment.application;
-    }
-
-    @Test
-    public void onHeightChanged_shouldAddTheValueToInitialTopValue() {
-        mView = LayoutInflater.from(mContext)
-                .inflate(R.layout.test_linear_car_ui_recycler_view, null);
-
-        mCarUiRecyclerView = mView.findViewById(R.id.test_prv);
-
-        assertThat(mCarUiRecyclerView.getPaddingBottom()).isEqualTo(0);
-        assertThat(mCarUiRecyclerView.getPaddingTop()).isEqualTo(0);
-        assertThat(mCarUiRecyclerView.getPaddingStart()).isEqualTo(0);
-        assertThat(mCarUiRecyclerView.getPaddingEnd()).isEqualTo(0);
-
-        mCarUiRecyclerView.onHeightChanged(10);
-
-        assertThat(mCarUiRecyclerView.getPaddingTop()).isEqualTo(10);
-        assertThat(mCarUiRecyclerView.getPaddingBottom()).isEqualTo(0);
-        assertThat(mCarUiRecyclerView.getPaddingStart()).isEqualTo(0);
-        assertThat(mCarUiRecyclerView.getPaddingEnd()).isEqualTo(0);
+        mResources = mContext.getResources();
     }
 
     @Test
     public void setAdapter_shouldInitializeLinearLayoutManager() {
         mView = LayoutInflater.from(mContext)
-                .inflate(R.layout.test_linear_car_ui_recycler_view, null);
+                .inflate(mResources.getIdentifier("test_linear_car_ui_recycler_view", "layout",
+                        mContext.getPackageName()), null);
 
-        mCarUiRecyclerView = mView.findViewById(R.id.test_prv);
+        mCarUiRecyclerView = mView.findViewById(
+                mResources.getIdentifier("test_prv", "id", mContext.getPackageName()));
 
         assertThat(mCarUiRecyclerView.getLayoutManager()).isInstanceOf(
                 LinearLayoutManager.class);
@@ -83,9 +67,11 @@
     @Test
     public void setAdapter_shouldInitializeGridLayoutManager() {
         mView = LayoutInflater.from(mContext)
-                .inflate(R.layout.test_grid_car_ui_recycler_view, null);
+                .inflate(mResources.getIdentifier("test_grid_car_ui_recycler_view", "layout",
+                        mContext.getPackageName()), null);
 
-        mCarUiRecyclerView = mView.findViewById(R.id.test_prv);
+        mCarUiRecyclerView = mView.findViewById(
+                mResources.getIdentifier("test_prv", "id", mContext.getPackageName()));
 
         assertThat(mCarUiRecyclerView.getLayoutManager()).isInstanceOf(
                 GridLayoutManager.class);
@@ -94,9 +80,11 @@
     @Test
     public void init_shouldContainRecyclerView() {
         mView = LayoutInflater.from(mContext)
-                .inflate(R.layout.test_grid_car_ui_recycler_view, null);
+                .inflate(mResources.getIdentifier("test_linear_car_ui_recycler_view", "layout",
+                        mContext.getPackageName()), null);
 
-        mCarUiRecyclerView = mView.findViewById(R.id.test_prv);
+        mCarUiRecyclerView = mView.findViewById(
+                mResources.getIdentifier("test_prv", "id", mContext.getPackageName()));
 
         assertThat(mCarUiRecyclerView).isNotNull();
     }
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiSmoothScrollerTest.java b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/CarUiSmoothScrollerTest.java
similarity index 100%
rename from car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiSmoothScrollerTest.java
rename to car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/CarUiSmoothScrollerTest.java
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiSnapHelperTest.java b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/CarUiSnapHelperTest.java
similarity index 100%
rename from car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiSnapHelperTest.java
rename to car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/CarUiSnapHelperTest.java
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/DefaultScrollBarTest.java b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/DefaultScrollBarTest.java
similarity index 100%
rename from car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/DefaultScrollBarTest.java
rename to car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/recyclerview/DefaultScrollBarTest.java
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/ExtendedShadowTypeface.java b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/toolbar/ExtendedShadowTypeface.java
similarity index 100%
rename from car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/ExtendedShadowTypeface.java
rename to car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/toolbar/ExtendedShadowTypeface.java
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/ShadowAsyncLayoutInflater.java b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/toolbar/ShadowAsyncLayoutInflater.java
similarity index 100%
rename from car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/ShadowAsyncLayoutInflater.java
rename to car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/toolbar/ShadowAsyncLayoutInflater.java
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/TestActivity.java b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/toolbar/TestActivity.java
similarity index 91%
rename from car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/TestActivity.java
rename to car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/toolbar/TestActivity.java
index dcbe1cd..b6fa3c4 100644
--- a/car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/TestActivity.java
+++ b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/toolbar/TestActivity.java
@@ -18,8 +18,6 @@
 import android.app.Activity;
 import android.os.Bundle;
 
-import com.android.car.ui.R;
-
 /** An activity to use in the Toolbar tests */
 public class TestActivity extends Activity {
 
@@ -28,7 +26,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.test_toolbar);
+        setContentView(getResources().getIdentifier("test_toolbar", "layout", getPackageName()));
     }
 
     @Override
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/ToolbarTest.java b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/toolbar/ToolbarTest.java
similarity index 90%
rename from car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/ToolbarTest.java
rename to car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/toolbar/ToolbarTest.java
index 6cd127b..330660b 100644
--- a/car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/ToolbarTest.java
+++ b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/toolbar/ToolbarTest.java
@@ -63,6 +63,10 @@
         mToolbar = mActivity.findViewById(R.id.toolbar);
     }
 
+    private int getTestDrawableInt(String drawable) {
+        return mResources.getIdentifier(drawable, "drawable", mContext.getPackageName());
+    }
+
     @Test
     public void getters_nochanges_shouldReturnDefaults() {
         assertThat(mToolbar.getBackgroundShown()).isEqualTo(true);
@@ -100,7 +104,7 @@
     @Test
     public void showLogo_whenSet_andStateIsHome() {
         mToolbar.setState(Toolbar.State.HOME);
-        mToolbar.setLogo(R.drawable.test_ic_launcher);
+        mToolbar.setLogo(getTestDrawableInt("test_ic_launcher"));
 
         assertThat(mToolbar.findViewById(R.id.car_ui_toolbar_logo).isShown()).isTrue();
         assertThat(mToolbar.findViewById(R.id.car_ui_toolbar_title_logo).isShown()).isFalse();
@@ -113,7 +117,7 @@
 
         Toolbar toolbar = new Toolbar(mContext);
         toolbar.setState(Toolbar.State.HOME);
-        toolbar.setLogo(R.drawable.test_ic_launcher);
+        mToolbar.setLogo(getTestDrawableInt("test_ic_launcher"));
 
         assertThat(isViewInToolbarShown(toolbar, R.id.car_ui_toolbar_logo)).isFalse();
         assertThat(isViewInToolbarShown(toolbar, R.id.car_ui_toolbar_title_logo)).isFalse();
@@ -122,7 +126,7 @@
     @Test
     public void showTitleLogo_whenSet_andStateIsNotHome() {
         mToolbar.setState(Toolbar.State.SUBPAGE);
-        mToolbar.setLogo(R.drawable.test_ic_launcher);
+        mToolbar.setLogo(getTestDrawableInt("test_ic_launcher"));
 
         assertThat(mToolbar.findViewById(R.id.car_ui_toolbar_logo).isShown()).isFalse();
         assertThat(mToolbar.findViewById(R.id.car_ui_toolbar_title_logo).isShown()).isTrue();
@@ -184,8 +188,12 @@
 
         // Set title and tabs for toolbar.
         toolbar.setTitle("Test title");
-        toolbar.addTab(new TabLayout.Tab(mContext.getDrawable(R.drawable.test_ic_launcher), "Foo"));
-        toolbar.addTab(new TabLayout.Tab(mContext.getDrawable(R.drawable.test_ic_launcher), "Foo"));
+        toolbar.addTab(
+                new TabLayout.Tab(mContext.getDrawable(getTestDrawableInt("test_ic_launcher")),
+                        "Foo"));
+        toolbar.addTab(
+                new TabLayout.Tab(mContext.getDrawable(getTestDrawableInt("test_ic_launcher")),
+                        "Foo"));
 
         // Toolbar should display two rows, showing both title and tabs.
         assertThat(isViewInToolbarShown(toolbar, R.id.car_ui_toolbar_tabs)).isTrue();
@@ -193,7 +201,7 @@
     }
 
     @Test
-    public void testState_twoRow_withTitle()  {
+    public void testState_twoRow_withTitle() {
         mockResources();
         when(mResources.getBoolean(R.bool.car_ui_toolbar_tabs_on_second_row)).thenReturn(true);
 
@@ -214,8 +222,12 @@
 
         Toolbar toolbar = new Toolbar(mContext);
         assertThat(toolbar.isTabsInSecondRow()).isTrue();
-        toolbar.addTab(new TabLayout.Tab(mContext.getDrawable(R.drawable.test_ic_launcher), "Foo"));
-        toolbar.addTab(new TabLayout.Tab(mContext.getDrawable(R.drawable.test_ic_launcher), "Foo"));
+        toolbar.addTab(
+                new TabLayout.Tab(mContext.getDrawable(getTestDrawableInt("test_ic_launcher")),
+                        "Foo"));
+        toolbar.addTab(
+                new TabLayout.Tab(mContext.getDrawable(getTestDrawableInt("test_ic_launcher")),
+                        "Foo"));
 
         // Toolbar should display two rows with an empty title and tabs.
         assertThat(isViewInToolbarShown(toolbar, R.id.car_ui_toolbar_tabs)).isTrue();
@@ -232,8 +244,12 @@
 
         // Set title and tabs for toolbar.
         toolbar.setTitle("Test title");
-        toolbar.addTab(new TabLayout.Tab(mContext.getDrawable(R.drawable.test_ic_launcher), "Foo"));
-        toolbar.addTab(new TabLayout.Tab(mContext.getDrawable(R.drawable.test_ic_launcher), "Foo"));
+        toolbar.addTab(
+                new TabLayout.Tab(mContext.getDrawable(getTestDrawableInt("test_ic_launcher")),
+                        "Foo"));
+        toolbar.addTab(
+                new TabLayout.Tab(mContext.getDrawable(getTestDrawableInt("test_ic_launcher")),
+                        "Foo"));
 
         // With only one row available, toolbar will only show tabs and not the title.
         assertThat(isViewInToolbarShown(toolbar, R.id.car_ui_toolbar_tabs)).isTrue();
@@ -245,7 +261,6 @@
         mockResources();
         when(mResources.getBoolean(R.bool.car_ui_toolbar_tabs_on_second_row)).thenReturn(false);
 
-
         Toolbar toolbar = new Toolbar(mContext);
         assertThat(toolbar.isTabsInSecondRow()).isFalse();
 
@@ -261,12 +276,15 @@
         mockResources();
         when(mResources.getBoolean(R.bool.car_ui_toolbar_tabs_on_second_row)).thenReturn(false);
 
-
         Toolbar toolbar = new Toolbar(mContext);
         assertThat(toolbar.isTabsInSecondRow()).isFalse();
 
-        toolbar.addTab(new TabLayout.Tab(mContext.getDrawable(R.drawable.test_ic_launcher), "Foo"));
-        toolbar.addTab(new TabLayout.Tab(mContext.getDrawable(R.drawable.test_ic_launcher), "Foo"));
+        toolbar.addTab(
+                new TabLayout.Tab(mContext.getDrawable(getTestDrawableInt("test_ic_launcher")),
+                        "Foo"));
+        toolbar.addTab(
+                new TabLayout.Tab(mContext.getDrawable(getTestDrawableInt("test_ic_launcher")),
+                        "Foo"));
 
         // Toolbar should display one row with only tabs.
         assertThat(isViewInToolbarShown(toolbar, R.id.car_ui_toolbar_tabs)).isTrue();
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/utils/CarUxRestrictionsUtilTest.java b/car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/utils/CarUxRestrictionsUtilTest.java
similarity index 100%
rename from car-ui-lib/tests/robotests/src/com/android/car/ui/utils/CarUxRestrictionsUtilTest.java
rename to car-ui-lib/car-ui-lib/src/test/java/com/android/car/ui/utils/CarUxRestrictionsUtilTest.java
diff --git a/car-ui-lib/tests/robotests/res/drawable/test_ic_launcher.png b/car-ui-lib/car-ui-lib/src/test/res/drawable/test_ic_launcher.png
similarity index 100%
rename from car-ui-lib/tests/robotests/res/drawable/test_ic_launcher.png
rename to car-ui-lib/car-ui-lib/src/test/res/drawable/test_ic_launcher.png
Binary files differ
diff --git a/car-ui-lib/tests/robotests/res/layout/test_custom_view.xml b/car-ui-lib/car-ui-lib/src/test/res/layout/test_custom_view.xml
similarity index 100%
rename from car-ui-lib/tests/robotests/res/layout/test_custom_view.xml
rename to car-ui-lib/car-ui-lib/src/test/res/layout/test_custom_view.xml
diff --git a/car-ui-lib/tests/robotests/res/layout/test_grid_car_ui_recycler_view.xml b/car-ui-lib/car-ui-lib/src/test/res/layout/test_grid_car_ui_recycler_view.xml
similarity index 100%
rename from car-ui-lib/tests/robotests/res/layout/test_grid_car_ui_recycler_view.xml
rename to car-ui-lib/car-ui-lib/src/test/res/layout/test_grid_car_ui_recycler_view.xml
diff --git a/car-ui-lib/tests/robotests/res/layout/test_linear_car_ui_recycler_view.xml b/car-ui-lib/car-ui-lib/src/test/res/layout/test_linear_car_ui_recycler_view.xml
similarity index 100%
rename from car-ui-lib/tests/robotests/res/layout/test_linear_car_ui_recycler_view.xml
rename to car-ui-lib/car-ui-lib/src/test/res/layout/test_linear_car_ui_recycler_view.xml
diff --git a/car-ui-lib/tests/robotests/res/layout/test_toolbar.xml b/car-ui-lib/car-ui-lib/src/test/res/layout/test_toolbar.xml
similarity index 100%
rename from car-ui-lib/tests/robotests/res/layout/test_toolbar.xml
rename to car-ui-lib/car-ui-lib/src/test/res/layout/test_toolbar.xml
diff --git a/car-ui-lib/documentation/images/android_project_view.png b/car-ui-lib/documentation/images/android_project_view.png
new file mode 100644
index 0000000..9b82bc2
--- /dev/null
+++ b/car-ui-lib/documentation/images/android_project_view.png
Binary files differ
diff --git a/car-ui-lib/documentation/images/launch_paintbooth.png b/car-ui-lib/documentation/images/launch_paintbooth.png
new file mode 100644
index 0000000..ba901b6
--- /dev/null
+++ b/car-ui-lib/documentation/images/launch_paintbooth.png
Binary files differ
diff --git a/car-ui-lib/documentation/images/navigating_to_car_ui_lib.png b/car-ui-lib/documentation/images/navigating_to_car_ui_lib.png
new file mode 100644
index 0000000..4897ad6
--- /dev/null
+++ b/car-ui-lib/documentation/images/navigating_to_car_ui_lib.png
Binary files differ
diff --git a/car-ui-lib/documentation/images/open_existing_android_studio_project.png b/car-ui-lib/documentation/images/open_existing_android_studio_project.png
new file mode 100644
index 0000000..29c2f96
--- /dev/null
+++ b/car-ui-lib/documentation/images/open_existing_android_studio_project.png
Binary files differ
diff --git a/car-ui-lib/documentation/images/running_tests.png b/car-ui-lib/documentation/images/running_tests.png
new file mode 100644
index 0000000..796bf3a
--- /dev/null
+++ b/car-ui-lib/documentation/images/running_tests.png
Binary files differ
diff --git a/car-ui-lib/generate_rros.mk b/car-ui-lib/generate_rros.mk
index 4f67520..c0d2966 100644
--- a/car-ui-lib/generate_rros.mk
+++ b/car-ui-lib/generate_rros.mk
@@ -21,7 +21,8 @@
 define generate-rro
   include $$(CLEAR_VARS)
 
-  rro_package_name := $(2)-$(subst .,-,$(1))
+  rro_package_name := $(subst .,-,$(2))-$(subst .,-,$(1))
+  manifest_file := $(4)
   LOCAL_RESOURCE_DIR := $(3)
   LOCAL_RRO_THEME := $$(rro_package_name)
   LOCAL_PACKAGE_NAME := $$(rro_package_name)
@@ -34,7 +35,7 @@
   LOCAL_AAPT_FLAGS := --no-resource-deduping
 
   gen := $$(call intermediates-dir-for,ETC,$$(rro_package_name))/AndroidManifest.xml
-  $$(gen): $(LOCAL_PATH)/AndroidManifest.xml
+  $$(gen): $$(manifest_file)
 	@echo Generate $$@
 	$$(hide) mkdir -p $$(dir $$@)
 	$$(hide) sed -e "s/{{TARGET_PACKAGE_NAME}}/$(1)/" \
@@ -44,11 +45,17 @@
   include $$(BUILD_RRO_PACKAGE)
 endef
 
+ifndef CAR_UI_RRO_MANIFEST_FILE
+CAR_UI_RRO_MANIFEST_FILE = $(LOCAL_PATH)/AndroidManifest.xml
+endif
+
 $(foreach t,\
   $(CAR_UI_RRO_TARGETS),\
-  $(eval $(call generate-rro,$(t),$(CAR_UI_RRO_SET_NAME),$(CAR_UI_RESOURCE_DIR))))
+  $(eval $(call generate-rro,$(t),$(CAR_UI_RRO_SET_NAME),$(CAR_UI_RESOURCE_DIR),$(CAR_UI_RRO_MANIFEST_FILE))) \
+  )
 
 # Clear variables
 CAR_UI_RRO_SET_NAME :=
+CAR_UI_RRO_MANIFEST_FILE :=
 CAR_UI_RESOURCE_DIR :=
 CAR_UI_RRO_TARGETS :=
diff --git a/car-ui-lib/tests/paintbooth/AndroidManifest-gradle.xml b/car-ui-lib/paintbooth/AndroidManifest-gradle.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/AndroidManifest-gradle.xml
rename to car-ui-lib/paintbooth/AndroidManifest-gradle.xml
diff --git a/car-ui-lib/tests/paintbooth/AndroidManifest.xml b/car-ui-lib/paintbooth/AndroidManifest.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/AndroidManifest.xml
rename to car-ui-lib/paintbooth/AndroidManifest.xml
diff --git a/car-ui-lib/tests/paintbooth/build.gradle b/car-ui-lib/paintbooth/build.gradle
similarity index 90%
rename from car-ui-lib/tests/paintbooth/build.gradle
rename to car-ui-lib/paintbooth/build.gradle
index e85a629..94ab49e 100644
--- a/car-ui-lib/tests/paintbooth/build.gradle
+++ b/car-ui-lib/paintbooth/build.gradle
@@ -17,11 +17,11 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 29
+    compileSdkVersion 30
     defaultConfig {
         applicationId "com.android.car.ui.paintbooth"
         minSdkVersion 28
-        targetSdkVersion 29
+        targetSdkVersion 30
         versionCode 1
         versionName "1.0"
     }
@@ -35,19 +35,17 @@
         main {
             manifest.srcFile 'AndroidManifest-gradle.xml'
             java {
-                srcDirs = ['src']
                 filter.excludes = [
                         "com/android/car/ui/paintbooth/overlays/OverlayManagerImpl.java",
                         "com/android/car/ui/paintbooth/currentactivity/ActivityTaskManagerImpl.java",
                 ]
             }
-            res.srcDirs = ['res', 'res-public']
         }
     }
 }
 
 dependencies {
-    implementation project(':')
+    implementation project(':car-ui-lib')
     debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0'
     api 'androidx.annotation:annotation:1.1.0'
     api 'androidx.constraintlayout:constraintlayout:1.1.3'
diff --git a/car-ui-lib/tests/paintbooth/gradlew b/car-ui-lib/paintbooth/gradlew
similarity index 100%
rename from car-ui-lib/tests/paintbooth/gradlew
rename to car-ui-lib/paintbooth/gradlew
diff --git a/car-ui-lib/tests/paintbooth/gradlew.bat b/car-ui-lib/paintbooth/gradlew.bat
similarity index 100%
rename from car-ui-lib/tests/paintbooth/gradlew.bat
rename to car-ui-lib/paintbooth/gradlew.bat
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/MainActivity.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/MainActivity.java
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/VisibleBoundsSimulator.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/VisibleBoundsSimulator.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/VisibleBoundsSimulator.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/VisibleBoundsSimulator.java
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
similarity index 97%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
index f6db6d3..1819427 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
@@ -38,7 +38,7 @@
 import java.util.ArrayList;
 
 /**
- * Activity that shows {@link CarUiRecyclerView} with dummy {@link CarUiContentListItem} entries
+ * Activity that shows {@link CarUiRecyclerView} with sample {@link CarUiContentListItem} entries
  */
 public class CarUiListItemActivity extends Activity implements InsetsChangedListener {
 
@@ -55,11 +55,11 @@
         toolbar.setState(Toolbar.State.SUBPAGE);
 
         CarUiRecyclerView recyclerView = findViewById(R.id.list);
-        mAdapter = new CarUiListItemAdapter(generateDummyData());
+        mAdapter = new CarUiListItemAdapter(generateSampleData());
         recyclerView.setAdapter(mAdapter);
     }
 
-    private ArrayList<CarUiListItem> generateDummyData() {
+    private ArrayList<CarUiListItem> generateSampleData() {
         Context context = this;
 
         CarUiHeaderListItem header = new CarUiHeaderListItem("First header");
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiRecyclerViewActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiRecyclerViewActivity.java
similarity index 94%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiRecyclerViewActivity.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiRecyclerViewActivity.java
index 810b10c..b7032c0 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/CarUiRecyclerViewActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiRecyclerViewActivity.java
@@ -33,7 +33,7 @@
 import java.util.ArrayList;
 
 /**
- * Activity that shows CarUiRecyclerView example with dummy data.
+ * Activity that shows CarUiRecyclerView example with sample data.
  */
 public class CarUiRecyclerViewActivity extends Activity implements InsetsChangedListener {
     private final ArrayList<String> mData = new ArrayList<>();
@@ -51,11 +51,11 @@
         CarUiRecyclerView recyclerView = findViewById(R.id.list);
         recyclerView.setLayoutManager(new LinearLayoutManager(this));
 
-        RecyclerViewAdapter adapter = new RecyclerViewAdapter(generateDummyData());
+        RecyclerViewAdapter adapter = new RecyclerViewAdapter(generateSampleData());
         recyclerView.setAdapter(adapter);
     }
 
-    private ArrayList<String> generateDummyData() {
+    private ArrayList<String> generateSampleData() {
         for (int i = 0; i <= mDataToGenerate; i++) {
             mData.add("data" + i);
         }
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java
similarity index 93%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java
index 7d98a1e..5d51d4c 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java
@@ -31,7 +31,7 @@
 
 import java.util.ArrayList;
 
-/** Activity that shows GridCarUiRecyclerView example with dummy data. */
+/** Activity that shows GridCarUiRecyclerView example with sample data. */
 public class GridCarUiRecyclerViewActivity extends Activity implements
         InsetsChangedListener {
     private final ArrayList<String> mData = new ArrayList<>();
@@ -48,11 +48,11 @@
 
         CarUiRecyclerView recyclerView = findViewById(R.id.list);
 
-        RecyclerViewAdapter adapter = new RecyclerViewAdapter(generateDummyData());
+        RecyclerViewAdapter adapter = new RecyclerViewAdapter(generateSampleData());
         recyclerView.setAdapter(adapter);
     }
 
-    private ArrayList<String> generateDummyData() {
+    private ArrayList<String> generateSampleData() {
         for (int i = 1; i <= mDataToGenerate; i++) {
             mData.add("data" + i);
         }
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/RecyclerViewAdapter.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/RecyclerViewAdapter.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/caruirecyclerview/RecyclerViewAdapter.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/RecyclerViewAdapter.java
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/currentactivity/ActivityTaskManager.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/currentactivity/ActivityTaskManager.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/currentactivity/ActivityTaskManager.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/currentactivity/ActivityTaskManager.java
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/currentactivity/ActivityTaskManagerImpl.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/currentactivity/ActivityTaskManagerImpl.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/currentactivity/ActivityTaskManagerImpl.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/currentactivity/ActivityTaskManagerImpl.java
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/currentactivity/CurrentActivityService.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/currentactivity/CurrentActivityService.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/currentactivity/CurrentActivityService.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/currentactivity/CurrentActivityService.java
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
similarity index 89%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
index 7cf1cce..fd70730 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.app.Activity;
+import android.app.AlertDialog;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.util.Pair;
@@ -35,6 +36,8 @@
 import com.android.car.ui.baselayout.InsetsChangedListener;
 import com.android.car.ui.core.CarUi;
 import com.android.car.ui.paintbooth.R;
+import com.android.car.ui.recyclerview.CarUiContentListItem;
+import com.android.car.ui.recyclerview.CarUiListItemAdapter;
 import com.android.car.ui.recyclerview.CarUiRadioButtonListItem;
 import com.android.car.ui.recyclerview.CarUiRadioButtonListItemAdapter;
 import com.android.car.ui.recyclerview.CarUiRecyclerView;
@@ -83,6 +86,8 @@
                 v -> showDialogWithLongSubtitleAndIcon()));
         mButtons.add(Pair.create(R.string.dialog_show_single_choice,
                 v -> showDialogWithSingleChoiceItems()));
+        mButtons.add(Pair.create(R.string.dialog_show_list_items_without_default_button,
+                v -> showDialogWithListItemsWithoutDefaultButton()));
         mButtons.add(Pair.create(R.string.dialog_show_permission_dialog,
                 v -> showPermissionDialog()));
         mButtons.add(Pair.create(R.string.dialog_show_multi_permission_dialog,
@@ -199,6 +204,35 @@
                 .show();
     }
 
+
+    private void showDialogWithListItemsWithoutDefaultButton() {
+        ArrayList<CarUiContentListItem> data = new ArrayList<>();
+        AlertDialog[] dialog = new AlertDialog[1];
+
+        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setTitle("First item");
+        item.setOnItemClickedListener(i -> dialog[0].dismiss());
+        data.add(item);
+
+
+        item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setTitle("Second item");
+        item.setOnItemClickedListener(i -> dialog[0].dismiss());
+        data.add(item);
+
+        item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setTitle("Third item");
+        item.setOnItemClickedListener(i -> dialog[0].dismiss());
+        data.add(item);
+
+        dialog[0] = new AlertDialogBuilder(this)
+                .setTitle("Select one option.")
+                .setSubtitle("Ony one option may be selected at a time")
+                .setAdapter(new CarUiListItemAdapter(data))
+                .setAllowDismissButton(false)
+                .show();
+    }
+
     private void showDialogWithSubtitleAndIcon() {
         new AlertDialogBuilder(this)
                 .setTitle("My Title!")
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/overlays/OverlayActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/overlays/OverlayActivity.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/overlays/OverlayActivity.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/overlays/OverlayActivity.java
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/overlays/OverlayManager.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/overlays/OverlayManager.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/overlays/OverlayManager.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/overlays/OverlayManager.java
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/overlays/OverlayManagerImpl.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/overlays/OverlayManagerImpl.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/overlays/OverlayManagerImpl.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/overlays/OverlayManagerImpl.java
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/preferences/PreferenceActivity.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceActivity.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/preferences/PreferenceActivity.java
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java
similarity index 86%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java
index 233c873..e9f990d 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java
@@ -20,7 +20,6 @@
 
 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;
 
 /**
@@ -45,10 +44,5 @@
         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/NoCarUiToolbarActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/NoCarUiToolbarActivity.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/NoCarUiToolbarActivity.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/NoCarUiToolbarActivity.java
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/OldToolbarActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/OldToolbarActivity.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/OldToolbarActivity.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/OldToolbarActivity.java
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
similarity index 99%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
index 938f448..f32818a 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
@@ -320,7 +320,7 @@
         Mutable<Boolean> showingLauncherIcon = new Mutable<>(false);
         mButtons.add(Pair.create(getString(R.string.toolbar_toggle_search_icon), v -> {
             if (showingLauncherIcon.value) {
-                toolbar.setSearchIcon(0);
+                toolbar.setSearchIcon(null);
             } else {
                 toolbar.setSearchIcon(R.drawable.ic_launcher);
             }
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/widgets/WidgetActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widgets/WidgetActivity.java
similarity index 100%
rename from car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/widgets/WidgetActivity.java
rename to car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widgets/WidgetActivity.java
diff --git a/car-ui-lib/tests/paintbooth/res-public/values/public.xml b/car-ui-lib/paintbooth/src/main/res-public/values/public.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res-public/values/public.xml
rename to car-ui-lib/paintbooth/src/main/res-public/values/public.xml
diff --git a/car-ui-lib/tests/paintbooth/res/drawable/ic_cut.xml b/car-ui-lib/paintbooth/src/main/res/drawable/ic_cut.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/drawable/ic_cut.xml
rename to car-ui-lib/paintbooth/src/main/res/drawable/ic_cut.xml
diff --git a/car-ui-lib/tests/paintbooth/res/drawable/ic_launcher.png b/car-ui-lib/paintbooth/src/main/res/drawable/ic_launcher.png
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/drawable/ic_launcher.png
rename to car-ui-lib/paintbooth/src/main/res/drawable/ic_launcher.png
Binary files differ
diff --git a/car-ui-lib/tests/paintbooth/res/drawable/ic_sample_logo.png b/car-ui-lib/paintbooth/src/main/res/drawable/ic_sample_logo.png
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/drawable/ic_sample_logo.png
rename to car-ui-lib/paintbooth/src/main/res/drawable/ic_sample_logo.png
Binary files differ
diff --git a/car-ui-lib/tests/paintbooth/res/drawable/ic_settings_gear.xml b/car-ui-lib/paintbooth/src/main/res/drawable/ic_settings_gear.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/drawable/ic_settings_gear.xml
rename to car-ui-lib/paintbooth/src/main/res/drawable/ic_settings_gear.xml
diff --git a/car-ui-lib/tests/paintbooth/res/drawable/ic_settings_wifi.xml b/car-ui-lib/paintbooth/src/main/res/drawable/ic_settings_wifi.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/drawable/ic_settings_wifi.xml
rename to car-ui-lib/paintbooth/src/main/res/drawable/ic_settings_wifi.xml
diff --git a/car-ui-lib/tests/paintbooth/res/drawable/ic_tracklist.xml b/car-ui-lib/paintbooth/src/main/res/drawable/ic_tracklist.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/drawable/ic_tracklist.xml
rename to car-ui-lib/paintbooth/src/main/res/drawable/ic_tracklist.xml
diff --git a/car-ui-lib/tests/paintbooth/res/drawable/simulated_screen_shape.xml b/car-ui-lib/paintbooth/src/main/res/drawable/simulated_screen_shape.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/drawable/simulated_screen_shape.xml
rename to car-ui-lib/paintbooth/src/main/res/drawable/simulated_screen_shape.xml
diff --git a/car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_activity.xml b/car-ui-lib/paintbooth/src/main/res/layout/car_ui_recycler_view_activity.xml
similarity index 75%
rename from car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_activity.xml
rename to car-ui-lib/paintbooth/src/main/res/layout/car_ui_recycler_view_activity.xml
index 5ae7e7f..89a73d8 100644
--- a/car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_activity.xml
+++ b/car-ui-lib/paintbooth/src/main/res/layout/car_ui_recycler_view_activity.xml
@@ -14,9 +14,15 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.car.ui.recyclerview.CarUiRecyclerView
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/list"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@drawable/car_ui_activity_background"/>
+    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"/>
+</FrameLayout>
+
+
diff --git a/car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_activity_with_old_toolbar.xml b/car-ui-lib/paintbooth/src/main/res/layout/car_ui_recycler_view_activity_with_old_toolbar.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_activity_with_old_toolbar.xml
rename to car-ui-lib/paintbooth/src/main/res/layout/car_ui_recycler_view_activity_with_old_toolbar.xml
diff --git a/car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_list_item.xml b/car-ui-lib/paintbooth/src/main/res/layout/car_ui_recycler_view_list_item.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/layout/car_ui_recycler_view_list_item.xml
rename to car-ui-lib/paintbooth/src/main/res/layout/car_ui_recycler_view_list_item.xml
diff --git a/car-ui-lib/tests/paintbooth/res/layout/details_preference_widget.xml b/car-ui-lib/paintbooth/src/main/res/layout/details_preference_widget.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/layout/details_preference_widget.xml
rename to car-ui-lib/paintbooth/src/main/res/layout/details_preference_widget.xml
diff --git a/car-ui-lib/tests/paintbooth/res/layout/grid_car_ui_recycler_view_activity.xml b/car-ui-lib/paintbooth/src/main/res/layout/grid_car_ui_recycler_view_activity.xml
similarity index 72%
rename from car-ui-lib/tests/paintbooth/res/layout/grid_car_ui_recycler_view_activity.xml
rename to car-ui-lib/paintbooth/src/main/res/layout/grid_car_ui_recycler_view_activity.xml
index 2b0d1a4..233c148 100644
--- a/car-ui-lib/tests/paintbooth/res/layout/grid_car_ui_recycler_view_activity.xml
+++ b/car-ui-lib/paintbooth/src/main/res/layout/grid_car_ui_recycler_view_activity.xml
@@ -14,12 +14,16 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.car.ui.recyclerview.CarUiRecyclerView
+<FrameLayout
     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"
-    android:background="@drawable/car_ui_activity_background" />
+    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"
+        app:numOfColumns="4"
+        app:layoutStyle="grid"/>
+</FrameLayout>
diff --git a/car-ui-lib/tests/paintbooth/res/layout/list_item.xml b/car-ui-lib/paintbooth/src/main/res/layout/list_item.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/layout/list_item.xml
rename to car-ui-lib/paintbooth/src/main/res/layout/list_item.xml
diff --git a/car-ui-lib/tests/paintbooth/res/layout/list_item_switch.xml b/car-ui-lib/paintbooth/src/main/res/layout/list_item_switch.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/layout/list_item_switch.xml
rename to car-ui-lib/paintbooth/src/main/res/layout/list_item_switch.xml
diff --git a/car-ui-lib/tests/paintbooth/res/layout/no_car_ui_toolbar_activity.xml b/car-ui-lib/paintbooth/src/main/res/layout/no_car_ui_toolbar_activity.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/layout/no_car_ui_toolbar_activity.xml
rename to car-ui-lib/paintbooth/src/main/res/layout/no_car_ui_toolbar_activity.xml
diff --git a/car-ui-lib/tests/paintbooth/res/layout/simulated_screen_shape_container.xml b/car-ui-lib/paintbooth/src/main/res/layout/simulated_screen_shape_container.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/layout/simulated_screen_shape_container.xml
rename to car-ui-lib/paintbooth/src/main/res/layout/simulated_screen_shape_container.xml
diff --git a/car-ui-lib/tests/paintbooth/res/layout/widgets_activity.xml b/car-ui-lib/paintbooth/src/main/res/layout/widgets_activity.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/layout/widgets_activity.xml
rename to car-ui-lib/paintbooth/src/main/res/layout/widgets_activity.xml
diff --git a/car-ui-lib/tests/paintbooth/res/values/arrays.xml b/car-ui-lib/paintbooth/src/main/res/values/arrays.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/values/arrays.xml
rename to car-ui-lib/paintbooth/src/main/res/values/arrays.xml
diff --git a/car-ui-lib/tests/paintbooth/res/values/colors.xml b/car-ui-lib/paintbooth/src/main/res/values/colors.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/values/colors.xml
rename to car-ui-lib/paintbooth/src/main/res/values/colors.xml
diff --git a/car-ui-lib/tests/paintbooth/res/values/dimens.xml b/car-ui-lib/paintbooth/src/main/res/values/dimens.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/values/dimens.xml
rename to car-ui-lib/paintbooth/src/main/res/values/dimens.xml
diff --git a/car-ui-lib/tests/paintbooth/res/values/strings.xml b/car-ui-lib/paintbooth/src/main/res/values/strings.xml
similarity index 98%
rename from car-ui-lib/tests/paintbooth/res/values/strings.xml
rename to car-ui-lib/paintbooth/src/main/res/values/strings.xml
index f347784..04f1413 100644
--- a/car-ui-lib/tests/paintbooth/res/values/strings.xml
+++ b/car-ui-lib/paintbooth/src/main/res/values/strings.xml
@@ -270,6 +270,9 @@
   <!-- Text to show Dialog with single choice items-->
   <string name="dialog_show_single_choice">Show with single choice items</string>
 
+  <!-- Text to show a dialog with single choice items and no default button [CHAR_LIMIT=200] -->
+  <string name="dialog_show_list_items_without_default_button">Show with single choice items and no default button</string>
+
   <!-- Text to show a permission Dialog [CHAR_LIMIT=50] -->
   <string name="dialog_show_permission_dialog">Show permission dialog</string>
 
diff --git a/car-ui-lib/tests/paintbooth/res/xml/current_activity_service.xml b/car-ui-lib/paintbooth/src/main/res/xml/current_activity_service.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/xml/current_activity_service.xml
rename to car-ui-lib/paintbooth/src/main/res/xml/current_activity_service.xml
diff --git a/car-ui-lib/tests/paintbooth/res/xml/menuitems.xml b/car-ui-lib/paintbooth/src/main/res/xml/menuitems.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/xml/menuitems.xml
rename to car-ui-lib/paintbooth/src/main/res/xml/menuitems.xml
diff --git a/car-ui-lib/tests/paintbooth/res/xml/preference_overlays.xml b/car-ui-lib/paintbooth/src/main/res/xml/preference_overlays.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/xml/preference_overlays.xml
rename to car-ui-lib/paintbooth/src/main/res/xml/preference_overlays.xml
diff --git a/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml b/car-ui-lib/paintbooth/src/main/res/xml/preference_samples.xml
similarity index 100%
rename from car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
rename to car-ui-lib/paintbooth/src/main/res/xml/preference_samples.xml
diff --git a/car-ui-lib/referencedesign/Android.mk b/car-ui-lib/referencedesign/Android.mk
index 7da04dc..890719e 100644
--- a/car-ui-lib/referencedesign/Android.mk
+++ b/car-ui-lib/referencedesign/Android.mk
@@ -2,6 +2,7 @@
 include $(CLEAR_VARS)
 
 CAR_UI_RRO_SET_NAME := googlecarui
+CAR_UI_RRO_MANIFEST_FILE := $(LOCAL_PATH)/AndroidManifest.xml
 CAR_UI_RESOURCE_DIR := $(LOCAL_PATH)/res
 CAR_UI_RRO_TARGETS := \
     com.android.car.ui.paintbooth \
@@ -19,10 +20,10 @@
     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.apps.automotive.templates.host \
     com.google.android.embedded.projection \
     com.google.android.gms \
     com.google.android.packageinstaller \
@@ -31,3 +32,20 @@
     com.android.vending \
 
 include packages/apps/Car/libs/car-ui-lib/generate_rros.mk
+
+CAR_UI_RRO_SET_NAME := googlecarui.overlayable
+CAR_UI_RRO_MANIFEST_FILE := $(LOCAL_PATH)/AndroidManifest-overlayable.xml
+CAR_UI_RESOURCE_DIR := $(LOCAL_PATH)/res
+CAR_UI_RRO_TARGETS := \
+    com.google.android.apps.automotive.inputmethod \
+    com.google.android.apps.automotive.inputmethod.dev \
+    com.google.android.apps.automotive.templates.host \
+    com.google.android.embedded.projection \
+    com.google.android.gms \
+    com.google.android.packageinstaller \
+    com.google.android.permissioncontroller \
+    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-overlayable.xml b/car-ui-lib/referencedesign/AndroidManifest-overlayable.xml
new file mode 100644
index 0000000..1b0585a
--- /dev/null
+++ b/car-ui-lib/referencedesign/AndroidManifest-overlayable.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="{{RRO_PACKAGE_NAME}}">
+    <application android:hasCode="false"/>
+    <overlay android:priority="10"
+        android:targetPackage="{{TARGET_PACKAGE_NAME}}"
+        android:targetName="car-ui-lib"
+        android:resourcesMap="@xml/overlays"
+        android:isStatic="true"
+        android:requiredSystemPropertyName="ro.build.car_ui_rros_enabled"
+        android:requiredSystemPropertyValue="true"/>
+</manifest>
diff --git a/car-ui-lib/referencedesign/AndroidManifest.xml b/car-ui-lib/referencedesign/AndroidManifest.xml
index a6dbae3..0994382 100644
--- a/car-ui-lib/referencedesign/AndroidManifest.xml
+++ b/car-ui-lib/referencedesign/AndroidManifest.xml
@@ -5,6 +5,6 @@
         android:targetPackage="{{TARGET_PACKAGE_NAME}}"
         android:resourcesMap="@xml/overlays"
         android:isStatic="true"
-        android:requiredSystemPropertyName="ro.build.characteristics"
-        android:requiredSystemPropertyValue="automotive"/>
+        android:requiredSystemPropertyName="ro.build.car_ui_rros_enabled"
+        android:requiredSystemPropertyValue="true"/>
 </manifest>
diff --git a/car-ui-lib/referencedesign/product.mk b/car-ui-lib/referencedesign/product.mk
index 915f31b..2a74f1c 100644
--- a/car-ui-lib/referencedesign/product.mk
+++ b/car-ui-lib/referencedesign/product.mk
@@ -17,13 +17,31 @@
     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-apps-automotive-templates-host \
     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 \
+
+
+# Include generated RROs that that use targetName
+PRODUCT_PACKAGES += \
+    googlecarui-overlayable-com-google-android-apps-automotive-inputmethod \
+    googlecarui-overlayable-com-google-android-apps-automotive-inputmethod-dev \
+    googlecarui-overlayable-com-google-android-apps-automotive-templates-host \
+    googlecarui-overlayable-com-google-android-embedded-projection \
+    googlecarui-overlayable-com-google-android-gms \
+    googlecarui-overlayable-com-google-android-packageinstaller \
+    googlecarui-overlayable-com-google-android-permissioncontroller \
+    googlecarui-overlayable-com-google-android-carassistant \
+    googlecarui-overlayable-com-google-android-tts \
+    googlecarui-overlayable-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/res/drawable/car_ui_focus_area_background_highlight.xml b/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_inner_ring_color.xml
similarity index 69%
copy from car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml
copy to car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_inner_ring_color.xml
index 74b8d13..1f197c0 100644
--- a/car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml
+++ b/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_inner_ring_color.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2020 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.
@@ -14,7 +14,7 @@
   ~ 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="#00FF00"/>
-</shape>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="#3D94CBFF"/>
+    <item android:color="@android:color/transparent"/>
+</selector>
diff --git a/car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml b/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_outer_ring_color.xml
similarity index 70%
copy from car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml
copy to car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_outer_ring_color.xml
index 74b8d13..d03fe13 100644
--- a/car-ui-lib/res/drawable/car_ui_focus_area_background_highlight.xml
+++ b/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_outer_ring_color.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2020 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.
@@ -14,7 +14,7 @@
   ~ 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="#00FF00"/>
-</shape>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:color="#94CBFF"/>
+    <item android:color="@android:color/transparent"/>
+</selector>
diff --git a/car-ui-lib/res/color/car_ui_text_color_secondary.xml b/car-ui-lib/referencedesign/res/color/car_ui_text_color_secondary.xml
similarity index 100%
rename from car-ui-lib/res/color/car_ui_text_color_secondary.xml
rename to car-ui-lib/referencedesign/res/color/car_ui_text_color_secondary.xml
diff --git a/car-ui-lib/referencedesign/res/color/car_ui_toolbar_menu_item_icon_background_color.xml b/car-ui-lib/referencedesign/res/color/car_ui_toolbar_menu_item_icon_background_color.xml
new file mode 100644
index 0000000..01b70aa
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/color/car_ui_toolbar_menu_item_icon_background_color.xml
@@ -0,0 +1,31 @@
+<?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.
+  -->
+<!-- The same as @color/car_ui_text_color_primary but with an activated state.
+     ColorStateLists don't support switching to complex colors, so we have to repeat
+     car_ui_text_color_primary here. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_activated="false"
+          android:color="@android:color/transparent"/>
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/colorForeground"/>
+    <item app:state_ux_restricted="true"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/colorForeground"/>
+    <item android:color="?android:attr/colorForeground" />
+</selector>
diff --git a/car-ui-lib/referencedesign/res/color/car_ui_toolbar_menu_item_icon_color.xml b/car-ui-lib/referencedesign/res/color/car_ui_toolbar_menu_item_icon_color.xml
new file mode 100644
index 0000000..a15bcba
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/color/car_ui_toolbar_menu_item_icon_color.xml
@@ -0,0 +1,31 @@
+<?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.
+  -->
+<!-- The same as @color/car_ui_text_color_primary but with an activated state.
+     ColorStateLists don't support switching to complex colors, so we have to repeat
+     car_ui_text_color_primary here. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_activated="true"
+          android:color="?android:attr/colorBackground"/>
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/colorForeground"/>
+    <item app:state_ux_restricted="true"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/colorForeground"/>
+    <item android:color="?android:attr/colorForeground" />
+</selector>
diff --git a/car-ui-lib/res/drawable/car_ui_recyclerview_button_ripple_background.xml b/car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_button_ripple_background.xml
similarity index 100%
copy from car-ui-lib/res/drawable/car_ui_recyclerview_button_ripple_background.xml
copy to car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_button_ripple_background.xml
diff --git a/car-ui-lib/res/drawable/car_ui_recyclerview_ic_down.xml b/car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_ic_down.xml
similarity index 100%
copy from car-ui-lib/res/drawable/car_ui_recyclerview_ic_down.xml
copy to car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_ic_down.xml
diff --git a/car-ui-lib/res/drawable/car_ui_recyclerview_ic_up.xml b/car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_ic_up.xml
similarity index 100%
copy from car-ui-lib/res/drawable/car_ui_recyclerview_ic_up.xml
copy to car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_ic_up.xml
diff --git a/car-ui-lib/res/drawable/car_ui_recyclerview_scrollbar_thumb.xml b/car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml
similarity index 100%
copy from car-ui-lib/res/drawable/car_ui_recyclerview_scrollbar_thumb.xml
copy to car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml
diff --git a/car-ui-lib/res/drawable/car_ui_icon_arrow_back.xml b/car-ui-lib/referencedesign/res/drawable/car_ui_icon_arrow_back.xml
similarity index 100%
rename from car-ui-lib/res/drawable/car_ui_icon_arrow_back.xml
rename to car-ui-lib/referencedesign/res/drawable/car_ui_icon_arrow_back.xml
diff --git a/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_preference_background.xml b/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_preference_background.xml
new file mode 100644
index 0000000..405403b
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_preference_background.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Highlight the preference when it's focused but not selected. The preference is selected in
+    direct manipulation mode. -->
+    <item android:state_focused="true" android:state_selected="false">
+        <shape android:shape="rectangle">
+            <solid android:color="#3D94CBFF"/>
+            <stroke android:width="8dp"
+                    android:color="#94CBFF"/>
+        </shape>
+    </item>
+</selector>
diff --git a/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_thumb.xml b/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_thumb.xml
new file mode 100644
index 0000000..2d6723e
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_thumb.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Padding ensures the intrinsic size of this drawable includes the rings. -->
+    <item>
+        <shape android:shape="oval">
+            <solid android:color="?android:attr/colorAccent"/>
+            <size android:width="24dp" android:height="24dp"/>
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="ring"
+               android:innerRadius="12dp"
+               android:thickness="4dp"
+               android:useLevel="false">
+            <solid android:color="@color/car_ui_seekbar_thumb_inner_ring_color"/>
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="ring"
+               android:innerRadius="16dp"
+               android:thickness="8dp"
+               android:useLevel="false">
+            <solid android:color="@color/car_ui_seekbar_thumb_outer_ring_color"/>
+        </shape>
+    </item>
+</layer-list>
diff --git a/car-ui-lib/res/drawable/car_ui_toolbar_menu_item_icon_background.xml b/car-ui-lib/referencedesign/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
similarity index 80%
copy from car-ui-lib/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
copy to car-ui-lib/referencedesign/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
index 33594c2..3f96409 100644
--- a/car-ui-lib/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
+++ b/car-ui-lib/referencedesign/res/drawable/car_ui_toolbar_menu_item_icon_background.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
   ~
-  ~ Copyright (C) 2019 Google Inc.
+  ~ Copyright (C) 2020 Google Inc.
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -19,9 +19,7 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="oval">
     <size
-        android:width="@dimen/car_ui_toolbar_menu_item_icon_background_size"
-        android:height="@dimen/car_ui_toolbar_menu_item_icon_background_size"/>
+        android:width="54dp"
+        android:height="54dp"/>
     <solid android:color="@color/car_ui_toolbar_menu_item_icon_background_color"/>
 </shape>
-
-
diff --git a/car-ui-lib/referencedesign/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml b/car-ui-lib/referencedesign/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
index 03f8d82..14f6cd0 100644
--- a/car-ui-lib/referencedesign/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
+++ b/car-ui-lib/referencedesign/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
@@ -16,6 +16,32 @@
   ~ limitations under the License.
   ~
  -->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="#27ffffff"
-        android:radius="48dp"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:state_pressed="true">
+        <shape android:shape="oval">
+            <solid android:color="#8A94CBFF"/>
+            <stroke android:width="4dp"
+                    android:color="#94CBFF"/>
+            <size android:width="48dp"
+                  android:height="48dp"/>
+        </shape>
+    </item>
+    <item android:state_focused="true">
+        <shape android:shape="oval">
+            <solid android:color="#3D94CBFF"/>
+            <stroke android:width="8dp"
+                    android:color="#94CBFF"/>
+            <size android:width="48dp"
+                  android:height="48dp"/>
+        </shape>
+    </item>
+    <item>
+        <ripple android:color="#27ffffff">
+            <item android:id="@android:id/mask">
+                <shape android:shape="oval">
+                    <solid android:color="#FFFFFF"/>
+                </shape>
+            </item>
+        </ripple>
+    </item>
+</selector>
diff --git a/car-ui-lib/referencedesign/res/layout-ldrtl-port/car_ui_base_layout_toolbar.xml b/car-ui-lib/referencedesign/res/layout-ldrtl-port/car_ui_base_layout_toolbar.xml
index 10c7de6..d988179 100644
--- a/car-ui-lib/referencedesign/res/layout-ldrtl-port/car_ui_base_layout_toolbar.xml
+++ b/car-ui-lib/referencedesign/res/layout-ldrtl-port/car_ui_base_layout_toolbar.xml
@@ -28,11 +28,11 @@
          highlight is disabled, so it's invisible to the user no matter whether it's focused or not.
          -->
     <com.android.car.ui.FocusParkingView
-        android:layout_width="1dp"
-        android:layout_height="1dp"/>
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
 
     <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"
@@ -64,7 +64,8 @@
 
             <FrameLayout
                 android:id="@+id/car_ui_toolbar_nav_icon_container"
-                android:layout_width="112dp"
+                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_guideline"
                 app:layout_constraintLeft_toLeftOf="parent"
@@ -72,11 +73,11 @@
 
                 <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"
-                    android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple"
                     android:tint="@color/car_ui_text_color_primary"/>
 
                 <ImageView
diff --git a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_base_layout_toolbar.xml b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_base_layout_toolbar.xml
index 2028530..52c9f3f 100644
--- a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_base_layout_toolbar.xml
+++ b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_base_layout_toolbar.xml
@@ -27,11 +27,11 @@
          highlight is disabled, so it's invisible to the user no matter whether it's focused or not.
          -->
     <com.android.car.ui.FocusParkingView
-        android:layout_width="1dp"
-        android:layout_height="1dp"/>
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
 
     <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"
@@ -56,7 +56,8 @@
 
             <FrameLayout
                 android:id="@+id/car_ui_toolbar_nav_icon_container"
-                android:layout_width="112dp"
+                style="@style/Widget.CarUi.Toolbar.NavIconContainer"
+                android:layout_width="@dimen/car_ui_toolbar_margin"
                 android:layout_height="0dp"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintLeft_toLeftOf="parent"
@@ -64,7 +65,7 @@
 
                 <ImageView
                     android:id="@+id/car_ui_toolbar_nav_icon"
-                    android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple"
+                    style="@style/Widget.CarUi.Toolbar.NavIcon"
                     android:tint="@color/car_ui_text_color_primary"
                     android:layout_width="@dimen/car_ui_toolbar_nav_icon_size"
                     android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
diff --git a/car-ui-lib/res/layout-ldrtl/car_ui_recycler_view.xml b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recycler_view.xml
similarity index 100%
rename from car-ui-lib/res/layout-ldrtl/car_ui_recycler_view.xml
rename to car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recycler_view.xml
diff --git a/car-ui-lib/res/layout/car_ui_recyclerview_scrollbar.xml b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recyclerview_scrollbar.xml
similarity index 100%
copy from car-ui-lib/res/layout/car_ui_recyclerview_scrollbar.xml
copy to car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recyclerview_scrollbar.xml
diff --git a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar.xml b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar.xml
index a2d884d..2714f44 100644
--- a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar.xml
+++ b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar.xml
@@ -23,7 +23,8 @@
 
     <FrameLayout
         android:id="@+id/car_ui_toolbar_nav_icon_container"
-        android:layout_width="112dp"
+        style="@style/Widget.CarUi.Toolbar.NavIconContainer"
+        android:layout_width="@dimen/car_ui_toolbar_margin"
         android:layout_height="0dp"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
@@ -31,7 +32,7 @@
 
         <ImageView
             android:id="@+id/car_ui_toolbar_nav_icon"
-            android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple"
+            style="@style/Widget.CarUi.Toolbar.NavIcon"
             android:tint="@color/car_ui_text_color_primary"
             android:layout_width="@dimen/car_ui_toolbar_nav_icon_size"
             android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
diff --git a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar_two_row.xml b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar_two_row.xml
index 395e48a..6935121 100644
--- a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar_two_row.xml
+++ b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar_two_row.xml
@@ -30,7 +30,8 @@
 
     <FrameLayout
         android:id="@+id/car_ui_toolbar_nav_icon_container"
-        android:layout_width="112dp"
+        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_guideline"
         app:layout_constraintLeft_toLeftOf="parent"
@@ -38,11 +39,11 @@
 
         <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"
-            android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple"
             android:tint="@color/car_ui_text_color_primary"/>
 
         <ImageView
diff --git a/car-ui-lib/referencedesign/res/layout-port/car_ui_base_layout_toolbar.xml b/car-ui-lib/referencedesign/res/layout-port/car_ui_base_layout_toolbar.xml
index 7aaa72d..283baf8 100644
--- a/car-ui-lib/referencedesign/res/layout-port/car_ui_base_layout_toolbar.xml
+++ b/car-ui-lib/referencedesign/res/layout-port/car_ui_base_layout_toolbar.xml
@@ -28,11 +28,11 @@
          highlight is disabled, so it's invisible to the user no matter whether it's focused or not.
          -->
     <com.android.car.ui.FocusParkingView
-        android:layout_width="1dp"
-        android:layout_height="1dp"/>
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
 
     <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"
@@ -64,7 +64,8 @@
 
             <FrameLayout
                 android:id="@+id/car_ui_toolbar_nav_icon_container"
-                android:layout_width="112dp"
+                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_guideline"
                 app:layout_constraintStart_toStartOf="parent"
@@ -72,11 +73,11 @@
 
                 <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"
-                    android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple"
                     android:tint="@color/car_ui_text_color_primary"/>
 
                 <ImageView
diff --git a/car-ui-lib/referencedesign/res/layout/car_ui_preference_widget_seekbar.xml b/car-ui-lib/referencedesign/res/layout/car_ui_preference_widget_seekbar.xml
new file mode 100644
index 0000000..16d4ea0
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/layout/car_ui_preference_widget_seekbar.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:orientation="horizontal"
+    android:background="@drawable/car_ui_seekbar_preference_background"
+    android:clipChildren="false"
+    android:clipToPadding="false">
+
+    <ImageView
+        android:id="@android:id/icon"
+        android:layout_width="44dp"
+        android:layout_height="44dp"
+        android:layout_marginTop="16dp"
+        android:layout_marginBottom="16dp"
+        android:layout_marginEnd="16dp"
+        android:scaleType="fitCenter"
+        android:tint="@color/car_ui_text_color_primary"/>
+
+    <RelativeLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_marginTop="16dp"
+        android:layout_marginBottom="16dp"
+        android:clipChildren="false"
+        android:clipToPadding="false">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textAppearance="@style/TextAppearance.CarUi.PreferenceTitle"/>
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignStart="@android:id/title"
+            android:layout_below="@android:id/title"
+            android:textAppearance="@style/TextAppearance.CarUi.PreferenceSummary"/>
+
+        <!-- Using UnPressableLinearLayout as a workaround to disable the pressed state propagation
+        to the children of this container layout. Otherwise, the animated pressed state will also
+        play for the thumb in the AbsSeekBar in addition to the preference's ripple background.
+        The background of the SeekBar is also set to null to disable the ripple background -->
+        <androidx.preference.UnPressableLinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignStart="@android:id/title"
+            android:layout_below="@android:id/summary"
+            android:clipChildren="false"
+            android:clipToPadding="false">
+            <SeekBar
+                android:id="@+id/seekbar"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                style="@style/Widget.CarUi.SeekbarPreference.Seekbar"/>
+
+            <TextView
+                android:id="@+id/seekbar_value"
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:visibility="gone"/>
+        </androidx.preference.UnPressableLinearLayout>
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item.xml b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item.xml
new file mode 100644
index 0000000..18f2e16
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:focusable="false">
+    <FrameLayout
+        android:id="@+id/car_ui_toolbar_menu_item_icon_container"
+        style="@style/Widget.CarUi.Toolbar.MenuItem.IndividualContainer"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple">
+        <ImageView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:src="@drawable/car_ui_toolbar_menu_item_icon_background"
+            android:scaleType="center"/>
+        <ImageView
+            android:id="@+id/car_ui_toolbar_menu_item_icon"
+            android:layout_width="44dp"
+            android:layout_height="44dp"
+            android:layout_gravity="center"
+            android:tint="@color/car_ui_toolbar_menu_item_icon_color"
+            android:tintMode="src_in"/>
+        <com.android.car.ui.uxr.DrawableStateSwitch
+            android:id="@+id/car_ui_toolbar_menu_item_switch"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:background="@null"
+            android:focusable="false"
+            android:clickable="false"/>
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/car_ui_toolbar_menu_item_text_container"
+        style="@style/Widget.CarUi.Toolbar.MenuItem.IndividualContainer"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/selectableItemBackground">
+        <!-- These buttons must have clickable="false" or they will steal the click events from the container -->
+        <com.android.car.ui.uxr.DrawableStateButton
+            android:id="@+id/car_ui_toolbar_menu_item_text"
+            style="@style/Widget.CarUi.Toolbar.TextButton"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:background="@null"
+            android:focusable="false"
+            android:clickable="false"/>
+        <com.android.car.ui.uxr.DrawableStateButton
+            android:id="@+id/car_ui_toolbar_menu_item_text_with_icon"
+            style="@style/Widget.CarUi.Toolbar.TextButton.WithIcon"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:background="@null"
+            android:focusable="false"
+            android:clickable="false"/>
+    </FrameLayout>
+</FrameLayout>
diff --git a/car-ui-lib/referencedesign/res/values-ldrtl/values.xml b/car-ui-lib/referencedesign/res/values-ldrtl/values.xml
index 2d50133..24552a5 100644
--- a/car-ui-lib/referencedesign/res/values-ldrtl/values.xml
+++ b/car-ui-lib/referencedesign/res/values-ldrtl/values.xml
@@ -1,4 +1,17 @@
 <resources>
     <bool name="car_ui_toolbar_nav_icon_reserve_space">false</bool>
     <bool name="car_ui_toolbar_logo_fills_nav_icon_space">false</bool>
+
+    <dimen name="car_ui_scrollbar_margin">112dp</dimen>
+    <dimen name="car_ui_scrollbar_container_width">112dp</dimen>
+    <dimen name="car_ui_scrollbar_button_size">76dp</dimen>
+    <dimen name="car_ui_scrollbar_thumb_width">7dp</dimen>
+    <dimen name="car_ui_scrollbar_separator_margin">16dp</dimen>
+
+    <string name="car_ui_scrollbar_page_up_button">Scroll up</string>
+    <string name="car_ui_scrollbar_page_down_button">Scroll down</string>
+
+    <color name="car_ui_ripple_color">#27ffffff</color>
+    <color name="car_ui_scrollbar_thumb">#99ffffff</color>
+    <dimen name="car_ui_scrollbar_thumb_radius">100dp</dimen>
 </resources>
diff --git a/car-ui-lib/res/values-night/colors.xml b/car-ui-lib/referencedesign/res/values-night/colors.xml
similarity index 83%
rename from car-ui-lib/res/values-night/colors.xml
rename to car-ui-lib/referencedesign/res/values-night/colors.xml
index 4295f07..42d706d 100644
--- a/car-ui-lib/res/values-night/colors.xml
+++ b/car-ui-lib/referencedesign/res/values-night/colors.xml
@@ -15,5 +15,7 @@
 -->
 <resources>
     <!-- Rotary focus -->
-    <color name="car_ui_rotary_focus_color">#2371cd</color>
+
+    <color name="car_ui_rotary_focus_stroke_color">#94CBFF</color>
+    <color name="car_ui_rotary_focus_fill_color">#3D94CBFF</color>
 </resources>
diff --git a/car-ui-lib/res/values-port/bools.xml b/car-ui-lib/referencedesign/res/values-port/bools.xml
similarity index 100%
rename from car-ui-lib/res/values-port/bools.xml
rename to car-ui-lib/referencedesign/res/values-port/bools.xml
diff --git a/car-ui-lib/referencedesign/res/values/attrs.xml b/car-ui-lib/referencedesign/res/values/attrs.xml
index a0b53df..0708c10 100644
--- a/car-ui-lib/referencedesign/res/values/attrs.xml
+++ b/car-ui-lib/referencedesign/res/values/attrs.xml
@@ -56,4 +56,6 @@
     <attr name="layout_goneMarginStart" format="dimension"/>
     <attr name="layout_goneMarginEnd" format="dimension"/>
 
+    <attr name="state_ux_restricted" format="boolean"/>
+
 </resources>
diff --git a/car-ui-lib/res/values/ids.xml b/car-ui-lib/referencedesign/res/values/dimens.xml
similarity index 77%
copy from car-ui-lib/res/values/ids.xml
copy to car-ui-lib/referencedesign/res/values/dimens.xml
index 2c23ac3..80fc15b 100644
--- a/car-ui-lib/res/values/ids.xml
+++ b/car-ui-lib/referencedesign/res/values/dimens.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.
@@ -14,6 +14,5 @@
  limitations under the License.
 -->
 <resources>
-    <!-- Id used for the search button when using Toolbar.createSearch() method -->
-    <item name="search" type="id"/>
+    <dimen name="car_ui_toolbar_margin">76dp</dimen>
 </resources>
diff --git a/car-ui-lib/referencedesign/res/values/styles.xml b/car-ui-lib/referencedesign/res/values/styles.xml
new file mode 100644
index 0000000..433911c
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/values/styles.xml
@@ -0,0 +1,84 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="Widget.CarUi" parent="android:Widget.DeviceDefault"/>
+
+    <style name="Widget.CarUi.Toolbar"/>
+
+    <style name="Widget.CarUi.Toolbar.NavIconContainer">
+        <item name="android:layout_marginVertical">10dp</item>
+        <item name="android:layout_marginHorizontal">18dp</item>
+        <item name="android:background">@drawable/car_ui_toolbar_menu_item_icon_ripple</item>
+    </style>
+
+    <style name="Widget.CarUi.Toolbar.NavIcon">
+        <item name="android:tint">@color/car_ui_text_color_primary</item>
+        <item name="android:src">@drawable/car_ui_icon_arrow_back</item>
+    </style>
+
+    <style name="Widget.CarUi.Toolbar.MenuItem"/>
+
+    <style name="Widget.CarUi.Toolbar.MenuItem.IndividualContainer">
+        <item name="android:minHeight">76dp</item>
+        <item name="android:minWidth">76dp</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:focusable">true</item>
+    </style>
+
+    <style name="Widget.CarUi.Button.Borderless.Colored"
+           parent="android:Widget.DeviceDefault.Button.Borderless.Colored"/>
+
+    <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">
+        <item name="android:textColor">@color/car_ui_toolbar_menu_item_icon_color</item>
+    </style>
+
+    <style name="Widget.CarUi.SeekbarPreference"/>
+
+    <!-- Style applied to the seekbar widget within the seekbar preference -->
+    <style name="Widget.CarUi.SeekbarPreference.Seekbar">
+        <item name="android:background">@null</item>
+        <item name="android:clickable">false</item>
+        <item name="android:focusable">false</item>
+        <item name="android:thumb">@drawable/car_ui_seekbar_thumb</item>
+        <item name="android:splitTrack">false</item>
+    </style>
+
+    <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">
+        <item name="android:textSize">32sp</item>
+    </style>
+
+    <style name="TextAppearance.CarUi.PreferenceTitle" parent="TextAppearance.CarUi.Body1"/>
+
+    <style name="TextAppearance.CarUi.Body3">
+        <item name="android:textSize">24sp</item>
+    </style>
+
+    <style name="TextAppearance.CarUi.PreferenceSummary" parent="TextAppearance.CarUi.Body3">
+        <item name="android:textColor">@color/car_ui_text_color_secondary</item>
+    </style>
+
+</resources>
diff --git a/car-ui-lib/referencedesign/res/xml/overlays.xml b/car-ui-lib/referencedesign/res/xml/overlays.xml
index 0d0eb74..cc9b092 100644
--- a/car-ui-lib/referencedesign/res/xml/overlays.xml
+++ b/car-ui-lib/referencedesign/res/xml/overlays.xml
@@ -2,9 +2,53 @@
     <item target="layout/car_ui_base_layout_toolbar" value="@layout/car_ui_base_layout_toolbar"/>
     <item target="layout/car_ui_toolbar" value="@layout/car_ui_toolbar"/>
     <item target="layout/car_ui_toolbar_two_row" value="@layout/car_ui_toolbar_two_row"/>
+    <item target="layout/car_ui_toolbar_menu_item" value="@layout/car_ui_toolbar_menu_item"/>
+    <item target="layout/car_ui_preference_widget_seekbar" value="@layout/car_ui_preference_widget_seekbar"/>
+
+    <item target="drawable/car_ui_icon_arrow_back" value="@drawable/car_ui_icon_arrow_back"/>
+    <item target="drawable/car_ui_toolbar_menu_item_icon_ripple" value="@drawable/car_ui_toolbar_menu_item_icon_ripple"/>
+    <item target="drawable/car_ui_toolbar_menu_item_icon_background" value="@drawable/car_ui_toolbar_menu_item_icon_background"/>
+    <item target="drawable/car_ui_toolbar_menu_item_icon_background" value="@drawable/car_ui_toolbar_menu_item_icon_background"/>
+
+    <item target="style/Widget.CarUi" value="@style/Widget.CarUi"/>
+    <item target="style/Widget.CarUi.Toolbar" value="@style/Widget.CarUi.Toolbar"/>
+    <item target="style/Widget.CarUi.Toolbar.NavIconContainer" value="@style/Widget.CarUi.Toolbar.NavIconContainer"/>
+    <item target="style/Widget.CarUi.Toolbar.NavIcon" value="@style/Widget.CarUi.Toolbar.NavIcon"/>
+    <item target="style/Widget.CarUi.Toolbar.MenuItem" value="@style/Widget.CarUi.Toolbar.MenuItem"/>
+    <item target="style/Widget.CarUi.Toolbar.MenuItem.IndividualContainer" value="@style/Widget.CarUi.Toolbar.MenuItem.IndividualContainer"/>
+    <item target="style/Widget.CarUi.Button.Borderless.Colored" value="@style/Widget.CarUi.Button.Borderless.Colored"/>
+    <item target="style/Widget.CarUi.Toolbar.TextButton" value="@style/Widget.CarUi.Toolbar.TextButton"/>
+    <item target="style/Widget.CarUi.Toolbar.TextButton.WithIcon" value="@style/Widget.CarUi.Toolbar.TextButton.WithIcon"/>
+    <item target="style/Widget.CarUi.SeekbarPreference" value="@style/Widget.CarUi.SeekbarPreference"/>
+    <item target="style/Widget.CarUi.SeekbarPreference.Seekbar" value="@style/Widget.CarUi.SeekbarPreference.Seekbar"/>
+
+    <item target="dimen/car_ui_toolbar_margin" value="@dimen/car_ui_toolbar_margin"/>
+
+    <item target="color/car_ui_text_color_secondary" value="@color/car_ui_text_color_secondary"/>
+    <item target="color/car_ui_toolbar_menu_item_icon_background_color" value="@color/car_ui_toolbar_menu_item_icon_background_color"/>
+    <item target="color/car_ui_toolbar_menu_item_icon_color" value="@color/car_ui_toolbar_menu_item_icon_color"/>
+
+    <item target="drawable/car_ui_icon_arrow_back" value="@drawable/car_ui_icon_arrow_back"/>
+    <item target="drawable/car_ui_toolbar_menu_item_icon_ripple" value="@drawable/car_ui_toolbar_menu_item_icon_ripple"/>
+
+    <item target="style/TextAppearance.CarUi" value="@style/TextAppearance.CarUi"/>
+    <item target="style/TextAppearance.CarUi.Body1" value="@style/TextAppearance.CarUi.Body1"/>
+    <item target="style/TextAppearance.CarUi.PreferenceTitle" value="@style/TextAppearance.CarUi.PreferenceTitle"/>
+    <item target="style/TextAppearance.CarUi.Body3" value="@style/TextAppearance.CarUi.Body3"/>
+    <item target="style/TextAppearance.CarUi.PreferenceSummary" value="@style/TextAppearance.CarUi.PreferenceSummary"/>
+
+    <item target="style/Widget.CarUi" value="@style/Widget.CarUi"/>
+    <item target="style/Widget.CarUi.Toolbar" value="@style/Widget.CarUi.Toolbar"/>
+    <item target="style/Widget.CarUi.Toolbar.NavIconContainer" value="@style/Widget.CarUi.Toolbar.NavIconContainer"/>
+    <item target="style/Widget.CarUi.Toolbar.NavIcon" value="@style/Widget.CarUi.Toolbar.NavIcon"/>
+
+    <item target="dimen/car_ui_toolbar_margin" value="@dimen/car_ui_toolbar_margin"/>
+    <item target="layout/car_ui_recycler_view" value="@layout/car_ui_recycler_view"/>
 
     <item target="bool/car_ui_toolbar_nav_icon_reserve_space" value="@bool/car_ui_toolbar_nav_icon_reserve_space" />
     <item target="bool/car_ui_toolbar_logo_fills_nav_icon_space" value="@bool/car_ui_toolbar_logo_fills_nav_icon_space" />
+    <item target="bool/car_ui_toolbar_tab_flexible_layout" value="@bool/car_ui_toolbar_tab_flexible_layout" />
+    <item target="bool/car_ui_toolbar_tabs_on_second_row" value="@bool/car_ui_toolbar_tabs_on_second_row" />
 
     <item target="id/car_ui_toolbar_background" value="@id/car_ui_toolbar_background" />
     <item target="id/car_ui_toolbar_nav_icon_container" value="@id/car_ui_toolbar_nav_icon_container" />
@@ -19,7 +63,21 @@
     <item target="id/car_ui_toolbar_menu_items_container" value="@id/car_ui_toolbar_menu_items_container" />
     <item target="id/car_ui_toolbar_search_view_container" value="@id/car_ui_toolbar_search_view_container" />
     <item target="id/car_ui_toolbar_progress_bar" value="@id/car_ui_toolbar_progress_bar" />
-    <item target="id/content" value="@id/content" />
+    <item target="id/car_ui_base_layout_content_container" value="@id/car_ui_base_layout_content_container" />
+    <item target="id/car_ui_toolbar_menu_item_icon_container" value="@id/car_ui_toolbar_menu_item_icon_container"/>
+    <item target="id/car_ui_toolbar_menu_item_icon" value="@id/car_ui_toolbar_menu_item_icon"/>
+    <item target="id/car_ui_toolbar_menu_item_switch" value="@id/car_ui_toolbar_menu_item_switch"/>
+    <item target="id/car_ui_toolbar_menu_item_text_container" value="@id/car_ui_toolbar_menu_item_text_container"/>
+    <item target="id/car_ui_toolbar_menu_item_text" value="@id/car_ui_toolbar_menu_item_text"/>
+    <item target="id/car_ui_toolbar_menu_item_text_with_icon" value="@id/car_ui_toolbar_menu_item_text_with_icon"/>
+    <item target="id/seekbar" value="@id/seekbar"/>
+    <item target="id/seekbar_value" value="@id/seekbar_value"/>
+    <item target="id/car_ui_recycler_view" value="@id/car_ui_recycler_view" />
+    <item target="id/car_ui_scroll_bar" value="@id/car_ui_scroll_bar"/>
+    <item target="id/car_ui_scrollbar_page_down" value="@id/car_ui_scrollbar_page_down"/>
+    <item target="id/car_ui_scrollbar_page_up" value="@id/car_ui_scrollbar_page_up"/>
+    <item target="id/car_ui_scrollbar_thumb" value="@id/car_ui_scrollbar_thumb"/>
+    <item target="id/car_ui_scrollbar_track" value="@id/car_ui_scrollbar_track"/>
 
     <item target="attr/layout_constraintGuide_begin" value="@attr/layout_constraintGuide_begin"/>
     <item target="attr/layout_constraintGuide_end" value="@attr/layout_constraintGuide_end"/>
@@ -42,4 +100,5 @@
     <item target="attr/layout_goneMarginLeft" value="@attr/layout_goneMarginBottom"/>
     <item target="attr/layout_goneMarginLeft" value="@attr/layout_goneMarginStart"/>
     <item target="attr/layout_goneMarginLeft" value="@attr/layout_goneMarginEnd"/>
+    <item target="attr/state_ux_restricted" value="@attr/state_ux_restricted"/>
 </overlay>
diff --git a/car-ui-lib/res/drawable/car_ui_focus_area_foreground_highlight.xml b/car-ui-lib/res/drawable/car_ui_focus_area_foreground_highlight.xml
deleted file mode 100644
index b6955f6..0000000
--- a/car-ui-lib/res/drawable/car_ui_focus_area_foreground_highlight.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle" >
-    <stroke android:width="10dp" android:color="#FF0000"/>
-</shape>
diff --git a/car-ui-lib/res/layout/car_ui_check_box_list_item.xml b/car-ui-lib/res/layout/car_ui_check_box_list_item.xml
deleted file mode 100644
index 2f5c7a8..0000000
--- a/car-ui-lib/res/layout/car_ui_check_box_list_item.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<androidx.constraintlayout.widget.ConstraintLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:background="?android:attr/selectableItemBackground"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/car_ui_list_item_check_box_height">
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/car_ui_check_box_start_guideline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        app:layout_constraintGuide_begin="@dimen/car_ui_list_item_check_box_start_inset" />
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/car_ui_check_box_end_guideline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        app:layout_constraintGuide_end="@dimen/car_ui_list_item_check_box_end_inset" />
-
-    <FrameLayout
-        android:id="@+id/check_box_container"
-        android:layout_width="@dimen/car_ui_list_item_check_box_icon_container_width"
-        android:layout_height="0dp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="@+id/car_ui_check_box_start_guideline"
-        app:layout_constraintTop_toTopOf="parent">
-
-        <CheckBox
-            android:id="@+id/checkbox"
-            android:clickable="false"
-            android:focusable="false"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center" />
-    </FrameLayout>
-
-    <TextView
-        android:id="@+id/text"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/car_ui_list_item_text_start_margin"
-        android:textAppearance="@style/TextAppearance.CarUi.ListItem"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="@+id/car_ui_check_box_end_guideline"
-        app:layout_constraintStart_toEndOf="@+id/check_box_container"
-        app:layout_constraintTop_toTopOf="parent" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-ui-lib/res/values-af/strings.xml b/car-ui-lib/res/values-af/strings.xml
deleted file mode 100644
index 75130b9..0000000
--- a/car-ui-lib/res/values-af/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Aan"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Af"</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
deleted file mode 100644
index a78faec..0000000
--- a/car-ui-lib/res/values-am/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"አብራ"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ቅናሽ"</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
deleted file mode 100644
index b64c521..0000000
--- a/car-ui-lib/res/values-ar/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"مفعّل"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"غير مفعَّل"</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
deleted file mode 100644
index c391666..0000000
--- a/car-ui-lib/res/values-as/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"অন আছে"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"অফ আছে"</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
deleted file mode 100644
index 5e3670d..0000000
--- a/car-ui-lib/res/values-az/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Aktiv"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Deaktiv"</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
deleted file mode 100644
index f1c2eb7..0000000
--- a/car-ui-lib/res/values-b+sr+Latn/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Uključeno"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Isključeno"</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
deleted file mode 100644
index 054ba36..0000000
--- a/car-ui-lib/res/values-be/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Уключана"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Выключана"</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
deleted file mode 100644
index 3ed930c..0000000
--- a/car-ui-lib/res/values-bg/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Вкл."</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Изкл."</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
deleted file mode 100644
index 702a5bb..0000000
--- a/car-ui-lib/res/values-bn/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"চালু"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"বন্ধ"</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
deleted file mode 100644
index 7b5376a..0000000
--- a/car-ui-lib/res/values-bs/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Uključeno"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Isključeno"</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
deleted file mode 100644
index 5d284b7..0000000
--- a/car-ui-lib/res/values-ca/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Activat"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Desactivat"</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
deleted file mode 100644
index d230329..0000000
--- a/car-ui-lib/res/values-cs/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Zap"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Vyp"</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
deleted file mode 100644
index 630494f..0000000
--- a/car-ui-lib/res/values-da/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Til"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Fra"</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
deleted file mode 100644
index 2c655d4..0000000
--- a/car-ui-lib/res/values-de/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"An"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Aus"</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
deleted file mode 100644
index d08f663..0000000
--- a/car-ui-lib/res/values-el/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Ενεργό"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Ανενεργή"</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
deleted file mode 100644
index 271d61f..0000000
--- a/car-ui-lib/res/values-en-rAU/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"On"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Off"</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
deleted file mode 100644
index 271d61f..0000000
--- a/car-ui-lib/res/values-en-rCA/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"On"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Off"</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
deleted file mode 100644
index 271d61f..0000000
--- a/car-ui-lib/res/values-en-rGB/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"On"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Off"</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
deleted file mode 100644
index 271d61f..0000000
--- a/car-ui-lib/res/values-en-rIN/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"On"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Off"</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
deleted file mode 100644
index 9993024..0000000
--- a/car-ui-lib/res/values-en-rXC/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎On‎‏‎‎‏‎"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‏‏‏‎‎‎‎Off‎‏‎‎‏‎"</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
deleted file mode 100644
index 71178e9..0000000
--- a/car-ui-lib/res/values-es-rUS/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Sí"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"No"</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
deleted file mode 100644
index dd5f07f..0000000
--- a/car-ui-lib/res/values-es/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Activado"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Desactivado"</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
deleted file mode 100644
index 2c4c2bb..0000000
--- a/car-ui-lib/res/values-et/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Sees"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Väljas"</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
deleted file mode 100644
index b8b0c79..0000000
--- a/car-ui-lib/res/values-eu/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Aktibatuta"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Desaktibatuta"</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
deleted file mode 100644
index c0cc566..0000000
--- a/car-ui-lib/res/values-fa/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"روشن"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"خاموش"</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
deleted file mode 100644
index 4b4a942..0000000
--- a/car-ui-lib/res/values-fi/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Päällä"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Pois"</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
deleted file mode 100644
index b83c159..0000000
--- a/car-ui-lib/res/values-fr-rCA/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Activé"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Désactivé"</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
deleted file mode 100644
index 067f056..0000000
--- a/car-ui-lib/res/values-fr/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Activé"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Désactivé"</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
deleted file mode 100644
index 853a780..0000000
--- a/car-ui-lib/res/values-gl/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Si"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Non"</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
deleted file mode 100644
index 7e937b2..0000000
--- a/car-ui-lib/res/values-gu/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"ચાલુ"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"બંધ"</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
deleted file mode 100644
index 30edfb9..0000000
--- a/car-ui-lib/res/values-hi/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"चालू है"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"बंद है"</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
deleted file mode 100644
index 8f19523..0000000
--- a/car-ui-lib/res/values-hr/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Uključeno"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Isključeno"</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
deleted file mode 100644
index f731494..0000000
--- a/car-ui-lib/res/values-hu/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Be"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Ki"</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
deleted file mode 100644
index fc5f19e..0000000
--- a/car-ui-lib/res/values-hy/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Միացված է"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Անջատված է"</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
deleted file mode 100644
index 3bffad0..0000000
--- a/car-ui-lib/res/values-in/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Aktif"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Nonaktif"</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
deleted file mode 100644
index 206dd62..0000000
--- a/car-ui-lib/res/values-is/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Kveikt"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Slökkt"</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
deleted file mode 100644
index 9534e68..0000000
--- a/car-ui-lib/res/values-it/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"On"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Off"</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
deleted file mode 100644
index 24b6a1d..0000000
--- a/car-ui-lib/res/values-iw/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"פועל"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"כבוי"</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
deleted file mode 100644
index b80bef6..0000000
--- a/car-ui-lib/res/values-ja/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"ON"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"OFF"</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
deleted file mode 100644
index 3c90f08..0000000
--- a/car-ui-lib/res/values-ka/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"ჩართულია"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"გამორთულია"</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
deleted file mode 100644
index 80170fa..0000000
--- a/car-ui-lib/res/values-kk/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Қосулы"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Өшірулі"</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
deleted file mode 100644
index 044333c..0000000
--- a/car-ui-lib/res/values-km/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"បើក"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"បិទ"</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
deleted file mode 100644
index 261d650..0000000
--- a/car-ui-lib/res/values-kn/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"ಆನ್ ಆಗಿದೆ"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ಆಫ್ ಆಗಿದೆ"</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
deleted file mode 100644
index f8efa76..0000000
--- a/car-ui-lib/res/values-ko/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"사용 설정됨"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"사용 중지됨"</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
deleted file mode 100644
index 5143048..0000000
--- a/car-ui-lib/res/values-ky/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Күйүк"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Өчүк"</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
deleted file mode 100644
index 3c65459..0000000
--- a/car-ui-lib/res/values-lo/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"ເປີດ"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ປິດ"</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
deleted file mode 100644
index 674e342..0000000
--- a/car-ui-lib/res/values-lt/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Įjungta"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Išjungta"</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
deleted file mode 100644
index fa02dd9..0000000
--- a/car-ui-lib/res/values-lv/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Ieslēgta"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Izslēgta"</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
deleted file mode 100644
index 9905cd0..0000000
--- a/car-ui-lib/res/values-mk/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Вклучено"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Исклучено"</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
deleted file mode 100644
index c94f404..0000000
--- a/car-ui-lib/res/values-ml/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"ഓണാണ്"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ഓഫാണ്"</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
deleted file mode 100644
index ac81c8a..0000000
--- a/car-ui-lib/res/values-mn/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Асаалттай"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Унтраалттай"</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
deleted file mode 100644
index b86a9de..0000000
--- a/car-ui-lib/res/values-mr/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"सुरू आहे"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"बंद आहे"</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
deleted file mode 100644
index c5c780f..0000000
--- a/car-ui-lib/res/values-ms/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Hidup"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Mati"</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
deleted file mode 100644
index b1c9f63..0000000
--- a/car-ui-lib/res/values-my/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"ဖွင့်ထားသည်"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ပိတ်ထားသည်"</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
deleted file mode 100644
index ed93746..0000000
--- a/car-ui-lib/res/values-nb/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"På"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Av"</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
deleted file mode 100644
index 9e86b17..0000000
--- a/car-ui-lib/res/values-ne/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"सुचारू छ"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"बन्द छ"</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
deleted file mode 100644
index 50418fc..0000000
--- a/car-ui-lib/res/values-nl/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Aan"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Uit"</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
deleted file mode 100644
index 28ac864..0000000
--- a/car-ui-lib/res/values-or/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"ଚାଲୁ ଅଛି"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ବନ୍ଦ ଅଛି"</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
deleted file mode 100644
index 251b8bb..0000000
--- a/car-ui-lib/res/values-pa/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"ਚਾਲੂ"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ਬੰਦ"</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
deleted file mode 100644
index ec5388c..0000000
--- a/car-ui-lib/res/values-pl/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Wł."</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Wył."</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-pt-rPT/strings.xml b/car-ui-lib/res/values-pt-rPT/strings.xml
deleted file mode 100644
index ec04d23..0000000
--- a/car-ui-lib/res/values-pt-rPT/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Ativado"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Desativado"</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
deleted file mode 100644
index 69fc9fe..0000000
--- a/car-ui-lib/res/values-pt/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Ativada"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Desativada"</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
deleted file mode 100644
index 32e31eb..0000000
--- a/car-ui-lib/res/values-ro/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Activat"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Dezactivat"</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
deleted file mode 100644
index 29373a6..0000000
--- a/car-ui-lib/res/values-ru/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Включено"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Выключено"</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
deleted file mode 100644
index a0ea615..0000000
--- a/car-ui-lib/res/values-si/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"ක්‍රියාත්මකයි"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ක්‍රියාවිරහිතයි"</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
deleted file mode 100644
index 3925a81..0000000
--- a/car-ui-lib/res/values-sk/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Zap."</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Vyp."</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
deleted file mode 100644
index 6333890..0000000
--- a/car-ui-lib/res/values-sl/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Vklopljeno"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Izklopljeno"</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
deleted file mode 100644
index fb50946..0000000
--- a/car-ui-lib/res/values-sq/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Aktiv"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Joaktiv"</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
deleted file mode 100644
index fd83edf..0000000
--- a/car-ui-lib/res/values-sr/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Укључено"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Искључено"</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
deleted file mode 100644
index 1b31e43..0000000
--- a/car-ui-lib/res/values-sv/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"På"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Av"</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
deleted file mode 100644
index 999ccb6..0000000
--- a/car-ui-lib/res/values-sw/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Imewashwa"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Imezimwa"</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
deleted file mode 100644
index 8598837..0000000
--- a/car-ui-lib/res/values-ta/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"ஆன்"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ஆஃப்"</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
deleted file mode 100644
index 3ccf486..0000000
--- a/car-ui-lib/res/values-te/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"ఆన్‌లో ఉంది"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ఆఫ్‌లో ఉంది"</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
deleted file mode 100644
index 6557b2c..0000000
--- a/car-ui-lib/res/values-th/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"เปิด"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"ปิด"</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
deleted file mode 100644
index b879f3b..0000000
--- a/car-ui-lib/res/values-tl/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"I-on"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"I-off"</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
deleted file mode 100644
index 47a1978..0000000
--- a/car-ui-lib/res/values-tr/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Açık"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Kapalı"</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
deleted file mode 100644
index bd50346..0000000
--- a/car-ui-lib/res/values-uk/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Увімкнено"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Вимкнено"</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
deleted file mode 100644
index efffbcd..0000000
--- a/car-ui-lib/res/values-ur/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"آن ہے"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"آف ہے"</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
deleted file mode 100644
index 1dd26ac..0000000
--- a/car-ui-lib/res/values-uz/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Yoniq"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Yoqilmagan"</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
deleted file mode 100644
index 39df5ce..0000000
--- a/car-ui-lib/res/values-vi/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_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_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-zh-rCN/strings.xml b/car-ui-lib/res/values-zh-rCN/strings.xml
deleted file mode 100644
index a581718..0000000
--- a/car-ui-lib/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"开启"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"关闭"</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
deleted file mode 100644
index dc5482d..0000000
--- a/car-ui-lib/res/values-zh-rHK/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"已開啟"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"已關閉"</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
deleted file mode 100644
index 0d28130..0000000
--- a/car-ui-lib/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"開啟"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"關閉"</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
deleted file mode 100644
index a3066d0..0000000
--- a/car-ui-lib/res/values-zu/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2019 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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_preference_switch_on" msgid="5512335363135043642">"Vuliwe"</string>
-    <string name="car_ui_preference_switch_off" msgid="4393435896926380920">"Valiwe"</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
deleted file mode 100644
index 4b06a60..0000000
--- a/car-ui-lib/res/values/attrs.xml
+++ /dev/null
@@ -1,147 +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>
-    <!-- Global theme options for CarUi -->
-    <declare-styleable name="CarUi">
-        <!-- When set to true, the window decor will contain an OEM-customizable layout -->
-        <attr name="carUiBaseLayout" format="boolean"/>
-        <!-- When set to true, a CarUi Toolbar will be provided in the window decor -->
-        <attr name="carUiToolbar" format="boolean"/>
-    </declare-styleable>
-
-    <declare-styleable name="CarUiToolbar">
-        <!-- Title of the toolbar, only displayed in certain conditions -->
-        <attr name="title" format="string"/>
-        <!-- Logo drawable for the toolbar. Appears when there's no back/close button shown -->
-        <attr name="logo" format="reference"/>
-        <!-- Hint for the search bar in the toolbar -->
-        <attr name="searchHint" format="string"/>
-        <!-- Whether or not to show the MenuItems while searching. Default false. -->
-        <attr name="showMenuItemsWhileSearching" format="boolean"/>
-        <!-- Initial state of the toolbar. See the Toolbar.State enum for more information -->
-        <attr name="car_ui_state" format="enum">
-            <enum name="home" value="0"/>
-            <enum name="subpage" value="1"/>
-            <enum name="search" value="2"/>
-        </attr>
-        <!-- Whether or not the toolbar should have a background. Default true. -->
-        <attr name="showBackground" format="boolean"/>
-        <!-- Mode of the navigation button See the Toolbar.NavButtonMode enum for more information -->
-        <attr name="car_ui_navButtonMode" format="enum">
-            <enum name="back" value="0"/>
-            <enum name="close" value="1"/>
-            <enum name="down" value="2"/>
-        </attr>
-        <!-- XML resource of MenuItems. See Toolbar.setMenuItems(int) for more information. -->
-        <attr name="menuItems" format="reference"/>
-        <!-- Whether or not to show tabs in the SUBPAGE state. Default false -->
-        <attr name="showTabsInSubpage" format="boolean"/>
-    </declare-styleable>
-
-    <declare-styleable name="CarUiToolbarMenuItem">
-        <!-- Id of MenuItem, used to differentiate them -->
-        <attr name="id" format="reference"/>
-        <!-- Show/hide the MenuItem -->
-        <attr name="visible" format="boolean"/>
-        <!-- Set this to true to make a search MenuItem. This will override every other property except id, visible, and onclick. -->
-        <attr name="search" format="boolean"/>
-        <!-- Set this to true to make a settings MenuItem. This will override every other property except id, visible, and onclick. -->
-        <attr name="settings" format="boolean"/>
-        <!-- Title -->
-        <attr name="title"/>
-        <!-- Icon -->
-        <attr name="icon" format="reference"/>
-        <!-- True to tint the icon to a consistent color. Default true, all the other booleans default to false -->
-        <attr name="tinted" format="boolean"/>
-        <!-- Show both the icon and title at the same time -->
-        <attr name="showIconAndTitle" format="boolean"/>
-        <!-- True if this MenuItem should be a switch -->
-        <attr name="checkable" format="boolean"/>
-        <!-- Whether the switch should be checked or not. Setting this implies checkable=true -->
-        <attr name="checked" format="boolean"/>
-        <!-- True if this MenuItem should be activatable, in which case it will visually toggle states when clicked -->
-        <attr name="activatable" format="boolean"/>
-        <!-- Whether the MenuItem starts activated. Setting this implies activatable=true -->
-        <attr name="activated" format="boolean"/>
-        <!-- How to display the MenuItem. "always" means always show it on the toolbar, "never" means never show it on the toolbar and instead show it in the overflow menu -->
-        <attr name="displayBehavior" format="enum">
-            <enum name="always" value="0"/>
-            <enum name="never" value="1"/>
-        </attr>
-        <!-- Ux restrictions required to interact with this MenuItem -->
-        <attr name="uxRestrictions">
-            <!-- Values are copied from android.car.drivingstate.CarUxRestrictions. Note:
-            UX_RESTRICTIONS_BASELINE is not allowed here because it's useless and confusing. -->
-            <flag name="UX_RESTRICTIONS_NO_DIALPAD" value="1"/>
-            <flag name="UX_RESTRICTIONS_NO_FILTERING" value="2"/>
-            <flag name="UX_RESTRICTIONS_LIMIT_STRING_LENGTH" value="4"/>
-            <flag name="UX_RESTRICTIONS_NO_KEYBOARD" value="8"/>
-            <flag name="UX_RESTRICTIONS_NO_VIDEO" value="16"/>
-            <flag name="UX_RESTRICTIONS_LIMIT_CONTENT" value="32"/>
-            <flag name="UX_RESTRICTIONS_NO_SETUP" value="64"/>
-            <flag name="UX_RESTRICTIONS_NO_TEXT_MESSAGE" value="128"/>
-            <flag name="UX_RESTRICTIONS_NO_VOICE_TRANSCRIPTION" value="256"/>
-            <flag name="UX_RESTRICTIONS_FULLY_RESTRICTED" value="511"/>
-        </attr>
-        <!-- The name of a method that takes a MenuItem as an argument in you'r toolbar's Activity. Will be called when the MenuItem is clicked -->
-        <attr name="onClick" format="string"/>
-    </declare-styleable>
-
-    <!-- Theme attribute to specifying a default style for all CarUiToolbars -->
-    <attr name="CarUiToolbarStyle" format="reference"/>
-
-    <declare-styleable name="CarUiRecyclerView">
-        <!-- 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="topOffset" format="integer" />
-        <!-- Bottom offset for car ui recycler view for linear layout. -->
-        <attr name="bottomOffset" format="integer" />
-
-        <!-- Number of columns in a grid layout. -->
-        <attr name="numOfColumns" format="integer" />
-
-        <!-- car ui recycler view layout. -->
-        <attr name="layoutStyle" format="enum">
-            <!-- linear layout -->
-            <enum name="linear" value="0" />
-            <!-- grid layout -->
-            <enum name="grid" value="1" />
-        </attr>
-    </declare-styleable>
-
-    <declare-styleable name="CarUiPreference">
-        <!-- Toggle for showing chevron -->
-        <attr name="showChevron" format="boolean" />
-        <!-- Show ripple when disabled preference is clicked -->
-        <attr name="showRippleOnDisabledPreference" format="boolean" />
-    </declare-styleable>
-
-    <declare-styleable name="CarUiTwoActionPreference">
-        <!-- Determines if the secondary action is initially shown -->
-        <attr name="actionShown" format="boolean"/>
-    </declare-styleable>
-
-    <!-- Theme attribute to specify a default style for all CarUiPreferences -->
-    <attr name="carUiPreferenceStyle" format="reference" />
-
-    <!-- Theme attribute to specify a default style for all CarUiRecyclerViews -->
-    <attr name="carUiRecyclerViewStyle" format="reference" />
-
-    <attr name="state_ux_restricted" format="boolean" />
-</resources>
diff --git a/car-ui-lib/res/values/integers.xml b/car-ui-lib/res/values/integers.xml
deleted file mode 100644
index 963c955..0000000
--- a/car-ui-lib/res/values/integers.xml
+++ /dev/null
@@ -1,21 +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>
-    <!-- Default max string length -->
-    <integer name="car_ui_default_max_string_length">120</integer>
-    <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/settings.gradle b/car-ui-lib/settings.gradle
index 50c2c6d..4deba21 100644
--- a/car-ui-lib/settings.gradle
+++ b/car-ui-lib/settings.gradle
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
+include ':car-ui-lib'
+project(':car-ui-lib').projectDir = new File('./car-ui-lib')
 include ':PaintBooth'
-project(':PaintBooth').projectDir = new File('./tests/paintbooth')
-include ':RoboTests'
-project(':RoboTests').projectDir = new File('./tests/robotests')
-include ':InstrumentionTests'
-project(':InstrumentionTests').projectDir = new File('./tests/unit')
+project(':PaintBooth').projectDir = new File('./paintbooth')
+include ':shared-library'
+project(':shared-library').projectDir = new File('./sharedlibrary')
 
 rootProject.name='Chassis'
diff --git a/car-media-common/res/drawable/fab_empty_foreground.xml b/car-ui-lib/sharedlibrary/AndroidManifest-gradle.xml
similarity index 60%
copy from car-media-common/res/drawable/fab_empty_foreground.xml
copy to car-ui-lib/sharedlibrary/AndroidManifest-gradle.xml
index d9fb901..53744fd 100644
--- a/car-media-common/res/drawable/fab_empty_foreground.xml
+++ b/car-ui-lib/sharedlibrary/AndroidManifest-gradle.xml
@@ -1,20 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright 2018, 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.
   You may obtain a copy of the License at
 
-      http://www.apache.org/licenses/LICENSE-2.0
+    http://www.apache.org/licenses/LICENSE-2.0
 
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
--->
-<ripple
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:radius="0dp"
-    android:color="@color/car_dark_blue_grey_700" />
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.ui.sharedlibrary">
+  <application>
+      <library android:name="com.android.car.ui.sharedlibrary" />
+  </application>
+</manifest>
diff --git a/car-ui-lib/sharedlibrary/AndroidManifest.xml b/car-ui-lib/sharedlibrary/AndroidManifest.xml
index 61b4515..89a2407 100644
--- a/car-ui-lib/sharedlibrary/AndroidManifest.xml
+++ b/car-ui-lib/sharedlibrary/AndroidManifest.xml
@@ -19,8 +19,8 @@
           package="com.android.car.ui.sharedlibrary">
   <uses-sdk
       android:minSdkVersion="28"
-      android:targetSdkVersion="28" />
-    <application>
-        <library android:name="com.android.car.ui.sharedlibrary" />
-    </application>
+      android:targetSdkVersion="30"/>
+  <application>
+      <library android:name="com.android.car.ui.sharedlibrary"/>
+  </application>
 </manifest>
diff --git a/car-ui-lib/sharedlibrary/README.md b/car-ui-lib/sharedlibrary/README.md
index 21c6172..6835846 100644
--- a/car-ui-lib/sharedlibrary/README.md
+++ b/car-ui-lib/sharedlibrary/README.md
@@ -2,4 +2,4 @@
 Please refer to [Android Automotive 'Chassiss' library](../README.md)
 
 **Required**
-This module needs to be, and is included in PRODUCT_PACKAGES in vendor/auto/embeded/products/car.mk. It needs to be pre-installed with the system image.
\ No newline at end of file
+This module needs to be, and is included in PRODUCT_PACKAGES in vendor/auto/embeded/products/car.mk. It needs to be pre-installed with the system image.
diff --git a/car-ui-lib/sharedlibrary/build.gradle b/car-ui-lib/sharedlibrary/build.gradle
new file mode 100644
index 0000000..f5a4831
--- /dev/null
+++ b/car-ui-lib/sharedlibrary/build.gradle
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Library-level build file
+
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 30
+
+    defaultConfig {
+        minSdkVersion 28
+        targetSdkVersion 30
+        versionCode 1
+        versionName "1.0"
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest-gradle.xml'
+        }
+    }
+}
+
+dependencies {
+}
diff --git a/car-ui-lib/sharedlibrary/res/values/public.xml b/car-ui-lib/sharedlibrary/res/values/public.xml
deleted file mode 100644
index 80ea2e7..0000000
--- a/car-ui-lib/sharedlibrary/res/values/public.xml
+++ /dev/null
@@ -1,53 +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.
--->
-<resources>
-    <!-- Any resource or attribute that is or can be referenced from RRO needs to be public go/rro-on-r -->
-
-    <!-- To add new resources to this file follow the following naming convention -->
-    <!-- 0cXXXX is used for styles -->
-    <!-- 0dXXXX is used for attributes -->
-    <!-- 0eXXXX is used for drawables -->
-    <!-- The first time that a resource type is seen, Android resource manager, assigns YY of the ID (0xXXYYXXXX) of the resouce to that type.
-         And you'll get a compile error if you use the same type with a different YY -->
-
-    <public type="style" name="Theme.CarUi" id="0x000c0000" />
-
-    <public type="attr" name="logo" id="0x000d0001" />
-    <public type="attr" name="car_ui_state" id="0x000d0002" />
-    <public type="attr" name="title" id="0x000d0003" />
-    <public type="attr" name="layoutStyle" id="0x000d0004" />
-    <public type="attr" name="numOfColumns" id="0x000d0005" />
-    <public type="attr" name="layout_constraintTop_toTopOf" id="0x000d0006" />
-    <public type="attr" name="layout_constraintTop_toBottomOf" id="0x000d0007" />
-    <public type="attr" name="layout_constraintLeft_toLeftOf" id="0x000d0008" />
-    <public type="attr" name="layout_constraintBottom_toTopOf" id="0x000d0009" />
-    <public type="attr" name="layout_constraintBottom_toBottomOf" id="0x000d0010" />
-    <public type="attr" name="layout_constraintRight_toRightOf" id="0x000d0011" />
-    <public type="attr" name="search" id="0x000d0012" />
-    <public type="attr" name="settings" id="0x000d0013" />
-    <public type="attr" name="id" id="0x000d0014" />
-    <public type="attr" name="icon" id="0x000d0015" />
-    <public type="attr" name="onClick" id="0x000d0016" />
-    <public type="attr" name="checkable" id="0x000d0017" />
-    <public type="attr" name="uxRestrictions" id="0x000d0018" />
-    <public type="attr" name="singleLineTitle" id="0x000d0019" />
-    <public type="attr" name="useSimpleSummaryProvider" id="0x000d0020" />
-    <public type="attr" name="initialExpandedChildrenCount" id="0x000d0021" />
-    <public type="attr" name="enableCopying" id="0x000d0022" />
-
-    <public type="drawable" name="car_ui_activity_background" id="0x000e0000" />
-
-</resources>
diff --git a/car-ui-lib/sharedlibrary/src/main/res/values/public.xml b/car-ui-lib/sharedlibrary/src/main/res/values/public.xml
new file mode 100644
index 0000000..7ec6c82
--- /dev/null
+++ b/car-ui-lib/sharedlibrary/src/main/res/values/public.xml
@@ -0,0 +1,27 @@
+<?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>
+    <!-- Any resource or attribute that is or can be referenced from RRO needs to be public go/rro-on-r -->
+    <!-- Keep in mind public resources are ignored when building with gradle -->
+
+    <!-- To add new resources to this file follow the following naming convention -->
+    <!-- 0cXXXX is used for styles -->
+    <!-- 0dXXXX is used for attributes -->
+    <!-- 0eXXXX is used for drawables -->
+    <!-- The first time that a resource type is seen, Android resource manager, assigns YY of the ID (0xXXYYXXXX) of the resource to that type.
+         And you'll get a compile error if you use the same type with a different YY -->
+
+</resources>
diff --git a/car-ui-lib/src/com/android/car/ui/FocusArea.java b/car-ui-lib/src/com/android/car/ui/FocusArea.java
deleted file mode 100644
index f969380..0000000
--- a/car-ui-lib/src/com/android/car/ui/FocusArea.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.ui;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import androidx.annotation.Nullable;
-
-/**
- * A {@link LinearLayout} used as a navigation block for the rotary controller.
- * <p>
- * The {@link com.android.car.rotary.RotaryService} looks for instances of {@link FocusArea} in the
- * view hierarchy when handling rotate and nudge actions. When receiving a rotation event ({@link
- * android.car.input.RotaryEvent}), RotaryService will move the focus to another {@link View} that
- * can take focus within the same FocusArea. When receiving a nudge event ({@link
- * KeyEvent#KEYCODE_SYSTEM_NAVIGATION_UP}, {@link KeyEvent#KEYCODE_SYSTEM_NAVIGATION_DOWN}, {@link
- * KeyEvent#KEYCODE_SYSTEM_NAVIGATION_LEFT}, or {@link KeyEvent#KEYCODE_SYSTEM_NAVIGATION_RIGHT}),
- * RotaryService will move the focus to another view that can take focus in another (typically
- * adjacent) FocusArea.
- * <p>
- * If enabled, FocusArea can draw highlights when one of its descendants has focus.
- * <p>
- * When creating a navigation block in the layout file, if you intend to use a LinearLayout as a
- * container for that block, just use a FocusArea instead; otherwise wrap the block in a FocusArea.
- * <p>
- * DO NOT nest a FocusArea inside another FocusArea because it will result in undefined navigation
- * behavior.
- */
-public class FocusArea extends LinearLayout {
-
-    /** Whether the FocusArea's descendant has focus (the FocusArea itself is not focusable). */
-    private boolean mHasFocus;
-
-    /**
-     * Whether to draw {@link #mForegroundHighlight} when one of the FocusArea's descendants has
-     * focus.
-     */
-    private boolean mEnableForegroundHighlight;
-
-    /**
-     * Whether to draw {@link #mBackgroundHighlight} when one of the FocusArea's descendants has
-     * focus.
-     */
-    private boolean mEnableBackgroundHighlight;
-
-    /**
-     * Highlight (typically outline of the FocusArea) drawn on top of the FocusArea and its
-     * descendants.
-     */
-    private Drawable mForegroundHighlight;
-
-    /**
-     * Highlight (typically a solid or gradient shape) drawn on top of the FocusArea but behind its
-     * descendants.
-     */
-    private Drawable mBackgroundHighlight;
-
-    /** The padding (in pixels) of the FocusArea highlight. */
-    private int mPaddingLeft;
-    private int mPaddingRight;
-    private int mPaddingTop;
-    private int mPaddingBottom;
-
-    public FocusArea(Context context) {
-        super(context);
-        init();
-    }
-
-    public FocusArea(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-        init();
-    }
-
-    public FocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        init();
-    }
-
-    public FocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        init();
-    }
-
-    private void init() {
-        mEnableForegroundHighlight = getContext().getResources().getBoolean(
-                R.bool.car_ui_enable_focus_area_foreground_highlight);
-        mEnableBackgroundHighlight = getContext().getResources().getBoolean(
-                R.bool.car_ui_enable_focus_area_background_highlight);
-        mForegroundHighlight = getContext().getResources().getDrawable(
-                R.drawable.car_ui_focus_area_foreground_highlight, getContext().getTheme());
-        mBackgroundHighlight = getContext().getResources().getDrawable(
-                R.drawable.car_ui_focus_area_background_highlight, getContext().getTheme());
-
-        // Ensure that an AccessibilityNodeInfo is created for this view.
-        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-
-        // By default all ViewGroup subclasses do not call their draw() and onDraw() methods. We
-        // should enable it since we override these methods.
-        setWillNotDraw(false);
-
-        // Update highlight of the FocusArea when the focus of its descendants has changed.
-        getViewTreeObserver().addOnGlobalFocusChangeListener(
-                (oldFocus, newFocus) -> {
-                    boolean hasFocus = hasFocus();
-                    if (mHasFocus != hasFocus) {
-                        mHasFocus = hasFocus;
-                        invalidate();
-                    }
-                });
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        // Draw highlight on top of this FocusArea (including its background and content) but
-        // behind its children.
-        if (mEnableBackgroundHighlight && mHasFocus) {
-            mBackgroundHighlight.setBounds(
-                    mPaddingLeft + getScrollX(),
-                    mPaddingTop + getScrollY(),
-                    getScrollX() + getWidth() - mPaddingRight,
-                    getScrollY() + getHeight() - mPaddingBottom);
-            mBackgroundHighlight.draw(canvas);
-        }
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        super.draw(canvas);
-
-        // Draw highlight on top of this FocusArea (including its background and content) and its
-        // children (including background, content, focus highlight, etc).
-        if (mEnableForegroundHighlight && mHasFocus) {
-            mForegroundHighlight.setBounds(
-                    mPaddingLeft + getScrollX(),
-                    mPaddingTop + getScrollY(),
-                    getScrollX() + getWidth() - mPaddingRight,
-                    getScrollY() + getHeight() - mPaddingBottom);
-            mForegroundHighlight.draw(canvas);
-        }
-    }
-
-    @Override
-    public CharSequence getAccessibilityClassName() {
-        return FocusArea.class.getName();
-    }
-
-    /** Sets the padding (in pixels) of the FocusArea highlight. */
-    public void setHighlightPadding(int left, int top, int right, int bottom) {
-        if (mPaddingLeft == left && mPaddingTop == top && mPaddingRight == right
-                && mPaddingBottom == bottom) {
-            return;
-        }
-        mPaddingLeft = left;
-        mPaddingTop = top;
-        mPaddingRight = right;
-        mPaddingBottom = bottom;
-        invalidate();
-    }
-}
diff --git a/car-ui-lib/src/com/android/car/ui/FocusParkingView.java b/car-ui-lib/src/com/android/car/ui/FocusParkingView.java
deleted file mode 100644
index 1b4bd13..0000000
--- a/car-ui-lib/src/com/android/car/ui/FocusParkingView.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.car.ui;
-
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_DISMISS;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-
-/**
- * A transparent {@link View} that can take focus. It's used by {@link
- * com.android.car.rotary.RotaryService} to support rotary controller navigation. Each {@link
- * android.view.Window} must have at least one FocusParkingView. The {@link FocusParkingView} must
- * be the first in Tab order, and outside of all {@link FocusArea}s.
- *
- * <p>
- * Android doesn't clear focus automatically when focus is set in another window. If we try to clear
- * focus in the previous window, Android will re-focus a view in that window, resulting in two
- * windows being focused simultaneously. Adding this view to each window can fix this issue. This
- * view is transparent and its default focus highlight is disabled, so it's invisible to the user no
- * matter whether it's focused or not. It can take focus so that RotaryService can "park" the focus
- * on it to remove the focus highlight.
- * <p>
- * If the focused view is scrolled off the screen, Android will refocus the first focusable view in
- * the window. The FocusParkingView should be the first view so that it gets focus. The
- * RotaryService detects this and moves focus to the scrolling container.
- * <p>
- * If there is only one focus area in the current window, rotating the controller within the focus
- * area will cause RotaryService to move the focus around from the view on the right to the view on
- * the left or vice versa. Adding this view to each window can fix this issue. When RotaryService
- * finds out the focus target is a FocusParkingView, it will know a wrap-around is going to happen.
- * Then it will avoid the wrap-around by not moving focus.
- */
-public class FocusParkingView extends View {
-
-    public FocusParkingView(Context context) {
-        super(context);
-        init();
-    }
-
-    public FocusParkingView(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-        init();
-    }
-
-    public FocusParkingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        init();
-    }
-
-    public FocusParkingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        init();
-    }
-
-    private void init() {
-        // This view is focusable, visible and enabled so it can take focus.
-        setFocusable(View.FOCUSABLE);
-        setVisibility(VISIBLE);
-        setEnabled(true);
-
-        // This view is not clickable so it won't affect the app's behavior when the user clicks on
-        // it by accident.
-        setClickable(false);
-
-        // This view is always transparent.
-        setAlpha(0f);
-
-        // Prevent Android from drawing the default focus highlight for this view when it's focused.
-        setDefaultFocusHighlightEnabled(false);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        // This size of the view is always 1 x 1 pixel, no matter what value is set in the layout
-        // file (match_parent, wrap_content, 100dp, 0dp, etc). Small size is to ensure it has little
-        // impact on the layout, non-zero size is to ensure it can take focus.
-        setMeasuredDimension(1, 1);
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasWindowFocus) {
-        if (!hasWindowFocus) {
-            // We need to clear the focus (by parking the focus on the FocusParkingView) once the
-            // current window goes to background. This can't be done by RotaryService because
-            // RotaryService sees the window as removed, thus can't perform any action (such as
-            // focus, clear focus) on the nodes in the window. So FocusParkingView has to grab the
-            // focus proactively.
-            requestFocus();
-        }
-        super.onWindowFocusChanged(hasWindowFocus);
-    }
-
-    @Override
-    public CharSequence getAccessibilityClassName() {
-        return FocusParkingView.class.getName();
-    }
-
-    @Override
-    public boolean performAccessibilityAction(int action, Bundle arguments) {
-        if (action == ACTION_DISMISS) {
-            // Try to move focus to the default focus.
-            getRootView().restoreDefaultFocus();
-            // The action failed if the FocusParkingView is still focused.
-            return !isFocused();
-        }
-        return super.performAccessibilityAction(action, arguments);
-    }
-}
diff --git a/car-ui-lib/src/com/android/car/ui/utils/RotaryConstants.java b/car-ui-lib/src/com/android/car/ui/utils/RotaryConstants.java
deleted file mode 100644
index cb418bb..0000000
--- a/car-ui-lib/src/com/android/car/ui/utils/RotaryConstants.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.ui.utils;
-
-/** Constants for the rotary controller. */
-public final class RotaryConstants {
-    /**
-     * Content description indicating that the rotary controller should scroll this view
-     * horizontally.
-     */
-    public static final String ROTARY_HORIZONTALLY_SCROLLABLE =
-            "android.rotary.HORIZONTALLY_SCROLLABLE";
-
-    /**
-     * Content description indicating that the rotary controller should scroll this view
-     * vertically.
-     */
-    public static final String ROTARY_VERTICALLY_SCROLLABLE =
-            "android.rotary.VERTICALLY_SCROLLABLE";
-
-    /** Prevent instantiation. */
-    private RotaryConstants() {}
-}
diff --git a/car-ui-lib/tests/apitest/auto-generate-resources.py b/car-ui-lib/tests/apitest/auto-generate-resources.py
index 1fcd866..10143c0 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
 #
@@ -19,11 +19,28 @@
 import sys
 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
@@ -45,9 +62,10 @@
 
     output_file = args.file or 'current.xml'
     if args.compare:
-        compare_resources(ROOT_FOLDER+'/res', OUTPUT_FILE_PATH + 'current.xml')
+        compare_resources(ROOT_FOLDER+'/car-ui-lib/src/main/res', OUTPUT_FILE_PATH + 'current.xml')
     else:
-        generate_current_file(ROOT_FOLDER+'/res', output_file)
+        generate_current_file(ROOT_FOLDER+'/car-ui-lib/src/main/res', output_file)
+        generate_overlayable_file(ROOT_FOLDER+'/car-ui-lib/src/main/res')
 
 def generate_current_file(res_folder, output_file='current.xml'):
     resources = get_all_resources(res_folder)
@@ -67,28 +85,64 @@
 
     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_constraintHorizontal_bias', '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_toBottomOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintBottom_toTopOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintStart_toStartOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintStart_toEndOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintEnd_toEndOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintEnd_toStartOf', '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_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
@@ -97,22 +151,11 @@
 
     root = etree.Element('resources')
 
-    root.addprevious(etree.Comment(' Copyright (C) 2020 The Android Open Source Project\n\n' +
-
-                                   '     Licensed under the Apache License, Version 2.0 (the "License");\n' +
-                                   '     you may not use this file except in compliance with the License.\n' +
-                                   '     You may obtain a copy of the License at\n\n' +
-
-                                   '     http://www.apache.org/licenses/LICENSE-2.0\n\n'
-
-                                   '     Unless required by applicable law or agreed to in writing, software\n'
-                                   '     distributed under the License is distributed on an "AS IS" BASIS,\n'
-                                   '     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n'
-                                   '     See the License for the specific language governing permissions and\n'
-                                   '     limitations under the License.\n'))
+    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', 'CarUiLibOverlayableResources')
+    overlayable.set('name', 'car-ui-lib')
 
     policy = etree.SubElement(overlayable, 'policy')
     policy.set('type', 'public')
@@ -124,8 +167,8 @@
 
     data = etree.ElementTree(root)
 
-    output_file=ROOT_FOLDER+'/res/values/overlayable.xml'
-    with open(output_file, 'w') as f:
+    output_file=ROOT_FOLDER+'/car-ui-lib/src/main/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):
diff --git a/car-ui-lib/tests/apitest/current.xml b/car-ui-lib/tests/apitest/current.xml
index 0731374..6abc24d 100644
--- a/car-ui-lib/tests/apitest/current.xml
+++ b/car-ui-lib/tests/apitest/current.xml
@@ -5,8 +5,12 @@
   <public type="attr" name="carUiPreferenceStyle"/>
   <public type="attr" name="carUiRecyclerViewStyle"/>
   <public type="attr" name="state_ux_restricted"/>
+  <public type="bool" name="car_ui_alert_dialog_force_dismiss_button"/>
+  <public type="bool" name="car_ui_clear_focus_area_history_when_rotating"/>
   <public type="bool" name="car_ui_enable_focus_area_background_highlight"/>
   <public type="bool" name="car_ui_enable_focus_area_foreground_highlight"/>
+  <public type="bool" name="car_ui_escrow_check_components_automatically"/>
+  <public type="bool" name="car_ui_focus_area_default_focus_overrides_history"/>
   <public type="bool" name="car_ui_list_item_single_line_title"/>
   <public type="bool" name="car_ui_preference_list_show_full_screen"/>
   <public type="bool" name="car_ui_preference_show_chevron"/>
@@ -24,7 +28,12 @@
   <public type="color" name="car_ui_preference_two_action_divider_color"/>
   <public type="color" name="car_ui_recyclerview_divider_color"/>
   <public type="color" name="car_ui_ripple_color"/>
-  <public type="color" name="car_ui_rotary_focus_color"/>
+  <public type="color" name="car_ui_rotary_focus_fill_color"/>
+  <public type="color" name="car_ui_rotary_focus_fill_secondary_color"/>
+  <public type="color" name="car_ui_rotary_focus_pressed_fill_color"/>
+  <public type="color" name="car_ui_rotary_focus_pressed_stroke_color"/>
+  <public type="color" name="car_ui_rotary_focus_stroke_color"/>
+  <public type="color" name="car_ui_rotary_focus_stroke_secondary_color"/>
   <public type="color" name="car_ui_scrollbar_thumb"/>
   <public type="color" name="car_ui_text_color_hint"/>
   <public type="color" name="car_ui_text_color_primary"/>
@@ -102,12 +111,15 @@
   <public type="dimen" name="car_ui_recyclerview_divider_height"/>
   <public type="dimen" name="car_ui_recyclerview_divider_start_margin"/>
   <public type="dimen" name="car_ui_recyclerview_divider_top_margin"/>
+  <public type="dimen" name="car_ui_rotary_focus_pressed_stroke_width"/>
+  <public type="dimen" name="car_ui_rotary_focus_stroke_width"/>
   <public type="dimen" name="car_ui_scrollbar_button_size"/>
   <public type="dimen" name="car_ui_scrollbar_container_width"/>
   <public type="dimen" name="car_ui_scrollbar_decelerate_interpolator_factor"/>
   <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_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"/>
@@ -158,6 +170,7 @@
   <public type="drawable" name="car_ui_icon_delete"/>
   <public type="drawable" name="car_ui_icon_down"/>
   <public type="drawable" name="car_ui_icon_edit"/>
+  <public type="drawable" name="car_ui_icon_lock"/>
   <public type="drawable" name="car_ui_icon_overflow_menu"/>
   <public type="drawable" name="car_ui_icon_save"/>
   <public type="drawable" name="car_ui_icon_search"/>
@@ -167,6 +180,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"/>
@@ -190,10 +204,12 @@
   <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_check_box_end_guideline"/>
-  <public type="id" name="car_ui_check_box_start_guideline"/>
+  <public type="id" name="car_ui_base_layout_content_container"/>
+  <public type="id" name="car_ui_focus_area"/>
   <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_container_without_widget"/>
   <public type="id" name="car_ui_preference_fragment_container"/>
   <public type="id" name="car_ui_recycler_view"/>
   <public type="id" name="car_ui_scroll_bar"/>
@@ -211,6 +227,7 @@
   <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_container"/>
   <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"/>
@@ -232,11 +249,8 @@
   <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"/>
   <public type="id" name="content_icon"/>
   <public type="id" name="icon"/>
   <public type="id" name="icon_container"/>
@@ -256,13 +270,17 @@
   <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="text_container"/>
   <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_focus_area_history_cache_type"/>
+  <public type="integer" name="car_ui_focus_area_history_expiration_period_ms"/>
+  <public type="integer" name="car_ui_focus_history_cache_type"/>
+  <public type="integer" name="car_ui_focus_history_expiration_period_ms"/>
   <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"/>
@@ -271,9 +289,9 @@
   <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_list_preference_with_toolbar"/>
   <public type="layout" name="car_ui_preference"/>
@@ -311,6 +329,7 @@
   <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"/>
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 8429d33..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
 #
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/Android.bp b/car-ui-lib/tests/paintbooth/Android.bp
deleted file mode 100644
index e303d83..0000000
--- a/car-ui-lib/tests/paintbooth/Android.bp
+++ /dev/null
@@ -1,56 +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.
-//
-
-android_app {
-    name: "PaintBooth",
-
-    srcs: [
-    	"src/**/*.java",
-    	"src/**/*.kt",
-    ],
-
-    required: ["privapp_whitelist_com.android.car.ui.paintbooth"],
-
-    resource_dirs: ["res", "res-public"],
-
-    platform_apis: true,
-
-    certificate: "platform",
-
-    privileged: true,
-
-    static_libs: [
-        "car-ui-lib",
-        "android.car.userlib",
-		"guava",
-		"gson-prebuilt-jar",
-    ],
-
-    optimize: {
-        enabled: false,
-    },
-
-    dex_preopt: {
-        enabled: false,
-    },
-
-    product_variables: {
-        pdk: {
-            enabled: false,
-        },
-    },
-    export_package_resources: true,
-}
diff --git a/car-ui-lib/tests/robotests/Android.bp b/car-ui-lib/tests/robotests/Android.bp
deleted file mode 100644
index 20e67ef..0000000
--- a/car-ui-lib/tests/robotests/Android.bp
+++ /dev/null
@@ -1,53 +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.
-//
-
-//###########################################################
-// CarUi lib just for Robolectric test target.     #
-//###########################################################
-android_app {
-    name: "CarUi",
-
-    resource_dirs: [
-        "res",
-    ],
-
-    platform_apis: true,
-
-    privileged: true,
-
-    libs: ["android.car"],
-
-    static_libs: ["car-ui-lib"],
-
-}
-
-//###############################################
-// Car Ui Robolectric test target. #
-//###############################################
-android_robolectric_test {
-    name: "CarUiRoboTests",
-
-    srcs: ["src/**/*.java"],
-
-    java_resource_dirs: ["config"],
-
-    libs: [
-        "android.car",
-        "testng",
-    ],
-
-    instrumentation_for: "CarUi",
-}
diff --git a/car-ui-lib/tests/robotests/build.gradle b/car-ui-lib/tests/robotests/build.gradle
deleted file mode 100644
index 88387b8..0000000
--- a/car-ui-lib/tests/robotests/build.gradle
+++ /dev/null
@@ -1,87 +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.
- */
-
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
-    repositories {
-        google()
-        jcenter()
-    }
-
-    dependencies {
-        classpath 'com.android.tools.build:gradle:3.5.3'
-
-        // NOTE: Do not place your application dependencies here; they belong
-        // in the individual module build.gradle files
-    }
-}
-
-allprojects {
-    repositories {
-        google()
-        jcenter()
-    }
-}
-
-// Library-level build file
-
-apply plugin: 'com.android.library'
-
-android {
-    compileSdkVersion 29
-
-    defaultConfig {
-        minSdkVersion 28
-        targetSdkVersion 29
-        versionCode 1
-        versionName "1.0"
-    }
-
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
-    }
-
-    sourceSets {
-        main {
-            manifest.srcFile 'AndroidManifest.xml'
-            res.srcDirs = ['res']
-        }
-
-        test {
-            java.srcDirs = ['src']
-        }
-    }
-
-    testOptions {
-        unitTests {
-            includeAndroidResources = true
-        }
-    }
-}
-
-dependencies {
-    implementation project(':')
-    testImplementation "androidx.test.ext:junit:1.1.1"
-    testImplementation 'org.robolectric:robolectric:4.2'
-    testImplementation "org.mockito:mockito-core:2.19.0"
-    testImplementation "com.google.truth:truth:0.29"
-    testImplementation "org.testng:testng:6.9.9"
-
-    // This is the gradle equivalent of linking to android.car in our Android.mk
-    implementation files('../../../../../../../out/target/common/obj/JAVA_LIBRARIES/android.car_intermediates/classes.jar')
-}
diff --git a/car-ui-lib/tests/robotests/config/robolectric.properties b/car-ui-lib/tests/robotests/config/robolectric.properties
deleted file mode 100644
index 849e07f..0000000
--- a/car-ui-lib/tests/robotests/config/robolectric.properties
+++ /dev/null
@@ -1,16 +0,0 @@
-#
-# 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.
-#
-sdk=NEWEST_SDK
diff --git a/car-ui-lib/tests/unit/res/drawable/ic_launcher.png b/car-ui-lib/tests/unit/res/drawable/ic_launcher.png
deleted file mode 100644
index 2af53a4..0000000
--- a/car-ui-lib/tests/unit/res/drawable/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/car-ui-lib/tests/unit/res/layout/focus_parking_view_test_activity.xml b/car-ui-lib/tests/unit/res/layout/focus_parking_view_test_activity.xml
deleted file mode 100644
index f3f623d..0000000
--- a/car-ui-lib/tests/unit/res/layout/focus_parking_view_test_activity.xml
+++ /dev/null
@@ -1,31 +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.
-  -->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <!-- In some cases Android will focus the first focusable view automatically. To prevent the
-         FocusParkingView getting focused unintentionally, we put a focusable Button above the
-         FocusParkingView. -->
-    <Button
-        android:layout_width="100dp"
-        android:layout_height="40dp"/>
-    <com.android.car.ui.FocusParkingView
-        android:id="@+id/focus_parking"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
-</FrameLayout>
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/FocusAreaTest.java b/car-ui-lib/tests/unit/src/com/android/car/ui/FocusAreaTest.java
deleted file mode 100644
index 8b2ddc1..0000000
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/FocusAreaTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.ui;
-
-import static org.junit.Assert.assertTrue;
-
-import android.view.View;
-
-import androidx.test.rule.ActivityTestRule;
-
-import com.android.car.ui.tests.unit.R;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/** Unit tests for {@link FocusArea}. */
-public class FocusAreaTest {
-    private static final long WAIT_TIME_MS = 3000;
-
-    @Rule
-    public ActivityTestRule<FocusAreaTestActivity> mActivityRule =
-            new ActivityTestRule<>(FocusAreaTestActivity.class);
-
-    private FocusAreaTestActivity mActivity;
-    private TestFocusArea mFocusArea;
-    private View mChild;
-    private View mNonChild;
-
-    @Before
-    public void setUp() {
-        mActivity = mActivityRule.getActivity();
-        mFocusArea = mActivity.findViewById(R.id.focus_area);
-        mChild = mActivity.findViewById(R.id.child);
-        mNonChild = mActivity.findViewById(R.id.non_child);
-    }
-
-    @Test
-    public void testLoseFocus() throws Exception {
-        mChild.post(() -> {
-            mChild.requestFocus();
-        });
-        mFocusArea.setOnDrawCalled(false);
-        mFocusArea.setDrawCalled(false);
-
-        // FocusArea lost focus.
-        CountDownLatch latch = new CountDownLatch(1);
-        mNonChild.post(() -> {
-            mNonChild.requestFocus();
-            mNonChild.post(() -> {
-                latch.countDown();
-            });
-        });
-        assertDrawMethodsCalled(latch);
-    }
-
-    @Test
-    public void testGetFocus() throws Exception {
-        mNonChild.post(() -> {
-            mNonChild.requestFocus();
-        });
-        mFocusArea.setOnDrawCalled(false);
-        mFocusArea.setDrawCalled(false);
-
-        // FocusArea got focus.
-        CountDownLatch latch = new CountDownLatch(1);
-        mChild.post(() -> {
-            mChild.requestFocus();
-            mChild.post(() -> {
-                latch.countDown();
-            });
-        });
-        assertDrawMethodsCalled(latch);
-    }
-
-    private void assertDrawMethodsCalled(CountDownLatch latch) throws Exception {
-        latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertTrue(mFocusArea.onDrawCalled());
-        assertTrue(mFocusArea.drawCalled());
-    }
-}
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/FocusParkingViewTest.java b/car-ui-lib/tests/unit/src/com/android/car/ui/FocusParkingViewTest.java
deleted file mode 100644
index 52236bd..0000000
--- a/car-ui-lib/tests/unit/src/com/android/car/ui/FocusParkingViewTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.ui;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.rule.ActivityTestRule;
-
-import com.android.car.ui.tests.unit.R;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/** Unit test for {@link FocusParkingView}. */
-public class FocusParkingViewTest {
-
-    private static final long WAIT_TIME_MS = 3000;
-
-    @Rule
-    public ActivityTestRule<FocusParkingViewTestActivity> mActivityRule =
-            new ActivityTestRule<>(FocusParkingViewTestActivity.class);
-
-    private FocusParkingViewTestActivity mActivity;
-
-    @Before
-    public void setUp() {
-        mActivity = mActivityRule.getActivity();
-    }
-
-    @Test
-    public void testFocusParkingViewCanTakeFocus() throws Exception {
-        FocusParkingView focusParkingView = mActivity.findViewById(R.id.focus_parking);
-
-        CountDownLatch latch = new CountDownLatch(1);
-        focusParkingView.post(() -> {
-            focusParkingView.requestFocus();
-            focusParkingView.post(() -> {
-                latch.countDown();
-            });
-        });
-        latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-
-        assertThat(focusParkingView.isFocused()).isTrue();
-    }
-    @Test
-    public void testFocusParkingViewFocusedWhenWindowLostFocus() throws Exception {
-        FocusParkingView focusParkingView = mActivity.findViewById(R.id.focus_parking);
-        assertThat(focusParkingView.isFocused()).isFalse();
-
-        CountDownLatch latch = new CountDownLatch(1);
-        focusParkingView.post(() -> {
-            focusParkingView.onWindowFocusChanged(false);
-            focusParkingView.post(() -> {
-                latch.countDown();
-            });
-        });
-        latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-
-        assertThat(focusParkingView.isFocused()).isTrue();
-    }
-}
diff --git a/car-ui-lib/trailing-blank-line-hook.sh b/car-ui-lib/trailing-blank-line-hook.sh
index d69489d..a850d8e 100755
--- a/car-ui-lib/trailing-blank-line-hook.sh
+++ b/car-ui-lib/trailing-blank-line-hook.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-result=$(find car-ui-lib/ -type f \( -iname \*.java -o -iname \*.xml \) -a \( ! -wholename \*/.idea/\* \) -print0 | xargs -0 -L1 bash -c 'test "$(tail -c 1 "$0")" && echo "No new line at end of $0"')
+result=$(find car-ui-lib/ -type f \( -iname \*.java -o -iname \*.xml \) -a \( ! -wholename \*/.idea/\* \) \( ! -wholename \*/build/\* \) -print0 | xargs -0 -L1 bash -c 'test "$(tail -c 1 "$0")" && echo "No new line at end of $0"')
 if [ \( ! -z "$result" \)  -o \( $(echo "$result" | wc -l) -gt 1 \) ]
 then
     echo "$result" && false;
diff --git a/car-ui-lib/sharedlibrary/Android.bp b/car-uxr-client-lib/Android.bp
similarity index 72%
rename from car-ui-lib/sharedlibrary/Android.bp
rename to car-uxr-client-lib/Android.bp
index 1abaa30..c112734 100644
--- a/car-ui-lib/sharedlibrary/Android.bp
+++ b/car-uxr-client-lib/Android.bp
@@ -1,4 +1,3 @@
-
 //
 // Copyright (C) 2020 The Android Open Source Project
 //
@@ -14,18 +13,24 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-android_app {
-    name: "car-ui-lib-sharedlibrary",
-    visibility: [
-        "//packages/apps/Car/libs/car-ui-lib/tests/PaintBooth:__pkg__",
-    ],
-    platform_apis: true,
-    resource_dirs: ["res"],
-    aaptflags: ["--shared-lib"],
+android_library {
+
+    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",
     ],
 }
diff --git a/car-media-common/res/drawable/fab_empty_foreground.xml b/car-uxr-client-lib/AndroidManifest.xml
similarity index 67%
rename from car-media-common/res/drawable/fab_empty_foreground.xml
rename to car-uxr-client-lib/AndroidManifest.xml
index d9fb901..9550419 100644
--- a/car-media-common/res/drawable/fab_empty_foreground.xml
+++ b/car-uxr-client-lib/AndroidManifest.xml
@@ -1,20 +1,20 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright 2018, 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.
   You may obtain a copy of the License at
 
-      http://www.apache.org/licenses/LICENSE-2.0
+    http://www.apache.org/licenses/LICENSE-2.0
 
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
--->
-<ripple
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:radius="0dp"
-    android:color="@color/car_dark_blue_grey_700" />
+  -->
+
+<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 55ee640..4383a20 100644
--- a/connected-device-lib/Android.bp
+++ b/connected-device-lib/Android.bp
@@ -21,13 +21,11 @@
 
     manifest: "AndroidManifest.xml",
 
-    resource_dirs: ["res"],
-
     optimize: {
         enabled: false,
     },
 
-    libs: ["android.car"],
+    libs: ["android.car-system-stubs"],
 
     static_libs: [
         "androidx.room_room-runtime",
@@ -40,5 +38,6 @@
         "androidx.room_room-compiler-plugin",
     ],
 
-    platform_apis: true,
+    sdk_version: "system_current",
+    min_sdk_version: "28",
 }
diff --git a/connected-device-lib/proto/ble_device_message.proto b/connected-device-lib/proto/device_message.proto
similarity index 87%
rename from connected-device-lib/proto/ble_device_message.proto
rename to connected-device-lib/proto/device_message.proto
index 581d6a0..79b98df 100644
--- a/connected-device-lib/proto/ble_device_message.proto
+++ b/connected-device-lib/proto/device_message.proto
@@ -20,11 +20,11 @@
 
 import "packages/apps/Car/libs/connected-device-lib/proto/operation_type.proto";
 
-option java_package = "com.android.car.connecteddevice.BleStreamProtos";
-option java_outer_classname = "BleDeviceMessageProto";
+option java_package = "com.android.car.connecteddevice.StreamProtos";
+option java_outer_classname = "DeviceMessageProto";
 
 // A message between devices.
-message BleDeviceMessage {
+message Message {
   // The operation that this message represents.
   OperationType operation = 1;
 
diff --git a/connected-device-lib/proto/operation_type.proto b/connected-device-lib/proto/operation_type.proto
index d447ccc..3e38e27 100644
--- a/connected-device-lib/proto/operation_type.proto
+++ b/connected-device-lib/proto/operation_type.proto
@@ -18,8 +18,8 @@
 
 package com.android.car.connecteddevice.proto;
 
-option java_package = "com.android.car.connecteddevice.BleStreamProtos";
-option java_outer_classname = "BleOperationProto";
+option java_package = "com.android.car.connecteddevice.StreamProtos";
+option java_outer_classname = "OperationProto";
 
 // The different message types that indicate the content of the payload.
 //
diff --git a/connected-device-lib/proto/ble_packet.proto b/connected-device-lib/proto/packet.proto
similarity index 85%
rename from connected-device-lib/proto/ble_packet.proto
rename to connected-device-lib/proto/packet.proto
index c2ce262..f761622 100644
--- a/connected-device-lib/proto/ble_packet.proto
+++ b/connected-device-lib/proto/packet.proto
@@ -18,11 +18,11 @@
 
 package com.android.car.connecteddevice.proto;
 
-option java_package = "com.android.car.connecteddevice.BleStreamProtos";
-option java_outer_classname = "BlePacketProto";
+option java_package = "com.android.car.connecteddevice.StreamProtos";
+option java_outer_classname = "PacketProto";
 
-// A packet across a BLE channel.
-message BlePacket {
+// A packet across a stream.
+message Packet {
   // A 1-based packet number. The first message will have a value of "1" rather
   // than "0".
   fixed32 packet_number = 1;
diff --git a/connected-device-lib/proto/ble_version_exchange.proto b/connected-device-lib/proto/version_exchange.proto
similarity index 91%
rename from connected-device-lib/proto/ble_version_exchange.proto
rename to connected-device-lib/proto/version_exchange.proto
index a7e8021..a7c3493 100644
--- a/connected-device-lib/proto/ble_version_exchange.proto
+++ b/connected-device-lib/proto/version_exchange.proto
@@ -18,10 +18,10 @@
 
 package com.android.car.connecteddevice.proto;
 
-option java_package = "com.android.car.connecteddevice.BleStreamProtos";
+option java_package = "com.android.car.connecteddevice.StreamProtos";
 option java_outer_classname = "VersionExchangeProto";
 
-message BleVersionExchange {
+message VersionExchange {
   // Minimum supported protobuf version.
   int32 minSupportedMessagingVersion = 1;
 
diff --git a/connected-device-lib/res/values/config.xml b/connected-device-lib/res/values/config.xml
index b090b92..c719462 100644
--- a/connected-device-lib/res/values/config.xml
+++ b/connected-device-lib/res/values/config.xml
@@ -16,22 +16,5 @@
   -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- 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>
-    <string name="car_secure_write_uuid" translatable="false">5e2a68a5-27be-43f9-8d1e-4546976fabd7</string>
-
     <string name="connected_device_shared_preferences" translatable="false">com.android.car.connecteddevice</string>
-
-    <!--
-    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..7697131 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 {
@@ -26,10 +26,10 @@
      *
      * @param deviceName The device name to identify the car.
      */
-    void onAssociationStartSuccess(@NonNull String deviceName);
+    default void onAssociationStartSuccess(String deviceName) {}
 
     /** Invoked when IHU failed to start advertising for association. */
-    void onAssociationStartFailure();
+    default void onAssociationStartFailure() {}
 
     /**
      * Invoked when a {@link ConnectedDeviceManager.DeviceError} has been encountered in attempting
@@ -37,7 +37,7 @@
      *
      * @param error The failure indication.
      */
-    void onAssociationError(@ConnectedDeviceManager.DeviceError int error);
+    default void onAssociationError(@ConnectedDeviceManager.DeviceError int error) {}
 
     /**
      * Invoked when a verification code needs to be displayed. The user needs to confirm, and
@@ -45,12 +45,12 @@
      *
      * @param code The verification code.
      */
-    void onVerificationCodeAvailable(@NonNull String code);
+    default void onVerificationCodeAvailable(@NonNull String code) {}
 
     /**
      * Invoked when the association has completed.
      *
      * @param deviceId The id of the newly associated device.
      */
-    void onAssociationCompleted(@NonNull String deviceId);
+    default void onAssociationCompleted(@NonNull String deviceId) {}
 }
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 4add85e..b1397e2 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ConnectedDeviceManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ConnectedDeviceManager.java
@@ -22,29 +22,25 @@
 
 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;
-import com.android.car.connecteddevice.ble.CarBleManager;
-import com.android.car.connecteddevice.ble.CarBlePeripheralManager;
-import com.android.car.connecteddevice.ble.DeviceMessage;
+import com.android.car.connecteddevice.connection.CarBluetoothManager;
+import com.android.car.connecteddevice.connection.DeviceMessage;
 import com.android.car.connecteddevice.model.AssociatedDevice;
 import com.android.car.connecteddevice.model.ConnectedDevice;
+import com.android.car.connecteddevice.model.OobEligibleDevice;
+import com.android.car.connecteddevice.oob.BluetoothRfcommChannel;
+import com.android.car.connecteddevice.oob.OobChannel;
 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage.AssociatedDeviceCallback;
 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;
@@ -73,15 +69,9 @@
     // 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;
-
-    private final CarBlePeripheralManager mPeripheralManager;
+    private final CarBluetoothManager mCarBluetoothManager;
 
     private final ThreadSafeCallbacks<DeviceAssociationCallback> mDeviceAssociationCallbacks =
             new ThreadSafeCallbacks<>();
@@ -97,7 +87,7 @@
             new ConcurrentHashMap<>();
 
     // deviceId -> device
-    private final Map<String, InternalConnectedDevice> mConnectedDevices =
+    private final Map<String, ConnectedDevice> mConnectedDevices =
             new ConcurrentHashMap<>();
 
     // recipientId -> (deviceId -> message bytes)
@@ -120,22 +110,23 @@
 
     private MessageDeliveryDelegate mMessageDeliveryDelegate;
 
+    private OobChannel mOobChannel;
+
     @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;
     public static final int DEVICE_ERROR_INVALID_DEVICE_ID = 2;
@@ -147,53 +138,15 @@
     public static final int DEVICE_ERROR_INSECURE_RECIPIENT_ID_DETECTED = 8;
     public static final int DEVICE_ERROR_UNEXPECTED_DISCONNECTION = 9;
 
-    public ConnectedDeviceManager(@NonNull Context context) {
-        this(context, new ConnectedDeviceStorage(context), new BleCentralManager(context),
-                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_default_mtu_size));
-    }
-
-    private ConnectedDeviceManager(
-            @NonNull Context context,
-            @NonNull ConnectedDeviceStorage storage,
-            @NonNull BleCentralManager bleCentralManager,
-            @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 defaultMtuSize) {
-        this(storage,
-                new CarBleCentralManager(context, bleCentralManager, storage, serviceUuid, bgMask,
-                        writeCharacteristicUuid, readCharacteristicUuid),
-                new CarBlePeripheralManager(blePeripheralManager, storage, associationServiceUuid,
-                        reconnectServiceUuid, reconnectDataUuid, writeCharacteristicUuid,
-                        readCharacteristicUuid, MAX_ADVERTISEMENT_DURATION, defaultMtuSize));
-    }
-
-    @VisibleForTesting
-    ConnectedDeviceManager(
-            @NonNull ConnectedDeviceStorage storage,
-            @NonNull CarBleCentralManager centralManager,
-            @NonNull CarBlePeripheralManager peripheralManager) {
+    public ConnectedDeviceManager(@NonNull CarBluetoothManager carBluetoothManager,
+            @NonNull ConnectedDeviceStorage storage) {
         Executor callbackExecutor = Executors.newSingleThreadExecutor();
         mStorage = storage;
-        mCentralManager = centralManager;
-        mPeripheralManager = peripheralManager;
-        mCentralManager.registerCallback(generateCarBleCallback(centralManager), callbackExecutor);
-        mPeripheralManager.registerCallback(generateCarBleCallback(peripheralManager),
+        mCarBluetoothManager = carBluetoothManager;
+        mCarBluetoothManager.registerCallback(generateCarBleCallback(carBluetoothManager),
                 callbackExecutor);
         mStorage.setAssociatedDeviceCallback(mAssociatedDeviceCallback);
+        logd(TAG, "ConnectedDeviceManager created successfully.");
     }
 
     /**
@@ -207,29 +160,32 @@
             logd(TAG, "Starting ConnectedDeviceManager.");
             EventLog.onConnectedDeviceManagerStarted();
         }
-        // TODO (b/141312136) Start central manager
-        mPeripheralManager.start();
+        mCarBluetoothManager.start();
         connectToActiveUserDevice();
     }
 
     /** Reset internal processes and disconnect any active connections. */
     public void reset() {
         logd(TAG, "Resetting ConnectedDeviceManager.");
-        for (InternalConnectedDevice device : mConnectedDevices.values()) {
-            removeConnectedDevice(device.mConnectedDevice.getDeviceId(), device.mCarBleManager);
+        for (ConnectedDevice device : mConnectedDevices.values()) {
+            removeConnectedDevice(device.getDeviceId());
         }
-        mPeripheralManager.stop();
-        // TODO (b/141312136) Stop central manager
+        mCarBluetoothManager.stop();
         mIsConnectingToUserDevice.set(false);
+        if (mOobChannel != null) {
+            mOobChannel.interrupt();
+            mOobChannel = null;
+        }
+        mAssociationCallback = null;
     }
 
     /** Returns {@link List<ConnectedDevice>} of devices currently connected. */
     @NonNull
     public List<ConnectedDevice> getActiveUserConnectedDevices() {
         List<ConnectedDevice> activeUserConnectedDevices = new ArrayList<>();
-        for (InternalConnectedDevice device : mConnectedDevices.values()) {
-            if (device.mConnectedDevice.isAssociatedWithActiveUser()) {
-                activeUserConnectedDevices.add(device.mConnectedDevice);
+        for (ConnectedDevice device : mConnectedDevices.values()) {
+            if (device.isAssociatedWithActiveUser()) {
+                activeUserConnectedDevices.add(device);
             }
         }
         logd(TAG, "Returned " + activeUserConnectedDevices.size() + " active user devices.");
@@ -243,7 +199,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);
     }
 
@@ -264,7 +220,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);
     }
 
@@ -289,7 +245,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;
@@ -297,6 +254,7 @@
             List<AssociatedDevice> userDevices = mStorage.getActiveUserAssociatedDevices();
             if (userDevices.isEmpty()) {
                 logw(TAG, "No devices associated with active user. Ignoring.");
+                mIsConnectingToUserDevice.set(false);
                 return;
             }
 
@@ -305,16 +263,17 @@
             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()));
+            mCarBluetoothManager.connectToDevice(UUID.fromString(userDevice.getDeviceId()));
         } catch (Exception e) {
             loge(TAG, "Exception while attempting connection with active user's device.", e);
         }
@@ -329,19 +288,57 @@
         mAssociationCallback = callback;
         Executors.defaultThreadFactory().newThread(() -> {
             logd(TAG, "Received request to start association.");
-            mPeripheralManager.startAssociation(getNameForAssociation(),
+            mCarBluetoothManager.startAssociation(getNameForAssociation(),
                     mInternalAssociationCallback);
         }).start();
     }
 
+    /**
+     * Start association with an out of band device.
+     *
+     * @param device   The out of band eligible device.
+     * @param callback Callback for association events.
+     */
+    public void startOutOfBandAssociation(@NonNull OobEligibleDevice device,
+            @NonNull AssociationCallback callback) {
+        logd(TAG, "Received request to start out of band association.");
+        mAssociationCallback = callback;
+        mOobChannel = new BluetoothRfcommChannel();
+        mOobChannel.completeOobDataExchange(device, new OobChannel.Callback() {
+            @Override
+            public void onOobExchangeSuccess() {
+                logd(TAG, "Out of band exchange succeeded. Proceeding to association with device.");
+                Executors.defaultThreadFactory().newThread(() -> {
+                    mCarBluetoothManager.startOutOfBandAssociation(
+                            getNameForAssociation(),
+                            mOobChannel, mInternalAssociationCallback);
+                }).start();
+            }
+
+            @Override
+            public void onOobExchangeFailure() {
+                loge(TAG, "Out of band exchange failed.");
+                mInternalAssociationCallback.onAssociationError(
+                        DEVICE_ERROR_INVALID_ENCRYPTION_KEY);
+                mOobChannel = null;
+                mAssociationCallback = null;
+            }
+        });
+    }
+
     /** Stop the association with any device. */
     public void stopAssociation(@NonNull AssociationCallback callback) {
         if (mAssociationCallback != callback) {
             logd(TAG, "Stop association called with unrecognized callback. Ignoring.");
             return;
         }
+        logd(TAG, "Stopping association.");
         mAssociationCallback = null;
-        mPeripheralManager.stopAssociation(mInternalAssociationCallback);
+        mCarBluetoothManager.stopAssociation();
+        if (mOobChannel != null) {
+            mOobChannel.interrupt();
+        }
+        mOobChannel = null;
     }
 
     /**
@@ -356,7 +353,7 @@
 
     /** Notify that the user has accepted a pairing code or any out-of-band confirmation. */
     public void notifyOutOfBandAccepted() {
-        mPeripheralManager.notifyOutOfBandAccepted();
+        mCarBluetoothManager.notifyOutOfBandAccepted();
     }
 
     /**
@@ -378,6 +375,7 @@
         logd(TAG, "enableAssociatedDeviceConnection() called on " + deviceId);
         mStorage.updateAssociatedDeviceConnectionEnabled(deviceId,
                 /* isConnectionEnabled= */ true);
+
         connectToActiveUserDevice();
     }
 
@@ -394,23 +392,23 @@
     }
 
     private void disconnectDevice(String deviceId) {
-        InternalConnectedDevice device = mConnectedDevices.get(deviceId);
+        ConnectedDevice device = mConnectedDevices.get(deviceId);
         if (device != null) {
-            device.mCarBleManager.disconnectDevice(deviceId);
-            removeConnectedDevice(deviceId, device.mCarBleManager);
+            mCarBluetoothManager.disconnectDevice(deviceId);
+            removeConnectedDevice(deviceId);
         }
     }
 
     /**
      * Register a callback for a specific device and recipient.
      *
-     * @param device {@link ConnectedDevice} to register triggers on.
+     * @param device      {@link ConnectedDevice} to register triggers on.
      * @param recipientId {@link UUID} to register as recipient of.
-     * @param callback {@link DeviceCallback} to register.
-     * @param executor {@link Executor} on which to execute callback.
+     * @param callback    {@link DeviceCallback} to register.
+     * @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;
@@ -419,7 +417,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
@@ -474,9 +472,9 @@
      * registered.
      *
      * @param recipientId Recipient's id
-     * @param deviceId Device id
+     * @param deviceId    Device id
      * @return The missed {@code byte[]} messages, or {@code null} if no messages were
-     *         missed.
+     * missed.
      */
     @Nullable
     private List<byte[]> popMissedMessages(@NonNull UUID recipientId, @NonNull String deviceId) {
@@ -491,9 +489,9 @@
     /**
      * Unregister callback from device events.
      *
-     * @param device {@link ConnectedDevice} callback was registered on.
+     * @param device      {@link ConnectedDevice} callback was registered on.
      * @param recipientId {@link UUID} callback was registered under.
-     * @param callback {@link DeviceCallback} to unregister.
+     * @param callback    {@link DeviceCallback} to unregister.
      */
     public void unregisterDeviceCallback(@NonNull ConnectedDevice device,
             @NonNull UUID recipientId, @NonNull DeviceCallback callback) {
@@ -519,9 +517,9 @@
     /**
      * Securely send message to a device.
      *
-     * @param device {@link ConnectedDevice} to send the message to.
+     * @param device      {@link ConnectedDevice} to send the message to.
      * @param recipientId Recipient {@link UUID}.
-     * @param message Message to send.
+     * @param message     Message to send.
      * @throws IllegalStateException Secure channel has not been established.
      */
     public void sendMessageSecurely(@NonNull ConnectedDevice device, @NonNull UUID recipientId,
@@ -532,9 +530,9 @@
     /**
      * Send an unencrypted message to a device.
      *
-     * @param device {@link ConnectedDevice} to send the message to.
+     * @param device      {@link ConnectedDevice} to send the message to.
      * @param recipientId Recipient {@link UUID}.
-     * @param message Message to send.
+     * @param message     Message to send.
      */
     public void sendMessageUnsecurely(@NonNull ConnectedDevice device, @NonNull UUID recipientId,
             @NonNull byte[] message) {
@@ -548,19 +546,19 @@
                 + " containing " + message.length + ". Message will be sent securely: "
                 + isEncrypted + ".");
 
-        InternalConnectedDevice connectedDevice = mConnectedDevices.get(deviceId);
+        ConnectedDevice connectedDevice = mConnectedDevices.get(deviceId);
         if (connectedDevice == null) {
             loge(TAG, "Attempted to send message to unknown device " + deviceId + ". Ignoring.");
             return;
         }
 
-        if (isEncrypted && !connectedDevice.mConnectedDevice.hasSecureChannel()) {
+        if (isEncrypted && !connectedDevice.hasSecureChannel()) {
             throw new IllegalStateException("Cannot send a message securely to device that has not "
                     + "established a secure channel.");
         }
 
-        connectedDevice.mCarBleManager.sendMessage(deviceId,
-                new DeviceMessage(recipientId, isEncrypted, message));
+        mCarBluetoothManager.sendMessage(deviceId, new DeviceMessage(recipientId, isEncrypted,
+                message));
     }
 
     private boolean isRecipientBlacklisted(UUID recipientId) {
@@ -581,11 +579,11 @@
             return;
         }
 
-        InternalConnectedDevice connectedDevice = mConnectedDevices.get(deviceId);
+        ConnectedDevice connectedDevice = mConnectedDevices.get(deviceId);
         if (connectedDevice != null) {
             recipientCallbacks.get(recipientId).invoke(
                     callback ->
-                            callback.onDeviceError(connectedDevice.mConnectedDevice,
+                            callback.onDeviceError(connectedDevice,
                                     DEVICE_ERROR_INSECURE_RECIPIENT_ID_DETECTED)
             );
         }
@@ -595,7 +593,7 @@
     }
 
     @VisibleForTesting
-    void addConnectedDevice(@NonNull String deviceId, @NonNull CarBleManager bleManager) {
+    void addConnectedDevice(@NonNull String deviceId) {
         if (mConnectedDevices.containsKey(deviceId)) {
             // Device already connected. No-op until secure channel established.
             return;
@@ -608,30 +606,24 @@
                 /* hasSecureChannel= */ false
         );
 
-        mConnectedDevices.put(deviceId, new InternalConnectedDevice(connectedDevice, bleManager));
+        mConnectedDevices.put(deviceId, connectedDevice);
         invokeConnectionCallbacks(connectedDevice.isAssociatedWithActiveUser(),
                 callback -> callback.onDeviceConnected(connectedDevice));
     }
 
     @VisibleForTesting
-    void removeConnectedDevice(@NonNull String deviceId, @NonNull CarBleManager bleManager) {
-        logd(TAG, "Device " + deviceId + " disconnected from manager " + bleManager);
-        InternalConnectedDevice connectedDevice = getConnectedDeviceForManager(deviceId,
-                bleManager);
-
-        // If disconnect happened on peripheral, open for future requests to connect.
-        if (bleManager == mPeripheralManager) {
-            mIsConnectingToUserDevice.set(false);
-        }
-
+    void removeConnectedDevice(@NonNull String deviceId) {
+        logd(TAG, "Device " + deviceId + " disconnected.");
+        ConnectedDevice connectedDevice = mConnectedDevices.get(deviceId);
+        mIsConnectingToUserDevice.set(false);
         if (connectedDevice == null) {
             return;
         }
 
         mConnectedDevices.remove(deviceId);
-        boolean isAssociated = connectedDevice.mConnectedDevice.isAssociatedWithActiveUser();
+        boolean isAssociated = connectedDevice.isAssociatedWithActiveUser();
         invokeConnectionCallbacks(isAssociated,
-                callback -> callback.onDeviceDisconnected(connectedDevice.mConnectedDevice));
+                callback -> callback.onDeviceDisconnected(connectedDevice));
 
         if (isAssociated || mConnectedDevices.isEmpty()) {
             // Try to regain connection to active user's device.
@@ -640,25 +632,23 @@
     }
 
     @VisibleForTesting
-    void onSecureChannelEstablished(@NonNull String deviceId,
-            @NonNull CarBleManager bleManager) {
+    void onSecureChannelEstablished(@NonNull String deviceId) {
         if (mConnectedDevices.get(deviceId) == null) {
             loge(TAG, "Secure channel established on unknown device " + deviceId + ".");
             return;
         }
-        ConnectedDevice connectedDevice = mConnectedDevices.get(deviceId).mConnectedDevice;
+        ConnectedDevice connectedDevice = mConnectedDevices.get(deviceId);
         ConnectedDevice updatedConnectedDevice = new ConnectedDevice(connectedDevice.getDeviceId(),
                 connectedDevice.getDeviceName(), connectedDevice.isAssociatedWithActiveUser(),
                 /* hasSecureChannel= */ true);
 
-        boolean notifyCallbacks = getConnectedDeviceForManager(deviceId, bleManager) != null;
+        boolean notifyCallbacks = mConnectedDevices.get(deviceId) != null;
 
         // TODO (b/143088482) Implement interrupt
         // Ignore if central already holds the active device connection and interrupt the
         // connection.
 
-        mConnectedDevices.put(deviceId,
-                new InternalConnectedDevice(updatedConnectedDevice, bleManager));
+        mConnectedDevices.put(deviceId, updatedConnectedDevice);
         logd(TAG, "Secure channel established to " + deviceId + " . Notifying callbacks: "
                 + notifyCallbacks + ".");
         if (notifyCallbacks) {
@@ -673,7 +663,7 @@
                 + message.getRecipient() + " containing " + message.getMessage().length
                 + " bytes.");
 
-        InternalConnectedDevice connectedDevice = mConnectedDevices.get(deviceId);
+        ConnectedDevice connectedDevice = mConnectedDevices.get(deviceId);
         if (connectedDevice == null) {
             logw(TAG, "Received message from unknown device " + deviceId + "or to unknown "
                     + "recipient " + message.getRecipient() + ".");
@@ -681,8 +671,7 @@
         }
 
         if (mMessageDeliveryDelegate != null
-                && !mMessageDeliveryDelegate.shouldDeliverMessageForDevice(
-                        connectedDevice.mConnectedDevice)) {
+                && !mMessageDeliveryDelegate.shouldDeliverMessageForDevice(connectedDevice)) {
             logw(TAG, "The message delegate has rejected this message. It will not be "
                     + "delivered to the intended recipient.");
             return;
@@ -703,39 +692,36 @@
         }
 
         recipientCallbacks.invoke(
-                callback -> callback.onMessageReceived(connectedDevice.mConnectedDevice,
-                        message.getMessage()));
+                callback -> callback.onMessageReceived(connectedDevice, message.getMessage()));
     }
 
     @VisibleForTesting
     void deviceErrorOccurred(@NonNull String deviceId) {
-        InternalConnectedDevice connectedDevice = mConnectedDevices.get(deviceId);
+        ConnectedDevice connectedDevice = mConnectedDevices.get(deviceId);
         if (connectedDevice == null) {
             logw(TAG, "Failed to establish secure channel on unknown device " + deviceId + ".");
             return;
         }
 
-        notifyAllDeviceCallbacks(deviceId,
-                callback -> callback.onDeviceError(connectedDevice.mConnectedDevice,
+        notifyAllDeviceCallbacks(deviceId, callback -> callback.onDeviceError(connectedDevice,
                         DEVICE_ERROR_INVALID_SECURITY_KEY));
     }
 
     @VisibleForTesting
     void onAssociationCompleted(@NonNull String deviceId) {
-        InternalConnectedDevice connectedDevice =
-                getConnectedDeviceForManager(deviceId, mPeripheralManager);
+        ConnectedDevice connectedDevice = mConnectedDevices.get(deviceId);
         if (connectedDevice == null) {
             return;
         }
 
         // The previous device is now obsolete and should be replaced with a new one properly
         // reflecting the state of belonging to the active user and notify features.
-        if (connectedDevice.mConnectedDevice.isAssociatedWithActiveUser()) {
+        if (connectedDevice.isAssociatedWithActiveUser()) {
             // Device was already marked as belonging to active user. No need to reissue callbacks.
             return;
         }
-        removeConnectedDevice(deviceId, mPeripheralManager);
-        addConnectedDevice(deviceId, mPeripheralManager);
+        removeConnectedDevice(deviceId);
+        addConnectedDevice(deviceId);
     }
 
     @NonNull
@@ -743,17 +729,6 @@
         return mStorage.getActiveUserAssociatedDeviceIds();
     }
 
-    @Nullable
-    private InternalConnectedDevice getConnectedDeviceForManager(@NonNull String deviceId,
-            @NonNull CarBleManager bleManager) {
-        InternalConnectedDevice connectedDevice = mConnectedDevices.get(deviceId);
-        if (connectedDevice != null && connectedDevice.mCarBleManager == bleManager) {
-            return connectedDevice;
-        }
-
-        return null;
-    }
-
     private void invokeConnectionCallbacks(boolean belongsToActiveUser,
             @NonNull Consumer<ConnectionCallback> notification) {
         logd(TAG, "Notifying connection callbacks for device belonging to active user "
@@ -792,23 +767,24 @@
     }
 
     @NonNull
-    private CarBleManager.Callback generateCarBleCallback(@NonNull CarBleManager carBleManager) {
-        return new CarBleManager.Callback() {
+    private CarBluetoothManager.Callback generateCarBleCallback(
+            @NonNull CarBluetoothManager carBluetoothManager) {
+        return new CarBluetoothManager.Callback() {
             @Override
             public void onDeviceConnected(String deviceId) {
                 EventLog.onDeviceIdReceived();
-                addConnectedDevice(deviceId, carBleManager);
+                addConnectedDevice(deviceId);
             }
 
             @Override
             public void onDeviceDisconnected(String deviceId) {
-                removeConnectedDevice(deviceId, carBleManager);
+                removeConnectedDevice(deviceId);
             }
 
             @Override
             public void onSecureChannelEstablished(String deviceId) {
                 EventLog.onSecureChannelEstablished();
-                ConnectedDeviceManager.this.onSecureChannelEstablished(deviceId, carBleManager);
+                ConnectedDeviceManager.this.onSecureChannelEstablished(deviceId);
             }
 
             @Override
@@ -863,26 +839,25 @@
 
     private final AssociatedDeviceCallback mAssociatedDeviceCallback =
             new AssociatedDeviceCallback() {
-        @Override
-        public void onAssociatedDeviceAdded(
-                AssociatedDevice device) {
-            mDeviceAssociationCallbacks.invoke(callback ->
-                    callback.onAssociatedDeviceAdded(device));
-        }
+                @Override
+                public void onAssociatedDeviceAdded(AssociatedDevice device) {
+                    mDeviceAssociationCallbacks.invoke(callback ->
+                            callback.onAssociatedDeviceAdded(device));
+                }
 
-        @Override
-        public void onAssociatedDeviceRemoved(AssociatedDevice device) {
-            mDeviceAssociationCallbacks.invoke(callback ->
-                    callback.onAssociatedDeviceRemoved(device));
-            logd(TAG, "Successfully removed associated device " + device + ".");
-        }
+                @Override
+                public void onAssociatedDeviceRemoved(AssociatedDevice device) {
+                    mDeviceAssociationCallbacks.invoke(callback ->
+                            callback.onAssociatedDeviceRemoved(device));
+                    logd(TAG, "Successfully removed associated device " + device + ".");
+                }
 
-        @Override
-        public void onAssociatedDeviceUpdated(AssociatedDevice device) {
-            mDeviceAssociationCallbacks.invoke(callback ->
-                    callback.onAssociatedDeviceUpdated(device));
-        }
-    };
+                @Override
+                public void onAssociatedDeviceUpdated(AssociatedDevice device) {
+                    mDeviceAssociationCallbacks.invoke(callback ->
+                            callback.onAssociatedDeviceUpdated(device));
+                }
+            };
 
     /** Callback for triggered connection events from {@link ConnectedDeviceManager}. */
     public interface ConnectionCallback {
@@ -927,15 +902,4 @@
         /** Indicate whether a message should be delivered for the specified device. */
         boolean shouldDeliverMessageForDevice(@NonNull ConnectedDevice device);
     }
-
-    private static class InternalConnectedDevice {
-        private final ConnectedDevice mConnectedDevice;
-        private final CarBleManager mCarBleManager;
-
-        InternalConnectedDevice(@NonNull ConnectedDevice connectedDevice,
-                @NonNull CarBleManager carBleManager) {
-            mConnectedDevice = connectedDevice;
-            mCarBleManager = carBleManager;
-        }
-    }
 }
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
deleted file mode 100644
index 7ff3959..0000000
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/BleDeviceMessageStream.java
+++ /dev/null
@@ -1,407 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.connecteddevice.ble;
-
-import static com.android.car.connecteddevice.BleStreamProtos.BleOperationProto.OperationType;
-import static com.android.car.connecteddevice.BleStreamProtos.BlePacketProto.BlePacket;
-import static com.android.car.connecteddevice.BleStreamProtos.VersionExchangeProto.BleVersionExchange;
-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.BluetoothDevice;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.os.Handler;
-import android.os.Looper;
-
-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;
-import java.util.concurrent.atomic.AtomicLong;
-
-/** BLE message stream to a device. */
-class BleDeviceMessageStream {
-
-    private static final String TAG = "BleDeviceMessageStream";
-
-    // 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 = 2;
-
-    /*
-     * During bandwidth testing, it was discovered that allowing the stream to send as fast as it
-     * can blocked outgoing notifications from being received by the connected device. Adding a
-     * throttle to the outgoing messages alleviated this block and allowed both sides to
-     * send/receive in parallel successfully.
-     */
-    private static final long THROTTLE_DEFAULT_MS = 10L;
-    private static final long THROTTLE_WAIT_MS = 75L;
-
-    private final ArrayDeque<BlePacket> mPacketQueue = new ArrayDeque<>();
-
-    private final Map<Integer, ByteArrayOutputStream> mPendingData = new HashMap<>();
-
-    // messageId -> nextExpectedPacketNumber
-    private final Map<Integer, Integer> mPendingPacketNumber = new HashMap<>();
-
-    private final MessageIdGenerator mMessageIdGenerator = new MessageIdGenerator();
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-
-    private final AtomicBoolean mIsVersionExchanged = new AtomicBoolean(false);
-
-    private final AtomicBoolean mIsSendingInProgress = new AtomicBoolean(false);
-
-    private final AtomicLong mThrottleDelay = new AtomicLong(THROTTLE_DEFAULT_MS);
-
-    private final BlePeripheralManager mBlePeripheralManager;
-
-    private final BluetoothDevice mDevice;
-
-    private final BluetoothGattCharacteristic mWriteCharacteristic;
-
-    private final BluetoothGattCharacteristic mReadCharacteristic;
-
-    private MessageReceivedListener mMessageReceivedListener;
-
-    private MessageReceivedErrorListener mMessageReceivedErrorListener;
-
-    private int mMaxWriteSize;
-
-    BleDeviceMessageStream(@NonNull BlePeripheralManager blePeripheralManager,
-            @NonNull BluetoothDevice device,
-            @NonNull BluetoothGattCharacteristic writeCharacteristic,
-            @NonNull BluetoothGattCharacteristic readCharacteristic,
-            int defaultMaxWriteSize) {
-        mBlePeripheralManager = blePeripheralManager;
-        mDevice = device;
-        mWriteCharacteristic = writeCharacteristic;
-        mReadCharacteristic = readCharacteristic;
-        mBlePeripheralManager.addOnCharacteristicWriteListener(this::onCharacteristicWrite);
-        mBlePeripheralManager.addOnCharacteristicReadListener(this::onCharacteristicRead);
-        mMaxWriteSize = defaultMaxWriteSize;
-    }
-
-    /**
-     * Writes the given message to the write characteristic of this stream with operation type
-     * {@code CLIENT_MESSAGE}.
-     *
-     * This method will handle the chunking of messages based on the max write size.
-     *
-     * @param deviceMessage The data object contains recipient, isPayloadEncrypted and message.
-     */
-    void writeMessage(@NonNull DeviceMessage deviceMessage) {
-        writeMessage(deviceMessage, OperationType.CLIENT_MESSAGE);
-    }
-
-    /**
-     * Writes the given message to the write characteristic of this stream.
-     *
-     * This method will handle the chunking of messages based on the max write size. If it is
-     * a handshake message, the message recipient should be {@code null} and it cannot be
-     * encrypted.
-     *
-     * @param deviceMessage The data object contains recipient, isPayloadEncrypted and message.
-     * @param operationType The {@link OperationType} of this message.
-     */
-    void writeMessage(@NonNull DeviceMessage deviceMessage, OperationType operationType) {
-        logd(TAG, "Writing message with " + deviceMessage.getMessage().length + " bytes to device: "
-                + mDevice.getAddress() + ".");
-        BleDeviceMessage.Builder builder = BleDeviceMessage.newBuilder()
-                .setOperation(operationType)
-                .setIsPayloadEncrypted(deviceMessage.isMessageEncrypted())
-                .setPayload(ByteString.copyFrom(deviceMessage.getMessage()));
-
-        UUID recipient = deviceMessage.getRecipient();
-        if (recipient != null) {
-            builder.setRecipient(ByteString.copyFrom(ByteUtils.uuidToBytes(recipient)));
-        }
-
-        BleDeviceMessage bleDeviceMessage = builder.build();
-        byte[] rawBytes = bleDeviceMessage.toByteArray();
-        List<BlePacket> blePackets;
-        try {
-            blePackets = BlePacketFactory.makeBlePackets(rawBytes, mMessageIdGenerator.next(),
-                    mMaxWriteSize);
-        } catch (BlePacketFactoryException e) {
-            loge(TAG, "Error while creating message packets.", e);
-            return;
-        }
-        mPacketQueue.addAll(blePackets);
-        writeNextMessageInQueue();
-    }
-
-    private void writeNextMessageInQueue() {
-        mHandler.postDelayed(() -> {
-            if (mPacketQueue.isEmpty()) {
-                logd(TAG, "No more packets to send.");
-                return;
-            }
-            if (mIsSendingInProgress.get()) {
-                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);
-        }, mThrottleDelay.get());
-    }
-
-    private void onCharacteristicRead(@NonNull BluetoothDevice device) {
-        if (!mDevice.equals(device)) {
-            logw(TAG, "Received a read notification from a device (" + device.getAddress()
-                    + ") that is not the expected device (" + mDevice.getAddress() + ") registered "
-                    + "to this stream. Ignoring.");
-            return;
-        }
-
-        logd(TAG, "Releasing lock on characteristic.");
-        mIsSendingInProgress.set(false);
-        writeNextMessageInQueue();
-    }
-
-    private void onCharacteristicWrite(@NonNull BluetoothDevice device,
-            @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) {
-        logd(TAG, "Received a message from a device (" + device.getAddress() + ").");
-        if (!mDevice.equals(device)) {
-            logw(TAG, "Received a message from a device (" + device.getAddress() + ") that is not "
-                    + "the expected device (" + mDevice.getAddress() + ") registered to this "
-                    + "stream. Ignoring.");
-            return;
-        }
-
-        if (!characteristic.getUuid().equals(mReadCharacteristic.getUuid())) {
-            logw(TAG, "Received a write to a characteristic (" + characteristic.getUuid() + ") that"
-                    + " is not the expected UUID (" + mReadCharacteristic.getUuid() + "). "
-                    + "Ignoring.");
-            return;
-        }
-
-        if (!mIsVersionExchanged.get()) {
-            processVersionExchange(device, value);
-            return;
-        }
-
-        BlePacket packet;
-        try {
-            packet = BlePacket.parseFrom(value);
-        } catch (InvalidProtocolBufferException e) {
-            loge(TAG, "Can not parse Ble packet from client.", e);
-            if (mMessageReceivedErrorListener != null) {
-                mMessageReceivedErrorListener.onMessageReceivedError(e);
-            }
-            return;
-        }
-        processPacket(packet);
-    }
-
-    private void processVersionExchange(@NonNull BluetoothDevice device, @NonNull byte[] value) {
-        BleVersionExchange versionExchange;
-        try {
-            versionExchange = BleVersionExchange.parseFrom(value);
-        } catch (InvalidProtocolBufferException e) {
-            loge(TAG, "Could not parse version exchange message", e);
-            if (mMessageReceivedErrorListener != null) {
-                mMessageReceivedErrorListener.onMessageReceivedError(e);
-            }
-            return;
-        }
-        int minMessagingVersion = versionExchange.getMinSupportedMessagingVersion();
-        int maxMessagingVersion = versionExchange.getMaxSupportedMessagingVersion();
-        int minSecurityVersion = versionExchange.getMinSupportedSecurityVersion();
-        int maxSecurityVersion = versionExchange.getMaxSupportedSecurityVersion();
-        if (minMessagingVersion > MESSAGING_VERSION || maxMessagingVersion < MESSAGING_VERSION
-                || minSecurityVersion > SECURITY_VERSION || maxSecurityVersion < SECURITY_VERSION) {
-            loge(TAG, "Unsupported message version for min " + minMessagingVersion + " and max "
-                    + maxMessagingVersion + " or security version for " + minSecurityVersion
-                    + " and max " + maxSecurityVersion + ".");
-            if (mMessageReceivedErrorListener != null) {
-                mMessageReceivedErrorListener.onMessageReceivedError(
-                        new IllegalStateException("Unsupported version."));
-            }
-            return;
-        }
-
-        BleVersionExchange headunitVersion = BleVersionExchange.newBuilder()
-                .setMinSupportedMessagingVersion(MESSAGING_VERSION)
-                .setMaxSupportedMessagingVersion(MESSAGING_VERSION)
-                .setMinSupportedSecurityVersion(SECURITY_VERSION)
-                .setMaxSupportedSecurityVersion(SECURITY_VERSION)
-                .build();
-        mWriteCharacteristic.setValue(headunitVersion.toByteArray());
-        mBlePeripheralManager.notifyCharacteristicChanged(device, mWriteCharacteristic,
-                /* confirm= */ false);
-        mIsVersionExchanged.set(true);
-        logd(TAG, "Sent supported version to the phone.");
-    }
-
-    @VisibleForTesting
-    void processPacket(@NonNull BlePacket packet) {
-        // Messages are coming in. Need to throttle outgoing messages to allow outgoing
-        // notifications to make it to the device.
-        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);
-
-        byte[] payload = packet.getPayload().toByteArray();
-        try {
-            currentPayloadStream.write(payload);
-        } catch (IOException e) {
-            loge(TAG, "Error writing packet to stream.", e);
-            if (mMessageReceivedErrorListener != null) {
-                mMessageReceivedErrorListener.onMessageReceivedError(e);
-            }
-            return;
-        }
-        logd(TAG, "Parsed packet " + packet.getPacketNumber() + " of "
-                + packet.getTotalPackets() + " for message " + messageId + ". Writing "
-                + payload.length + ".");
-
-        if (packet.getPacketNumber() != packet.getTotalPackets()) {
-            return;
-        }
-
-        byte[] messageBytes = currentPayloadStream.toByteArray();
-        mPendingData.remove(messageId);
-
-        // All message packets received. Resetting throttle back to default until next message
-        // started.
-        mThrottleDelay.set(THROTTLE_DEFAULT_MS);
-
-        logd(TAG, "Received complete device message " + messageId + " of " + messageBytes.length
-                + " bytes.");
-        BleDeviceMessage message;
-        try {
-            message = BleDeviceMessage.parseFrom(messageBytes);
-        } catch (InvalidProtocolBufferException e) {
-            loge(TAG, "Cannot parse device message from client.", e);
-            if (mMessageReceivedErrorListener != null) {
-                mMessageReceivedErrorListener.onMessageReceivedError(e);
-            }
-            return;
-        }
-
-        DeviceMessage deviceMessage = new DeviceMessage(
-                ByteUtils.bytesToUUID(message.getRecipient().toByteArray()),
-                message.getIsPayloadEncrypted(), message.getPayload().toByteArray());
-        if (mMessageReceivedListener != null) {
-            mMessageReceivedListener.onMessageReceived(deviceMessage, message.getOperation());
-        }
-    }
-
-    /** The maximum amount of bytes that can be written over BLE. */
-    void setMaxWriteSize(int maxWriteSize) {
-        if (maxWriteSize <= 0) {
-            return;
-        }
-        mMaxWriteSize = maxWriteSize;
-    }
-
-    /**
-     * Set the given listener to be notified when a new message was received from the
-     * client. If listener is {@code null}, clear.
-     */
-    void setMessageReceivedListener(@Nullable MessageReceivedListener listener) {
-        mMessageReceivedListener = listener;
-    }
-
-    /**
-     * Set the given listener to be notified when there was an error during receiving
-     * message from the client. If listener is {@code null}, clear.
-     */
-    void setMessageReceivedErrorListener(
-            @Nullable MessageReceivedErrorListener listener) {
-        mMessageReceivedErrorListener = listener;
-    }
-
-    /**
-     * Listener to be invoked when a complete message is received from the client.
-     */
-    interface MessageReceivedListener {
-
-        /**
-         * Called when a complete message is received from the client.
-         *
-         * @param deviceMessage The message received from the client.
-         * @param operationType The {@link OperationType} of the received message.
-         */
-        void onMessageReceived(@NonNull DeviceMessage deviceMessage, OperationType operationType);
-    }
-
-    /**
-     * Listener to be invoked when there was an error during receiving message from the client.
-     */
-    interface MessageReceivedErrorListener {
-        /**
-         * Called when there was an error during receiving message from the client.
-         *
-         * @param exception The error.
-         */
-        void onMessageReceivedError(@NonNull Exception exception);
-    }
-
-    /** A generator of unique IDs for messages. */
-    private static class MessageIdGenerator {
-        private final AtomicInteger mMessageId = new AtomicInteger(0);
-
-        int next() {
-            int current = mMessageId.getAndIncrement();
-            mMessageId.compareAndSet(Integer.MAX_VALUE, 0);
-            return current;
-        }
-    }
-}
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
deleted file mode 100644
index 2c32beb..0000000
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleManager.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.connecteddevice.ble;
-
-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 com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
-import com.android.car.connecteddevice.util.ThreadSafeCallbacks;
-
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.concurrent.Executor;
-
-/**
- * Generic BLE manager for a car that keeps track of connected devices and their associated
- * callbacks.
- */
-public abstract class CarBleManager {
-
-    private static final String TAG = "CarBleManager";
-
-    final ConnectedDeviceStorage mStorage;
-
-    final CopyOnWriteArraySet<BleDevice> mConnectedDevices = new CopyOnWriteArraySet<>();
-
-    final ThreadSafeCallbacks<Callback> mCallbacks = new ThreadSafeCallbacks<>();
-
-    protected CarBleManager(@NonNull ConnectedDeviceStorage connectedDeviceStorage) {
-        mStorage = connectedDeviceStorage;
-    }
-
-    /**
-     * Initialize and start the manager.
-     */
-    public void start() {
-    }
-
-    /**
-     * Stop the manager and clean up.
-     */
-    public void stop() {
-        for (BleDevice device : mConnectedDevices) {
-            if (device.mGatt != null) {
-                device.mGatt.close();
-            }
-        }
-        mConnectedDevices.clear();
-    }
-
-    /**
-     * Register a {@link Callback} to be notified on the {@link Executor}.
-     */
-    public void registerCallback(@NonNull Callback callback, @NonNull Executor executor) {
-        mCallbacks.add(callback, executor);
-    }
-
-    /**
-     * Unregister a callback.
-     *
-     * @param callback The {@link Callback} to unregister.
-     */
-    public void unregisterCallback(@NonNull Callback callback) {
-        mCallbacks.remove(callback);
-    }
-
-    /**
-     * Send a message to a connected device.
-     *
-     * @param deviceId Id of connected device.
-     * @param message  {@link DeviceMessage} to send.
-     */
-    public void sendMessage(@NonNull String deviceId, @NonNull DeviceMessage message) {
-        BleDevice device = getConnectedDevice(deviceId);
-        if (device == null) {
-            logw(TAG, "Attempted to send message to unknown device $deviceId. Ignored.");
-            return;
-        }
-
-        sendMessage(device, message);
-    }
-
-    /**
-     * Send a message to a connected device.
-     *
-     * @param device  The connected {@link BleDevice}.
-     * @param message {@link DeviceMessage} to send.
-     */
-    public void sendMessage(@NonNull BleDevice device, @NonNull DeviceMessage message) {
-        String deviceId = device.mDeviceId;
-        if (deviceId == null) {
-            deviceId = "Unidentified device";
-        }
-
-        logd(TAG, "Writing " + message.getMessage().length + " bytes to " + deviceId + ".");
-        device.mSecureChannel.sendClientMessage(message);
-    }
-
-    /**
-     * Get the {@link BleDevice} with matching {@link BluetoothGatt} if available. Returns
-     * {@code null} if no matches are found.
-     */
-    @Nullable
-    BleDevice getConnectedDevice(@NonNull BluetoothGatt gatt) {
-        for (BleDevice device : mConnectedDevices) {
-            if (device.mGatt == gatt) {
-                return device;
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Get the {@link BleDevice} with matching {@link BluetoothDevice} if available. Returns
-     * {@code null} if no matches are found.
-     */
-    @Nullable
-    BleDevice getConnectedDevice(@NonNull BluetoothDevice device) {
-        for (BleDevice connectedDevice : mConnectedDevices) {
-            if (device.equals(connectedDevice.mDevice)) {
-                return connectedDevice;
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Get the {@link BleDevice} with matching device id if available. Returns {@code null} if
-     * no matches are found.
-     */
-    @Nullable
-    BleDevice getConnectedDevice(@NonNull String deviceId) {
-        for (BleDevice device : mConnectedDevices) {
-            if (deviceId.equals(device.mDeviceId)) {
-                return device;
-            }
-        }
-
-        return null;
-    }
-
-    /** Add the {@link BleDevice} that has connected. */
-    void addConnectedDevice(@NonNull BleDevice device) {
-        mConnectedDevices.add(device);
-    }
-
-    /** Return the number of devices currently connected. */
-    int getConnectedDevicesCount() {
-        return mConnectedDevices.size();
-    }
-
-    /** Remove [@link BleDevice} that has been disconnected. */
-    void removeConnectedDevice(@NonNull BleDevice device) {
-        mConnectedDevices.remove(device);
-    }
-
-    /** Disconnect the provided device from this manager. */
-    public abstract void disconnectDevice(@NonNull String deviceId);
-
-    /** State for a connected device. */
-    enum BleDeviceState {
-        CONNECTING,
-        PENDING_VERIFICATION,
-        CONNECTED,
-        UNKNOWN
-    }
-
-    /**
-     * Container class to hold information about a connected device.
-     */
-    static class BleDevice {
-
-        BluetoothDevice mDevice;
-        BluetoothGatt mGatt;
-        BleDeviceState mState;
-        String mDeviceId;
-        SecureBleChannel mSecureChannel;
-
-        BleDevice(@NonNull BluetoothDevice device, @Nullable BluetoothGatt gatt) {
-            mDevice = device;
-            mGatt = gatt;
-            mState = BleDeviceState.UNKNOWN;
-        }
-    }
-
-    /**
-     * Callback for triggered events from {@link CarBleManager}.
-     */
-    public interface Callback {
-        /**
-         * Triggered when device is connected and device id retrieved. Device is now ready to
-         * receive messages.
-         *
-         * @param deviceId Id of device that has connected.
-         */
-        void onDeviceConnected(@NonNull String deviceId);
-
-        /**
-         * Triggered when device is disconnected.
-         *
-         * @param deviceId Id of device that has disconnected.
-         */
-        void onDeviceDisconnected(@NonNull String deviceId);
-
-        /**
-         * Triggered when device has established encryption for secure communication.
-         *
-         * @param deviceId Id of device that has established encryption.
-         */
-        void onSecureChannelEstablished(@NonNull String deviceId);
-
-        /**
-         * Triggered when a new message is received.
-         *
-         * @param deviceId Id of the device that sent the message.
-         * @param message  {@link DeviceMessage} received.
-         */
-        void onMessageReceived(@NonNull String deviceId, @NonNull DeviceMessage message);
-
-        /**
-         * Triggered when an error when establishing the secure channel.
-         *
-         * @param deviceId Id of the device that experienced the error.
-         */
-        void onSecureChannelError(@NonNull String deviceId);
-    }
-}
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
deleted file mode 100644
index 3fe8e3b..0000000
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBlePeripheralManager.java
+++ /dev/null
@@ -1,670 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.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;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattService;
-import android.bluetooth.le.AdvertiseCallback;
-import android.bluetooth.le.AdvertiseData;
-import android.bluetooth.le.AdvertiseSettings;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ParcelUuid;
-
-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.
- */
-public class CarBlePeripheralManager extends CarBleManager {
-
-    private static final String TAG = "CarBlePeripheralManager";
-
-    // Attribute protocol bytes attached to message. Available write size is MTU size minus att
-    // 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 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 final Duration mMaxReconnectAdvertisementDuration;
-
-    private final int mDefaultMtuSize;
-
-    private String mOriginalBluetoothName;
-
-    private String mClientDeviceName;
-
-    private String mClientDeviceAddress;
-
-    private String mReconnectDeviceId;
-
-    private byte[] mReconnectChallenge;
-
-    private AssociationCallback mAssociationCallback;
-
-    private AdvertiseCallback mAdvertiseCallback;
-
-    /**
-     * 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 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 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 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,
-                BluetoothGattCharacteristic.PROPERTY_READ);
-        mReadCharacteristic = new BluetoothGattCharacteristic(readCharacteristicUuid,
-                BluetoothGattCharacteristic.PROPERTY_WRITE
-                        | 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);
-    }
-
-    @Override
-    public void stop() {
-        super.stop();
-        reset();
-    }
-
-    @Override
-    public void disconnectDevice(@NonNull String deviceId) {
-        BleDevice connectedDevice = getConnectedDevice();
-        if (connectedDevice == null || !deviceId.equals(connectedDevice.mDeviceId)) {
-            return;
-        }
-        reset();
-    }
-
-    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. */
-    public void connectToDevice(@NonNull UUID deviceId) {
-        for (BleDevice device : mConnectedDevices) {
-            if (UUID.fromString(device.mDeviceId).equals(deviceId)) {
-                logd(TAG, "Already connected to device " + deviceId + ".");
-                // Already connected to this device. Ignore requests to connect again.
-                return;
-            }
-        }
-
-        // 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,
-                        mMaxReconnectAdvertisementDuration.toMillis());
-                logd(TAG, "Successfully started advertising for device " + deviceId + ".");
-            }
-        };
-        mBlePeripheralManager.unregisterCallback(mAssociationPeripheralCallback);
-        mBlePeripheralManager.registerCallback(mReconnectPeripheralCallback);
-        mTimeoutHandler.removeCallbacks(mTimeoutRunnable);
-        byte[] advertiseData = createReconnectData(mReconnectDeviceId);
-        if (advertiseData == null) {
-            loge(TAG, "Unable to create advertisement data. Aborting reconnect.");
-            return;
-        }
-        startAdvertising(mReconnectServiceUuid, mAdvertiseCallback, /* includeDeviceName= */ false,
-                advertiseData, mReconnectDataUuid);
-    }
-
-    /**
-     * 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
-    private BleDevice getConnectedDevice() {
-        if (mConnectedDevices.isEmpty()) {
-            return null;
-        }
-        return mConnectedDevices.iterator().next();
-    }
-
-    /** Start the association with a new device */
-    public void startAssociation(@NonNull String nameForAssociation,
-            @NonNull AssociationCallback callback) {
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        if (adapter == null) {
-            loge(TAG, "Bluetooth is unavailable on this device. Unable to start associating.");
-            return;
-        }
-
-        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() {
-            @Override
-            public void onStartSuccess(AdvertiseSettings settingsInEffect) {
-                super.onStartSuccess(settingsInEffect);
-                callback.onAssociationStartSuccess(nameForAssociation);
-                logd(TAG, "Successfully started advertising for association.");
-            }
-
-            @Override
-            public void onStartFailure(int errorCode) {
-                super.onStartFailure(errorCode);
-                callback.onAssociationStartFailure();
-                logd(TAG, "Failed to start advertising for association. Error code: " + errorCode);
-            }
-        };
-        attemptAssociationAdvertising(nameForAssociation, callback);
-    }
-
-    /** Stop the association with any device. */
-    public void stopAssociation(@NonNull AssociationCallback callback) {
-        if (!isAssociating() || callback != mAssociationCallback) {
-            return;
-        }
-        reset();
-    }
-
-    private void attemptAssociationAdvertising(@NonNull String adapterName,
-            @NonNull AssociationCallback callback) {
-        if (mOriginalBluetoothName != null
-                && adapterName.equals(BluetoothAdapter.getDefaultAdapter().getName())) {
-            startAdvertising(mAssociationServiceUuid, mAdvertiseCallback,
-                    /* includeDeviceName= */ true, /* serviceData= */ null,
-                    /* serviceDataUuid= */ null);
-            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[] serviceData,
-            @Nullable UUID serviceDataUuid) {
-        BluetoothGattService gattService = new BluetoothGattService(serviceUuid,
-                BluetoothGattService.SERVICE_TYPE_PRIMARY);
-        gattService.addCharacteristic(mWriteCharacteristic);
-        gattService.addCharacteristic(mReadCharacteristic);
-
-        AdvertiseData.Builder builder = new AdvertiseData.Builder()
-                .setIncludeDeviceName(includeDeviceName);
-        ParcelUuid uuid = new ParcelUuid(serviceUuid);
-        builder.addServiceUuid(uuid);
-        if (serviceData != null) {
-            ParcelUuid dataUuid = uuid;
-            if (serviceDataUuid != null) {
-                dataUuid = new ParcelUuid(serviceDataUuid);
-            }
-            builder.addServiceData(dataUuid, serviceData);
-        }
-
-        mBlePeripheralManager.startAdvertising(gattService, builder.build(), callback);
-    }
-
-    /** Notify that the user has accepted a pairing code or other out-of-band confirmation. */
-    public void notifyOutOfBandAccepted() {
-        if (getConnectedDevice() == null) {
-            disconnectWithError("Null connected device found when out-of-band confirmation "
-                    + "received.");
-            return;
-        }
-
-        AssociationSecureChannel secureChannel =
-                (AssociationSecureChannel) getConnectedDevice().mSecureChannel;
-        if (secureChannel == null) {
-            disconnectWithError("Null SecureBleChannel found for the current connected device "
-                    + "when out-of-band confirmation received.");
-            return;
-        }
-
-        secureChannel.notifyOutOfBandAccepted();
-    }
-
-    @VisibleForTesting
-    @Nullable
-    SecureBleChannel getConnectedDeviceChannel() {
-        BleDevice connectedDevice = getConnectedDevice();
-        if (connectedDevice == null) {
-            return null;
-        }
-
-        return connectedDevice.mSecureChannel;
-    }
-
-    private void setDeviceId(@NonNull String deviceId) {
-        logd(TAG, "Setting device id: " + deviceId);
-        BleDevice connectedDevice = getConnectedDevice();
-        if (connectedDevice == null) {
-            disconnectWithError("Null connected device found when device id received.");
-            return;
-        }
-
-        connectedDevice.mDeviceId = deviceId;
-        mCallbacks.invoke(callback -> callback.onDeviceConnected(deviceId));
-    }
-
-    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);
-        mTimeoutHandler.removeCallbacks(mTimeoutRunnable);
-        mClientDeviceAddress = device.getAddress();
-        mClientDeviceName = device.getName();
-        if (mClientDeviceName == null) {
-            logd(TAG, "Device connected, but name is null; issuing request to retrieve device "
-                    + "name.");
-            mBlePeripheralManager.retrieveDeviceName(device);
-        }
-
-        BleDeviceMessageStream secureStream = new BleDeviceMessageStream(mBlePeripheralManager,
-                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.mSecureChannel = secureChannel;
-        addConnectedDevice(bleDevice);
-        if (isReconnect) {
-            setDeviceId(mReconnectDeviceId);
-            mReconnectDeviceId = null;
-            mReconnectChallenge = null;
-        }
-    }
-
-    private void setMtuSize(int mtuSize) {
-        BleDevice connectedDevice = getConnectedDevice();
-        if (connectedDevice != null
-                && connectedDevice.mSecureChannel != null
-                && connectedDevice.mSecureChannel.getStream() != null) {
-            connectedDevice.mSecureChannel.getStream()
-                    .setMaxWriteSize(mtuSize - ATT_PROTOCOL_BYTES);
-        }
-    }
-
-    private boolean isAssociating() {
-        return mAssociationCallback != null;
-    }
-
-    private final BlePeripheralManager.Callback mReconnectPeripheralCallback =
-            new BlePeripheralManager.Callback() {
-
-                @Override
-                public void onDeviceNameRetrieved(String deviceName) {
-                    // Ignored.
-                }
-
-                @Override
-                public void onMtuSizeChanged(int size) {
-                    setMtuSize(size);
-                }
-
-                @Override
-                public void onRemoteDeviceConnected(BluetoothDevice device) {
-                    addConnectedDevice(device, /* isReconnect= */ true);
-                }
-
-                @Override
-                public void onRemoteDeviceDisconnected(BluetoothDevice device) {
-                    String deviceId = mReconnectDeviceId;
-                    BleDevice connectedDevice = getConnectedDevice(device);
-                    // Reset before invoking callbacks to avoid a race condition with reconnect
-                    // logic.
-                    reset();
-                    if (connectedDevice != null) {
-                        deviceId = connectedDevice.mDeviceId;
-                    }
-                    final String finalDeviceId = deviceId;
-                    if (finalDeviceId != null) {
-                        logd(TAG, "Connected device " + finalDeviceId + " disconnected.");
-                        mCallbacks.invoke(callback -> callback.onDeviceDisconnected(finalDeviceId));
-                    }
-                }
-            };
-
-    private final BlePeripheralManager.Callback mAssociationPeripheralCallback =
-            new BlePeripheralManager.Callback() {
-                @Override
-                public void onDeviceNameRetrieved(String deviceName) {
-                    if (deviceName == null) {
-                        return;
-                    }
-                    mClientDeviceName = deviceName;
-                    BleDevice connectedDevice = getConnectedDevice();
-                    if (connectedDevice == null || connectedDevice.mDeviceId == null) {
-                        return;
-                    }
-                    mStorage.updateAssociatedDeviceName(connectedDevice.mDeviceId, deviceName);
-                }
-
-                @Override
-                public void onMtuSizeChanged(int size) {
-                    setMtuSize(size);
-                }
-
-                @Override
-                public void onRemoteDeviceConnected(BluetoothDevice device) {
-                    resetBluetoothAdapterName();
-                    addConnectedDevice(device, /* isReconnect= */ false);
-                    BleDevice connectedDevice = getConnectedDevice();
-                    if (connectedDevice == null || connectedDevice.mSecureChannel == null) {
-                        return;
-                    }
-                    ((AssociationSecureChannel) connectedDevice.mSecureChannel)
-                            .setShowVerificationCodeListener(
-                                    code -> {
-                                        if (!isAssociating()) {
-                                            loge(TAG, "No valid callback for association.");
-                                            return;
-                                        }
-                                        mAssociationCallback.onVerificationCodeAvailable(code);
-                                    });
-                }
-
-                @Override
-                public void onRemoteDeviceDisconnected(BluetoothDevice device) {
-                    BleDevice connectedDevice = getConnectedDevice(device);
-                    if (isAssociating()) {
-                        mAssociationCallback.onAssociationError(
-                                DEVICE_ERROR_UNEXPECTED_DISCONNECTION);
-                    }
-                    // 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));
-                    }
-                }
-            };
-
-    private final SecureBleChannel.Callback mSecureChannelCallback =
-            new SecureBleChannel.Callback() {
-                @Override
-                public void onSecureChannelEstablished() {
-                    BleDevice connectedDevice = getConnectedDevice();
-                    if (connectedDevice == null || connectedDevice.mDeviceId == null) {
-                        disconnectWithError("Null device id found when secure channel "
-                                + "established.");
-                        return;
-                    }
-                    String deviceId = connectedDevice.mDeviceId;
-                    if (mClientDeviceAddress == null) {
-                        disconnectWithError("Null device address found when secure channel "
-                                + "established.");
-                        return;
-                    }
-                    if (isAssociating()) {
-                        logd(TAG, "Secure channel established for un-associated device. Saving "
-                                + "association of that device for current user.");
-                        mStorage.addAssociatedDeviceForActiveUser(
-                                new AssociatedDevice(deviceId, mClientDeviceAddress,
-                                        mClientDeviceName, /* isConnectionEnabled= */ true));
-                        if (mAssociationCallback != null) {
-                            mAssociationCallback.onAssociationCompleted(deviceId);
-                            mAssociationCallback = null;
-                        }
-                    }
-                    mCallbacks.invoke(callback -> callback.onSecureChannelEstablished(deviceId));
-                }
-
-                @Override
-                public void onEstablishSecureChannelFailure(int error) {
-                    BleDevice connectedDevice = getConnectedDevice();
-                    if (connectedDevice == null || connectedDevice.mDeviceId == null) {
-                        disconnectWithError("Null device id found when secure channel failed to "
-                                + "establish.");
-                        return;
-                    }
-                    String deviceId = connectedDevice.mDeviceId;
-                    mCallbacks.invoke(callback -> callback.onSecureChannelError(deviceId));
-
-                    if (isAssociating()) {
-                        mAssociationCallback.onAssociationError(error);
-                    }
-
-                    disconnectWithError("Error while establishing secure connection.");
-                }
-
-                @Override
-                public void onMessageReceived(DeviceMessage deviceMessage) {
-                    BleDevice connectedDevice = getConnectedDevice();
-                    if (connectedDevice == null || connectedDevice.mDeviceId == null) {
-                        disconnectWithError("Null device id found when message received.");
-                        return;
-                    }
-
-                    logd(TAG, "Received new message from " + connectedDevice.mDeviceId
-                            + " with " + deviceMessage.getMessage().length + " bytes in its "
-                            + "payload. Notifying " + mCallbacks.size() + " callbacks.");
-                    mCallbacks.invoke(
-                            callback -> callback.onMessageReceived(connectedDevice.mDeviceId,
-                                    deviceMessage));
-                }
-
-                @Override
-                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) {
-                    setDeviceId(deviceId);
-                }
-            };
-
-    private final Runnable mTimeoutRunnable = new Runnable() {
-        @Override
-        public void run() {
-            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/AssociationSecureChannel.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/AssociationSecureChannel.java
similarity index 86%
rename from connected-device-lib/src/com/android/car/connecteddevice/ble/AssociationSecureChannel.java
rename to connected-device-lib/src/com/android/car/connecteddevice/connection/AssociationSecureChannel.java
index 349af9e..65a05da 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/AssociationSecureChannel.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/AssociationSecureChannel.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection;
 
 import static android.car.encryptionrunner.EncryptionRunnerFactory.EncryptionRunnerType;
 import static android.car.encryptionrunner.EncryptionRunnerFactory.newRunner;
@@ -23,16 +23,17 @@
 import static com.android.car.connecteddevice.util.SafeLog.logd;
 import static com.android.car.connecteddevice.util.SafeLog.loge;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 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 com.android.internal.annotations.VisibleForTesting;
 
 import java.security.InvalidParameterException;
 import java.util.Arrays;
@@ -41,7 +42,7 @@
 /**
  * A secure channel established with the association flow.
  */
-class AssociationSecureChannel extends SecureBleChannel {
+public class AssociationSecureChannel extends SecureChannel {
 
     private static final String TAG = "AssociationSecureChannel";
 
@@ -58,11 +59,11 @@
 
     private String mDeviceId;
 
-    AssociationSecureChannel(BleDeviceMessageStream stream, ConnectedDeviceStorage storage) {
+    public AssociationSecureChannel(DeviceMessageStream stream, ConnectedDeviceStorage storage) {
         this(stream, storage, newRunner(EncryptionRunnerType.UKEY2));
     }
 
-    AssociationSecureChannel(BleDeviceMessageStream stream, ConnectedDeviceStorage storage,
+    AssociationSecureChannel(DeviceMessageStream stream, ConnectedDeviceStorage storage,
             EncryptionRunner encryptionRunner) {
         super(stream, encryptionRunner);
         encryptionRunner.setIsReconnect(false);
@@ -98,7 +99,6 @@
         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 + ".");
@@ -112,10 +112,15 @@
             notifySecureChannelFailure(CHANNEL_ERROR_INVALID_VERIFICATION);
             return;
         }
+        processVerificationCode(code);
+    }
 
+    private void processVerificationCode(@NonNull String code) {
         if (mShowVerificationCodeListener == null) {
-            loge(TAG, "No verification code listener has been set. Unable to display verification "
-                    + "code to user.");
+            loge(TAG,
+                    "No verification code listener has been set. Unable to display "
+                            + "verification "
+                            + "code to user.");
             notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE);
             return;
         }
@@ -149,13 +154,13 @@
     }
 
     /** Set the listener that notifies to show verification code. {@code null} to clear. */
-    void setShowVerificationCodeListener(@Nullable ShowVerificationCodeListener listener) {
+    public void setShowVerificationCodeListener(@Nullable ShowVerificationCodeListener listener) {
         mShowVerificationCodeListener = listener;
     }
 
     @VisibleForTesting
     @Nullable
-    ShowVerificationCodeListener getShowVerificationCodeListener() {
+    public ShowVerificationCodeListener getShowVerificationCodeListener() {
         return mShowVerificationCodeListener;
     }
 
@@ -163,7 +168,7 @@
      * 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() {
+    public void notifyOutOfBandAccepted() {
         HandshakeMessage message;
         try {
             message = getEncryptionRunner().verifyPin();
@@ -200,8 +205,17 @@
         sendHandshakeMessage(ByteUtils.uuidToBytes(uniqueId), /* isEncrypted= */ true);
     }
 
+    @HandshakeState
+    int getState() {
+        return mState;
+    }
+
+    void setState(@HandshakeState int state) {
+        mState = state;
+    }
+
     /** Listener that will be invoked to display verification code. */
-    interface ShowVerificationCodeListener {
+    public interface ShowVerificationCodeListener {
         /**
          * Invoke when a verification need to be displayed during device association.
          *
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/connection/CarBluetoothManager.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/CarBluetoothManager.java
new file mode 100644
index 0000000..9ca699b
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/CarBluetoothManager.java
@@ -0,0 +1,485 @@
+/*
+ * 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.connection;
+
+import static com.android.car.connecteddevice.ConnectedDeviceManager.DEVICE_ERROR_INVALID_HANDSHAKE;
+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.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.car.connecteddevice.AssociationCallback;
+import com.android.car.connecteddevice.model.AssociatedDevice;
+import com.android.car.connecteddevice.oob.OobChannel;
+import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
+import com.android.car.connecteddevice.util.ThreadSafeCallbacks;
+
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.Executor;
+
+/**
+ * Generic BLE manager for a car that keeps track of connected devices and their associated
+ * callbacks.
+ */
+public abstract class CarBluetoothManager {
+
+    private static final String TAG = "CarBluetoothManager";
+
+    protected final ConnectedDeviceStorage mStorage;
+
+    protected final CopyOnWriteArraySet<ConnectedRemoteDevice> mConnectedDevices =
+            new CopyOnWriteArraySet<>();
+
+    protected final ThreadSafeCallbacks<Callback> mCallbacks = new ThreadSafeCallbacks<>();
+
+    private String mClientDeviceName;
+
+    private String mClientDeviceAddress;
+
+    protected CarBluetoothManager(@NonNull ConnectedDeviceStorage connectedDeviceStorage) {
+        mStorage = connectedDeviceStorage;
+    }
+
+    /** Attempt to connect to device with provided id. */
+    public void connectToDevice(@NonNull UUID deviceId) {
+        for (ConnectedRemoteDevice device : mConnectedDevices) {
+            if (UUID.fromString(device.mDeviceId).equals(deviceId)) {
+                logd(TAG, "Already connected to device " + deviceId + ".");
+                // Already connected to this device. Ignore requests to connect again.
+                return;
+            }
+        }
+        // Clear any previous session before starting a new one.
+        reset();
+        initiateConnectionToDevice(deviceId);
+    }
+
+    /** Start to connect to associated devices */
+    public abstract void initiateConnectionToDevice(@NonNull UUID deviceId);
+
+    /** Start the association with a new device */
+    public abstract void startAssociation(@NonNull String nameForAssociation,
+            @NonNull AssociationCallback callback);
+
+    /** Start the association with a new device using out of band verification code exchange */
+    public abstract void startOutOfBandAssociation(
+            @NonNull String nameForAssociation,
+            @NonNull OobChannel oobChannel,
+            @NonNull AssociationCallback callback);
+
+    /** Disconnect the provided device from this manager. */
+    public abstract void disconnectDevice(@NonNull String deviceId);
+
+    /** Get current {@link AssociationCallback}. */
+    @Nullable
+    public abstract AssociationCallback getAssociationCallback();
+
+    /** Set current {@link AssociationCallback}. */
+    public abstract void setAssociationCallback(@Nullable AssociationCallback callback);
+
+    /** Set the value of the client device name */
+    public void setClientDeviceName(String deviceName) {
+        mClientDeviceName = deviceName;
+    }
+
+    /** Set the value of client device's mac address */
+    public void setClientDeviceAddress(String macAddress) {
+        mClientDeviceAddress = macAddress;
+    }
+
+    /**
+     * Initialize and start the manager.
+     */
+    @CallSuper
+    public void start() {}
+
+    /**
+     * Stop the manager and clean up.
+     */
+    public void stop() {
+        for (ConnectedRemoteDevice device : mConnectedDevices) {
+            if (device.mGatt != null) {
+                device.mGatt.close();
+            }
+        }
+        mConnectedDevices.clear();
+    }
+
+    /**
+     * Stop the association process with any device.
+     */
+    public void stopAssociation() {
+        if (!isAssociating()) {
+            return;
+        }
+        reset();
+    }
+
+    /**
+     * Register a {@link Callback} to be notified on the {@link Executor}.
+     */
+    public void registerCallback(@NonNull Callback callback, @NonNull Executor executor) {
+        mCallbacks.add(callback, executor);
+    }
+
+    /**
+     * Unregister a callback.
+     *
+     * @param callback The {@link Callback} to unregister.
+     */
+    public void unregisterCallback(@NonNull Callback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    /**
+     * Send a message to a connected device.
+     *
+     * @param deviceId Id of connected device.
+     * @param message  {@link DeviceMessage} to send.
+     */
+    public void sendMessage(@NonNull String deviceId, @NonNull DeviceMessage message) {
+        ConnectedRemoteDevice device = getConnectedDevice(deviceId);
+        if (device == null) {
+            logw(TAG, "Attempted to send message to unknown device $deviceId. Ignored.");
+            return;
+        }
+
+        sendMessage(device, message);
+    }
+
+    /**
+     * Send a message to a connected device.
+     *
+     * @param device  The connected {@link ConnectedRemoteDevice}.
+     * @param message {@link DeviceMessage} to send.
+     */
+    public void sendMessage(@NonNull ConnectedRemoteDevice device, @NonNull DeviceMessage message) {
+        String deviceId = device.mDeviceId;
+        if (deviceId == null) {
+            deviceId = "Unidentified device";
+        }
+
+        logd(TAG, "Writing " + message.getMessage().length + " bytes to " + deviceId + ".");
+        device.mSecureChannel.sendClientMessage(message);
+    }
+
+    /** Clean manager status and callbacks. */
+    @CallSuper
+    public void reset() {
+        mClientDeviceAddress = null;
+        mClientDeviceName = null;
+        mConnectedDevices.clear();
+    }
+
+    /** Notify that the user has accepted a pairing code or other out-of-band confirmation. */
+    public void notifyOutOfBandAccepted() {
+        if (getConnectedDevice() == null) {
+            disconnectWithError("Null connected device found when out-of-band confirmation "
+                    + "received.");
+            return;
+        }
+
+        AssociationSecureChannel secureChannel =
+                (AssociationSecureChannel) getConnectedDevice().mSecureChannel;
+        if (secureChannel == null) {
+            disconnectWithError("Null SecureBleChannel found for the current connected device "
+                    + "when out-of-band confirmation received.");
+            return;
+        }
+
+        secureChannel.notifyOutOfBandAccepted();
+    }
+
+    /** Returns the secure channel of current connected device. */
+    @Nullable
+    public SecureChannel getConnectedDeviceChannel() {
+        ConnectedRemoteDevice connectedDevice = getConnectedDevice();
+        if (connectedDevice == null) {
+            return null;
+        }
+
+        return connectedDevice.mSecureChannel;
+    }
+
+    /**
+     * Get the {@link ConnectedRemoteDevice} with matching {@link BluetoothGatt} if available.
+     * Returns
+     * {@code null} if no matches are found.
+     */
+    @Nullable
+    protected final ConnectedRemoteDevice getConnectedDevice(@NonNull BluetoothGatt gatt) {
+        for (ConnectedRemoteDevice device : mConnectedDevices) {
+            if (device.mGatt == gatt) {
+                return device;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the {@link ConnectedRemoteDevice} with matching {@link BluetoothDevice} if available.
+     * Returns
+     * {@code null} if no matches are found.
+     */
+    @Nullable
+    protected final ConnectedRemoteDevice getConnectedDevice(@NonNull BluetoothDevice device) {
+        for (ConnectedRemoteDevice connectedDevice : mConnectedDevices) {
+            if (device.equals(connectedDevice.mDevice)) {
+                return connectedDevice;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the {@link ConnectedRemoteDevice} with matching device id if available. Returns {@code
+     * null} if no matches are found.
+     */
+    @Nullable
+    protected final ConnectedRemoteDevice getConnectedDevice(@NonNull String deviceId) {
+        for (ConnectedRemoteDevice device : mConnectedDevices) {
+            if (deviceId.equals(device.mDeviceId)) {
+                return device;
+            }
+        }
+
+        return null;
+    }
+
+    /** Add the {@link ConnectedRemoteDevice} that has connected. */
+    protected final void addConnectedDevice(@NonNull ConnectedRemoteDevice device) {
+        mConnectedDevices.add(device);
+    }
+
+    /** Return the number of devices currently connected. */
+    protected final int getConnectedDevicesCount() {
+        return mConnectedDevices.size();
+    }
+
+    /** Remove [@link BleDevice} that has been disconnected. */
+    protected final void removeConnectedDevice(@NonNull ConnectedRemoteDevice device) {
+        mConnectedDevices.remove(device);
+    }
+
+    /** Return [@code true} if the manager is currently in an association process. */
+    protected final boolean isAssociating() {
+        return getAssociationCallback() != null;
+    }
+
+    /**
+     * Set the device id of {@link ConnectedRemoteDevice} and then notify device connected callback.
+     *
+     * @param deviceId The device id received from remote device.
+     */
+    protected final void setDeviceIdAndNotifyCallbacks(@NonNull String deviceId) {
+        logd(TAG, "Setting device id: " + deviceId);
+        ConnectedRemoteDevice connectedDevice = getConnectedDevice();
+        if (connectedDevice == null) {
+            disconnectWithError("Null connected device found when device id received.");
+            return;
+        }
+
+        connectedDevice.mDeviceId = deviceId;
+        mCallbacks.invoke(callback -> callback.onDeviceConnected(deviceId));
+    }
+
+    /** Log error which causes the disconnect with {@link Exception} and notify callbacks. */
+    protected final void disconnectWithError(@NonNull String errorMessage, @Nullable Exception e) {
+        loge(TAG, errorMessage, e);
+        if (isAssociating()) {
+            getAssociationCallback().onAssociationError(DEVICE_ERROR_INVALID_HANDSHAKE);
+        }
+        reset();
+    }
+
+    /** Log error which cause the disconnection and notify callbacks. */
+    protected final void disconnectWithError(@NonNull String errorMessage) {
+        disconnectWithError(errorMessage, null);
+    }
+
+    /** Return the current connected device. */
+    @Nullable
+    protected final ConnectedRemoteDevice getConnectedDevice() {
+        if (mConnectedDevices.isEmpty()) {
+            return null;
+        }
+        // Directly return the next because there will only be one device connected at one time.
+        return mConnectedDevices.iterator().next();
+    }
+
+    protected final SecureChannel.Callback mSecureChannelCallback =
+            new SecureChannel.Callback() {
+                @Override
+                public void onSecureChannelEstablished() {
+                    ConnectedRemoteDevice connectedDevice = getConnectedDevice();
+                    if (connectedDevice == null || connectedDevice.mDeviceId == null) {
+                        disconnectWithError("Null device id found when secure channel "
+                                + "established.");
+                        return;
+                    }
+                    String deviceId = connectedDevice.mDeviceId;
+                    if (mClientDeviceAddress == null) {
+                        disconnectWithError("Null device address found when secure channel "
+                                + "established.");
+                        return;
+                    }
+
+                    if (isAssociating()) {
+                        logd(TAG, "Secure channel established for un-associated device. Saving "
+                                + "association of that device for current user.");
+                        mStorage.addAssociatedDeviceForActiveUser(
+                                new AssociatedDevice(deviceId, mClientDeviceAddress,
+                                        mClientDeviceName, /* isConnectionEnabled= */ true));
+                        AssociationCallback callback = getAssociationCallback();
+                        if (callback != null) {
+                            callback.onAssociationCompleted(deviceId);
+                            setAssociationCallback(null);
+                        }
+                    }
+                    mCallbacks.invoke(callback -> callback.onSecureChannelEstablished(deviceId));
+                }
+
+                @Override
+                public void onEstablishSecureChannelFailure(int error) {
+                    ConnectedRemoteDevice connectedDevice = getConnectedDevice();
+                    if (connectedDevice == null || connectedDevice.mDeviceId == null) {
+                        disconnectWithError("Null device id found when secure channel "
+                                + "failed to establish.");
+                        return;
+                    }
+                    String deviceId = connectedDevice.mDeviceId;
+                    mCallbacks.invoke(callback -> callback.onSecureChannelError(deviceId));
+
+                    if (isAssociating()) {
+                        getAssociationCallback().onAssociationError(error);
+                    }
+
+                    disconnectWithError("Error while establishing secure connection.");
+                }
+
+                @Override
+                public void onMessageReceived(DeviceMessage deviceMessage) {
+                    ConnectedRemoteDevice connectedDevice = getConnectedDevice();
+                    if (connectedDevice == null || connectedDevice.mDeviceId == null) {
+                        disconnectWithError("Null device id found when message received.");
+                        return;
+                    }
+
+                    logd(TAG, "Received new message from " + connectedDevice.mDeviceId
+                            + " with " + deviceMessage.getMessage().length + " bytes in its "
+                            + "payload. Notifying " + mCallbacks.size() + " callbacks.");
+                    mCallbacks.invoke(
+                            callback -> callback.onMessageReceived(connectedDevice.mDeviceId,
+                                    deviceMessage));
+                }
+
+                @Override
+                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) {
+                    setDeviceIdAndNotifyCallbacks(deviceId);
+                }
+            };
+
+
+    /** State for a connected device. */
+    public enum ConnectedDeviceState {
+        CONNECTING,
+        PENDING_VERIFICATION,
+        CONNECTED,
+        UNKNOWN
+    }
+
+    /**
+     * Container class to hold information about a connected device.
+     */
+    public static class ConnectedRemoteDevice {
+        @NonNull
+        public BluetoothDevice mDevice;
+        @Nullable
+        public BluetoothGatt mGatt;
+        @NonNull
+        public ConnectedDeviceState mState;
+        @Nullable
+        public String mDeviceId;
+        @Nullable
+        public SecureChannel mSecureChannel;
+
+        public ConnectedRemoteDevice(@NonNull BluetoothDevice device,
+                @Nullable BluetoothGatt gatt) {
+            mDevice = device;
+            mGatt = gatt;
+            mState = ConnectedDeviceState.UNKNOWN;
+        }
+    }
+
+    /**
+     * Callback for triggered events from {@link CarBluetoothManager}.
+     */
+    public interface Callback {
+        /**
+         * Triggered when device is connected and device id retrieved. Device is now ready to
+         * receive messages.
+         *
+         * @param deviceId Id of device that has connected.
+         */
+        void onDeviceConnected(@NonNull String deviceId);
+
+        /**
+         * Triggered when device is disconnected.
+         *
+         * @param deviceId Id of device that has disconnected.
+         */
+        void onDeviceDisconnected(@NonNull String deviceId);
+
+        /**
+         * Triggered when device has established encryption for secure communication.
+         *
+         * @param deviceId Id of device that has established encryption.
+         */
+        void onSecureChannelEstablished(@NonNull String deviceId);
+
+        /**
+         * Triggered when a new message is received.
+         *
+         * @param deviceId Id of the device that sent the message.
+         * @param message  {@link DeviceMessage} received.
+         */
+        void onMessageReceived(@NonNull String deviceId, @NonNull DeviceMessage message);
+
+        /**
+         * Triggered when an error when establishing the secure channel.
+         *
+         * @param deviceId Id of the device that experienced the error.
+         */
+        void onSecureChannelError(@NonNull String deviceId);
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/DeviceMessage.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/DeviceMessage.java
similarity index 89%
rename from connected-device-lib/src/com/android/car/connecteddevice/ble/DeviceMessage.java
rename to connected-device-lib/src/com/android/car/connecteddevice/connection/DeviceMessage.java
index 9d3ac48..128decb 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/DeviceMessage.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/DeviceMessage.java
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection;
 
-import static com.android.car.connecteddevice.BleStreamProtos.BleDeviceMessageProto.BleDeviceMessage;
+import static com.android.car.connecteddevice.StreamProtos.DeviceMessageProto.Message;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import java.util.Arrays;
 import java.util.Objects;
 import java.util.UUID;
 
-/** Holds the needed data from a {@link BleDeviceMessage}. */
+/** Holds the needed data from a {@link Message}. */
 public class DeviceMessage {
 
     private static final String TAG = "DeviceMessage";
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/connection/DeviceMessageStream.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/DeviceMessageStream.java
new file mode 100644
index 0000000..c23618a
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/DeviceMessageStream.java
@@ -0,0 +1,362 @@
+/*
+ * 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.connection;
+
+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 androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.connecteddevice.StreamProtos.DeviceMessageProto.Message;
+import com.android.car.connecteddevice.StreamProtos.OperationProto.OperationType;
+import com.android.car.connecteddevice.StreamProtos.PacketProto.Packet;
+import com.android.car.connecteddevice.StreamProtos.VersionExchangeProto.VersionExchange;
+import com.android.car.connecteddevice.util.ByteUtils;
+import com.android.car.protobuf.ByteString;
+import com.android.car.protobuf.InvalidProtocolBufferException;
+
+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;
+
+/**
+ * Abstract class which includes common logic of different types of {@link DeviceMessageStream}.
+ */
+public abstract class DeviceMessageStream {
+
+    private static final String TAG = "DeviceMessageStream";
+
+    // Only version 2 of the messaging and version 2 of the security supported.
+    @VisibleForTesting
+    public static final int MESSAGING_VERSION = 2;
+    @VisibleForTesting
+    public static final int SECURITY_VERSION = 2;
+
+    private final ArrayDeque<Packet> mPacketQueue = new ArrayDeque<>();
+
+    private final Map<Integer, ByteArrayOutputStream> mPendingData = new HashMap<>();
+
+    // messageId -> nextExpectedPacketNumber
+    private final Map<Integer, Integer> mPendingPacketNumber = new HashMap<>();
+
+    private final MessageIdGenerator mMessageIdGenerator = new MessageIdGenerator();
+
+    private final AtomicBoolean mIsVersionExchanged = new AtomicBoolean(false);
+
+    private final AtomicBoolean mIsSendingInProgress = new AtomicBoolean(false);
+
+    private int mMaxWriteSize;
+
+    /** Listener which will be notified when there is new {@link DeviceMessage} received. */
+    private MessageReceivedListener mMessageReceivedListener;
+
+    /** Listener which will be notified when there is error parsing the received message. */
+    private MessageReceivedErrorListener mMessageReceivedErrorListener;
+
+    public DeviceMessageStream(int defaultMaxWriteSize) {
+        mMaxWriteSize = defaultMaxWriteSize;
+    }
+
+    /**
+     * Send data to the connected device. Note: {@link #sendCompleted()} must be called when the
+     * bytes have successfully been sent to indicate the stream is ready to send more data.
+     */
+    protected abstract void send(byte[] data);
+
+    /**
+     * Set the given listener to be notified when a new message was received from the client. If
+     * listener is {@code null}, clear.
+     */
+    public final void setMessageReceivedListener(@Nullable MessageReceivedListener listener) {
+        mMessageReceivedListener = listener;
+    }
+
+    /**
+     * Set the given listener to be notified when there was an error during receiving message from
+     * the client. If listener is {@code null}, clear.
+     */
+    public final void setMessageReceivedErrorListener(
+            @Nullable MessageReceivedErrorListener listener) {
+        mMessageReceivedErrorListener = listener;
+    }
+
+    /**
+     * Notify the {@code mMessageReceivedListener} about the message received if it is  not {@code
+     * null}.
+     *
+     * @param deviceMessage The message received.
+     * @param operationType The operation type of the message.
+     */
+    protected final void notifyMessageReceivedListener(@NonNull DeviceMessage deviceMessage,
+            OperationType operationType) {
+        if (mMessageReceivedListener != null) {
+            mMessageReceivedListener.onMessageReceived(deviceMessage, operationType);
+        }
+    }
+
+    /**
+     * Notify the {@code mMessageReceivedErrorListener} about the message received if it is  not
+     * {@code null}.
+     *
+     * @param e The exception happened when parsing the received message.
+     */
+    protected final void notifyMessageReceivedErrorListener(Exception e) {
+        if (mMessageReceivedErrorListener != null) {
+            mMessageReceivedErrorListener.onMessageReceivedError(e);
+        }
+    }
+
+    /**
+     * Writes the given message to the write characteristic of this stream with operation type
+     * {@code CLIENT_MESSAGE}.
+     *
+     * This method will handle the chunking of messages based on the max write size.
+     *
+     * @param deviceMessage The data object contains recipient, isPayloadEncrypted and message.
+     */
+    public final void writeMessage(@NonNull DeviceMessage deviceMessage) {
+        writeMessage(deviceMessage, OperationType.CLIENT_MESSAGE);
+    }
+
+    /**
+     * Send {@link DeviceMessage} to remote connected devices.
+     *
+     * @param deviceMessage The message which need to be sent
+     * @param operationType The operation type of current message
+     */
+    public final void writeMessage(@NonNull DeviceMessage deviceMessage,
+            OperationType operationType) {
+        Message.Builder builder = Message.newBuilder()
+                .setOperation(operationType)
+                .setIsPayloadEncrypted(deviceMessage.isMessageEncrypted())
+                .setPayload(ByteString.copyFrom(deviceMessage.getMessage()));
+
+        UUID recipient = deviceMessage.getRecipient();
+        if (recipient != null) {
+            builder.setRecipient(ByteString.copyFrom(ByteUtils.uuidToBytes(recipient)));
+        }
+
+        Message message = builder.build();
+        byte[] rawBytes = message.toByteArray();
+        List<Packet> packets;
+        try {
+            packets = PacketFactory.makePackets(rawBytes, mMessageIdGenerator.next(),
+                    mMaxWriteSize);
+        } catch (PacketFactoryException e) {
+            loge(TAG, "Error while creating message packets.", e);
+            return;
+        }
+        mPacketQueue.addAll(packets);
+        writeNextMessageInQueue();
+    }
+
+    private void writeNextMessageInQueue() {
+        if (mPacketQueue.isEmpty()) {
+            logd(TAG, "No more packets to send.");
+            return;
+        }
+        boolean isLockAcquired = mIsSendingInProgress.compareAndSet(false, true);
+        if (!isLockAcquired) {
+            logd(TAG, "Unable to send packet at this time.");
+            return;
+        }
+
+        Packet packet = mPacketQueue.remove();
+        logd(TAG, "Writing packet " + packet.getPacketNumber() + " of "
+                + packet.getTotalPackets() + " for " + packet.getMessageId() + ".");
+        send(packet.toByteArray());
+    }
+
+    /** Process incoming data from stream. */
+    protected final void onDataReceived(byte[] data) {
+        if (!hasVersionBeenExchanged()) {
+            processVersionExchange(data);
+            return;
+        }
+
+        Packet packet;
+        try {
+            packet = Packet.parseFrom(data);
+        } catch (InvalidProtocolBufferException e) {
+            loge(TAG, "Can not parse packet from client.", e);
+            notifyMessageReceivedErrorListener(e);
+            return;
+        }
+        processPacket(packet);
+    }
+
+    protected final void processPacket(@NonNull Packet packet) {
+        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 + ".");
+            notifyMessageReceivedErrorListener(
+                    new IllegalStateException("Packet received out of order."));
+            return;
+        }
+        mPendingPacketNumber.put(messageId, packetNumber + 1);
+
+        ByteArrayOutputStream currentPayloadStream =
+                mPendingData.getOrDefault(messageId, new ByteArrayOutputStream());
+        mPendingData.putIfAbsent(messageId, currentPayloadStream);
+
+        byte[] payload = packet.getPayload().toByteArray();
+        try {
+            currentPayloadStream.write(payload);
+        } catch (IOException e) {
+            loge(TAG, "Error writing packet to stream.", e);
+            notifyMessageReceivedErrorListener(e);
+            return;
+        }
+        logd(TAG, "Parsed packet " + packet.getPacketNumber() + " of "
+                + packet.getTotalPackets() + " for message " + messageId + ". Writing "
+                + payload.length + ".");
+
+        if (packet.getPacketNumber() != packet.getTotalPackets()) {
+            return;
+        }
+
+        byte[] messageBytes = currentPayloadStream.toByteArray();
+        mPendingData.remove(messageId);
+
+        logd(TAG, "Received complete device message " + messageId + " of " + messageBytes.length
+                + " bytes.");
+        Message message;
+        try {
+            message = Message.parseFrom(messageBytes);
+        } catch (InvalidProtocolBufferException e) {
+            loge(TAG, "Cannot parse device message from client.", e);
+            notifyMessageReceivedErrorListener(e);
+            return;
+        }
+
+        DeviceMessage deviceMessage = new DeviceMessage(
+                ByteUtils.bytesToUUID(message.getRecipient().toByteArray()),
+                message.getIsPayloadEncrypted(), message.getPayload().toByteArray());
+        notifyMessageReceivedListener(deviceMessage, message.getOperation());
+    }
+
+    /** The maximum amount of bytes that can be written in a single packet. */
+    public final void setMaxWriteSize(@IntRange(from = 1) int maxWriteSize) {
+        if (maxWriteSize <= 0) {
+            return;
+        }
+        mMaxWriteSize = maxWriteSize;
+    }
+
+    private boolean hasVersionBeenExchanged() {
+        return mIsVersionExchanged.get();
+    }
+
+    /** Indicate current send operation has completed. */
+    @VisibleForTesting
+    public final void sendCompleted() {
+        mIsSendingInProgress.set(false);
+        writeNextMessageInQueue();
+    }
+
+    private void processVersionExchange(@NonNull byte[] value) {
+        VersionExchange versionExchange;
+        try {
+            versionExchange = VersionExchange.parseFrom(value);
+        } catch (InvalidProtocolBufferException e) {
+            loge(TAG, "Could not parse version exchange message", e);
+            notifyMessageReceivedErrorListener(e);
+
+            return;
+        }
+        int minMessagingVersion = versionExchange.getMinSupportedMessagingVersion();
+        int maxMessagingVersion = versionExchange.getMaxSupportedMessagingVersion();
+        int minSecurityVersion = versionExchange.getMinSupportedSecurityVersion();
+        int maxSecurityVersion = versionExchange.getMaxSupportedSecurityVersion();
+        if (minMessagingVersion > MESSAGING_VERSION || maxMessagingVersion < MESSAGING_VERSION
+                || minSecurityVersion > SECURITY_VERSION || maxSecurityVersion < SECURITY_VERSION) {
+            loge(TAG, "Unsupported message version for min " + minMessagingVersion + " and max "
+                    + maxMessagingVersion + " or security version for " + minSecurityVersion
+                    + " and max " + maxSecurityVersion + ".");
+            notifyMessageReceivedErrorListener(new IllegalStateException("Unsupported version."));
+            return;
+        }
+
+        VersionExchange headunitVersion = VersionExchange.newBuilder()
+                .setMinSupportedMessagingVersion(MESSAGING_VERSION)
+                .setMaxSupportedMessagingVersion(MESSAGING_VERSION)
+                .setMinSupportedSecurityVersion(SECURITY_VERSION)
+                .setMaxSupportedSecurityVersion(SECURITY_VERSION)
+                .build();
+
+        send(headunitVersion.toByteArray());
+        mIsVersionExchanged.set(true);
+        logd(TAG, "Sent supported version to the phone.");
+    }
+
+
+    /** A generator of unique IDs for messages. */
+    private static class MessageIdGenerator {
+        private final AtomicInteger mMessageId = new AtomicInteger(0);
+
+        int next() {
+            int current = mMessageId.getAndIncrement();
+            mMessageId.compareAndSet(Integer.MAX_VALUE, 0);
+            return current;
+        }
+    }
+
+    /**
+     * Listener to be invoked when a complete message is received from the client.
+     */
+    public interface MessageReceivedListener {
+
+        /**
+         * Called when a complete message is received from the client.
+         *
+         * @param deviceMessage The message received from the client.
+         * @param operationType The {@link OperationType} of the received message.
+         */
+        void onMessageReceived(@NonNull DeviceMessage deviceMessage,
+                OperationType operationType);
+    }
+
+    /**
+     * Listener to be invoked when there was an error during receiving message from the client.
+     */
+    public interface MessageReceivedErrorListener {
+        /**
+         * Called when there was an error during receiving message from the client.
+         *
+         * @param exception The error.
+         */
+        void onMessageReceivedError(@NonNull Exception exception);
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/connection/OobAssociationSecureChannel.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/OobAssociationSecureChannel.java
new file mode 100644
index 0000000..c6b0a7d
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/OobAssociationSecureChannel.java
@@ -0,0 +1,132 @@
+/*
+ * 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.connection;
+
+import static android.car.encryptionrunner.HandshakeMessage.HandshakeState;
+
+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 androidx.annotation.NonNull;
+
+import com.android.car.connecteddevice.oob.OobConnectionManager;
+import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.util.Arrays;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+
+/**
+ * A secure channel established with the association flow with an out-of-band verification.
+ */
+public class OobAssociationSecureChannel extends AssociationSecureChannel {
+
+    private static final String TAG = "OobAssociationSecureChannel";
+
+    private final OobConnectionManager mOobConnectionManager;
+
+    private byte[] mOobCode;
+
+    public OobAssociationSecureChannel(
+            DeviceMessageStream stream,
+            ConnectedDeviceStorage storage,
+            OobConnectionManager oobConnectionManager) {
+        this(stream, storage, oobConnectionManager, EncryptionRunnerFactory.newRunner(
+                EncryptionRunnerFactory.EncryptionRunnerType.OOB_UKEY2));
+    }
+
+    OobAssociationSecureChannel(
+            DeviceMessageStream stream,
+            ConnectedDeviceStorage storage,
+            OobConnectionManager oobConnectionManager,
+            EncryptionRunner encryptionRunner) {
+        super(stream, storage, encryptionRunner);
+        mOobConnectionManager = oobConnectionManager;
+    }
+
+    @Override
+    void processHandshake(@NonNull byte[] message) throws HandshakeException {
+        switch (getState()) {
+            case HandshakeState.IN_PROGRESS:
+                processHandshakeInProgress(message);
+                break;
+            case HandshakeState.OOB_VERIFICATION_NEEDED:
+                processHandshakeOobVerificationNeeded(message);
+                break;
+            default:
+                super.processHandshake(message);
+        }
+    }
+
+    private void processHandshakeInProgress(@NonNull byte[] message) throws HandshakeException {
+        HandshakeMessage handshakeMessage = getEncryptionRunner().continueHandshake(message);
+        setState(handshakeMessage.getHandshakeState());
+        int state = getState();
+        if (state != HandshakeState.OOB_VERIFICATION_NEEDED) {
+            loge(TAG, "processHandshakeInProgress: Encountered unexpected handshake state: "
+                    + state + ".");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE);
+            return;
+        }
+
+        mOobCode = handshakeMessage.getOobVerificationCode();
+        if (mOobCode == null) {
+            loge(TAG, "Unable to get out of band verification code.");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_VERIFICATION);
+            return;
+        }
+
+        byte[] encryptedCode;
+        try {
+            encryptedCode = mOobConnectionManager.encryptVerificationCode(mOobCode);
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException
+                | IllegalBlockSizeException | BadPaddingException e) {
+            loge(TAG, "Encryption failed for verification code exchange.", e);
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_HANDSHAKE);
+            return;
+        }
+
+        sendHandshakeMessage(encryptedCode, /* isEncrypted= */ false);
+    }
+
+    private void processHandshakeOobVerificationNeeded(@NonNull byte[] message) {
+        byte[] decryptedCode;
+        try {
+            decryptedCode = mOobConnectionManager.decryptVerificationCode(message);
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException
+                | IllegalBlockSizeException | BadPaddingException e) {
+            loge(TAG, "Decryption failed for verification code exchange", e);
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_HANDSHAKE);
+            return;
+        }
+
+        if (!Arrays.equals(mOobCode, decryptedCode)) {
+            loge(TAG, "Exchanged verification codes do not match. Aborting secure channel.");
+            notifySecureChannelFailure(CHANNEL_ERROR_INVALID_VERIFICATION);
+            return;
+        }
+
+        notifyOutOfBandAccepted();
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePacketFactory.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/PacketFactory.java
similarity index 79%
rename from connected-device-lib/src/com/android/car/connecteddevice/ble/BlePacketFactory.java
rename to connected-device-lib/src/com/android/car/connecteddevice/connection/PacketFactory.java
index a0d0bb1..5036a18 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePacketFactory.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/PacketFactory.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.
@@ -13,23 +13,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection;
 
 import static com.android.car.connecteddevice.util.SafeLog.loge;
 
-import com.android.car.connecteddevice.BleStreamProtos.BlePacketProto.BlePacket;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.connecteddevice.StreamProtos.PacketProto.Packet;
 import com.android.car.protobuf.ByteString;
-import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
 /**
- * Factory for creating {@link BlePacket} protos.
+ * Factory for creating {@link Packet} protos.
  */
-class BlePacketFactory {
-    private static final String TAG = "BlePacketFactory";
+class PacketFactory {
+    private static final String TAG = "PacketFactory";
 
     /**
      * The size in bytes of a {@code fixed32} field in the proto.
@@ -39,7 +40,7 @@
     /**
      * The bytes needed to encode the field number in the proto.
      *
-     * <p>Since the {@link BlePacket} only has 4 fields, it will only take 1 additional byte to
+     * <p>Since the {@link Packet} only has 4 fields, it will only take 1 additional byte to
      * encode.
      */
     private static final int FIELD_NUMBER_ENCODING_SIZE = 1;
@@ -53,15 +54,15 @@
     /**
      * Split given data if necessary to fit within the given {@code maxSize}.
      *
-     * @param payload The payload to potentially split across multiple {@link BlePacket}s.
+     * @param payload The payload to potentially split across multiple {@link Packet}s.
      * @param messageId The unique id for identifying message.
      * @param maxSize The maximum size of each chunk.
-     * @return A list of {@link BlePacket}s.
-     * @throws BlePacketFactoryException if an error occurred during the splitting of data.
+     * @return A list of {@link Packet}s.
+     * @throws PacketFactoryException if an error occurred during the splitting of data.
      */
-    static List<BlePacket> makeBlePackets(byte[] payload, int messageId, int maxSize)
-            throws BlePacketFactoryException {
-        List<BlePacket> blePackets = new ArrayList<>();
+    static List<Packet> makePackets(byte[] payload, int messageId, int maxSize)
+            throws PacketFactoryException {
+        List<Packet> blePackets = new ArrayList<>();
         int payloadSize = payload.length;
         int totalPackets = getTotalPacketNumber(messageId, payloadSize, maxSize);
         int maxPayloadSize = maxSize
@@ -70,7 +71,7 @@
         int start = 0;
         int end = Math.min(payloadSize, maxPayloadSize);
         for (int packetNum = 1; packetNum <= totalPackets; packetNum++) {
-            blePackets.add(BlePacket.newBuilder()
+            blePackets.add(Packet.newBuilder()
                     .setPacketNumber(packetNum)
                     .setTotalPackets(totalPackets)
                     .setMessageId(messageId)
@@ -83,7 +84,7 @@
     }
 
     /**
-     * Compute the header size for the {@link BlePacket} proto in bytes. This method assumes that
+     * Compute the header size for the {@link Packet} proto in bytes. This method assumes that
      * the proto contains a payload.
      */
     @VisibleForTesting
@@ -99,7 +100,7 @@
      */
     @VisibleForTesting
     static int getTotalPacketNumber(int messageId, int payloadSize, int maxSize)
-            throws BlePacketFactoryException {
+            throws PacketFactoryException {
         int headerSizeWithoutTotalPackets = FIXED_32_SIZE + FIELD_NUMBER_ENCODING_SIZE
                 + getEncodedSize(messageId) + FIELD_NUMBER_ENCODING_SIZE
                 + getEncodedSize(Math.min(payloadSize, maxSize)) + FIELD_NUMBER_ENCODING_SIZE;
@@ -109,7 +110,7 @@
                     + FIELD_NUMBER_ENCODING_SIZE;
             int maxPayloadSize = maxSize - packetHeaderSize;
             if (maxPayloadSize < 0) {
-                throw new BlePacketFactoryException("Packet header size too large.");
+                throw new PacketFactoryException("Packet header size too large.");
             }
             int totalPackets = (int) Math.ceil(payloadSize / (double) maxPayloadSize);
             if (getEncodedSize(totalPackets) == value) {
@@ -119,7 +120,7 @@
 
         loge(TAG, "Cannot get valid total packet number for message: messageId: "
                 + messageId + ", payloadSize: " + payloadSize + ", maxSize: " + maxSize);
-        throw new BlePacketFactoryException("No valid total packet number.");
+        throw new PacketFactoryException("No valid total packet number.");
     }
 
     /**
@@ -151,5 +152,5 @@
         return 5;
     }
 
-    private BlePacketFactory() {}
+    private PacketFactory() {}
 }
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePacketFactoryException.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/PacketFactoryException.java
similarity index 70%
rename from connected-device-lib/src/com/android/car/connecteddevice/ble/BlePacketFactoryException.java
rename to connected-device-lib/src/com/android/car/connecteddevice/connection/PacketFactoryException.java
index 690ce28..41c11fc 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePacketFactoryException.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/PacketFactoryException.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.
@@ -13,13 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection;
 
 /**
- * Exception for signaling {@link BlePacketFactory} errors.
+ * Exception for signaling {@link PacketFactory} errors.
  */
-class BlePacketFactoryException extends Exception {
-    BlePacketFactoryException(String message) {
+class PacketFactoryException extends Exception {
+    PacketFactoryException(String message) {
         super(message);
     }
 }
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/ReconnectSecureChannel.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/ReconnectSecureChannel.java
similarity index 89%
rename from connected-device-lib/src/com/android/car/connecteddevice/ble/ReconnectSecureChannel.java
rename to connected-device-lib/src/com/android/car/connecteddevice/connection/ReconnectSecureChannel.java
index 56eff5c..71c879a 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/ReconnectSecureChannel.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/ReconnectSecureChannel.java
@@ -14,13 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection;
 
 import static com.android.car.connecteddevice.util.SafeLog.logd;
 import static com.android.car.connecteddevice.util.SafeLog.loge;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.car.encryptionrunner.EncryptionRunner;
 import android.car.encryptionrunner.EncryptionRunnerFactory;
 import android.car.encryptionrunner.HandshakeException;
@@ -28,6 +26,9 @@
 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;
 
@@ -37,7 +38,7 @@
 /**
  * A secure channel established with the reconnection flow.
  */
-class ReconnectSecureChannel extends SecureBleChannel {
+public class ReconnectSecureChannel extends SecureChannel {
 
     private static final String TAG = "ReconnectSecureChannel";
 
@@ -55,17 +56,23 @@
     /**
      * Create a new secure reconnection channel.
      *
-     * @param stream The {@link BleDeviceMessageStream} for communication with the device.
+     * @param stream The {@link DeviceMessageStream} 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.
+     * @param expectedChallengeResponse Expected response to challenge issued in reconnect. Should
+     *                                  pass {@code null} when device verification is not needed
+     *                                  during the reconnection process.
      */
-    ReconnectSecureChannel(@NonNull BleDeviceMessageStream stream,
+    public ReconnectSecureChannel(@NonNull DeviceMessageStream stream,
             @NonNull ConnectedDeviceStorage storage, @NonNull String deviceId,
-            @NonNull byte[] expectedChallengeResponse) {
+            @Nullable byte[] expectedChallengeResponse) {
         super(stream, newReconnectRunner());
         mStorage = storage;
         mDeviceId = deviceId;
+        if (expectedChallengeResponse == null) {
+            // Skip the device verification step for spp reconnection
+            mHasVerifiedDevice.set(true);
+        }
         mExpectedChallengeResponse = expectedChallengeResponse;
     }
 
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/SecureBleChannel.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/SecureChannel.java
similarity index 87%
rename from connected-device-lib/src/com/android/car/connecteddevice/ble/SecureBleChannel.java
rename to connected-device-lib/src/com/android/car/connecteddevice/connection/SecureChannel.java
index 858d7c8..e6ad290 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/SecureBleChannel.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/SecureChannel.java
@@ -14,20 +14,21 @@
  * limitations under the License.
  */
 
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection;
 
 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.HandshakeException;
 import android.car.encryptionrunner.Key;
 
-import com.android.car.connecteddevice.BleStreamProtos.BleOperationProto.OperationType;
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.connecteddevice.StreamProtos.OperationProto.OperationType;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -36,25 +37,23 @@
 import java.util.function.Consumer;
 
 /**
- * Establishes a secure channel with {@link EncryptionRunner} over {@link BleDeviceMessageStream} as
+ * Establishes a secure channel with {@link EncryptionRunner} over {@link DeviceMessageStream} as
  * server side, sends and receives messages securely after the secure channel has been established.
  */
-abstract class SecureBleChannel {
+public abstract class SecureChannel {
 
-    private static final String TAG = "SecureBleChannel";
+    private static final String TAG = "SecureChannel";
 
     @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. */
@@ -73,7 +72,7 @@
     static final int CHANNEL_ERROR_STORAGE_ERROR = 6;
 
 
-    private final BleDeviceMessageStream mStream;
+    private final DeviceMessageStream mStream;
 
     private final EncryptionRunner mEncryptionRunner;
 
@@ -81,7 +80,7 @@
 
     private Callback mCallback;
 
-    SecureBleChannel(@NonNull BleDeviceMessageStream stream,
+    SecureChannel(@NonNull DeviceMessageStream stream,
             @NonNull EncryptionRunner encryptionRunner) {
         mStream = stream;
         mEncryptionRunner = encryptionRunner;
@@ -119,7 +118,7 @@
      *
      * @param deviceMessage The {@link DeviceMessage} to send.
      */
-    void sendClientMessage(@NonNull DeviceMessage deviceMessage)
+    public void sendClientMessage(@NonNull DeviceMessage deviceMessage)
             throws IllegalStateException {
         if (deviceMessage.isMessageEncrypted()) {
             encryptMessage(deviceMessage);
@@ -139,12 +138,12 @@
 
     /** Get the BLE stream backing this channel. */
     @NonNull
-    BleDeviceMessageStream getStream() {
+    public DeviceMessageStream getStream() {
         return mStream;
     }
 
     /** Register a callback that notifies secure channel events. */
-    void registerCallback(Callback callback) {
+    public void registerCallback(Callback callback) {
         mCallback = callback;
     }
 
@@ -157,7 +156,7 @@
 
     @VisibleForTesting
     @Nullable
-    Callback getCallback() {
+    public Callback getCallback() {
         return mCallback;
     }
 
@@ -252,7 +251,7 @@
      * Callbacks that will be invoked during establishing secure channel, sending and receiving
      * messages securely.
      */
-    interface Callback {
+    public interface Callback {
         /**
          * Invoked when secure channel has been established successfully.
          */
@@ -264,7 +263,7 @@
          *
          * @param error The failure indication.
          */
-        default void onEstablishSecureChannelFailure(@SecureBleChannel.ChannelError int error) { }
+        default void onEstablishSecureChannelFailure(@SecureChannel.ChannelError int error) { }
 
         /**
          * Invoked when a complete message is received securely from the client and decrypted.
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/BleCentralManager.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/ble/BleCentralManager.java
similarity index 97%
rename from connected-device-lib/src/com/android/car/connecteddevice/ble/BleCentralManager.java
rename to connected-device-lib/src/com/android/car/connecteddevice/connection/ble/BleCentralManager.java
index ca83a05..7a663f7 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/BleCentralManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/ble/BleCentralManager.java
@@ -14,15 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection.ble;
 
 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.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/connection/ble/BleDeviceMessageStream.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/ble/BleDeviceMessageStream.java
new file mode 100644
index 0000000..864475f
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/ble/BleDeviceMessageStream.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.connecteddevice.connection.ble;
+
+import static com.android.car.connecteddevice.util.SafeLog.logd;
+import static com.android.car.connecteddevice.util.SafeLog.logw;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+
+import com.android.car.connecteddevice.connection.DeviceMessageStream;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/** BLE message stream to a device. */
+public class BleDeviceMessageStream extends DeviceMessageStream {
+
+    private static final String TAG = "BleDeviceMessageStream";
+
+    /*
+     * During bandwidth testing, it was discovered that allowing the stream to send as fast as it
+     * can blocked outgoing notifications from being received by the connected device. Adding a
+     * throttle to the outgoing messages alleviated this block and allowed both sides to
+     * send/receive in parallel successfully.
+     */
+    private static final long THROTTLE_DEFAULT_MS = 10L;
+    private static final long THROTTLE_WAIT_MS = 75L;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    private final AtomicLong mThrottleDelay = new AtomicLong(THROTTLE_DEFAULT_MS);
+
+    private final BlePeripheralManager mBlePeripheralManager;
+
+    private final BluetoothDevice mDevice;
+
+    private final BluetoothGattCharacteristic mWriteCharacteristic;
+
+    private final BluetoothGattCharacteristic mReadCharacteristic;
+
+    BleDeviceMessageStream(@NonNull BlePeripheralManager blePeripheralManager,
+            @NonNull BluetoothDevice device,
+            @NonNull BluetoothGattCharacteristic writeCharacteristic,
+            @NonNull BluetoothGattCharacteristic readCharacteristic,
+            int defaultMaxWriteSize) {
+        super(defaultMaxWriteSize);
+        mBlePeripheralManager = blePeripheralManager;
+        mDevice = device;
+        mWriteCharacteristic = writeCharacteristic;
+        mReadCharacteristic = readCharacteristic;
+        mBlePeripheralManager.addOnCharacteristicWriteListener(this::onCharacteristicWrite);
+        mBlePeripheralManager.addOnCharacteristicReadListener(this::onCharacteristicRead);
+    }
+
+    @Override
+    protected void send(byte[] data) {
+        mWriteCharacteristic.setValue(data);
+        mBlePeripheralManager.notifyCharacteristicChanged(mDevice, mWriteCharacteristic,
+                /* confirm= */ false);
+    }
+
+    private void onCharacteristicRead(@NonNull BluetoothDevice device) {
+        if (!mDevice.equals(device)) {
+            logw(TAG, "Received a read notification from a device (" + device.getAddress()
+                    + ") that is not the expected device (" + mDevice.getAddress() + ") registered "
+                    + "to this stream. Ignoring.");
+            return;
+        }
+
+        logd(TAG, "Releasing lock on characteristic.");
+        sendCompleted();
+    }
+
+    private void onCharacteristicWrite(@NonNull BluetoothDevice device,
+            @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) {
+        logd(TAG, "Received a message from a device (" + device.getAddress() + ").");
+        if (!mDevice.equals(device)) {
+            logw(TAG, "Received a message from a device (" + device.getAddress() + ") that is not "
+                    + "the expected device (" + mDevice.getAddress() + ") registered to this "
+                    + "stream. Ignoring.");
+            return;
+        }
+
+        if (!characteristic.getUuid().equals(mReadCharacteristic.getUuid())) {
+            logw(TAG, "Received a write to a characteristic (" + characteristic.getUuid() + ") that"
+                    + " is not the expected UUID (" + mReadCharacteristic.getUuid() + "). "
+                    + "Ignoring.");
+            return;
+        }
+        onDataReceived(value);
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePeripheralManager.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/ble/BlePeripheralManager.java
similarity index 92%
rename from connected-device-lib/src/com/android/car/connecteddevice/ble/BlePeripheralManager.java
rename to connected-device-lib/src/com/android/car/connecteddevice/connection/ble/BlePeripheralManager.java
index 51da3d7..645cecb 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePeripheralManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/ble/BlePeripheralManager.java
@@ -14,14 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection.ble;
 
 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.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,14 +234,12 @@
      * 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) {
@@ -268,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));
@@ -280,19 +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),
-                    BLE_RETRY_INTERVAL_MS);
+                    () -> startAdvertisingInternally(settings, advertisement, scanResponse,
+                            advertiseCallback), BLE_RETRY_INTERVAL_MS);
             mAdvertiserStartCount += 1;
         } else {
             loge(TAG, "Cannot start BLE Advertisement. Advertise Retry count: "
@@ -305,9 +311,9 @@
                 @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;
@@ -318,6 +324,7 @@
                             }
                             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/connection/ble/CarBleCentralManager.java
similarity index 85%
rename from connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleCentralManager.java
rename to connected-device-lib/src/com/android/car/connecteddevice/connection/ble/CarBleCentralManager.java
index b95458d..e8d17e5 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleCentralManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/ble/CarBleCentralManager.java
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection.ble;
 
 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 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,11 @@
 import android.content.Context;
 import android.os.ParcelUuid;
 
+import androidx.annotation.NonNull;
+
+import com.android.car.connecteddevice.AssociationCallback;
+import com.android.car.connecteddevice.connection.CarBluetoothManager;
+import com.android.car.connecteddevice.oob.OobChannel;
 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
 
 import java.math.BigInteger;
@@ -47,7 +51,7 @@
  * Communication manager for a car that maintains continuous connections with all devices in the car
  * for the duration of a drive.
  */
-public class CarBleCentralManager extends CarBleManager {
+public class CarBleCentralManager extends CarBluetoothManager {
 
     private static final String TAG = "CarBleCentralManager";
 
@@ -65,7 +69,8 @@
             .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
             .build();
 
-    private final CopyOnWriteArraySet<BleDevice> mIgnoredDevices = new CopyOnWriteArraySet<>();
+    private final CopyOnWriteArraySet<ConnectedRemoteDevice> mIgnoredDevices =
+            new CopyOnWriteArraySet<>();
 
     private final Context mContext;
 
@@ -122,7 +127,7 @@
     @Override
     public void disconnectDevice(String deviceId) {
         logd(TAG, "Request to disconnect from device " + deviceId + ".");
-        BleDevice device = getConnectedDevice(deviceId);
+        ConnectedRemoteDevice device = getConnectedDevice(deviceId);
         if (device == null) {
             return;
         }
@@ -130,13 +135,45 @@
         deviceDisconnected(device, STATUS_FORCED_DISCONNECT);
     }
 
-    private void ignoreDevice(@NonNull BleDevice device) {
+    //TODO(b/141312136): Support car central role
+    @Override
+    public AssociationCallback getAssociationCallback() {
+        return null;
+    }
+
+    @Override
+    public void setAssociationCallback(AssociationCallback callback) {
+
+    }
+
+    @Override
+    public void connectToDevice(UUID deviceId) {
+
+    }
+
+    @Override
+    public void initiateConnectionToDevice(UUID deviceId) {
+
+    }
+
+    @Override
+    public void startAssociation(String nameForAssociation, AssociationCallback callback) {
+
+    }
+
+    @Override
+    public void startOutOfBandAssociation(String nameForAssociation, OobChannel oobChannel,
+            AssociationCallback callback) {
+
+    }
+
+    private void ignoreDevice(@NonNull ConnectedRemoteDevice device) {
         mIgnoredDevices.add(device);
     }
 
     private boolean isDeviceIgnored(@NonNull BluetoothDevice device) {
-        for (BleDevice bleDevice : mIgnoredDevices) {
-            if (device.equals(bleDevice.mDevice)) {
+        for (ConnectedRemoteDevice connectedDevice : mIgnoredDevices) {
+            if (device.equals(connectedDevice.mDevice)) {
                 return true;
             }
         }
@@ -205,8 +242,8 @@
             return;
         }
 
-        BleDevice bleDevice = new BleDevice(device, gatt);
-        bleDevice.mState = BleDeviceState.CONNECTING;
+        ConnectedRemoteDevice bleDevice = new ConnectedRemoteDevice(device, gatt);
+        bleDevice.mState = ConnectedDeviceState.CONNECTING;
         addConnectedDevice(bleDevice);
 
         // Stop scanning if we have reached the maximum number of connections.
@@ -215,19 +252,19 @@
         }
     }
 
-    private void deviceConnected(@NonNull BleDevice device) {
+    private void deviceConnected(@NonNull ConnectedRemoteDevice device) {
         if (device.mGatt == null) {
             loge(TAG, "Device connected with null gatt. Disconnecting.");
             deviceDisconnected(device, BluetoothProfile.STATE_DISCONNECTED);
             return;
         }
-        device.mState = BleDeviceState.PENDING_VERIFICATION;
+        device.mState = ConnectedDeviceState.PENDING_VERIFICATION;
         device.mGatt.discoverServices();
         logd(TAG, "New device connected: " + device.mGatt.getDevice().getAddress()
                 + ". Active connections: " + getConnectedDevicesCount() + ".");
     }
 
-    private void deviceDisconnected(@NonNull BleDevice device, int status) {
+    private void deviceDisconnected(@NonNull ConnectedRemoteDevice device, int status) {
         removeConnectedDevice(device);
         if (device.mGatt != null) {
             device.mGatt.close();
@@ -264,7 +301,7 @@
                 return;
             }
 
-            BleDevice connectedDevice = getConnectedDevice(gatt);
+            ConnectedRemoteDevice connectedDevice = getConnectedDevice(gatt);
             if (connectedDevice == null) {
                 return;
             }
@@ -290,7 +327,7 @@
                 return;
             }
 
-            BleDevice connectedDevice = getConnectedDevice(gatt);
+            ConnectedRemoteDevice connectedDevice = getConnectedDevice(gatt);
             if (connectedDevice == null) {
                 return;
             }
@@ -301,7 +338,7 @@
                 return;
             }
 
-            connectedDevice.mState = BleDeviceState.CONNECTED;
+            connectedDevice.mState = ConnectedDeviceState.CONNECTED;
             BluetoothGattCharacteristic writeCharacteristic =
                     service.getCharacteristic(mWriteCharacteristicUuid);
             BluetoothGattCharacteristic readCharacteristic =
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/connection/ble/CarBlePeripheralManager.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/ble/CarBlePeripheralManager.java
new file mode 100644
index 0000000..a05b5f7
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/ble/CarBlePeripheralManager.java
@@ -0,0 +1,510 @@
+/*
+ * 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.connection.ble;
+
+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.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelUuid;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.car.connecteddevice.AssociationCallback;
+import com.android.car.connecteddevice.connection.AssociationSecureChannel;
+import com.android.car.connecteddevice.connection.CarBluetoothManager;
+import com.android.car.connecteddevice.connection.OobAssociationSecureChannel;
+import com.android.car.connecteddevice.connection.ReconnectSecureChannel;
+import com.android.car.connecteddevice.connection.SecureChannel;
+import com.android.car.connecteddevice.oob.OobChannel;
+import com.android.car.connecteddevice.oob.OobConnectionManager;
+import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
+import com.android.car.connecteddevice.util.ByteUtils;
+import com.android.car.connecteddevice.util.EventLog;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.UUID;
+
+/**
+ * Communication manager that allows for targeted connections to a specific device in the car.
+ */
+public class CarBlePeripheralManager extends CarBluetoothManager {
+
+    private static final String TAG = "CarBlePeripheralManager";
+
+    // Attribute protocol bytes attached to message. Available write size is MTU size minus att
+    // bytes.
+    private static final int ATT_PROTOCOL_BYTES = 3;
+
+    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 BlePeripheralManager mBlePeripheralManager;
+
+    private final UUID mAssociationServiceUuid;
+
+    private final UUID mReconnectServiceUuid;
+
+    private final UUID mReconnectDataUuid;
+
+    private final BluetoothGattCharacteristic mWriteCharacteristic;
+
+    private final BluetoothGattCharacteristic mReadCharacteristic;
+
+    private HandlerThread mTimeoutHandlerThread;
+
+    private Handler mTimeoutHandler;
+
+    private final Duration mMaxReconnectAdvertisementDuration;
+
+    private final int mDefaultMtuSize;
+
+    private String mReconnectDeviceId;
+
+    private byte[] mReconnectChallenge;
+
+    private AdvertiseCallback mAdvertiseCallback;
+
+    private OobConnectionManager mOobConnectionManager;
+
+    private AssociationCallback mAssociationCallback;
+
+    /**
+     * 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 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 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 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,
+                BluetoothGattCharacteristic.PROPERTY_READ);
+        mReadCharacteristic = new BluetoothGattCharacteristic(readCharacteristicUuid,
+                BluetoothGattCharacteristic.PROPERTY_WRITE
+                        | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
+                BluetoothGattCharacteristic.PERMISSION_WRITE);
+        mReadCharacteristic.addDescriptor(mDescriptor);
+        mMaxReconnectAdvertisementDuration = maxReconnectAdvertisementDuration;
+        mDefaultMtuSize = defaultMtuSize;
+    }
+
+    @Override
+    public void start() {
+        super.start();
+        mTimeoutHandlerThread = new HandlerThread(TIMEOUT_HANDLER_THREAD_NAME);
+        mTimeoutHandlerThread.start();
+        mTimeoutHandler = new Handler(mTimeoutHandlerThread.getLooper());
+    }
+
+    @Override
+    public void stop() {
+        super.stop();
+        if (mTimeoutHandlerThread != null) {
+            mTimeoutHandlerThread.quit();
+        }
+        reset();
+    }
+
+    @Override
+    public void disconnectDevice(@NonNull String deviceId) {
+        if (deviceId.equals(mReconnectDeviceId)) {
+            logd(TAG, "Reconnection canceled for device " + deviceId + ".");
+            reset();
+            return;
+        }
+        ConnectedRemoteDevice connectedDevice = getConnectedDevice();
+        if (connectedDevice == null || !deviceId.equals(connectedDevice.mDeviceId)) {
+            return;
+        }
+        reset();
+    }
+
+    @Override
+    public AssociationCallback getAssociationCallback() {
+        return mAssociationCallback;
+    }
+
+    @Override
+    public void setAssociationCallback(AssociationCallback callback) {
+        mAssociationCallback = callback;
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        logd(TAG, "Resetting state.");
+        mBlePeripheralManager.cleanup();
+        mReconnectDeviceId = null;
+        mReconnectChallenge = null;
+        mOobConnectionManager = null;
+        mAssociationCallback = null;
+    }
+
+    @Override
+    public void initiateConnectionToDevice(@NonNull UUID deviceId) {
+        mReconnectDeviceId = deviceId.toString();
+        mAdvertiseCallback = new AdvertiseCallback() {
+            @Override
+            public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+                super.onStartSuccess(settingsInEffect);
+                mTimeoutHandler.postDelayed(mTimeoutRunnable,
+                        mMaxReconnectAdvertisementDuration.toMillis());
+                logd(TAG, "Successfully started advertising for device " + deviceId + ".");
+            }
+        };
+        mBlePeripheralManager.unregisterCallback(mAssociationPeripheralCallback);
+        mBlePeripheralManager.registerCallback(mReconnectPeripheralCallback);
+        mTimeoutHandler.removeCallbacks(mTimeoutRunnable);
+        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);
+
+    }
+
+    @Override
+    public void startAssociation(@NonNull String nameForAssociation,
+            @NonNull AssociationCallback callback) {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter == null) {
+            loge(TAG, "Bluetooth is unavailable on this device. Unable to start associating.");
+            return;
+        }
+
+        reset();
+        mAssociationCallback = callback;
+        mBlePeripheralManager.unregisterCallback(mReconnectPeripheralCallback);
+        mBlePeripheralManager.registerCallback(mAssociationPeripheralCallback);
+        mAdvertiseCallback = new AdvertiseCallback() {
+            @Override
+            public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+                super.onStartSuccess(settingsInEffect);
+                callback.onAssociationStartSuccess(nameForAssociation);
+                logd(TAG, "Successfully started advertising for association.");
+            }
+
+            @Override
+            public void onStartFailure(int errorCode) {
+                super.onStartFailure(errorCode);
+                callback.onAssociationStartFailure();
+                logd(TAG, "Failed to start advertising for association. Error code: " + errorCode);
+            }
+        };
+        startAdvertising(mAssociationServiceUuid, mAdvertiseCallback, /* advertiseData= */null,
+                /* advertiseDataUuid= */ null, nameForAssociation.getBytes(), mReconnectDataUuid);
+    }
+
+    /** Start the association with a new device using out of band verification code exchange */
+    @Override
+    public void startOutOfBandAssociation(
+            @NonNull String nameForAssociation,
+            @NonNull OobChannel oobChannel,
+            @NonNull AssociationCallback callback) {
+
+        logd(TAG, "Starting out of band association.");
+        startAssociation(nameForAssociation, new AssociationCallback() {
+            @Override
+            public void onAssociationStartSuccess(String deviceName) {
+                mAssociationCallback = callback;
+                boolean success = mOobConnectionManager.startOobExchange(oobChannel);
+                if (!success) {
+                    callback.onAssociationStartFailure();
+                    return;
+                }
+                callback.onAssociationStartSuccess(deviceName);
+            }
+
+            @Override
+            public void onAssociationStartFailure() {
+                callback.onAssociationStartFailure();
+            }
+        });
+        mOobConnectionManager = new OobConnectionManager();
+    }
+
+    private void startAdvertising(@NonNull UUID serviceUuid, @NonNull AdvertiseCallback callback,
+            @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.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);
+    }
+
+    private void addConnectedDevice(BluetoothDevice device, boolean isReconnect) {
+        addConnectedDevice(device, isReconnect, /* oobConnectionManager= */ null);
+    }
+
+    private void addConnectedDevice(@NonNull BluetoothDevice device, boolean isReconnect,
+            @Nullable OobConnectionManager oobConnectionManager) {
+        EventLog.onDeviceConnected();
+        mBlePeripheralManager.stopAdvertising(mAdvertiseCallback);
+        if (mTimeoutHandler != null) {
+            mTimeoutHandler.removeCallbacks(mTimeoutRunnable);
+        }
+
+        if (device.getName() == null) {
+            logd(TAG, "Device connected, but name is null; issuing request to retrieve device "
+                    + "name.");
+            mBlePeripheralManager.retrieveDeviceName(device);
+        } else {
+            setClientDeviceName(device.getName());
+        }
+        setClientDeviceAddress(device.getAddress());
+
+        BleDeviceMessageStream secureStream = new BleDeviceMessageStream(mBlePeripheralManager,
+                device, mWriteCharacteristic, mReadCharacteristic,
+                mDefaultMtuSize - ATT_PROTOCOL_BYTES);
+        secureStream.setMessageReceivedErrorListener(
+                exception -> {
+                    disconnectWithError("Error occurred in stream: " + exception.getMessage());
+                });
+        SecureChannel secureChannel;
+        if (isReconnect) {
+            secureChannel = new ReconnectSecureChannel(secureStream, mStorage, mReconnectDeviceId,
+                    mReconnectChallenge);
+        } else if (oobConnectionManager != null) {
+            secureChannel = new OobAssociationSecureChannel(secureStream, mStorage,
+                    oobConnectionManager);
+        } else {
+            secureChannel = new AssociationSecureChannel(secureStream, mStorage);
+        }
+        secureChannel.registerCallback(mSecureChannelCallback);
+        ConnectedRemoteDevice connectedDevice = new ConnectedRemoteDevice(device, /* gatt= */ null);
+        connectedDevice.mSecureChannel = secureChannel;
+        addConnectedDevice(connectedDevice);
+        if (isReconnect) {
+            setDeviceIdAndNotifyCallbacks(mReconnectDeviceId);
+            mReconnectDeviceId = null;
+            mReconnectChallenge = null;
+        }
+    }
+
+    private void setMtuSize(int mtuSize) {
+        ConnectedRemoteDevice connectedDevice = getConnectedDevice();
+        if (connectedDevice != null
+                && connectedDevice.mSecureChannel != null
+                && connectedDevice.mSecureChannel.getStream() != null) {
+            ((BleDeviceMessageStream) connectedDevice.mSecureChannel.getStream())
+                    .setMaxWriteSize(mtuSize - ATT_PROTOCOL_BYTES);
+        }
+    }
+
+    private final BlePeripheralManager.Callback mReconnectPeripheralCallback =
+            new BlePeripheralManager.Callback() {
+
+                @Override
+                public void onDeviceNameRetrieved(String deviceName) {
+                    // Ignored.
+                }
+
+                @Override
+                public void onMtuSizeChanged(int size) {
+                    setMtuSize(size);
+                }
+
+                @Override
+                public void onRemoteDeviceConnected(BluetoothDevice device) {
+                    addConnectedDevice(device, /* isReconnect= */ true);
+                }
+
+                @Override
+                public void onRemoteDeviceDisconnected(BluetoothDevice device) {
+                    String deviceId = mReconnectDeviceId;
+                    ConnectedRemoteDevice connectedDevice = getConnectedDevice(device);
+                    // Reset before invoking callbacks to avoid a race condition with reconnect
+                    // logic.
+                    reset();
+                    if (connectedDevice != null) {
+                        deviceId = connectedDevice.mDeviceId;
+                    }
+                    final String finalDeviceId = deviceId;
+                    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));
+                }
+            };
+
+    private final BlePeripheralManager.Callback mAssociationPeripheralCallback =
+            new BlePeripheralManager.Callback() {
+                @Override
+                public void onDeviceNameRetrieved(String deviceName) {
+                    if (deviceName == null) {
+                        return;
+                    }
+                    setClientDeviceName(deviceName);
+                    ConnectedRemoteDevice connectedDevice = getConnectedDevice();
+                    if (connectedDevice == null || connectedDevice.mDeviceId == null) {
+                        return;
+                    }
+                    mStorage.updateAssociatedDeviceName(connectedDevice.mDeviceId, deviceName);
+                }
+
+                @Override
+                public void onMtuSizeChanged(int size) {
+                    setMtuSize(size);
+                }
+
+                @Override
+                public void onRemoteDeviceConnected(BluetoothDevice device) {
+                    addConnectedDevice(device, /* isReconnect= */ false, mOobConnectionManager);
+                    ConnectedRemoteDevice connectedDevice = getConnectedDevice();
+                    if (connectedDevice == null || connectedDevice.mSecureChannel == null) {
+                        return;
+                    }
+                    ((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.");
+                    ConnectedRemoteDevice connectedDevice = getConnectedDevice(device);
+                    if (isAssociating()) {
+                        mAssociationCallback.onAssociationError(
+                                DEVICE_ERROR_UNEXPECTED_DISCONNECTION);
+                    }
+                    // Reset before invoking callbacks to avoid a race condition with reconnect
+                    // logic.
+                    reset();
+                    if (connectedDevice == null || connectedDevice.mDeviceId == null) {
+                        logw(TAG, "Callbacks were not issued for disconnect.");
+                        return;
+                    }
+                    mCallbacks.invoke(callback -> callback.onDeviceDisconnected(
+                            connectedDevice.mDeviceId));
+                }
+            };
+
+    private final Runnable mTimeoutRunnable = new Runnable() {
+        @Override
+        public void run() {
+            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/connection/spp/AcceptTask.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/AcceptTask.java
new file mode 100644
index 0000000..2ada256
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/AcceptTask.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.connecteddevice.connection.spp;
+
+import static com.android.car.connecteddevice.util.SafeLog.logd;
+import static com.android.car.connecteddevice.util.SafeLog.loge;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.BluetoothSocket;
+
+import androidx.annotation.Nullable;
+
+import java.io.IOException;
+import java.util.UUID;
+
+/**
+ * This task runs while listening for incoming connections. It behaves like a server. It runs until
+ * a connection is accepted (or until cancelled).
+ */
+public class AcceptTask implements Runnable {
+    private static final String TAG = "AcceptTask";
+    private static final String SERVICE_NAME_SECURE = "NAME_SECURE";
+    private static final String SERVICE_NAME_INSECURE = "NAME_INSECURE";
+    private final UUID mServiceUuid;
+    private final boolean mIsSecure;
+    private final OnTaskCompletedListener mListener;
+    private final BluetoothAdapter mAdapter;
+    private BluetoothServerSocket mServerSocket;
+
+    AcceptTask(BluetoothAdapter adapter, boolean isSecure, UUID serviceUuid,
+            OnTaskCompletedListener listener) {
+        mListener = listener;
+        mAdapter = adapter;
+        mServiceUuid = serviceUuid;
+        mIsSecure = isSecure;
+    }
+
+    /**
+     * Start the socket to listen to any incoming connection request.
+     *
+     * @return {@code true} if listening is started successfully.
+     */
+    boolean startListening() {
+        // Create a new listening server socket
+        try {
+            if (mIsSecure) {
+                mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord(SERVICE_NAME_SECURE,
+                        mServiceUuid);
+            } else {
+                mServerSocket = mAdapter.listenUsingInsecureRfcommWithServiceRecord(
+                        SERVICE_NAME_INSECURE, mServiceUuid);
+            }
+        } catch (IOException e) {
+            loge(TAG, "Socket listen() failed", e);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void run() {
+        logd(TAG, "BEGIN AcceptTask: " + this);
+        BluetoothSocket socket = null;
+
+        // Listen to the server socket if we're not connected
+        while (true) {
+            try {
+                socket = mServerSocket.accept();
+            } catch (IOException e) {
+                loge(TAG, "accept() failed", e);
+                break;
+            }
+            if (socket != null) {
+                break;
+            }
+        }
+
+        mListener.onTaskCompleted(socket, mIsSecure);
+    }
+
+    void cancel() {
+        logd(TAG, "CANCEL AcceptTask: " + this);
+        try {
+            if (mServerSocket != null) {
+                mServerSocket.close();
+            }
+        } catch (IOException e) {
+            loge(TAG, "close() of server failed", e);
+        }
+    }
+
+    interface OnTaskCompletedListener {
+        /**
+         * Will be called when the accept task is completed.
+         *
+         * @param socket   will be {@code null} if the task failed.
+         * @param isSecure is {@code true} when it is listening to a secure RFCOMM channel.
+         */
+        void onTaskCompleted(@Nullable BluetoothSocket socket, boolean isSecure);
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/CarSppManager.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/CarSppManager.java
new file mode 100644
index 0000000..e9f0cc1
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/CarSppManager.java
@@ -0,0 +1,276 @@
+/*
+ * 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.connection.spp;
+
+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 android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.NonNull;
+
+import com.android.car.connecteddevice.AssociationCallback;
+import com.android.car.connecteddevice.connection.AssociationSecureChannel;
+import com.android.car.connecteddevice.connection.CarBluetoothManager;
+import com.android.car.connecteddevice.connection.DeviceMessageStream;
+import com.android.car.connecteddevice.connection.ReconnectSecureChannel;
+import com.android.car.connecteddevice.connection.SecureChannel;
+import com.android.car.connecteddevice.oob.OobChannel;
+import com.android.car.connecteddevice.oob.OobConnectionManager;
+import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
+import com.android.car.connecteddevice.util.EventLog;
+
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+
+/**
+ * Communication manager that allows for targeted connections to a specific device from the car
+ * using {@link SppManager} .
+ */
+public class CarSppManager extends CarBluetoothManager {
+
+    private static final String TAG = "CarSppManager";
+
+    private final SppManager mSppManager;
+
+    private final UUID mAssociationServiceUuid;
+
+    private final int mPacketMaxBytes;
+
+    private String mReconnectDeviceId;
+
+    private OobConnectionManager mOobConnectionManager;
+
+    private Executor mCallbackExecutor;
+
+    private AssociationCallback mAssociationCallback;
+
+    /**
+     * Initialize a new instance of manager.
+     *
+     * @param sppManager             {@link SppManager} for establishing connection.
+     * @param connectedDeviceStorage Shared {@link ConnectedDeviceStorage} for companion features.
+     * @param packetMaxBytes         Maximum size in bytes to write in one packet.
+     */
+    public CarSppManager(@NonNull SppManager sppManager,
+            @NonNull ConnectedDeviceStorage connectedDeviceStorage,
+            @NonNull UUID associationServiceUuid,
+            int packetMaxBytes) {
+        super(connectedDeviceStorage);
+        mSppManager = sppManager;
+        mCallbackExecutor = Executors.newSingleThreadExecutor();
+        mAssociationServiceUuid = associationServiceUuid;
+        mPacketMaxBytes = packetMaxBytes;
+    }
+
+    @Override
+    public void stop() {
+        super.stop();
+        reset();
+    }
+
+    @Override
+    public void disconnectDevice(@NonNull String deviceId) {
+        ConnectedRemoteDevice connectedDevice = getConnectedDevice();
+        if (connectedDevice == null || !deviceId.equals(connectedDevice.mDeviceId)) {
+            return;
+        }
+        reset();
+    }
+
+    @Override
+    public AssociationCallback getAssociationCallback() {
+        return mAssociationCallback;
+    }
+
+    @Override
+    public void setAssociationCallback(AssociationCallback callback) {
+        mAssociationCallback = callback;
+    }
+
+    @Override
+    public void initiateConnectionToDevice(@NonNull UUID deviceId) {
+        logd(TAG, "Start spp reconnection listening for device with id: " + deviceId.toString());
+        mReconnectDeviceId = deviceId.toString();
+        mSppManager.unregisterCallback(mAssociationSppCallback);
+        mSppManager.registerCallback(mReconnectSppCallback, mCallbackExecutor);
+        mSppManager.startListening(deviceId);
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        mReconnectDeviceId = null;
+        mAssociationCallback = null;
+        mSppManager.cleanup();
+    }
+
+    /**
+     * Start the association by listening to incoming connect request.
+     */
+    @Override
+    public void startAssociation(@NonNull String nameForAssociation,
+            @NonNull AssociationCallback callback) {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter == null) {
+            loge(TAG, "Bluetooth is unavailable on this device. Unable to start associating.");
+            return;
+        }
+
+        reset();
+        mAssociationCallback = callback;
+        mSppManager.unregisterCallback(mReconnectSppCallback);
+        mSppManager.registerCallback(mAssociationSppCallback, mCallbackExecutor);
+        if (mSppManager.startListening(mAssociationServiceUuid)) {
+            callback.onAssociationStartSuccess(/* deviceName= */ null);
+        } else {
+            callback.onAssociationStartFailure();
+        }
+    }
+
+    /**
+     * Start the association with a new device using out of band verification code exchange
+     */
+    @Override
+    public void startOutOfBandAssociation(@NonNull String nameForAssociation,
+            @NonNull OobChannel oobChannel,
+            @NonNull AssociationCallback callback) {
+
+        logd(TAG, "Starting out of band association.");
+        startAssociation(nameForAssociation, new AssociationCallback() {
+            @Override
+            public void onAssociationStartSuccess(String deviceName) {
+                mAssociationCallback = callback;
+                boolean success = mOobConnectionManager.startOobExchange(oobChannel);
+                if (!success) {
+                    callback.onAssociationStartFailure();
+                    return;
+                }
+                callback.onAssociationStartSuccess(deviceName);
+            }
+
+            @Override
+            public void onAssociationStartFailure() {
+                callback.onAssociationStartFailure();
+            }
+        });
+        mOobConnectionManager = new OobConnectionManager();
+    }
+
+    private void onDeviceConnected(BluetoothDevice device, boolean isReconnect) {
+        onDeviceConnected(device, isReconnect, /* isOob= */ false);
+    }
+
+    private void onDeviceConnected(BluetoothDevice device, boolean isReconnect, boolean isOob) {
+        EventLog.onDeviceConnected();
+        setClientDeviceAddress(device.getAddress());
+        setClientDeviceName(device.getName());
+        DeviceMessageStream secureStream = new SppDeviceMessageStream(mSppManager, device,
+                mPacketMaxBytes);
+        secureStream.setMessageReceivedErrorListener(
+                exception -> {
+                    disconnectWithError("Error occurred in stream: " + exception.getMessage(),
+                            exception);
+                });
+        SecureChannel secureChannel;
+        // TODO(b/157492943): Define an out of band version of ReconnectSecureChannel
+        if (isReconnect) {
+            secureChannel = new ReconnectSecureChannel(secureStream, mStorage, mReconnectDeviceId,
+                    /* expectedChallengeResponse= */ null);
+        } else if (isOob) {
+            // TODO(b/160901821): Integrate Oob with Spp channel
+            loge(TAG, "Oob verification is currently not available for Spp");
+            return;
+        } else {
+            secureChannel = new AssociationSecureChannel(secureStream, mStorage);
+        }
+        secureChannel.registerCallback(mSecureChannelCallback);
+        ConnectedRemoteDevice connectedDevice = new ConnectedRemoteDevice(device, /* gatt= */ null);
+        connectedDevice.mSecureChannel = secureChannel;
+        addConnectedDevice(connectedDevice);
+        if (isReconnect) {
+            setDeviceIdAndNotifyCallbacks(mReconnectDeviceId);
+            mReconnectDeviceId = null;
+        }
+    }
+
+    private final SppManager.ConnectionCallback mReconnectSppCallback =
+            new SppManager.ConnectionCallback() {
+                @Override
+                public void onRemoteDeviceConnected(BluetoothDevice device) {
+                    onDeviceConnected(device, /* isReconnect= */ true);
+                }
+
+                @Override
+                public void onRemoteDeviceDisconnected(BluetoothDevice device) {
+                    ConnectedRemoteDevice connectedDevice = getConnectedDevice(device);
+                    // Reset before invoking callbacks to avoid a race condition with reconnect
+                    // logic.
+                    reset();
+                    String deviceId = connectedDevice == null ? mReconnectDeviceId
+                            : connectedDevice.mDeviceId;
+                    if (deviceId != null) {
+                        logd(TAG, "Connected device " + deviceId + " disconnected.");
+                        mCallbacks.invoke(callback -> callback.onDeviceDisconnected(deviceId));
+                    }
+                }
+            };
+
+    private final SppManager.ConnectionCallback mAssociationSppCallback =
+            new SppManager.ConnectionCallback() {
+                @Override
+                public void onRemoteDeviceConnected(BluetoothDevice device) {
+                    onDeviceConnected(device, /* isReconnect= */ false);
+                    ConnectedRemoteDevice connectedDevice = getConnectedDevice();
+                    if (connectedDevice == null || connectedDevice.mSecureChannel == null) {
+                        loge(TAG,
+                                "No connected device or secure channel found when try to "
+                                        + "associate.");
+                        return;
+                    }
+                    ((AssociationSecureChannel) connectedDevice.mSecureChannel)
+                            .setShowVerificationCodeListener(
+                                    code -> {
+                                        if (mAssociationCallback == null) {
+                                            loge(TAG, "No valid callback for association.");
+                                            return;
+                                        }
+                                        mAssociationCallback.onVerificationCodeAvailable(code);
+                                    });
+                }
+
+                @Override
+                public void onRemoteDeviceDisconnected(BluetoothDevice device) {
+                    ConnectedRemoteDevice connectedDevice = getConnectedDevice(device);
+                    if (isAssociating()) {
+                        mAssociationCallback.onAssociationError(
+                                DEVICE_ERROR_UNEXPECTED_DISCONNECTION);
+                    }
+                    // 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));
+                    }
+                }
+            };
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/ConnectedTask.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/ConnectedTask.java
new file mode 100644
index 0000000..2318e69
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/ConnectedTask.java
@@ -0,0 +1,104 @@
+/*
+ * 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.connection.spp;
+
+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.logi;
+
+import android.bluetooth.BluetoothSocket;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * This task runs during a connection with a remote device. It handles all incoming and outgoing
+ * data.
+ */
+public class ConnectedTask implements Runnable {
+    private static final String TAG = "ConnectedTask";
+    private final BluetoothSocket mSocket;
+    private final InputStream mInputStream;
+    private final OutputStream mOutputStream;
+    private Callback mCallback;
+
+    ConnectedTask(@NonNull InputStream inputStream, @NonNull OutputStream outputStream,
+            @NonNull BluetoothSocket socket,
+            @NonNull Callback callback) {
+        mInputStream = inputStream;
+        mOutputStream = outputStream;
+        mSocket = socket;
+        mCallback = callback;
+    }
+
+    @Override
+    public void run() {
+        logi(TAG, "Begin ConnectedTask: started to listen to incoming messages.");
+        // Keep listening to the InputStream when task started.
+        while (true) {
+            try {
+                int dataLength = mInputStream.available();
+                if (dataLength == 0) {
+                    continue;
+                }
+                byte[] buffer = new byte[dataLength];
+                // Read from the InputStream
+                mInputStream.read(buffer);
+                mCallback.onMessageReceived(buffer);
+                logd(TAG, "received raw bytes from remote device with length: " + dataLength);
+            } catch (IOException e) {
+                loge(TAG,
+                        "Encountered an exception when listening for incoming message, "
+                                + "disconnected", e);
+                mCallback.onDisconnected();
+                break;
+            }
+        }
+    }
+
+    /**
+     * Write to the connected OutputStream.
+     *
+     * @param buffer The bytes to write
+     */
+    void write(@NonNull byte[] buffer) {
+        try {
+            mOutputStream.write(buffer);
+            logd(TAG, "Sent buffer to remote device with length: " + buffer.length);
+        } catch (IOException e) {
+            loge(TAG, "Exception during write", e);
+        }
+    }
+
+    void cancel() {
+        logd(TAG, "cancel connected task: close connected socket.");
+        try {
+            mSocket.close();
+        } catch (IOException e) {
+            loge(TAG, "close() of connected socket failed", e);
+        }
+    }
+
+    interface Callback {
+        void onMessageReceived(@NonNull byte[] message);
+
+        void onDisconnected();
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/SppDeviceMessageStream.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/SppDeviceMessageStream.java
new file mode 100644
index 0000000..d92ddca
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/SppDeviceMessageStream.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.connecteddevice.connection.spp;
+
+import static com.android.car.connecteddevice.util.SafeLog.logd;
+import static com.android.car.connecteddevice.util.SafeLog.logw;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.connecteddevice.connection.DeviceMessageStream;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * Spp message stream to a device.
+ */
+class SppDeviceMessageStream extends DeviceMessageStream {
+
+    private static final String TAG = "SppDeviceMessageStream";
+
+    private final SppManager mSppManager;
+    private final BluetoothDevice mDevice;
+    private final Executor mCallbackExecutor = Executors.newSingleThreadExecutor();
+
+
+    SppDeviceMessageStream(@NonNull SppManager sppManager,
+            @NonNull BluetoothDevice device, int maxWriteSize) {
+        super(maxWriteSize);
+        mSppManager = sppManager;
+        mDevice = device;
+        mSppManager.addOnMessageReceivedListener(this::onMessageReceived, mCallbackExecutor);
+    }
+
+    @Override
+    protected void send(byte[] data) {
+        mSppManager.write(data);
+        sendCompleted();
+    }
+
+    @VisibleForTesting
+    void onMessageReceived(@NonNull BluetoothDevice device, @NonNull byte[] value) {
+        logd(TAG, "Received a message from a device (" + device.getAddress() + ").");
+        if (!mDevice.equals(device)) {
+            logw(TAG, "Received a message from a device (" + device.getAddress() + ") that is not "
+                    + "the expected device (" + mDevice.getAddress() + ") registered to this "
+                    + "stream. Ignoring.");
+            return;
+        }
+
+        onDataReceived(value);
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/SppManager.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/SppManager.java
new file mode 100644
index 0000000..57a9c89
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/SppManager.java
@@ -0,0 +1,331 @@
+/*
+ * 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.connection.spp;
+
+import static com.android.car.connecteddevice.util.SafeLog.logd;
+import static com.android.car.connecteddevice.util.SafeLog.loge;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.connecteddevice.util.ThreadSafeCallbacks;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * A generic class that handles all the Spp connection events including:
+ * <ol>
+ *     <li>listen and accept connection request from client.
+ *     <li>send a message through an established connection.
+ *     <li>notify any connection or message events happening during the connection.
+ * </ol>
+ */
+public class SppManager {
+    private static final String TAG = "SppManager";
+    // Service names and UUIDs of SDP(Service Discovery Protocol) record, need to keep it consistent
+    // among client and server.
+    private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+    private final boolean mIsSecure;
+    private Object mLock = new Object();
+    /**
+     * Task to listen to secure RFCOMM channel.
+     */
+    @VisibleForTesting
+    AcceptTask mAcceptTask;
+    /**
+     * Task to start and maintain a connection.
+     */
+    @VisibleForTesting
+    ConnectedTask mConnectedTask;
+    @VisibleForTesting
+    ExecutorService mConnectionExecutor = Executors.newSingleThreadExecutor();
+    private BluetoothDevice mDevice;
+    @GuardedBy("mLock")
+    private final SppPayloadStream mPayloadStream = new SppPayloadStream();
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    ConnectionState mState;
+    private final ThreadSafeCallbacks<ConnectionCallback> mCallbacks = new ThreadSafeCallbacks<>();
+    private final ThreadSafeCallbacks<OnMessageReceivedListener> mReceivedListeners =
+            new ThreadSafeCallbacks<>();
+
+    public SppManager(@NonNull boolean isSecure) {
+        mPayloadStream.setMessageCompletedListener(this::onMessageCompleted);
+        mIsSecure = isSecure;
+    }
+
+    @VisibleForTesting
+    enum ConnectionState {
+        NONE,
+        LISTEN,
+        CONNECTING,
+        CONNECTED,
+        DISCONNECTED,
+    }
+
+    /**
+     * Registers the given callback to be notified of various events within the {@link SppManager}.
+     *
+     * @param callback The callback to be notified.
+     */
+    void registerCallback(@NonNull ConnectionCallback callback, @NonNull Executor executor) {
+        mCallbacks.add(callback, executor);
+    }
+
+    /**
+     * Unregisters a previously registered callback.
+     *
+     * @param callback The callback to unregister.
+     */
+    void unregisterCallback(@NonNull ConnectionCallback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    /**
+     * Adds a listener to be notified of a write to characteristics.
+     *
+     * @param listener The listener to invoke.
+     */
+    void addOnMessageReceivedListener(@NonNull OnMessageReceivedListener listener,
+            @NonNull Executor executor) {
+        mReceivedListeners.add(listener, executor);
+    }
+
+    /**
+     * Removes the given listener from being notified of characteristic writes.
+     *
+     * @param listener The listener to remove.
+     */
+    void removeOnMessageReceivedListener(@NonNull OnMessageReceivedListener listener) {
+        mReceivedListeners.remove(listener);
+    }
+
+    /**
+     * Start listening to connection request from the client.
+     *
+     * @param serviceUuid The Uuid which the accept task is listening on.
+     * @return {@code true} if listening is started successfully
+     */
+    boolean startListening(@NonNull UUID serviceUuid) {
+        logd(TAG, "Start socket to listening to incoming connection request.");
+        if (mConnectedTask != null) {
+            mConnectedTask.cancel();
+            mConnectedTask = null;
+        }
+
+        // Start the task to listen on a BluetoothServerSocket
+        if (mAcceptTask != null) {
+            mAcceptTask.cancel();
+        }
+        mAcceptTask = new AcceptTask(mAdapter, mIsSecure, serviceUuid, mAcceptTaskListener);
+        if (!mAcceptTask.startListening()) {
+            // TODO(b/159376003): Handle listening error.
+            mAcceptTask.cancel();
+            mAcceptTask = null;
+            return false;
+        }
+        synchronized (mLock) {
+            mState = ConnectionState.LISTEN;
+        }
+        mConnectionExecutor.execute(mAcceptTask);
+        return true;
+    }
+
+    /**
+     * Send data to remote connected bluetooth device.
+     *
+     * @param data the raw data that wait to be sent
+     * @return {@code true} if the message is sent to client successfully.
+     */
+    boolean write(@NonNull byte[] data) {
+        ConnectedTask connectedTask;
+        // Synchronize a copy of the ConnectedTask
+        synchronized (mLock) {
+            if (mState != ConnectionState.CONNECTED) {
+                loge(TAG, "Try to send data when device is disconnected");
+                return false;
+            }
+            connectedTask = mConnectedTask;
+        }
+        byte[] dataReadyToSend = SppPayloadStream.wrapWithArrayLength(data);
+        if (dataReadyToSend == null) {
+            loge(TAG, "Wrapping data with array length failed.");
+            return false;
+        }
+        connectedTask.write(dataReadyToSend);
+        return true;
+    }
+
+    /**
+     * Cleans up the registered listeners.
+     */
+    void cleanup() {
+        // Clears all registered listeners. IHU only supports single connection.
+        mReceivedListeners.clear();
+    }
+
+    /**
+     * Start the ConnectedTask to begin and maintain a RFCOMM channel.
+     *
+     * @param socket   The BluetoothSocket on which the connection was made
+     * @param device   The BluetoothDevice that has been connected
+     * @param isSecure The type of current established channel
+     */
+    @GuardedBy("mLock")
+    private void startConnectionLocked(BluetoothSocket socket, BluetoothDevice device,
+            boolean isSecure) {
+        logd(TAG, "Get accepted bluetooth socket, start listening to incoming messages.");
+
+        // Cancel any task currently running a connection
+        if (mConnectedTask != null) {
+            mConnectedTask.cancel();
+            mConnectedTask = null;
+        }
+
+        // Cancel the accept task because we only want to connect to one device
+        if (mAcceptTask != null) {
+            mAcceptTask.cancel();
+            mAcceptTask = null;
+        }
+        logd(TAG, "Create ConnectedTask: is secure? " + isSecure);
+        InputStream inputStream;
+        OutputStream outputStream;
+        mDevice = device;
+
+        // Get the BluetoothSocket input and output streams
+        try {
+            inputStream = socket.getInputStream();
+            outputStream = socket.getOutputStream();
+        } catch (IOException e) {
+            loge(TAG, "Can not get stream from BluetoothSocket. Connection failed.", e);
+            return;
+        }
+        mState = ConnectionState.CONNECTED;
+        mCallbacks.invoke(callback -> callback.onRemoteDeviceConnected(device));
+
+        // Start the task to manage the connection and perform transmissions
+        mConnectedTask = new ConnectedTask(inputStream, outputStream, socket,
+                mConnectedTaskCallback);
+        mConnectionExecutor.execute(mConnectedTask);
+    }
+
+    private void onMessageCompleted(@NonNull byte[] message) {
+        mReceivedListeners.invoke(listener -> listener.onMessageReceived(mDevice, message));
+    }
+
+    @VisibleForTesting
+    final AcceptTask.OnTaskCompletedListener mAcceptTaskListener =
+            new AcceptTask.OnTaskCompletedListener() {
+                @Override
+                public void onTaskCompleted(BluetoothSocket socket, boolean isSecure) {
+                    if (socket == null) {
+                        loge(TAG, "AcceptTask failed getting the socket");
+                        return;
+                    }
+                    // Connection accepted
+                    synchronized (mLock) {
+                        switch (mState) {
+                            case LISTEN:
+                            case CONNECTING:
+                                startConnectionLocked(socket, socket.getRemoteDevice(), isSecure);
+                                break;
+                            case NONE:
+                                loge(TAG, "AcceptTask is done while in NONE state.");
+                                break;
+                            case CONNECTED:
+                                // Already connected. Terminate new socket.
+                                try {
+                                    socket.close();
+                                } catch (IOException e) {
+                                    loge(TAG, "Could not close unwanted socket", e);
+                                }
+                                break;
+                            case DISCONNECTED:
+                                loge(TAG, "AcceptTask is done while in DISCONNECTED state.");
+                                break;
+                        }
+                    }
+                }
+            };
+
+    @VisibleForTesting
+    final ConnectedTask.Callback mConnectedTaskCallback = new ConnectedTask.Callback() {
+        @Override
+        public void onMessageReceived(byte[] message) {
+            synchronized (mLock) {
+                try {
+                    mPayloadStream.write(message);
+                } catch (IOException e) {
+                    loge(TAG, "Error writes message to spp payload stream: " + e.getMessage());
+                }
+            }
+        }
+
+        @Override
+        public void onDisconnected() {
+            synchronized (mLock) {
+                mState = ConnectionState.DISCONNECTED;
+                mCallbacks.invoke(callback -> callback.onRemoteDeviceDisconnected(mDevice));
+            }
+        }
+    };
+
+    /**
+     * Interface to be notified of various events within the {@link SppManager}.
+     */
+    interface ConnectionCallback {
+
+        /**
+         * Triggered when a bluetooth device connected.
+         *
+         * @param device Remote device that connected on Spp.
+         */
+        void onRemoteDeviceConnected(@NonNull BluetoothDevice device);
+
+        /**
+         * Triggered when a bluetooth device disconnected.
+         *
+         * @param device Remote device that disconnected on Spp.
+         */
+        void onRemoteDeviceDisconnected(@NonNull BluetoothDevice device);
+    }
+
+    /**
+     * An interface for classes that wish to be notified of incoming messages.
+     */
+    interface OnMessageReceivedListener {
+        /**
+         * Triggered when this SppManager receives a write request from a remote device.
+         *
+         * @param device The bluetooth device that sending the message.
+         * @param value  The value that was written.
+         */
+        void onMessageReceived(@NonNull BluetoothDevice device, @NonNull byte[] value);
+    }
+
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/SppPayloadStream.java b/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/SppPayloadStream.java
new file mode 100644
index 0000000..f05a76f
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/connection/spp/SppPayloadStream.java
@@ -0,0 +1,121 @@
+/*
+ * 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.connection.spp;
+
+import static com.android.car.connecteddevice.util.SafeLog.loge;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * For spp stream will be segmented to several parts, a completed message length need to be prepend
+ * to any message sent. This class will take care of the decode and encode of the incoming and
+ * outgoing message.
+ */
+class SppPayloadStream {
+    private static final String TAG = "SppPayloadStream";
+    // An int will take 4 bytes.
+    private static final int LENGTH_BYTES_SIZE = 4;
+    private final ByteArrayOutputStream mPendingStream = new ByteArrayOutputStream();
+    private OnMessageCompletedListener mOnMessageCompletedListener;
+    private int mCurrentMessageTotalLength;
+
+    /**
+     * Writes data to the {@code pendingStream}, inform the {@code messageCompletedListener} when
+     * the message is completed, otherwise store the data into {@code pendingStream} and waiting for
+     * the following parts.
+     *
+     * @param data Received byte array
+     * @throws IOException If there are some errors writing data to {@code pendingStream}
+     */
+    public void write(@NonNull byte[] data) throws IOException {
+        if (mPendingStream.size() == 0) {
+            int currentLength = data.length;
+            // Arbitrarily choose a byte order, need to use the same byte order for server and
+            // client.
+            mCurrentMessageTotalLength = ByteBuffer.wrap(
+                    Arrays.copyOf(data, LENGTH_BYTES_SIZE)).order(
+                    ByteOrder.LITTLE_ENDIAN).getInt();
+            byte[] payload = Arrays.copyOfRange(data, LENGTH_BYTES_SIZE, currentLength);
+            mPendingStream.write(payload);
+        } else {
+            mPendingStream.write(data);
+        }
+
+        if (mPendingStream.size() > mCurrentMessageTotalLength) {
+            // TODO(b/159712861): Handle this situation, e.g. disconnect.
+            loge(TAG, "Received invalid message: " + mPendingStream.toByteArray());
+            return;
+        }
+
+        if (mPendingStream.size() < mCurrentMessageTotalLength) {
+            return;
+        }
+        if (mOnMessageCompletedListener != null) {
+            mOnMessageCompletedListener.onMessageCompleted(mPendingStream.toByteArray());
+        }
+        mPendingStream.reset();
+
+    }
+
+    /**
+     * Register the given listener to be notified when a completed message is received.
+     *
+     * @param listener The listener to be notified
+     */
+    public void setMessageCompletedListener(@Nullable OnMessageCompletedListener listener) {
+        mOnMessageCompletedListener = listener;
+    }
+
+    /**
+     * Wrap the raw byte array with array length.
+     * <p>
+     * Should be called every time when server wants to send a message to client.
+     *
+     * @param rawData Original data
+     * @return The wrapped data
+     * @throws IOException If there are some errors writing data to {@code outputStream}
+     */
+    @Nullable
+    public static byte[] wrapWithArrayLength(@NonNull byte[] rawData) {
+        int length = rawData.length;
+        byte[] lengthBytes = ByteBuffer.allocate(LENGTH_BYTES_SIZE).order(
+                ByteOrder.LITTLE_ENDIAN).putInt(length).array();
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try {
+            outputStream.write(lengthBytes);
+            outputStream.write(rawData);
+        } catch (IOException e) {
+            loge(TAG, "Error wrap data with array length");
+            return null;
+        }
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Interface to be notified when a completed message has been received.
+     */
+    interface OnMessageCompletedListener {
+        void onMessageCompleted(@NonNull byte[] message);
+    }
+}
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/model/OobEligibleDevice.java b/connected-device-lib/src/com/android/car/connecteddevice/model/OobEligibleDevice.java
new file mode 100644
index 0000000..6e910d3
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/model/OobEligibleDevice.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.connecteddevice.model;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.util.Objects;
+
+/** Device that may be used for an out-of-band channel. */
+public class OobEligibleDevice {
+
+    @Retention(SOURCE)
+    @IntDef(value = { OOB_TYPE_BLUETOOTH })
+    public @interface OobType {}
+    public static final int OOB_TYPE_BLUETOOTH = 0;
+
+    private final String mDeviceAddress;
+
+    @OobType
+    private final int mOobType;
+
+    public OobEligibleDevice(@NonNull String deviceAddress, @OobType int oobType) {
+        mDeviceAddress = deviceAddress;
+        mOobType = oobType;
+    }
+
+    @NonNull
+    public String getDeviceAddress() {
+        return mDeviceAddress;
+    }
+
+    @OobType
+    public int getOobType() {
+        return mOobType;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof OobEligibleDevice)) {
+            return false;
+        }
+        OobEligibleDevice device = (OobEligibleDevice) obj;
+        return Objects.equals(device.mDeviceAddress, mDeviceAddress)
+                && device.mOobType == mOobType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDeviceAddress, mOobType);
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/oob/BluetoothRfcommChannel.java b/connected-device-lib/src/com/android/car/connecteddevice/oob/BluetoothRfcommChannel.java
new file mode 100644
index 0000000..1325f11
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/oob/BluetoothRfcommChannel.java
@@ -0,0 +1,159 @@
+/*
+ * 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.oob;
+
+import static com.android.car.connecteddevice.util.SafeLog.logd;
+import static com.android.car.connecteddevice.util.SafeLog.loge;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.connecteddevice.model.OobEligibleDevice;
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.time.Duration;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Handles out of band data exchange over a secure RFCOMM channel.
+ */
+public class BluetoothRfcommChannel implements OobChannel {
+    private static final String TAG = "BluetoothRfcommChannel";
+    // TODO (b/159500330) Generate random UUID.
+    private static final UUID RFCOMM_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
+    private static final Duration CONNECT_RETRY_WAIT = Duration.ofSeconds(1);
+    private BluetoothSocket mBluetoothSocket;
+    private AtomicBoolean mIsInterrupted = new AtomicBoolean();
+    @VisibleForTesting
+    Callback mCallback;
+
+    @Override
+    public void completeOobDataExchange(@NonNull OobEligibleDevice device,
+            @NonNull Callback callback) {
+        completeOobDataExchange(device, callback, BluetoothAdapter.getDefaultAdapter());
+    }
+
+    @VisibleForTesting
+    void completeOobDataExchange(OobEligibleDevice device, Callback callback,
+            BluetoothAdapter bluetoothAdapter) {
+        mCallback = callback;
+
+        BluetoothDevice remoteDevice = bluetoothAdapter.getRemoteDevice(device.getDeviceAddress());
+
+        try {
+            mBluetoothSocket = remoteDevice.createRfcommSocketToServiceRecord(RFCOMM_UUID);
+        } catch (IOException e) {
+            notifyFailure("Rfcomm socket creation with " + remoteDevice.getName() + " failed.", e);
+            return;
+        }
+
+        bluetoothAdapter.cancelDiscovery();
+
+        new Thread() {
+            @Override
+            public void run() {
+                while (!isInterrupted()) {
+                    try {
+                        mBluetoothSocket.connect();
+                        break;
+                    } catch (IOException e) {
+                        logd(TAG, "Unable to connect, trying again in "
+                                + CONNECT_RETRY_WAIT.toMillis() + " ms.");
+                    }
+                    try {
+                        Thread.sleep(CONNECT_RETRY_WAIT.toMillis());
+                    } catch (InterruptedException e) {
+                        loge(TAG, "Thread was interrupted before connection could be made.", e);
+                        Thread.currentThread().interrupt();
+                        return;
+                    }
+                }
+
+                notifySuccess();
+            }
+        }.start();
+    }
+
+    @Override
+    public void sendOobData(byte[] oobData) {
+        if (isInterrupted()) {
+            return;
+        }
+        if (mBluetoothSocket == null) {
+            notifyFailure("Bluetooth socket is null, oob data cannot be sent",
+                    /* exception= */ null);
+            return;
+        }
+        try {
+            OutputStream stream = mBluetoothSocket.getOutputStream();
+            stream.write(oobData);
+            stream.flush();
+            stream.close();
+        } catch (IOException e) {
+            notifyFailure("Sending oob data failed", e);
+        }
+    }
+
+    @Override
+    public void interrupt() {
+        logd(TAG, "Interrupt received.");
+        mIsInterrupted.set(true);
+    }
+
+    @VisibleForTesting
+    boolean isInterrupted() {
+        if (!mIsInterrupted.get()) {
+            return false;
+        }
+
+        if (mBluetoothSocket == null) {
+            return true;
+        }
+
+        try {
+            OutputStream stream = mBluetoothSocket.getOutputStream();
+            stream.flush();
+            stream.close();
+        } catch (IOException e) {
+            loge(TAG, "Unable to clean up bluetooth socket on interrupt.", e);
+        }
+
+        mBluetoothSocket = null;
+        return true;
+    }
+
+    private void notifyFailure(@NonNull String message, @Nullable Exception exception) {
+        loge(TAG, message, exception);
+        if (mCallback != null && !isInterrupted()) {
+            mCallback.onOobExchangeFailure();
+        }
+    }
+
+    private void notifySuccess() {
+        if (mCallback != null && !isInterrupted()) {
+            mCallback.onOobExchangeSuccess();
+        }
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/oob/OobChannel.java b/connected-device-lib/src/com/android/car/connecteddevice/oob/OobChannel.java
new file mode 100644
index 0000000..9be7941
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/oob/OobChannel.java
@@ -0,0 +1,68 @@
+/*
+ * 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.oob;
+
+import androidx.annotation.NonNull;
+
+import com.android.car.connecteddevice.model.OobEligibleDevice;
+
+/**
+ * An interface for handling out of band data exchange. This interface should be implemented for
+ * every out of band channel that is supported in device association.
+ *
+ * Usage is:
+ * <pre>
+ *     1. Define success and failure responses in {@link Callback}
+ *     2. Call {@link OobChannel#completeOobDataExchange(OobEligibleDevice, Callback)}
+ * </pre>
+ */
+public interface OobChannel {
+    /**
+     * Exchange out of band data with a remote device. This must be done prior to the start of the
+     * association with that device.
+     *
+     * @param device The remote device to exchange out of band data with
+     */
+    void completeOobDataExchange(@NonNull OobEligibleDevice device, @NonNull Callback callback);
+
+    /**
+     * Send raw data over the out of band channel
+     *
+     * @param oobData to be sent
+     */
+    void sendOobData(@NonNull byte[] oobData);
+
+    /** Interrupt the current data exchange and prevent callbacks from being issued. */
+    void interrupt();
+
+    /**
+     * Callbacks for {@link OobChannel#completeOobDataExchange(OobEligibleDevice, Callback)}
+     */
+    interface Callback {
+        /**
+         * Called when {@link OobChannel#completeOobDataExchange(OobEligibleDevice, Callback)}
+         * finishes successfully.
+         */
+        void onOobExchangeSuccess();
+
+        /**
+         * Called when {@link OobChannel#completeOobDataExchange(OobEligibleDevice, Callback)}
+         * fails.
+         */
+        void onOobExchangeFailure();
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/oob/OobConnectionManager.java b/connected-device-lib/src/com/android/car/connecteddevice/oob/OobConnectionManager.java
new file mode 100644
index 0000000..f1564dd
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/oob/OobConnectionManager.java
@@ -0,0 +1,158 @@
+/*
+ * 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.oob;
+
+import static com.android.car.connecteddevice.util.SafeLog.loge;
+
+import android.security.keystore.KeyProperties;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.google.common.primitives.Bytes;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * This is a class that manages a token--{@link OobConnectionManager#mEncryptionKey}-- passed via
+ * an out of band {@link OobChannel} that is distinct from the channel that is currently being
+ * secured.
+ * <p>Intended usage:
+*  <pre>{@code
+ *  OobConnectionManager oobConncetionManager = new OobConnectionManager();
+ *  oobConnectionManager.startOobExchange(channel);
+ *  }</pre>
+ * <pre>{@code When a message is received:
+ *   verificationCode = OobConnectionManager#decryptVerificationCode(byte[])
+ *   check that verification code is valid
+ *   if it is:
+ *     encryptedMessage =  OobConnectionManager#encryptVerificationCode(byte[])
+ *     send encryptedMessage
+ *     verify handshake
+ *   otherwise:
+ *     fail handshake
+ * }</pre>
+ *
+ * <pre>{@code
+ * when oobData is received via the out of band channel:
+ *   OobConnectionManager#setOobData(byte[])
+ *
+ * encryptedMessage = OobConnectionManager#encryptVerificationCode(byte[])
+ * sendMessage
+ * when a message is received:
+ *   verificationCode = OobConnectionManager#decryptVerificationCode(byte[])
+ *   check that verification code is valid
+ *   if it is:
+ *     verify handshake
+ *   otherwise:
+ *     fail handshake
+ * }</pre>
+ */
+public class OobConnectionManager {
+    private static final String TAG = "OobConnectionManager";
+    private static final String ALGORITHM = "AES/GCM/NoPadding";
+    // The nonce length is chosen to be consistent with the standard specification:
+    // Section 8.2 of https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
+    @VisibleForTesting
+    static final int NONCE_LENGTH_BYTES = 12;
+
+    private final Cipher mCipher;
+    @VisibleForTesting
+    byte[] mEncryptionIv = new byte[NONCE_LENGTH_BYTES];
+    @VisibleForTesting
+    byte[] mDecryptionIv = new byte[NONCE_LENGTH_BYTES];
+    @VisibleForTesting
+    SecretKey mEncryptionKey;
+
+    public OobConnectionManager() {
+        try {
+            mCipher = Cipher.getInstance(ALGORITHM);
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            loge(TAG, "Unable to create cipher with " + ALGORITHM + ".", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Encrypts {@param verificationCode} using {@link OobConnectionManager#mEncryptionKey}
+     */
+    @NonNull
+    public byte[] encryptVerificationCode(@NonNull byte[] verificationCode)
+            throws InvalidAlgorithmParameterException,
+            BadPaddingException, InvalidKeyException, IllegalBlockSizeException {
+        mCipher.init(Cipher.ENCRYPT_MODE, mEncryptionKey, new IvParameterSpec(mEncryptionIv));
+        return mCipher.doFinal(verificationCode);
+    }
+
+    /**
+     * Decrypts {@param encryptedMessage} using {@link OobConnectionManager#mEncryptionKey}
+     */
+    @NonNull
+    public byte[] decryptVerificationCode(@NonNull byte[] encryptedMessage)
+            throws InvalidAlgorithmParameterException, BadPaddingException, InvalidKeyException,
+            IllegalBlockSizeException {
+        mCipher.init(Cipher.DECRYPT_MODE, mEncryptionKey, new IvParameterSpec(mDecryptionIv));
+        return mCipher.doFinal(encryptedMessage);
+    }
+
+    void setOobData(@NonNull byte[] oobData) {
+        mEncryptionIv = Arrays.copyOfRange(oobData, 0, NONCE_LENGTH_BYTES);
+        mDecryptionIv = Arrays.copyOfRange(oobData, NONCE_LENGTH_BYTES,
+                NONCE_LENGTH_BYTES * 2);
+        mEncryptionKey = new SecretKeySpec(
+                Arrays.copyOfRange(oobData, NONCE_LENGTH_BYTES * 2, oobData.length),
+                KeyProperties.KEY_ALGORITHM_AES);
+    }
+
+    /**
+     * Start the out of band exchange with a given {@link OobChannel}.
+     *
+     * @param oobChannel Channel to be used for exchange.
+     * @return {@code true} if exchange started successfully. {@code false} if an error occurred.
+     */
+    public boolean startOobExchange(@NonNull OobChannel oobChannel) {
+        KeyGenerator keyGenerator = null;
+        try {
+            keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
+        } catch (NoSuchAlgorithmException e) {
+            loge(TAG, "Unable to get AES key generator.", e);
+            return false;
+        }
+        mEncryptionKey = keyGenerator.generateKey();
+
+        SecureRandom secureRandom = new SecureRandom();
+        secureRandom.nextBytes(mEncryptionIv);
+        secureRandom.nextBytes(mDecryptionIv);
+
+        oobChannel.sendOobData(
+                Bytes.concat(mDecryptionIv, mEncryptionIv, mEncryptionKey.getEncoded()));
+        return true;
+    }
+}
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 19d894e..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,11 +27,13 @@
 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;
 import com.android.car.connecteddevice.model.AssociatedDevice;
-import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
 import java.security.InvalidAlgorithmParameterException;
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 1efae3e..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.
  */
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 e550b54..92810a4 100644
--- a/connected-device-lib/tests/unit/Android.bp
+++ b/connected-device-lib/tests/unit/Android.bp
@@ -45,7 +45,8 @@
         "libstaticjvmtiagent",
     ],
 
-    platform_apis: true,
+    sdk_version: "system_current",
+    min_sdk_version: "28",
 
     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 3e345c2..fe0386c 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
@@ -31,18 +31,15 @@
 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;
 import com.android.car.connecteddevice.ConnectedDeviceManager.DeviceAssociationCallback;
 import com.android.car.connecteddevice.ConnectedDeviceManager.DeviceCallback;
 import com.android.car.connecteddevice.ConnectedDeviceManager.MessageDeliveryDelegate;
-import com.android.car.connecteddevice.ble.CarBleCentralManager;
-import com.android.car.connecteddevice.ble.CarBleManager;
-import com.android.car.connecteddevice.ble.CarBlePeripheralManager;
-import com.android.car.connecteddevice.ble.DeviceMessage;
+import com.android.car.connecteddevice.connection.CarBluetoothManager;
+import com.android.car.connecteddevice.connection.DeviceMessage;
 import com.android.car.connecteddevice.model.AssociatedDevice;
 import com.android.car.connecteddevice.model.ConnectedDevice;
 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
@@ -87,10 +84,7 @@
     private ConnectedDeviceStorage mMockStorage;
 
     @Mock
-    private CarBlePeripheralManager mMockPeripheralManager;
-
-    @Mock
-    private CarBleCentralManager mMockCentralManager;
+    private CarBluetoothManager mMockCarBluetoothManager;
 
     private ConnectedDeviceManager mConnectedDeviceManager;
 
@@ -106,8 +100,8 @@
                 .startMocking();
         ArgumentCaptor<AssociatedDeviceCallback> callbackCaptor = ArgumentCaptor
                 .forClass(AssociatedDeviceCallback.class);
-        mConnectedDeviceManager = new ConnectedDeviceManager(mMockStorage, mMockCentralManager,
-            mMockPeripheralManager);
+        mConnectedDeviceManager = new ConnectedDeviceManager(mMockCarBluetoothManager,
+                mMockStorage);
         verify(mMockStorage).setAssociatedDeviceCallback(callbackCaptor.capture());
         when(mMockStorage.getActiveUserAssociatedDevices()).thenReturn(mUserDevices);
         when(mMockStorage.getActiveUserAssociatedDeviceIds()).thenReturn(mUserDeviceIds);
@@ -129,7 +123,7 @@
 
     @Test
     public void getActiveUserConnectedDevices_includesNewlyConnectedDevice() {
-        String deviceId = connectNewDevice(mMockCentralManager);
+        String deviceId = connectNewDevice();
         List<ConnectedDevice> activeUserDevices =
                 mConnectedDeviceManager.getActiveUserConnectedDevices();
         ConnectedDevice expectedDevice = new ConnectedDevice(deviceId, /* deviceName= */ null,
@@ -143,14 +137,14 @@
         String otherUserDeviceId = UUID.randomUUID().toString();
         when(mMockStorage.getActiveUserAssociatedDeviceIds()).thenReturn(
                 Collections.singletonList(otherUserDeviceId));
-        mConnectedDeviceManager.addConnectedDevice(deviceId, mMockCentralManager);
+        mConnectedDeviceManager.addConnectedDevice(deviceId);
         assertThat(mConnectedDeviceManager.getActiveUserConnectedDevices()).isEmpty();
     }
 
     @Test
     public void getActiveUserConnectedDevices_reflectsSecureChannelEstablished() {
-        String deviceId = connectNewDevice(mMockCentralManager);
-        mConnectedDeviceManager.onSecureChannelEstablished(deviceId, mMockCentralManager);
+        String deviceId = connectNewDevice();
+        mConnectedDeviceManager.onSecureChannelEstablished(deviceId);
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         assertThat(connectedDevice.hasSecureChannel()).isTrue();
@@ -158,21 +152,14 @@
 
     @Test
     public void getActiveUserConnectedDevices_excludesDisconnectedDevice() {
-        String deviceId = connectNewDevice(mMockCentralManager);
-        mConnectedDeviceManager.removeConnectedDevice(deviceId, mMockCentralManager);
+        String deviceId = connectNewDevice();
+        mConnectedDeviceManager.removeConnectedDevice(deviceId);
         assertThat(mConnectedDeviceManager.getActiveUserConnectedDevices()).isEmpty();
     }
 
-    @Test
-    public void getActiveUserConnectedDevices_unaffectedByOtherManagerDisconnect() {
-        String deviceId = connectNewDevice(mMockCentralManager);
-        mConnectedDeviceManager.removeConnectedDevice(deviceId, mMockPeripheralManager);
-        assertThat(mConnectedDeviceManager.getActiveUserConnectedDevices()).hasSize(1);
-    }
-
     @Test(expected = IllegalStateException.class)
     public void sendMessageSecurely_throwsIllegalStateExceptionIfNoSecureChannel() {
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice device = mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         UUID recipientId = UUID.randomUUID();
         byte[] message = ByteUtils.randomBytes(10);
@@ -181,37 +168,38 @@
 
     @Test
     public void sendMessageSecurely_sendsEncryptedMessage() {
-        String deviceId = connectNewDevice(mMockCentralManager);
-        mConnectedDeviceManager.onSecureChannelEstablished(deviceId, mMockCentralManager);
+        String deviceId = connectNewDevice();
+        mConnectedDeviceManager.onSecureChannelEstablished(deviceId);
         ConnectedDevice device = mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         UUID recipientId = UUID.randomUUID();
         byte[] message = ByteUtils.randomBytes(10);
         mConnectedDeviceManager.sendMessageSecurely(device, recipientId, message);
         ArgumentCaptor<DeviceMessage> messageCaptor = ArgumentCaptor.forClass(DeviceMessage.class);
-        verify(mMockCentralManager).sendMessage(eq(deviceId), messageCaptor.capture());
+        verify(mMockCarBluetoothManager).sendMessage(eq(deviceId), messageCaptor.capture());
         assertThat(messageCaptor.getValue().isMessageEncrypted()).isTrue();
     }
 
     @Test
     public void sendMessageSecurely_doesNotSendIfDeviceDisconnected() {
-        String deviceId = connectNewDevice(mMockCentralManager);
+        String deviceId = connectNewDevice();
         ConnectedDevice device = mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
-        mConnectedDeviceManager.removeConnectedDevice(deviceId, mMockCentralManager);
+        mConnectedDeviceManager.removeConnectedDevice(deviceId);
         UUID recipientId = UUID.randomUUID();
         byte[] message = ByteUtils.randomBytes(10);
         mConnectedDeviceManager.sendMessageSecurely(device, recipientId, message);
-        verify(mMockCentralManager, times(0)).sendMessage(eq(deviceId), any(DeviceMessage.class));
+        verify(mMockCarBluetoothManager, times(0)).sendMessage(eq(deviceId),
+                any(DeviceMessage.class));
     }
 
     @Test
     public void sendMessageUnsecurely_sendsMessageWithoutEncryption() {
-        String deviceId = connectNewDevice(mMockCentralManager);
+        String deviceId = connectNewDevice();
         ConnectedDevice device = mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         UUID recipientId = UUID.randomUUID();
         byte[] message = ByteUtils.randomBytes(10);
         mConnectedDeviceManager.sendMessageUnsecurely(device, recipientId, message);
         ArgumentCaptor<DeviceMessage> messageCaptor = ArgumentCaptor.forClass(DeviceMessage.class);
-        verify(mMockCentralManager).sendMessage(eq(deviceId), messageCaptor.capture());
+        verify(mMockCarBluetoothManager).sendMessage(eq(deviceId), messageCaptor.capture());
         assertThat(messageCaptor.getValue().isMessageEncrypted()).isFalse();
     }
 
@@ -222,7 +210,7 @@
         ConnectionCallback connectionCallback = createConnectionCallback(semaphore);
         mConnectedDeviceManager.registerActiveUserConnectionCallback(connectionCallback,
                 mCallbackExecutor);
-        String deviceId = connectNewDevice(mMockCentralManager);
+        String deviceId = connectNewDevice();
         assertThat(tryAcquire(semaphore)).isTrue();
         ArgumentCaptor<ConnectedDevice> deviceCaptor =
                 ArgumentCaptor.forClass(ConnectedDevice.class);
@@ -243,7 +231,7 @@
         String otherUserDeviceId = UUID.randomUUID().toString();
         when(mMockStorage.getActiveUserAssociatedDeviceIds()).thenReturn(
                 Collections.singletonList(otherUserDeviceId));
-        mConnectedDeviceManager.addConnectedDevice(deviceId, mMockCentralManager);
+        mConnectedDeviceManager.addConnectedDevice(deviceId);
         assertThat(tryAcquire(semaphore)).isFalse();
     }
 
@@ -251,11 +239,11 @@
     public void connectionCallback_onDeviceConnectedNotInvokedForDifferentBleManager()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        String deviceId = connectNewDevice(mMockPeripheralManager);
+        String deviceId = connectNewDevice();
         ConnectionCallback connectionCallback = createConnectionCallback(semaphore);
         mConnectedDeviceManager.registerActiveUserConnectionCallback(connectionCallback,
                 mCallbackExecutor);
-        mConnectedDeviceManager.addConnectedDevice(deviceId, mMockCentralManager);
+        mConnectedDeviceManager.addConnectedDevice(deviceId);
         assertThat(tryAcquire(semaphore)).isFalse();
     }
 
@@ -263,11 +251,11 @@
     public void connectionCallback_onDeviceDisconnectedInvokedForActiveUserDevice()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        String deviceId = connectNewDevice(mMockCentralManager);
+        String deviceId = connectNewDevice();
         ConnectionCallback connectionCallback = createConnectionCallback(semaphore);
         mConnectedDeviceManager.registerActiveUserConnectionCallback(connectionCallback,
                 mCallbackExecutor);
-        mConnectedDeviceManager.removeConnectedDevice(deviceId, mMockCentralManager);
+        mConnectedDeviceManager.removeConnectedDevice(deviceId);
         assertThat(tryAcquire(semaphore)).isTrue();
         ArgumentCaptor<ConnectedDevice> deviceCaptor =
                 ArgumentCaptor.forClass(ConnectedDevice.class);
@@ -280,11 +268,11 @@
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
         String deviceId = UUID.randomUUID().toString();
-        mConnectedDeviceManager.addConnectedDevice(deviceId, mMockCentralManager);
+        mConnectedDeviceManager.addConnectedDevice(deviceId);
         ConnectionCallback connectionCallback = createConnectionCallback(semaphore);
         mConnectedDeviceManager.registerActiveUserConnectionCallback(connectionCallback,
                 mCallbackExecutor);
-        mConnectedDeviceManager.removeConnectedDevice(deviceId, mMockCentralManager);
+        mConnectedDeviceManager.removeConnectedDevice(deviceId);
         assertThat(tryAcquire(semaphore)).isFalse();
     }
 
@@ -296,14 +284,14 @@
         mConnectedDeviceManager.registerActiveUserConnectionCallback(connectionCallback,
                 mCallbackExecutor);
         mConnectedDeviceManager.unregisterConnectionCallback(connectionCallback);
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         assertThat(tryAcquire(semaphore)).isFalse();
     }
 
     @Test
     public void registerDeviceCallback_blacklistsDuplicateRecipientId()
             throws InterruptedException {
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         Semaphore firstSemaphore = new Semaphore(0);
@@ -344,14 +332,13 @@
     @Test
     public void deviceCallback_onSecureChannelEstablishedInvoked() throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         DeviceCallback deviceCallback = createDeviceCallback(semaphore);
         mConnectedDeviceManager.registerDeviceCallback(connectedDevice, mRecipientId,
                 deviceCallback, mCallbackExecutor);
-        mConnectedDeviceManager.onSecureChannelEstablished(connectedDevice.getDeviceId(),
-                mMockCentralManager);
+        mConnectedDeviceManager.onSecureChannelEstablished(connectedDevice.getDeviceId());
         connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         assertThat(tryAcquire(semaphore)).isTrue();
@@ -359,27 +346,10 @@
     }
 
     @Test
-    public void deviceCallback_onSecureChannelEstablishedNotInvokedWithSecondBleManager()
-            throws InterruptedException {
-        Semaphore semaphore = new Semaphore(0);
-        connectNewDevice(mMockCentralManager);
-        ConnectedDevice connectedDevice =
-                mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
-        mConnectedDeviceManager.onSecureChannelEstablished(connectedDevice.getDeviceId(),
-                mMockCentralManager);
-        DeviceCallback deviceCallback = createDeviceCallback(semaphore);
-        mConnectedDeviceManager.registerDeviceCallback(connectedDevice, mRecipientId,
-                deviceCallback, mCallbackExecutor);
-        mConnectedDeviceManager.onSecureChannelEstablished(connectedDevice.getDeviceId(),
-                mMockPeripheralManager);
-        assertThat(tryAcquire(semaphore)).isFalse();
-    }
-
-    @Test
     public void deviceCallback_onMessageReceivedInvokedForSameRecipientId()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         DeviceCallback deviceCallback = createDeviceCallback(semaphore);
@@ -396,7 +366,7 @@
     public void deviceCallback_onMessageReceivedNotInvokedForDifferentRecipientId()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         DeviceCallback deviceCallback = createDeviceCallback(semaphore);
@@ -411,7 +381,7 @@
     @Test
     public void deviceCallback_onDeviceErrorInvokedOnChannelError() throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         DeviceCallback deviceCallback = createDeviceCallback(semaphore);
@@ -426,7 +396,7 @@
     public void unregisterDeviceCallback_removesCallbackAndNotInvoked()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         DeviceCallback deviceCallback = createDeviceCallback(semaphore);
@@ -434,8 +404,7 @@
                 deviceCallback, mCallbackExecutor);
         mConnectedDeviceManager.unregisterDeviceCallback(connectedDevice, mRecipientId,
                 deviceCallback);
-        mConnectedDeviceManager.onSecureChannelEstablished(connectedDevice.getDeviceId(),
-                mMockPeripheralManager);
+        mConnectedDeviceManager.onSecureChannelEstablished(connectedDevice.getDeviceId());
         assertThat(tryAcquire(semaphore)).isFalse();
     }
 
@@ -443,7 +412,7 @@
     public void registerDeviceCallback_sendsMissedMessageAfterRegistration()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         byte[] payload = ByteUtils.randomBytes(10);
@@ -460,7 +429,7 @@
     public void registerDeviceCallback_sendsMultipleMissedMessagesAfterRegistration()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         byte[] payload1 = ByteUtils.randomBytes(10);
@@ -481,7 +450,7 @@
     public void registerDeviceCallback_doesNotSendMissedMessageForDifferentRecipient()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         byte[] payload = ByteUtils.randomBytes(10);
@@ -497,8 +466,8 @@
     public void registerDeviceCallback_doesNotSendMissedMessageForDifferentDevice()
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        connectNewDevice(mMockCentralManager);
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
+        connectNewDevice();
         List<ConnectedDevice> connectedDevices =
                 mConnectedDeviceManager.getActiveUserConnectedDevices();
         ConnectedDevice connectedDevice = connectedDevices.get(0);
@@ -516,7 +485,7 @@
     public void onAssociationCompleted_disconnectsOriginalDeviceAndReconnectsAsActiveUser()
             throws InterruptedException {
         String deviceId = UUID.randomUUID().toString();
-        mConnectedDeviceManager.addConnectedDevice(deviceId, mMockPeripheralManager);
+        mConnectedDeviceManager.addConnectedDevice(deviceId);
         Semaphore semaphore = new Semaphore(0);
         ConnectionCallback connectionCallback = createConnectionCallback(semaphore);
         mConnectedDeviceManager.registerActiveUserConnectionCallback(connectionCallback,
@@ -579,10 +548,10 @@
                 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))
+        mConnectedDeviceManager.addConnectedDevice(deviceId);
+        clearInvocations(mMockCarBluetoothManager);
+        mConnectedDeviceManager.removeConnectedDevice(deviceId);
+        verify(mMockCarBluetoothManager, timeout(1000))
                 .connectToDevice(eq(UUID.fromString(deviceId)));
     }
 
@@ -596,15 +565,15 @@
                 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)));
+        mConnectedDeviceManager.addConnectedDevice(deviceId);
+        clearInvocations(mMockCarBluetoothManager);
+        mConnectedDeviceManager.removeConnectedDevice(deviceId);
+        verify(mMockCarBluetoothManager, timeout(1000)).connectToDevice(
+                eq(UUID.fromString(userDeviceId)));
     }
 
     @Test
-    public void removeConnectedDevice__doesNotAdvertiseForNonActiveUserDeviceNotLastDevice() {
+    public void removeConnectedDevice_doesNotAdvertiseForNonActiveUserDeviceNotLastDevice() {
         String deviceId = UUID.randomUUID().toString();
         String userDeviceId = UUID.randomUUID().toString();
         when(mMockStorage.getActiveUserAssociatedDeviceIds()).thenReturn(
@@ -613,11 +582,11 @@
                 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))
+        mConnectedDeviceManager.addConnectedDevice(deviceId);
+        mConnectedDeviceManager.addConnectedDevice(userDeviceId);
+        clearInvocations(mMockCarBluetoothManager);
+        mConnectedDeviceManager.removeConnectedDevice(deviceId);
+        verify(mMockCarBluetoothManager, timeout(1000).times(0))
                 .connectToDevice(any());
     }
 
@@ -630,9 +599,9 @@
 
     @Test
     public void removeActiveUserAssociatedDevice_disconnectsIfConnected() {
-        String deviceId = connectNewDevice(mMockPeripheralManager);
+        String deviceId = connectNewDevice();
         mConnectedDeviceManager.removeActiveUserAssociatedDevice(deviceId);
-        verify(mMockPeripheralManager).disconnectDevice(deviceId);
+        verify(mMockCarBluetoothManager).disconnectDevice(deviceId);
     }
 
     @Test
@@ -651,14 +620,14 @@
 
     @Test
     public void disableAssociatedDeviceConnection_disconnectsIfConnected() {
-        String deviceId = connectNewDevice(mMockPeripheralManager);
+        String deviceId = connectNewDevice();
         mConnectedDeviceManager.disableAssociatedDeviceConnection(deviceId);
-        verify(mMockPeripheralManager).disconnectDevice(deviceId);
+        verify(mMockCarBluetoothManager).disconnectDevice(deviceId);
     }
 
     @Test
     public void onMessageReceived_deliversMessageIfDelegateIsNull() throws InterruptedException {
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         Semaphore semaphore = new Semaphore(0);
@@ -673,7 +642,7 @@
 
     @Test
     public void onMessageReceived_deliversMessageIfDelegateAccepts() throws InterruptedException {
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         Semaphore semaphore = new Semaphore(0);
@@ -690,7 +659,7 @@
     @Test
     public void onMessageReceived_doesNotDeliverMessageIfDelegateRejects()
             throws InterruptedException {
-        connectNewDevice(mMockCentralManager);
+        connectNewDevice();
         ConnectedDevice connectedDevice =
                 mConnectedDeviceManager.getActiveUserConnectedDevices().get(0);
         Semaphore semaphore = new Semaphore(0);
@@ -705,13 +674,13 @@
     }
 
     @NonNull
-    private String connectNewDevice(@NonNull CarBleManager carBleManager) {
+    private String connectNewDevice() {
         String deviceId = UUID.randomUUID().toString();
         AssociatedDevice device = new AssociatedDevice(deviceId, TEST_DEVICE_ADDRESS,
                 TEST_DEVICE_NAME, /* isConnectionEnabled= */ true);
         mUserDeviceIds.add(deviceId);
         mUserDevices.add(device);
-        mConnectedDeviceManager.addConnectedDevice(deviceId, carBleManager);
+        mConnectedDeviceManager.addConnectedDevice(deviceId);
         return deviceId;
     }
 
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
deleted file mode 100644
index ac58a97..0000000
--- a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/CarBlePeripheralManagerTest.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.connecteddevice.ble;
-
-import static com.google.common.truth.Truth.assertThat;
-
-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.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;
-
-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 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;
-import org.mockito.Mock;
-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 final Duration RECONNECT_ADVERTISEMENT_DURATION = Duration.ofSeconds(2);
-    private static final int DEFAULT_MTU_SIZE = 23;
-
-    private static String sAdapterName;
-
-    @Mock
-    private BlePeripheralManager mMockPeripheralManager;
-    @Mock
-    private ConnectedDeviceStorage mMockStorage;
-
-    private CarBlePeripheralManager mCarBlePeripheralManager;
-
-    private MockitoSession mMockitoSession;
-
-    @BeforeClass
-    public static void beforeSetUp() {
-        sAdapterName = BluetoothAdapter.getDefaultAdapter().getName();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mMockitoSession = mockitoSession()
-                .initMocks(this)
-                .strictness(Strictness.WARN)
-                .startMocking();
-        mCarBlePeripheralManager = new CarBlePeripheralManager(mMockPeripheralManager, mMockStorage,
-                ASSOCIATION_SERVICE_UUID, RECONNECT_SERVICE_UUID, RECONNECT_DATA_UUID,
-                WRITE_UUID, READ_UUID, RECONNECT_ADVERTISEMENT_DURATION, DEFAULT_MTU_SIZE);
-    }
-
-    @After
-    public void tearDown() {
-        if (mCarBlePeripheralManager != null) {
-            mCarBlePeripheralManager.stop();
-        }
-        if (mMockitoSession != null) {
-            mMockitoSession.finishMocking();
-        }
-    }
-
-    @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);
-    }
-
-    @Test
-    public void testStartAssociationAdvertisingFailure() throws InterruptedException {
-        Semaphore semaphore = new Semaphore(0);
-        AssociationCallback callback = createAssociationCallback(semaphore);
-        startAssociation(callback, getNameForAssociation());
-        ArgumentCaptor<AdvertiseCallback> callbackCaptor =
-                ArgumentCaptor.forClass(AdvertiseCallback.class);
-        verify(mMockPeripheralManager, timeout(3000))
-                .startAdvertising(any(), any(), callbackCaptor.capture());
-        AdvertiseCallback advertiseCallback = callbackCaptor.getValue();
-        int testErrorCode = 2;
-        advertiseCallback.onStartFailure(testErrorCode);
-        assertThat(tryAcquire(semaphore)).isTrue();
-        verify(callback).onAssociationStartFailure();
-    }
-
-    @Test
-    public void testNotifyAssociationSuccess() throws InterruptedException {
-        Semaphore semaphore = new Semaphore(0);
-        AssociationCallback callback = createAssociationCallback(semaphore);
-        String testDeviceName = getNameForAssociation();
-        startAssociation(callback, testDeviceName);
-        ArgumentCaptor<AdvertiseCallback> callbackCaptor =
-                ArgumentCaptor.forClass(AdvertiseCallback.class);
-        verify(mMockPeripheralManager, timeout(3000))
-                .startAdvertising(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));
-    }
-
-    @Test
-    public void testShowVerificationCode() throws InterruptedException {
-        Semaphore semaphore = new Semaphore(0);
-        AssociationCallback callback = createAssociationCallback(semaphore);
-        AssociationSecureChannel channel = getChannelForAssociation(callback);
-        channel.getShowVerificationCodeListener().showVerificationCode(TEST_VERIFICATION_CODE);
-        assertThat(tryAcquire(semaphore)).isTrue();
-        verify(callback).onVerificationCodeAvailable(eq(TEST_VERIFICATION_CODE));
-    }
-
-    @Test
-    public void testAssociationSuccess() throws InterruptedException {
-        Semaphore semaphore = new Semaphore(0);
-        AssociationCallback callback = createAssociationCallback(semaphore);
-        SecureBleChannel channel = getChannelForAssociation(callback);
-        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()));
-    }
-
-    @Test
-    public void testAssociationFailure_channelError() throws InterruptedException {
-        Semaphore semaphore = new Semaphore(0);
-        AssociationCallback callback = createAssociationCallback(semaphore);
-        SecureBleChannel channel = getChannelForAssociation(callback);
-        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));
-    }
-
-    @Test
-    public void connectToDevice_stopsAdvertisingAfterTimeout() {
-        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());
-        callbackCaptor.getValue().onStartSuccess(null);
-        verify(mMockPeripheralManager,
-                timeout(RECONNECT_ADVERTISEMENT_DURATION.plusSeconds(1).toMillis()))
-                .stopAdvertising(any(AdvertiseCallback.class));
-    }
-
-    private BlePeripheralManager.Callback startAssociation(AssociationCallback callback,
-            String deviceName) {
-        ArgumentCaptor<BlePeripheralManager.Callback> callbackCaptor =
-                ArgumentCaptor.forClass(BlePeripheralManager.Callback.class);
-        mCarBlePeripheralManager.startAssociation(deviceName, callback);
-        verify(mMockPeripheralManager, timeout(3000)).registerCallback(callbackCaptor.capture());
-        return callbackCaptor.getValue();
-    }
-
-    private AssociationSecureChannel getChannelForAssociation(AssociationCallback callback) {
-        BlePeripheralManager.Callback bleManagerCallback = startAssociation(callback,
-                getNameForAssociation());
-        BluetoothDevice bluetoothDevice = BluetoothAdapter.getDefaultAdapter()
-                .getRemoteDevice(TEST_REMOTE_DEVICE_ADDRESS);
-        bleManagerCallback.onRemoteDeviceConnected(bluetoothDevice);
-        return (AssociationSecureChannel) mCarBlePeripheralManager.getConnectedDeviceChannel();
-    }
-
-    private boolean tryAcquire(Semaphore semaphore) throws InterruptedException {
-        return semaphore.tryAcquire(100, TimeUnit.MILLISECONDS);
-    }
-
-    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/AssociationSecureChannelTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/AssociationSecureChannelTest.java
similarity index 87%
rename from connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/AssociationSecureChannelTest.java
rename to connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/AssociationSecureChannelTest.java
index 1a4941c..118eca9 100644
--- 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/connection/AssociationSecureChannelTest.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -27,14 +27,14 @@
 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 android.car.encryptionrunner.FakeEncryptionRunner;
 
 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.StreamProtos.OperationProto.OperationType;
+import com.android.car.connecteddevice.connection.ble.BleDeviceMessageStream;
 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
 import com.android.car.connecteddevice.util.ByteUtils;
 
@@ -68,7 +68,7 @@
     private MockitoSession mMockitoSession;
 
     private AssociationSecureChannel mChannel;
-    private MessageReceivedListener mMessageReceivedListener;
+    private DeviceMessageStream.MessageReceivedListener mMessageReceivedListener;
 
     @Before
     public void setUp() {
@@ -90,7 +90,7 @@
     public void testEncryptionHandshake_Association() throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
         ChannelCallback callbackSpy = spy(new ChannelCallback(semaphore));
-        setupAssociationSecureChannel(callbackSpy, EncryptionRunnerFactory::newDummyRunner);
+        setupAssociationSecureChannel(callbackSpy, EncryptionRunnerFactory::newFakeRunner);
         ArgumentCaptor<String> deviceIdCaptor = ArgumentCaptor.forClass(String.class);
         ArgumentCaptor<DeviceMessage> messageCaptor =
                 ArgumentCaptor.forClass(DeviceMessage.class);
@@ -98,7 +98,7 @@
         initHandshakeMessage();
         verify(mStreamMock).writeMessage(messageCaptor.capture(), any());
         byte[] response = messageCaptor.getValue().getMessage();
-        assertThat(response).isEqualTo(DummyEncryptionRunner.INIT_RESPONSE.getBytes());
+        assertThat(response).isEqualTo(FakeEncryptionRunner.INIT_RESPONSE.getBytes());
 
         respondToContinueMessage();
         verify(mShowVerificationCodeListenerMock).showVerificationCode(anyString());
@@ -123,13 +123,13 @@
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
         ChannelCallback callbackSpy = spy(new ChannelCallback(semaphore));
-        setupAssociationSecureChannel(callbackSpy, EncryptionRunnerFactory::newDummyRunner);
+        setupAssociationSecureChannel(callbackSpy, EncryptionRunnerFactory::newFakeRunner);
 
         // Wrong init handshake message
         respondToContinueMessage();
         assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
         verify(callbackSpy).onEstablishSecureChannelFailure(
-                eq(SecureBleChannel.CHANNEL_ERROR_INVALID_HANDSHAKE)
+                eq(SecureChannel.CHANNEL_ERROR_INVALID_HANDSHAKE)
         );
     }
 
@@ -138,7 +138,7 @@
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
         ChannelCallback callbackSpy = spy(new ChannelCallback(semaphore));
-        setupAssociationSecureChannel(callbackSpy, EncryptionRunnerFactory::newDummyRunner);
+        setupAssociationSecureChannel(callbackSpy, EncryptionRunnerFactory::newFakeRunner);
 
         initHandshakeMessage();
 
@@ -146,7 +146,7 @@
         initHandshakeMessage();
         assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
         verify(callbackSpy).onEstablishSecureChannelFailure(
-                eq(SecureBleChannel.CHANNEL_ERROR_INVALID_HANDSHAKE)
+                eq(SecureChannel.CHANNEL_ERROR_INVALID_HANDSHAKE)
         );
     }
 
@@ -156,8 +156,8 @@
                 encryptionRunnerProvider.getEncryptionRunner());
         mChannel.registerCallback(callback);
         mChannel.setShowVerificationCodeListener(mShowVerificationCodeListenerMock);
-        ArgumentCaptor<MessageReceivedListener> listenerCaptor =
-                ArgumentCaptor.forClass(MessageReceivedListener.class);
+        ArgumentCaptor<DeviceMessageStream.MessageReceivedListener> listenerCaptor =
+                ArgumentCaptor.forClass(DeviceMessageStream.MessageReceivedListener.class);
         verify(mStreamMock).setMessageReceivedListener(listenerCaptor.capture());
         mMessageReceivedListener = listenerCaptor.getValue();
     }
@@ -175,7 +175,7 @@
         DeviceMessage message = new DeviceMessage(
                 /* recipient= */ null,
                 /* isMessageEncrypted= */ false,
-                DummyEncryptionRunner.INIT.getBytes()
+                FakeEncryptionRunner.INIT.getBytes()
         );
         mMessageReceivedListener.onMessageReceived(message, OperationType.ENCRYPTION_HANDSHAKE);
     }
@@ -184,20 +184,20 @@
         DeviceMessage message = new DeviceMessage(
                 /* recipient= */ null,
                 /* isMessageEncrypted= */ false,
-                DummyEncryptionRunner.CLIENT_RESPONSE.getBytes()
+                FakeEncryptionRunner.CLIENT_RESPONSE.getBytes()
         );
         mMessageReceivedListener.onMessageReceived(message, OperationType.ENCRYPTION_HANDSHAKE);
     }
 
     /**
-     * Add the thread control logic into {@link SecureBleChannel.Callback} only for spy purpose.
+     * Add the thread control logic into {@link SecureChannel.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 static class ChannelCallback implements SecureChannel.Callback {
         private final Semaphore mSemaphore;
 
         ChannelCallback(Semaphore semaphore) {
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/connection/DeviceMessageStreamTest.java
similarity index 71%
rename from connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/BleDeviceMessageStreamTest.java
rename to connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/DeviceMessageStreamTest.java
index ed6fb44..661bece 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/connection/DeviceMessageStreamTest.java
@@ -14,39 +14,31 @@
  * limitations under the License.
  */
 
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection;
 
-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.android.car.connecteddevice.StreamProtos.DeviceMessageProto.Message;
+import static com.android.car.connecteddevice.StreamProtos.OperationProto.OperationType;
+import static com.android.car.connecteddevice.StreamProtos.PacketProto.Packet;
+import static com.android.car.connecteddevice.connection.DeviceMessageStream.MessageReceivedErrorListener;
+import static com.android.car.connecteddevice.connection.DeviceMessageStream.MessageReceivedListener;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mockitoSession;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
-import android.annotation.NonNull;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGattCharacteristic;
-
+import androidx.annotation.NonNull;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.car.connecteddevice.util.ByteUtils;
 import com.android.car.protobuf.ByteString;
 
-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.ArrayList;
 import java.util.Arrays;
@@ -56,42 +48,18 @@
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
-public class BleDeviceMessageStreamTest {
+public class DeviceMessageStreamTest {
 
-    private static final int PACKET_SIZE = 500;
+    private static final int WRITE_SIZE = 500;
 
-    private BleDeviceMessageStream mStream;
-
-    @Mock
-    private BlePeripheralManager mMockBlePeripheralManager;
-
-    @Mock
-    private BluetoothDevice mMockBluetoothDevice;
-
-    @Mock
-    private BluetoothGattCharacteristic mMockWriteCharacteristic;
-
-    @Mock
-    private BluetoothGattCharacteristic mMockReadCharacteristic;
-
-    private MockitoSession mMockingSession;
+    private DeviceMessageStream mStream;
 
     @Before
     public void setup() {
-        mMockingSession = mockitoSession()
-                .initMocks(this)
-                .strictness(Strictness.LENIENT)
-                .startMocking();
-
-        mStream = new BleDeviceMessageStream(mMockBlePeripheralManager, mMockBluetoothDevice,
-                mMockWriteCharacteristic, mMockReadCharacteristic, PACKET_SIZE);
-    }
-
-    @After
-    public void cleanup() {
-        if (mMockingSession != null) {
-            mMockingSession.finishMocking();
-        }
+        mStream = spy(new DeviceMessageStream(WRITE_SIZE) {
+            @Override
+            protected void send(byte[] data) { }
+        });
     }
 
     @Test
@@ -127,9 +95,9 @@
         Semaphore semaphore = new Semaphore(0);
         MessageReceivedListener listener = createMessageReceivedListener(semaphore);
         mStream.setMessageReceivedListener(listener);
-        byte[] data = ByteUtils.randomBytes((int) (PACKET_SIZE * 1.5));
-        List<BlePacket> packets1 = createPackets(data);
-        List<BlePacket> packets2 = createPackets(data);
+        byte[] data = ByteUtils.randomBytes((int) (WRITE_SIZE * 1.5));
+        List<Packet> packets1 = createPackets(data);
+        List<Packet> packets2 = createPackets(data);
 
         for (int i = 0; i < packets1.size(); i++) {
             mStream.processPacket(packets1.get(i));
@@ -157,8 +125,8 @@
         Semaphore semaphore = new Semaphore(0);
         mStream.setMessageReceivedListener(createMessageReceivedListener(semaphore));
         mStream.setMessageReceivedErrorListener(createMessageReceivedErrorListener(semaphore));
-        byte[] data = ByteUtils.randomBytes((int) (PACKET_SIZE * 1.5));
-        List<BlePacket> packets = createPackets(data);
+        byte[] data = ByteUtils.randomBytes((int) (WRITE_SIZE * 1.5));
+        List<Packet> packets = createPackets(data);
         for (int i = 0; i < packets.size() - 1; i++) {
             mStream.processPacket(packets.get(i));
         }
@@ -168,11 +136,11 @@
     @Test
     public void processPacket_ignoresDuplicatePacket() {
         Semaphore semaphore = new Semaphore(0);
-        byte[] data = ByteUtils.randomBytes((int) (PACKET_SIZE * 2.5));
+        byte[] data = ByteUtils.randomBytes((int) (WRITE_SIZE * 2.5));
         MessageReceivedListener listener = createMessageReceivedListener(semaphore);
         mStream.setMessageReceivedListener(listener);
         ArgumentCaptor<DeviceMessage> messageCaptor = ArgumentCaptor.forClass(DeviceMessage.class);
-        List<BlePacket> packets = createPackets(data);
+        List<Packet> 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.
@@ -186,7 +154,7 @@
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
         mStream.setMessageReceivedErrorListener(createMessageReceivedErrorListener(semaphore));
-        List<BlePacket> packets = createPackets(ByteUtils.randomBytes((int) (PACKET_SIZE * 2.5)));
+        List<Packet> packets = createPackets(ByteUtils.randomBytes((int) (WRITE_SIZE * 2.5)));
         mStream.processPacket(packets.get(0));
         mStream.processPacket(packets.get(1));
         mStream.processPacket(packets.get(0));
@@ -198,20 +166,20 @@
             throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
         mStream.setMessageReceivedErrorListener(createMessageReceivedErrorListener(semaphore));
-        List<BlePacket> packets = createPackets(ByteUtils.randomBytes((int) (PACKET_SIZE * 1.5)));
+        List<Packet> packets = createPackets(ByteUtils.randomBytes((int) (WRITE_SIZE * 1.5)));
         mStream.processPacket(packets.get(1));
         assertThat(tryAcquire(semaphore)).isTrue();
     }
 
     @NonNull
-    private List<BlePacket> createPackets(byte[] data) {
+    private List<Packet> createPackets(byte[] data) {
         try {
-            BleDeviceMessage message = BleDeviceMessage.newBuilder()
+            Message message = Message.newBuilder()
                     .setPayload(ByteString.copyFrom(data))
                     .setOperation(OperationType.CLIENT_MESSAGE)
                     .build();
-            return BlePacketFactory.makeBlePackets(message.toByteArray(),
-                    ThreadLocalRandom.current().nextInt(), PACKET_SIZE);
+            return PacketFactory.makePackets(message.toByteArray(),
+                    ThreadLocalRandom.current().nextInt(), WRITE_SIZE);
         } catch (Exception e) {
             assertWithMessage("Uncaught exception while making packets.").fail();
             return new ArrayList<>();
@@ -219,8 +187,8 @@
     }
 
     private void processMessage(byte[] data) {
-        List<BlePacket> packets = createPackets(data);
-        for (BlePacket packet : packets) {
+        List<Packet> packets = createPackets(data);
+        for (Packet packet : packets) {
             mStream.processPacket(packet);
         }
     }
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/connection/OobAssociationSecureChannelTest.java
similarity index 62%
copy from connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/AssociationSecureChannelTest.java
copy to connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/OobAssociationSecureChannelTest.java
index 1a4941c..3ad2a8d 100644
--- 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/connection/OobAssociationSecureChannelTest.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.
@@ -14,61 +14,69 @@
  * limitations under the License.
  */
 
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection;
+
+import static com.android.car.connecteddevice.StreamProtos.OperationProto.OperationType;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 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.reset;
 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 android.car.encryptionrunner.FakeEncryptionRunner;
 
-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.connection.ble.BleDeviceMessageStream;
+import com.android.car.connecteddevice.oob.OobConnectionManager;
 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.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
 import java.util.UUID;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
-@RunWith(AndroidJUnit4.class)
-public final class AssociationSecureChannelTest {
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+
+public class OobAssociationSecureChannelTest {
     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;
+    @Mock
+    private OobConnectionManager mOobConnectionManagerMock;
+
+    private OobAssociationSecureChannel mChannel;
+
+    private BleDeviceMessageStream.MessageReceivedListener mMessageReceivedListener;
+
+    private MockitoSession mMockitoSession;
 
     @Before
     public void setUp() {
@@ -87,10 +95,11 @@
     }
 
     @Test
-    public void testEncryptionHandshake_Association() throws InterruptedException {
+    public void testEncryptionHandshake_oobAssociation() throws InterruptedException {
         Semaphore semaphore = new Semaphore(0);
-        ChannelCallback callbackSpy = spy(new ChannelCallback(semaphore));
-        setupAssociationSecureChannel(callbackSpy, EncryptionRunnerFactory::newDummyRunner);
+        ChannelCallback
+                callbackSpy = spy(new ChannelCallback(semaphore));
+        setupOobAssociationSecureChannel(callbackSpy);
         ArgumentCaptor<String> deviceIdCaptor = ArgumentCaptor.forClass(String.class);
         ArgumentCaptor<DeviceMessage> messageCaptor =
                 ArgumentCaptor.forClass(DeviceMessage.class);
@@ -98,12 +107,13 @@
         initHandshakeMessage();
         verify(mStreamMock).writeMessage(messageCaptor.capture(), any());
         byte[] response = messageCaptor.getValue().getMessage();
-        assertThat(response).isEqualTo(DummyEncryptionRunner.INIT_RESPONSE.getBytes());
-
+        assertThat(response).isEqualTo(FakeEncryptionRunner.INIT_RESPONSE.getBytes());
+        reset(mStreamMock);
         respondToContinueMessage();
-        verify(mShowVerificationCodeListenerMock).showVerificationCode(anyString());
-
-        mChannel.notifyOutOfBandAccepted();
+        verify(mStreamMock).writeMessage(messageCaptor.capture(), any());
+        byte[] oobCodeResponse = messageCaptor.getValue().getMessage();
+        assertThat(oobCodeResponse).isEqualTo(FakeEncryptionRunner.VERIFICATION_CODE.getBytes());
+        respondToOobCode();
         sendDeviceId();
         assertThat(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)).isTrue();
         verify(callbackSpy).onDeviceIdReceived(deviceIdCaptor.capture());
@@ -118,48 +128,26 @@
         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());
+    private void setupOobAssociationSecureChannel(ChannelCallback callback) {
+        mChannel = new OobAssociationSecureChannel(mStreamMock, mStorageMock,
+                mOobConnectionManagerMock, EncryptionRunnerFactory.newOobFakeRunner());
         mChannel.registerCallback(callback);
-        mChannel.setShowVerificationCodeListener(mShowVerificationCodeListenerMock);
-        ArgumentCaptor<MessageReceivedListener> listenerCaptor =
-                ArgumentCaptor.forClass(MessageReceivedListener.class);
+        ArgumentCaptor<BleDeviceMessageStream.MessageReceivedListener> listenerCaptor =
+                ArgumentCaptor.forClass(BleDeviceMessageStream.MessageReceivedListener.class);
         verify(mStreamMock).setMessageReceivedListener(listenerCaptor.capture());
         mMessageReceivedListener = listenerCaptor.getValue();
+        try {
+            when(mOobConnectionManagerMock.encryptVerificationCode(any()))
+                    .thenReturn(FakeEncryptionRunner.VERIFICATION_CODE.getBytes());
+        } catch (InvalidAlgorithmParameterException | BadPaddingException | InvalidKeyException
+                | IllegalBlockSizeException e) {
+        }
+        try {
+            when(mOobConnectionManagerMock.decryptVerificationCode(any()))
+                    .thenReturn(FakeEncryptionRunner.VERIFICATION_CODE.getBytes());
+        } catch (InvalidAlgorithmParameterException | BadPaddingException | InvalidKeyException
+                | IllegalBlockSizeException e) {
+        }
     }
 
     private void sendDeviceId() {
@@ -175,7 +163,7 @@
         DeviceMessage message = new DeviceMessage(
                 /* recipient= */ null,
                 /* isMessageEncrypted= */ false,
-                DummyEncryptionRunner.INIT.getBytes()
+                FakeEncryptionRunner.INIT.getBytes()
         );
         mMessageReceivedListener.onMessageReceived(message, OperationType.ENCRYPTION_HANDSHAKE);
     }
@@ -184,20 +172,29 @@
         DeviceMessage message = new DeviceMessage(
                 /* recipient= */ null,
                 /* isMessageEncrypted= */ false,
-                DummyEncryptionRunner.CLIENT_RESPONSE.getBytes()
+                FakeEncryptionRunner.CLIENT_RESPONSE.getBytes()
+        );
+        mMessageReceivedListener.onMessageReceived(message, OperationType.ENCRYPTION_HANDSHAKE);
+    }
+
+    private void respondToOobCode() {
+        DeviceMessage message = new DeviceMessage(
+                /* recipient= */ null,
+                /* isMessageEncrypted= */ false,
+                FakeEncryptionRunner.VERIFICATION_CODE.getBytes()
         );
         mMessageReceivedListener.onMessageReceived(message, OperationType.ENCRYPTION_HANDSHAKE);
     }
 
     /**
-     * Add the thread control logic into {@link SecureBleChannel.Callback} only for spy purpose.
+     * Add the thread control logic into {@link SecureChannel.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 static class ChannelCallback implements SecureChannel.Callback {
         private final Semaphore mSemaphore;
 
         ChannelCallback(Semaphore semaphore) {
@@ -229,8 +226,4 @@
             mSemaphore.release();
         }
     }
-
-    interface EncryptionRunnerProvider {
-        EncryptionRunner getEncryptionRunner();
-    }
 }
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/BlePacketFactoryTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/PacketFactoryTest.java
similarity index 81%
rename from connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/BlePacketFactoryTest.java
rename to connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/PacketFactoryTest.java
index 8e8682f..d5fdc03 100644
--- a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/BlePacketFactoryTest.java
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/PacketFactoryTest.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.
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.car.connecteddevice.BleStreamProtos.BlePacketProto.BlePacket;
+import com.android.car.connecteddevice.StreamProtos.PacketProto.Packet;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -30,7 +30,7 @@
 import java.util.Random;
 
 @RunWith(AndroidJUnit4.class)
-public class BlePacketFactoryTest {
+public class PacketFactoryTest {
     @Test
     public void testGetHeaderSize() {
         // 1 byte to encode the ID, 1 byte for the field number.
@@ -51,13 +51,13 @@
         int expectedHeaderSize = messageIdEncodingSize + payloadSizeEncodingSize
                 + totalPacketsEncodingSize + packetNumberEncodingSize;
 
-        assertThat(BlePacketFactory.getPacketHeaderSize(totalPackets, messageId, payloadSize))
+        assertThat(PacketFactory.getPacketHeaderSize(totalPackets, messageId, payloadSize))
                 .isEqualTo(expectedHeaderSize);
     }
 
     @Test
     public void testGetTotalPackets_withVarintSize1_returnsCorrectPackets()
-            throws BlePacketFactoryException {
+            throws PacketFactoryException {
         int messageId = 1;
         int maxSize = 49;
         int payloadSize = 100;
@@ -67,13 +67,13 @@
         // payload. ceil(payloadSize/38) gives the total packets.
         int expectedTotalPackets = 3;
 
-        assertThat(BlePacketFactory.getTotalPacketNumber(messageId, payloadSize, maxSize))
+        assertThat(PacketFactory.getTotalPacketNumber(messageId, payloadSize, maxSize))
                 .isEqualTo(expectedTotalPackets);
     }
 
     @Test
     public void testGetTotalPackets_withVarintSize2_returnsCorrectPackets()
-            throws BlePacketFactoryException {
+            throws PacketFactoryException {
         int messageId = 1;
         int maxSize = 49;
         int payloadSize = 6000;
@@ -83,13 +83,13 @@
         // payload. ceil(payloadSize/37) gives the total packets.
         int expectedTotalPackets = 163;
 
-        assertThat(BlePacketFactory.getTotalPacketNumber(messageId, payloadSize, maxSize))
+        assertThat(PacketFactory.getTotalPacketNumber(messageId, payloadSize, maxSize))
                 .isEqualTo(expectedTotalPackets);
     }
 
     @Test
     public void testGetTotalPackets_withVarintSize3_returnsCorrectPackets()
-            throws BlePacketFactoryException {
+            throws PacketFactoryException {
         int messageId = 1;
         int maxSize = 49;
         int payloadSize = 1000000;
@@ -99,13 +99,13 @@
         // payload. ceil(payloadSize/36) gives the total packets.
         int expectedTotalPackets = 27778;
 
-        assertThat(BlePacketFactory.getTotalPacketNumber(messageId, payloadSize, maxSize))
+        assertThat(PacketFactory.getTotalPacketNumber(messageId, payloadSize, maxSize))
                 .isEqualTo(expectedTotalPackets);
     }
 
     @Test
     public void testGetTotalPackets_withVarintSize4_returnsCorrectPackets()
-            throws BlePacketFactoryException {
+            throws PacketFactoryException {
         int messageId = 1;
         int maxSize = 49;
         int payloadSize = 178400320;
@@ -115,7 +115,7 @@
         // payload. ceil(payloadSize/35) gives the total packets.
         int expectedTotalPackets = 5097152;
 
-        assertThat(BlePacketFactory.getTotalPacketNumber(messageId, payloadSize, maxSize))
+        assertThat(PacketFactory.getTotalPacketNumber(messageId, payloadSize, maxSize))
                 .isEqualTo(expectedTotalPackets);
     }
 
@@ -125,15 +125,15 @@
         byte[] payload = makePayload(/* length= */ 100);
         int maxSize = 1000;
 
-        List<BlePacket> packets =
-                BlePacketFactory.makeBlePackets(payload, /* mesageId= */ 1, maxSize);
+        List<Packet> packets =
+                PacketFactory.makePackets(payload, /* messageId= */ 1, maxSize);
 
         assertThat(packets).hasSize(1);
 
         ByteArrayOutputStream reconstructedPayload = new ByteArrayOutputStream();
 
         // Combine together all the payloads within the BlePackets.
-        for (BlePacket packet : packets) {
+        for (Packet packet : packets) {
             reconstructedPayload.write(packet.getPayload().toByteArray());
         }
 
@@ -146,15 +146,15 @@
         byte[] payload = makePayload(/* length= */ 10000);
         int maxSize = 50;
 
-        List<BlePacket> packets =
-                BlePacketFactory.makeBlePackets(payload, /* mesageId= */ 1, maxSize);
+        List<Packet> packets =
+                PacketFactory.makePackets(payload, /* messageId= */ 1, maxSize);
 
         assertThat(packets.size()).isGreaterThan(1);
 
         ByteArrayOutputStream reconstructedPayload = new ByteArrayOutputStream();
 
         // Combine together all the payloads within the BlePackets.
-        for (BlePacket packet : packets) {
+        for (Packet packet : packets) {
             reconstructedPayload.write(packet.getPayload().toByteArray());
         }
 
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/connection/SecureChannelTest.java
similarity index 78%
rename from connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ble/SecureBleChannelTest.java
rename to connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/SecureChannelTest.java
index e8f4da4..ce56ebb 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/connection/SecureChannelTest.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.
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.car.connecteddevice.ble;
+package com.android.car.connecteddevice.connection;
 
-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.android.car.connecteddevice.StreamProtos.OperationProto.OperationType.CLIENT_MESSAGE;
+import static com.android.car.connecteddevice.StreamProtos.OperationProto.OperationType.ENCRYPTION_HANDSHAKE;
+import static com.android.car.connecteddevice.connection.SecureChannel.CHANNEL_ERROR_INVALID_HANDSHAKE;
+import static com.android.car.connecteddevice.connection.SecureChannel.Callback;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -29,13 +29,14 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.car.encryptionrunner.DummyEncryptionRunner;
 import android.car.encryptionrunner.EncryptionRunnerFactory;
+import android.car.encryptionrunner.FakeEncryptionRunner;
 import android.car.encryptionrunner.HandshakeException;
 import android.car.encryptionrunner.Key;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.car.connecteddevice.connection.ble.BleDeviceMessageStream;
 import com.android.car.connecteddevice.util.ByteUtils;
 
 import org.junit.After;
@@ -53,7 +54,7 @@
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
-public class SecureBleChannelTest {
+public class SecureChannelTest {
 
     @Mock private BleDeviceMessageStream mMockStream;
 
@@ -81,7 +82,7 @@
 
     private MockitoSession mMockitoSession;
 
-    private SecureBleChannel mSecureBleChannel;
+    private SecureChannel mSecureChannel;
 
     @Before
     public void setUp() throws SignatureException {
@@ -90,12 +91,12 @@
                 .strictness(Strictness.WARN)
                 .startMocking();
 
-        mSecureBleChannel = new SecureBleChannel(mMockStream,
-                EncryptionRunnerFactory.newDummyRunner()) {
+        mSecureChannel = new SecureChannel(mMockStream,
+                EncryptionRunnerFactory.newFakeRunner()) {
             @Override
             void processHandshake(byte[] message) { }
         };
-        mSecureBleChannel.setEncryptionKey(mKey);
+        mSecureChannel.setEncryptionKey(mKey);
     }
 
     @After
@@ -110,7 +111,7 @@
         byte[] payload = ByteUtils.randomBytes(10);
         DeviceMessage message = new DeviceMessage(UUID.randomUUID(), /* isEncrypted= */ false,
                 payload);
-        mSecureBleChannel.processMessage(message);
+        mSecureChannel.processMessage(message);
         assertThat(message.getMessage()).isEqualTo(payload);
         verify(mKey, times(0)).decryptData(any());
     }
@@ -120,7 +121,7 @@
         byte[] payload = ByteUtils.randomBytes(10);
         DeviceMessage message = new DeviceMessage(UUID.randomUUID(), /* isEncrypted= */ true,
                 payload);
-        mSecureBleChannel.processMessage(message);
+        mSecureChannel.processMessage(message);
         verify(mKey).decryptData(any());
     }
 
@@ -131,14 +132,14 @@
         DeviceMessage message = new DeviceMessage(UUID.randomUUID(), /* isEncrypted= */ true,
                 ByteUtils.randomBytes(10));
 
-        mSecureBleChannel.setEncryptionKey(null);
-        mSecureBleChannel.registerCallback(new Callback() {
+        mSecureChannel.setEncryptionKey(null);
+        mSecureChannel.registerCallback(new Callback() {
             @Override
             public void onMessageReceivedError(Exception exception) {
                 semaphore.release();
             }
         });
-        mSecureBleChannel.processMessage(message);
+        mSecureChannel.processMessage(message);
         assertThat(tryAcquire(semaphore)).isTrue();
         assertThat(message.getMessage()).isNull();
     }
@@ -150,15 +151,15 @@
         DeviceMessage message = new DeviceMessage(UUID.randomUUID(), /* isEncrypted= */ true,
                 ByteUtils.randomBytes(10));
 
-        mSecureBleChannel.setEncryptionKey(null);
-        mSecureBleChannel.registerCallback(new Callback() {
+        mSecureChannel.setEncryptionKey(null);
+        mSecureChannel.registerCallback(new Callback() {
             @Override
             public void onEstablishSecureChannelFailure(int error) {
                 assertThat(error).isEqualTo(CHANNEL_ERROR_INVALID_HANDSHAKE);
                 semaphore.release();
             }
         });
-        mSecureBleChannel.onMessageReceived(message, ENCRYPTION_HANDSHAKE);
+        mSecureChannel.onMessageReceived(message, ENCRYPTION_HANDSHAKE);
         assertThat(tryAcquire(semaphore)).isTrue();
     }
 
@@ -169,24 +170,24 @@
         DeviceMessage message = new DeviceMessage(UUID.randomUUID(), /* isEncrypted= */ false,
                 /* message= */ null);
 
-        mSecureBleChannel.registerCallback(new Callback() {
+        mSecureChannel.registerCallback(new Callback() {
             @Override
             public void onMessageReceived(DeviceMessage message) {
                 semaphore.release();
             }
         });
-        mSecureBleChannel.onMessageReceived(message, CLIENT_MESSAGE);
+        mSecureChannel.onMessageReceived(message, CLIENT_MESSAGE);
         assertThat(tryAcquire(semaphore)).isFalse();
     }
 
     @Test
     public void onMessageReceived_processHandshakeExceptionIssuesSecureChannelFailureCallback()
             throws InterruptedException {
-        SecureBleChannel secureChannel = new SecureBleChannel(mMockStream,
-                EncryptionRunnerFactory.newDummyRunner()) {
+        SecureChannel secureChannel = new SecureChannel(mMockStream,
+                EncryptionRunnerFactory.newFakeRunner()) {
             @Override
             void processHandshake(byte[] message) throws HandshakeException {
-                DummyEncryptionRunner.throwHandshakeException("test");
+                FakeEncryptionRunner.throwHandshakeException("test");
             }
         };
         Semaphore semaphore = new Semaphore(0);
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/ble/CarBlePeripheralManagerTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/ble/CarBlePeripheralManagerTest.java
new file mode 100644
index 0000000..b4a229e
--- /dev/null
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/ble/CarBlePeripheralManagerTest.java
@@ -0,0 +1,244 @@
+/*
+ * 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.connection.ble;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mockitoSession;
+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.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.os.ParcelUuid;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.connecteddevice.AssociationCallback;
+import com.android.car.connecteddevice.connection.AssociationSecureChannel;
+import com.android.car.connecteddevice.connection.SecureChannel;
+import com.android.car.connecteddevice.model.AssociatedDevice;
+import com.android.car.connecteddevice.oob.OobConnectionManager;
+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.time.Duration;
+import java.util.UUID;
+
+@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 String TEST_ENCRYPTED_VERIFICATION_CODE = "12345";
+    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 OobConnectionManager mMockOobConnectionManager;
+    @Mock
+    private AssociationCallback mAssociationCallback;
+
+    private CarBlePeripheralManager mCarBlePeripheralManager;
+
+    private MockitoSession mMockitoSession;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.WARN)
+                .startMocking();
+        mCarBlePeripheralManager = new CarBlePeripheralManager(mMockPeripheralManager, mMockStorage,
+                ASSOCIATION_SERVICE_UUID, RECONNECT_SERVICE_UUID,
+                RECONNECT_DATA_UUID, WRITE_UUID, READ_UUID, RECONNECT_ADVERTISEMENT_DURATION,
+                DEFAULT_MTU_SIZE);
+
+        when(mMockOobConnectionManager.encryptVerificationCode(
+                TEST_VERIFICATION_CODE.getBytes())).thenReturn(
+                TEST_ENCRYPTED_VERIFICATION_CODE.getBytes());
+        when(mMockOobConnectionManager.decryptVerificationCode(
+                TEST_ENCRYPTED_VERIFICATION_CODE.getBytes())).thenReturn(
+                TEST_VERIFICATION_CODE.getBytes());
+        mCarBlePeripheralManager.start();
+    }
+
+    @After
+    public void tearDown() {
+        if (mCarBlePeripheralManager != null) {
+            mCarBlePeripheralManager.stop();
+        }
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void testStartAssociationAdvertisingSuccess() {
+        String testDeviceName = getNameForAssociation();
+        startAssociation(mAssociationCallback, testDeviceName);
+        ArgumentCaptor<AdvertiseData> advertisementDataCaptor =
+                ArgumentCaptor.forClass(AdvertiseData.class);
+        ArgumentCaptor<AdvertiseData> scanResponseDataCaptor =
+                ArgumentCaptor.forClass(AdvertiseData.class);
+        verify(mMockPeripheralManager).startAdvertising(any(), advertisementDataCaptor.capture(),
+                scanResponseDataCaptor.capture(), any());
+        AdvertiseData advertisementData = advertisementDataCaptor.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() {
+        startAssociation(mAssociationCallback, getNameForAssociation());
+        ArgumentCaptor<AdvertiseCallback> callbackCaptor =
+                ArgumentCaptor.forClass(AdvertiseCallback.class);
+        verify(mMockPeripheralManager).startAdvertising(any(), any(), any(),
+                callbackCaptor.capture());
+        AdvertiseCallback advertiseCallback = callbackCaptor.getValue();
+        int testErrorCode = 2;
+        advertiseCallback.onStartFailure(testErrorCode);
+        verify(mAssociationCallback).onAssociationStartFailure();
+    }
+
+    @Test
+    public void testNotifyAssociationSuccess() {
+        String testDeviceName = getNameForAssociation();
+        startAssociation(mAssociationCallback, testDeviceName);
+        ArgumentCaptor<AdvertiseCallback> callbackCaptor =
+                ArgumentCaptor.forClass(AdvertiseCallback.class);
+        verify(mMockPeripheralManager).startAdvertising(any(), any(), any(),
+                callbackCaptor.capture());
+        AdvertiseCallback advertiseCallback = callbackCaptor.getValue();
+        AdvertiseSettings settings = new AdvertiseSettings.Builder().build();
+        advertiseCallback.onStartSuccess(settings);
+        verify(mAssociationCallback).onAssociationStartSuccess(eq(testDeviceName));
+    }
+
+    @Test
+    public void testShowVerificationCode() {
+        AssociationSecureChannel channel = getChannelForAssociation(mAssociationCallback);
+        channel.getShowVerificationCodeListener().showVerificationCode(TEST_VERIFICATION_CODE);
+        verify(mAssociationCallback).onVerificationCodeAvailable(eq(TEST_VERIFICATION_CODE));
+    }
+
+    @Test
+    public void testAssociationSuccess() {
+        SecureChannel channel = getChannelForAssociation(mAssociationCallback);
+        SecureChannel.Callback channelCallback = channel.getCallback();
+        assertThat(channelCallback).isNotNull();
+        channelCallback.onDeviceIdReceived(TEST_REMOTE_DEVICE_ID.toString());
+        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());
+        verify(mAssociationCallback).onAssociationCompleted(eq(TEST_REMOTE_DEVICE_ID.toString()));
+    }
+
+    @Test
+    public void testAssociationFailure_channelError() {
+        SecureChannel channel = getChannelForAssociation(mAssociationCallback);
+        SecureChannel.Callback channelCallback = channel.getCallback();
+        int testErrorCode = 1;
+        assertThat(channelCallback).isNotNull();
+        channelCallback.onDeviceIdReceived(TEST_REMOTE_DEVICE_ID.toString());
+        channelCallback.onEstablishSecureChannelFailure(testErrorCode);
+        verify(mAssociationCallback).onAssociationError(eq(testErrorCode));
+    }
+
+    @Test
+    public void connectToDevice_stopsAdvertisingAfterTimeout() {
+        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(), any(),
+                callbackCaptor.capture());
+        callbackCaptor.getValue().onStartSuccess(null);
+        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 =
+                ArgumentCaptor.forClass(BlePeripheralManager.Callback.class);
+        mCarBlePeripheralManager.startAssociation(deviceName, callback);
+        verify(mMockPeripheralManager, timeout(3000)).registerCallback(callbackCaptor.capture());
+        return callbackCaptor.getValue();
+    }
+
+    private AssociationSecureChannel getChannelForAssociation(AssociationCallback callback) {
+        BlePeripheralManager.Callback bleManagerCallback = startAssociation(callback,
+                getNameForAssociation());
+        BluetoothDevice bluetoothDevice = BluetoothAdapter.getDefaultAdapter()
+                .getRemoteDevice(TEST_REMOTE_DEVICE_ADDRESS);
+        bleManagerCallback.onRemoteDeviceConnected(bluetoothDevice);
+        return (AssociationSecureChannel) mCarBlePeripheralManager.getConnectedDeviceChannel();
+    }
+
+    private String getNameForAssociation() {
+        return ByteUtils.generateRandomNumberString(DEVICE_NAME_LENGTH_LIMIT);
+
+    }
+}
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/spp/CarSppManagerTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/spp/CarSppManagerTest.java
new file mode 100644
index 0000000..ef0ee85
--- /dev/null
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/spp/CarSppManagerTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.connection.spp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.NonNull;
+
+import com.android.car.connecteddevice.AssociationCallback;
+import com.android.car.connecteddevice.connection.AssociationSecureChannel;
+import com.android.car.connecteddevice.connection.SecureChannel;
+import com.android.car.connecteddevice.model.AssociatedDevice;
+import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+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;
+
+public class CarSppManagerTest {
+    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 UUID TEST_SERVICE_UUID = UUID.randomUUID();
+    private static final String TEST_VERIFICATION_CODE = "000000";
+    private static final int MAX_PACKET_SIZE = 700;
+    @Mock
+    private SppManager mMockSppManager;
+    @Mock
+    private ConnectedDeviceStorage mMockStorage;
+
+    private CarSppManager mCarSppManager;
+
+    private MockitoSession mMockitoSession;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.WARN)
+                .startMocking();
+        mCarSppManager = new CarSppManager(mMockSppManager, mMockStorage, TEST_SERVICE_UUID,
+                MAX_PACKET_SIZE);
+    }
+
+    @After
+    public void tearDown() {
+        if (mCarSppManager != null) {
+            mCarSppManager.stop();
+        }
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void testStartAssociationSuccess() throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        AssociationCallback callback = createAssociationCallback(semaphore);
+        when(mMockSppManager.startListening(TEST_SERVICE_UUID)).thenReturn(true);
+
+        mCarSppManager.startAssociation(null, callback);
+
+        verify(mMockSppManager).startListening(TEST_SERVICE_UUID);
+        assertThat(tryAcquire(semaphore)).isTrue();
+        verify(callback).onAssociationStartSuccess(eq(null));
+    }
+
+    @Test
+    public void testStartAssociationFailure() throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        AssociationCallback callback = createAssociationCallback(semaphore);
+        when(mMockSppManager.startListening(TEST_SERVICE_UUID)).thenReturn(false);
+
+        mCarSppManager.startAssociation(null, callback);
+
+        assertThat(tryAcquire(semaphore)).isTrue();
+        verify(callback).onAssociationStartFailure();
+    }
+
+    @Test
+    public void testShowVerificationCode() throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        AssociationCallback callback = createAssociationCallback(semaphore);
+        AssociationSecureChannel channel = getChannelForAssociation(callback);
+
+        channel.getShowVerificationCodeListener().showVerificationCode(TEST_VERIFICATION_CODE);
+
+        assertThat(tryAcquire(semaphore)).isTrue();
+        verify(callback).onVerificationCodeAvailable(eq(TEST_VERIFICATION_CODE));
+    }
+
+    @Test
+    public void testAssociationSuccess() throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        AssociationCallback callback = createAssociationCallback(semaphore);
+        SecureChannel channel = getChannelForAssociation(callback);
+        SecureChannel.Callback channelCallback = channel.getCallback();
+
+        assertThat(channelCallback).isNotNull();
+
+        channelCallback.onDeviceIdReceived(TEST_REMOTE_DEVICE_ID.toString());
+        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()));
+    }
+
+    private boolean tryAcquire(Semaphore semaphore) throws InterruptedException {
+        return semaphore.tryAcquire(100, TimeUnit.MILLISECONDS);
+    }
+
+    private AssociationSecureChannel getChannelForAssociation(AssociationCallback callback) {
+        ArgumentCaptor<SppManager.ConnectionCallback> callbackCaptor =
+                ArgumentCaptor.forClass(SppManager.ConnectionCallback.class);
+        mCarSppManager.startAssociation(null, callback);
+        verify(mMockSppManager).registerCallback(callbackCaptor.capture(), any());
+        BluetoothDevice bluetoothDevice = BluetoothAdapter.getDefaultAdapter()
+                .getRemoteDevice(TEST_REMOTE_DEVICE_ADDRESS);
+        callbackCaptor.getValue().onRemoteDeviceConnected(bluetoothDevice);
+        return (AssociationSecureChannel) mCarSppManager.getConnectedDeviceChannel();
+    }
+
+    @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/connection/spp/ConnectedTaskTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/spp/ConnectedTaskTest.java
new file mode 100644
index 0000000..7ae8192
--- /dev/null
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/spp/ConnectedTaskTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.connection.spp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class ConnectedTaskTest {
+
+    private final byte[] mTestData = "testData".getBytes();
+    private ConnectedTask mConnectedTask;
+    private InputStream mInputStream = new ByteArrayInputStream(mTestData);
+    private OutputStream mOutputStream = new ByteArrayOutputStream();
+    private ConnectedTask.Callback mCallback;
+    private Executor mExecutor = Executors.newSingleThreadExecutor();
+    private Semaphore mSemaphore = new Semaphore(0);
+
+
+    @Before
+    public void setUp() {
+        mCallback = spy(new ConnectedTask.Callback() {
+            @Override
+            public void onMessageReceived(byte[] message) {
+                mSemaphore.release();
+            }
+
+            @Override
+            public void onDisconnected() {
+
+            }
+        });
+        mConnectedTask = new ConnectedTask(mInputStream, mOutputStream, null, mCallback);
+    }
+
+    @Test
+    public void testTaskRun_InformCallback() throws InterruptedException {
+        mExecutor.execute(mConnectedTask);
+        assertThat(tryAcquire(mSemaphore)).isTrue();
+        verify(mCallback).onMessageReceived(mTestData);
+    }
+
+    @Test
+    public void testWrite_WriteToOutputStream() {
+        mConnectedTask.write(mTestData);
+        assertThat(mOutputStream.toString()).isEqualTo(new String(mTestData));
+    }
+
+    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/connection/spp/SppDeviceMessageStreamTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/spp/SppDeviceMessageStreamTest.java
new file mode 100644
index 0000000..a126d04
--- /dev/null
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/spp/SppDeviceMessageStreamTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.connection.spp;
+
+import static org.mockito.Mockito.mockitoSession;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+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.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class SppDeviceMessageStreamTest {
+    private static final int MAX_WRITE_SIZE = 700;
+
+    @Mock
+    private SppManager mMockSppManager;
+    private BluetoothDevice mBluetoothDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+            "00:11:22:33:44:55");
+    private MockitoSession mMockingSession;
+    private SppDeviceMessageStream mSppDeviceMessageStream;
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.WARN)
+                .startMocking();
+        mSppDeviceMessageStream = spy(
+                new SppDeviceMessageStream(mMockSppManager, mBluetoothDevice, MAX_WRITE_SIZE));
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void send_callsWriteAndSendCompleted() {
+        byte[] data = ByteUtils.randomBytes(10);
+        mSppDeviceMessageStream.send(data);
+        verify(mMockSppManager).write(data);
+        verify(mSppDeviceMessageStream).sendCompleted();
+    }
+}
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/spp/SppManagerTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/spp/SppManagerTest.java
new file mode 100644
index 0000000..a99fc6b
--- /dev/null
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/spp/SppManagerTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.connection.spp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.verify;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.io.IOException;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class SppManagerTest {
+    private static final UUID TEST_SERVICE_UUID = UUID.randomUUID();
+    private final boolean mIsSecureRfcommChannel = true;
+    private final byte[] mTestData = "testData".getBytes();
+    private SppManager mSppManager;
+    private Executor mCallbackExecutor = Executors.newSingleThreadExecutor();
+    private byte[] mCompletedMessage = SppPayloadStream.wrapWithArrayLength(mTestData);
+    @Mock
+    private ConnectedTask mMockConnectedTask;
+
+    @Mock
+    private ExecutorService mMockExecutorService;
+    private MockitoSession mMockitoSession;
+
+
+    @Before
+    public void setUp() throws IOException {
+        mSppManager = new SppManager(mIsSecureRfcommChannel);
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.WARN)
+                .startMocking();
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void testStartListen_StartAcceptTask() {
+        mSppManager.mConnectionExecutor = mMockExecutorService;
+        mSppManager.startListening(TEST_SERVICE_UUID);
+        assertThat(mSppManager.mAcceptTask).isNotNull();
+        verify(mMockExecutorService).execute(mSppManager.mAcceptTask);
+    }
+
+    @Test
+    public void testWrite_CallConnectedTaskToWrite() {
+        mSppManager.mConnectedTask = mMockConnectedTask;
+        mSppManager.mState = SppManager.ConnectionState.CONNECTED;
+        mSppManager.write(mTestData);
+        verify(mMockConnectedTask).write(SppPayloadStream.wrapWithArrayLength(mTestData));
+    }
+
+    @Test
+    public void testConnectedTaskCallback_onMessageReceived_CallOnMessageReceivedListener()
+            throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        SppManager.OnMessageReceivedListener listener = createOnMessageReceivedListener(semaphore);
+        mSppManager.addOnMessageReceivedListener(listener, mCallbackExecutor);
+        mSppManager.mConnectedTaskCallback.onMessageReceived(mCompletedMessage);
+        assertThat(tryAcquire(semaphore)).isTrue();
+        verify(listener).onMessageReceived(any(), eq(mTestData));
+    }
+
+    @Test
+    public void testConnectedTaskCallback_onDisconnected_CallOnRemoteDeviceDisconnected()
+            throws InterruptedException {
+        Semaphore semaphore = new Semaphore(0);
+        SppManager.ConnectionCallback callback = createConnectionCallback(semaphore);
+        mSppManager.registerCallback(callback, mCallbackExecutor);
+        mSppManager.mConnectedTaskCallback.onDisconnected();
+        assertThat(tryAcquire(semaphore)).isTrue();
+        verify(callback).onRemoteDeviceDisconnected(any());
+    }
+
+    @NonNull
+    private SppManager.ConnectionCallback createConnectionCallback(
+            @NonNull final Semaphore semaphore) {
+        return spy(new SppManager.ConnectionCallback() {
+
+            @Override
+            public void onRemoteDeviceConnected(BluetoothDevice device) {
+                semaphore.release();
+            }
+
+            @Override
+            public void onRemoteDeviceDisconnected(BluetoothDevice device) {
+                semaphore.release();
+            }
+        });
+    }
+
+    @NonNull
+    private SppManager.OnMessageReceivedListener createOnMessageReceivedListener(
+            @NonNull final Semaphore semaphore) {
+        return spy((device, value) -> semaphore.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/connection/spp/SppPayloadStreamTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/spp/SppPayloadStreamTest.java
new file mode 100644
index 0000000..9f0b42b
--- /dev/null
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/connection/spp/SppPayloadStreamTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.connection.spp;
+
+import static org.mockito.Mockito.mockitoSession;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+public class SppPayloadStreamTest {
+    private SppPayloadStream mSppPayloadStream;
+    private MockitoSession mMockitoSession;
+    private final byte[] mTestData = "testData".getBytes();
+    private byte[] mCompletedMessage = SppPayloadStream.wrapWithArrayLength(mTestData);
+    ;
+    private byte[] mCompletedMessageSplit1;
+    private byte[] mCompletedMessageSplit2;
+    @Mock
+    private SppPayloadStream.OnMessageCompletedListener mMockListener;
+
+
+    @Before
+    public void setUp() {
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.WARN)
+                .startMocking();
+        mSppPayloadStream = new SppPayloadStream();
+        mSppPayloadStream.setMessageCompletedListener(mMockListener);
+        int length = mCompletedMessage.length;
+        mCompletedMessageSplit1 = Arrays.copyOfRange(mCompletedMessage, 0, (length + 1) / 2);
+        mCompletedMessageSplit2 = Arrays.copyOfRange(mCompletedMessage, (length + 1) / 2, length);
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void testWriteCompletedMessage_InformListener() throws IOException {
+        mSppPayloadStream.write(mCompletedMessage);
+        verify(mMockListener).onMessageCompleted(mTestData);
+    }
+
+    @Test
+    public void testWriteIncompleteMessage_DoNotInformListener() throws IOException {
+        mSppPayloadStream.write(mCompletedMessageSplit1);
+        verify(mMockListener, never()).onMessageCompleted(mTestData);
+    }
+
+    @Test
+    public void testWriteTwoMessage_InformListenerCompletedMessage() throws IOException {
+        mSppPayloadStream.write(mCompletedMessageSplit1);
+        mSppPayloadStream.write(mCompletedMessageSplit2);
+
+        verify(mMockListener).onMessageCompleted(mTestData);
+    }
+}
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/oob/BluetoothRfcommChannelTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/oob/BluetoothRfcommChannelTest.java
new file mode 100644
index 0000000..9628afa
--- /dev/null
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/oob/BluetoothRfcommChannelTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.oob;
+
+
+import static com.android.car.connecteddevice.model.OobEligibleDevice.OOB_TYPE_BLUETOOTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mockitoSession;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.connecteddevice.model.OobEligibleDevice;
+import com.android.car.connecteddevice.util.ByteUtils;
+
+import com.google.common.primitives.Bytes;
+
+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.io.IOException;
+import java.io.OutputStream;
+import java.util.UUID;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothRfcommChannelTest {
+    private BluetoothRfcommChannel mBluetoothRfcommChannel;
+    private OobEligibleDevice mOobEligibleDevice;
+    @Mock
+    private OobChannel.Callback mMockCallback;
+    @Mock
+    private BluetoothAdapter mMockBluetoothAdapter;
+    @Mock
+    private BluetoothDevice mMockBluetoothDevice;
+    @Mock
+    private BluetoothSocket mMockBluetoothSocket;
+    @Mock
+    private OutputStream mMockOutputStream;
+
+    private MockitoSession mMockingSession;
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.WARN)
+                .startMocking();
+
+
+        mBluetoothRfcommChannel = new BluetoothRfcommChannel();
+
+        mOobEligibleDevice = new OobEligibleDevice("00:11:22:33:44:55", OOB_TYPE_BLUETOOTH);
+
+        when(mMockBluetoothAdapter.getRemoteDevice(
+                mOobEligibleDevice.getDeviceAddress())).thenReturn(mMockBluetoothDevice);
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void completeOobExchange_success() throws Exception {
+        when(mMockBluetoothSocket.getOutputStream()).thenReturn(mMockOutputStream);
+        when(mMockBluetoothDevice.createRfcommSocketToServiceRecord(any(UUID.class))).thenReturn(
+                mMockBluetoothSocket);
+        mBluetoothRfcommChannel.completeOobDataExchange(mOobEligibleDevice, mMockCallback,
+                mMockBluetoothAdapter);
+
+        verify(mMockCallback, timeout(1000)).onOobExchangeSuccess();
+        OobConnectionManager oobConnectionManager = new OobConnectionManager();
+        oobConnectionManager.startOobExchange(mBluetoothRfcommChannel);
+
+        ArgumentCaptor<byte[]> oobDataCaptor = ArgumentCaptor.forClass(byte[].class);
+        verify(mMockOutputStream).write(oobDataCaptor.capture());
+        byte[] oobData = oobDataCaptor.getValue();
+
+        assertThat(oobData).isEqualTo(Bytes.concat(oobConnectionManager.mDecryptionIv,
+                oobConnectionManager.mEncryptionIv,
+                oobConnectionManager.mEncryptionKey.getEncoded()));
+    }
+
+    @Test
+    public void completeOobExchange_ioExceptionCausesRetry() throws Exception {
+        doThrow(IOException.class).doAnswer(invocation -> null)
+                .when(mMockBluetoothSocket).connect();
+        when(mMockBluetoothDevice.createRfcommSocketToServiceRecord(any(UUID.class))).thenReturn(
+                mMockBluetoothSocket);
+        mBluetoothRfcommChannel.completeOobDataExchange(mOobEligibleDevice, mMockCallback,
+                mMockBluetoothAdapter);
+        verify(mMockBluetoothSocket, timeout(3000).times(2)).connect();
+    }
+
+    @Test
+    public void completeOobExchange_createRfcommSocketFails_callOnFailed() throws Exception {
+        when(mMockBluetoothDevice.createRfcommSocketToServiceRecord(any(UUID.class))).thenThrow(
+                IOException.class);
+
+        mBluetoothRfcommChannel.completeOobDataExchange(mOobEligibleDevice, mMockCallback,
+                mMockBluetoothAdapter);
+        verify(mMockCallback).onOobExchangeFailure();
+    }
+
+    @Test
+    public void sendOobData_nullBluetoothDevice_callOnFailed() {
+        mBluetoothRfcommChannel.mCallback = mMockCallback;
+
+        mBluetoothRfcommChannel.sendOobData("someData".getBytes());
+        verify(mMockCallback).onOobExchangeFailure();
+    }
+
+    @Test
+    public void sendOobData_writeFails_callOnFailed() throws Exception {
+        byte[] testMessage = "testMessage".getBytes();
+        completeOobExchange_success();
+        doThrow(IOException.class).when(mMockOutputStream).write(testMessage);
+
+        mBluetoothRfcommChannel.sendOobData(testMessage);
+        verify(mMockCallback).onOobExchangeFailure();
+    }
+
+    @Test
+    public void interrupt_closesSocket() throws Exception {
+        when(mMockBluetoothSocket.getOutputStream()).thenReturn(mMockOutputStream);
+        when(mMockBluetoothDevice.createRfcommSocketToServiceRecord(any(UUID.class))).thenReturn(
+                mMockBluetoothSocket);
+        mBluetoothRfcommChannel.completeOobDataExchange(mOobEligibleDevice, mMockCallback,
+                mMockBluetoothAdapter);
+        mBluetoothRfcommChannel.interrupt();
+        mBluetoothRfcommChannel.sendOobData(ByteUtils.randomBytes(10));
+        verify(mMockOutputStream, times(0)).write(any());
+        verify(mMockOutputStream).flush();
+        verify(mMockOutputStream).close();
+    }
+
+    @Test
+    public void interrupt_preventsCallbacks() throws Exception {
+        when(mMockBluetoothSocket.getOutputStream()).thenReturn(mMockOutputStream);
+        when(mMockBluetoothDevice.createRfcommSocketToServiceRecord(any(UUID.class))).thenReturn(
+                mMockBluetoothSocket);
+        doAnswer(invocation -> {
+            mBluetoothRfcommChannel.interrupt();
+            return invocation.callRealMethod();
+        }).when(mMockBluetoothSocket).connect();
+        mBluetoothRfcommChannel.completeOobDataExchange(mOobEligibleDevice, mMockCallback,
+                mMockBluetoothAdapter);
+        verify(mMockCallback, times(0)).onOobExchangeSuccess();
+        verify(mMockCallback, times(0)).onOobExchangeFailure();
+    }
+}
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/oob/OobConnectionManagerTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/oob/OobConnectionManagerTest.java
new file mode 100644
index 0000000..13f93c9
--- /dev/null
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/oob/OobConnectionManagerTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.oob;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.security.keystore.KeyProperties;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.connecteddevice.model.OobEligibleDevice;
+
+import com.google.common.primitives.Bytes;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.InvalidKeyException;
+import java.security.SecureRandom;
+
+import javax.crypto.AEADBadTagException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+@RunWith(AndroidJUnit4.class)
+public class OobConnectionManagerTest {
+    private static final byte[] TEST_MESSAGE = "testMessage".getBytes();
+    private TestChannel mTestChannel;
+    private SecretKey mTestKey;
+    private byte[] mTestEncryptionIv = new byte[OobConnectionManager.NONCE_LENGTH_BYTES];
+    private byte[] mTestDecryptionIv = new byte[OobConnectionManager.NONCE_LENGTH_BYTES];
+    private byte[] mTestOobData;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestChannel = new TestChannel();
+        mTestKey = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES).generateKey();
+
+        SecureRandom secureRandom = new SecureRandom();
+        secureRandom.nextBytes(mTestEncryptionIv);
+        secureRandom.nextBytes(mTestDecryptionIv);
+
+        mTestOobData = Bytes.concat(mTestDecryptionIv, mTestEncryptionIv, mTestKey.getEncoded());
+    }
+
+    @Test
+    public void testInitAsServer_keyIsNull() {
+        OobConnectionManager oobConnectionManager = new OobConnectionManager();
+        assertThat(oobConnectionManager.mEncryptionKey).isNull();
+    }
+
+    @Test
+    public void testServer_onSetOobData_setsKeyAndNonce() {
+        OobConnectionManager oobConnectionManager = new OobConnectionManager();
+        oobConnectionManager.setOobData(mTestOobData);
+        assertThat(oobConnectionManager.mEncryptionKey).isEqualTo(mTestKey);
+        // The decryption IV for the server is the encryption IV for the client and vice versa
+        assertThat(oobConnectionManager.mDecryptionIv).isEqualTo(mTestEncryptionIv);
+        assertThat(oobConnectionManager.mEncryptionIv).isEqualTo(mTestDecryptionIv);
+    }
+
+    @Test
+    public void testInitAsClient_keyAndNoncesAreNonNullAndSent() {
+        OobConnectionManager oobConnectionManager = new OobConnectionManager();
+        oobConnectionManager.startOobExchange(mTestChannel);
+        assertThat(oobConnectionManager.mEncryptionKey).isNotNull();
+        assertThat(oobConnectionManager.mEncryptionIv).isNotNull();
+        assertThat(oobConnectionManager.mDecryptionIv).isNotNull();
+        assertThat(mTestChannel.mSentOobData).isEqualTo(Bytes.concat(
+                oobConnectionManager.mDecryptionIv,
+                oobConnectionManager.mEncryptionIv,
+                oobConnectionManager.mEncryptionKey.getEncoded()
+        ));
+    }
+
+    @Test
+    public void testServerEncryptAndClientDecrypt() throws Exception {
+        OobConnectionManager clientOobConnectionManager = new OobConnectionManager();
+        clientOobConnectionManager.startOobExchange(mTestChannel);
+        OobConnectionManager serverOobConnectionManager = new OobConnectionManager();
+        serverOobConnectionManager.setOobData(mTestChannel.mSentOobData);
+
+        byte[] encryptedTestMessage = clientOobConnectionManager.encryptVerificationCode(
+                TEST_MESSAGE);
+        byte[] decryptedTestMessage = serverOobConnectionManager.decryptVerificationCode(
+                encryptedTestMessage);
+
+        assertThat(decryptedTestMessage).isEqualTo(TEST_MESSAGE);
+    }
+
+    @Test
+    public void testClientEncryptAndServerDecrypt() throws Exception {
+        OobConnectionManager clientOobConnectionManager = new OobConnectionManager();
+        clientOobConnectionManager.startOobExchange(mTestChannel);
+        OobConnectionManager serverOobConnectionManager = new OobConnectionManager();
+        serverOobConnectionManager.setOobData(mTestChannel.mSentOobData);
+
+        byte[] encryptedTestMessage = serverOobConnectionManager.encryptVerificationCode(
+                TEST_MESSAGE);
+        byte[] decryptedTestMessage = clientOobConnectionManager.decryptVerificationCode(
+                encryptedTestMessage);
+
+        assertThat(decryptedTestMessage).isEqualTo(TEST_MESSAGE);
+    }
+
+    @Test
+    public void testEncryptAndDecryptWithDifferentNonces_throwsAEADBadTagException()
+            throws Exception {
+        // The OobConnectionManager stores a different nonce for encryption and decryption, so it
+        // can't decrypt messages that it encrypted itself. It can only send encrypted messages to
+        // an OobConnectionManager on another device that share its nonces and encryption key.
+        OobConnectionManager oobConnectionManager = new OobConnectionManager();
+        oobConnectionManager.startOobExchange(mTestChannel);
+        byte[] encryptedMessage = oobConnectionManager.encryptVerificationCode(TEST_MESSAGE);
+        assertThrows(AEADBadTagException.class,
+                () -> oobConnectionManager.decryptVerificationCode(encryptedMessage));
+    }
+
+    @Test
+    public void testDecryptWithShortMessage_throwsAEADBadTagException() {
+        OobConnectionManager oobConnectionManager = new OobConnectionManager();
+        oobConnectionManager.startOobExchange(mTestChannel);
+        assertThrows(AEADBadTagException.class,
+                () -> oobConnectionManager.decryptVerificationCode("short".getBytes()));
+    }
+
+    @Test
+    public void testEncryptWithNullKey_throwsInvalidKeyException() {
+        OobConnectionManager oobConnectionManager = new OobConnectionManager();
+        assertThrows(InvalidKeyException.class,
+                () -> oobConnectionManager.encryptVerificationCode(TEST_MESSAGE));
+    }
+
+    @Test
+    public void testDecryptWithNullKey_throwsInvalidKeyException() {
+        OobConnectionManager oobConnectionManager = new OobConnectionManager();
+        assertThrows(InvalidKeyException.class,
+                () -> oobConnectionManager.decryptVerificationCode(TEST_MESSAGE));
+    }
+
+    private static class TestChannel implements OobChannel {
+        byte[] mSentOobData = null;
+
+        @Override
+        public void completeOobDataExchange(OobEligibleDevice device, Callback callback) {
+
+        }
+
+        @Override
+        public void sendOobData(byte[] oobData) {
+            mSentOobData = oobData;
+        }
+
+        @Override
+        public void interrupt() {
+
+        }
+    }
+}
diff --git a/encryption-runner/Android.bp b/encryption-runner/Android.bp
index 2522d2d..c507c56 100644
--- a/encryption-runner/Android.bp
+++ b/encryption-runner/Android.bp
@@ -14,13 +14,15 @@
 
 android_library {
     name: "encryption-runner",
-    min_sdk_version: "23",
+    sdk_version: "current",
+    min_sdk_version: "28",
     product_variables: {
         pdk: {
             enabled: false,
         },
     },
     static_libs: [
+      "androidx.annotation_annotation",
       "ukey2",
     ],
     srcs: [
diff --git a/encryption-runner/src/android/car/encryptionrunner/EncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/EncryptionRunner.java
index 5721218..6a2b6d6 100644
--- a/encryption-runner/src/android/car/encryptionrunner/EncryptionRunner.java
+++ b/encryption-runner/src/android/car/encryptionrunner/EncryptionRunner.java
@@ -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
index d11ed4e..b479b50 100644
--- a/encryption-runner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java
+++ b/encryption-runner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java
@@ -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;
 
 /**
  * Factory that creates encryption runner.
@@ -29,10 +28,12 @@
         // prevent instantiation.
     }
 
-    @IntDef({EncryptionRunnerType.UKEY2})
+    @IntDef({EncryptionRunnerType.UKEY2, EncryptionRunnerType.OOB_UKEY2})
     public @interface EncryptionRunnerType {
         /** Use Ukey2 as underlying key exchange. */
         int UKEY2 = 0;
+        /** Use Ukey2 and an out of band channel as underlying key exchange. */
+        int OOB_UKEY2 = 1;
     }
 
     /**
@@ -42,6 +43,8 @@
         switch (type) {
             case EncryptionRunnerType.UKEY2:
                 return new Ukey2EncryptionRunner();
+            case EncryptionRunnerType.OOB_UKEY2:
+                return new OobUkey2EncryptionRunner();
             default:
                 throw new IllegalArgumentException("Unknown EncryptionRunnerType: " + type);
         }
@@ -62,7 +65,16 @@
      * for testing.
      */
     @VisibleForTesting
-    public static EncryptionRunner newDummyRunner() {
-        return new DummyEncryptionRunner();
+    public static EncryptionRunner newFakeRunner() {
+        return new FakeEncryptionRunner();
+    }
+
+    /**
+     * Creates a new {@link EncryptionRunner} that doesn't actually do encryption but is useful
+     * for out of band association testing.
+     */
+    @VisibleForTesting
+    public static EncryptionRunner newOobFakeRunner() {
+        return new OobFakeEncryptionRunner();
     }
 }
diff --git a/encryption-runner/src/android/car/encryptionrunner/DummyEncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/FakeEncryptionRunner.java
similarity index 87%
rename from encryption-runner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
rename to encryption-runner/src/android/car/encryptionrunner/FakeEncryptionRunner.java
index d247967..5fd1ff2 100644
--- a/encryption-runner/src/android/car/encryptionrunner/DummyEncryptionRunner.java
+++ b/encryption-runner/src/android/car/encryptionrunner/FakeEncryptionRunner.java
@@ -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;
@@ -28,10 +27,10 @@
  * production environments.
  */
 @VisibleForTesting
-public class DummyEncryptionRunner implements EncryptionRunner {
+public class FakeEncryptionRunner implements EncryptionRunner {
 
     private static final String KEY = "key";
-    private static final byte[] DUMMY_MESSAGE = "Dummy Message".getBytes();
+    private static final byte[] PLACEHOLDER_MESSAGE = "Placeholder Message".getBytes();
     @VisibleForTesting
     public static final String INIT = "init";
     @VisibleForTesting
@@ -42,7 +41,7 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({Mode.UNKNOWN, Mode.CLIENT, Mode.SERVER})
-    private @interface Mode {
+    @interface Mode {
 
         int UNKNOWN = 0;
         int CLIENT = 1;
@@ -51,7 +50,7 @@
 
     private boolean mIsReconnect;
     private boolean mInitReconnectVerification;
-    private Key mCurrentDummyKey;
+    private Key mCurrentFakeKey;
     @Mode
     private int mMode;
     @HandshakeMessage.HandshakeState
@@ -83,6 +82,20 @@
                 .build();
     }
 
+    @Mode
+    protected int getMode() {
+        return mMode;
+    }
+
+    @HandshakeMessage.HandshakeState
+    protected int getState() {
+        return mState;
+    }
+
+    protected void setState(@HandshakeMessage.HandshakeState int state) {
+        mState = state;
+    }
+
     private void checkRunnerIsNew() {
         if (mState != HandshakeMessage.HandshakeState.UNKNOWN) {
             throw new IllegalStateException("runner already initialized.");
@@ -130,12 +143,12 @@
     @Override
     public HandshakeMessage authenticateReconnection(byte[] message, byte[] previousKey)
             throws HandshakeException {
-        mCurrentDummyKey = new DummyKey();
-        // Blindly verify the reconnection because this is a dummy encryption runner.
+        mCurrentFakeKey = new FakeKey();
+        // Blindly verify the reconnection because this is a fake encryption runner.
         return HandshakeMessage.newBuilder()
                 .setHandshakeState(HandshakeMessage.HandshakeState.FINISHED)
-                .setKey(mCurrentDummyKey)
-                .setNextMessage(mInitReconnectVerification ? null : DUMMY_MESSAGE)
+                .setKey(mCurrentFakeKey)
+                .setNextMessage(mInitReconnectVerification ? null : PLACEHOLDER_MESSAGE)
                 .build();
     }
 
@@ -146,13 +159,13 @@
         mState = HandshakeMessage.HandshakeState.RESUMING_SESSION;
         return HandshakeMessage.newBuilder()
                 .setHandshakeState(mState)
-                .setNextMessage(DUMMY_MESSAGE)
+                .setNextMessage(PLACEHOLDER_MESSAGE)
                 .build();
     }
 
     @Override
     public Key keyOf(byte[] serialized) {
-        return new DummyKey();
+        return new FakeKey();
     }
 
     @Override
@@ -161,7 +174,7 @@
             throw new IllegalStateException("asking to verify pin, state = " + mState);
         }
         mState = HandshakeMessage.HandshakeState.FINISHED;
-        return HandshakeMessage.newBuilder().setKey(new DummyKey()).setHandshakeState(
+        return HandshakeMessage.newBuilder().setKey(new FakeKey()).setHandshakeState(
                 mState).build();
     }
 
@@ -180,7 +193,7 @@
         throw new HandshakeException(message);
     }
 
-    private class DummyKey implements Key {
+    class FakeKey implements Key {
         @Override
         public byte[] asBytes() {
             return KEY.getBytes();
diff --git a/encryption-runner/src/android/car/encryptionrunner/HandshakeMessage.java b/encryption-runner/src/android/car/encryptionrunner/HandshakeMessage.java
index d7c9765..a79c64e 100644
--- a/encryption-runner/src/android/car/encryptionrunner/HandshakeMessage.java
+++ b/encryption-runner/src/android/car/encryptionrunner/HandshakeMessage.java
@@ -16,11 +16,12 @@
 
 package android.car.encryptionrunner;
 
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-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;
 
diff --git a/encryption-runner/src/android/car/encryptionrunner/Key.java b/encryption-runner/src/android/car/encryptionrunner/Key.java
index e13585f..e80be25 100644
--- a/encryption-runner/src/android/car/encryptionrunner/Key.java
+++ b/encryption-runner/src/android/car/encryptionrunner/Key.java
@@ -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/encryption-runner/src/android/car/encryptionrunner/OobFakeEncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/OobFakeEncryptionRunner.java
new file mode 100644
index 0000000..3ab17d2
--- /dev/null
+++ b/encryption-runner/src/android/car/encryptionrunner/OobFakeEncryptionRunner.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.encryptionrunner;
+
+/**
+ * An encryption runner that doesn't actually do encryption. Useful for debugging out of band
+ * association. Do not use in production environments.
+ */
+public class OobFakeEncryptionRunner extends FakeEncryptionRunner {
+
+    @Override
+    public HandshakeMessage continueHandshake(byte[] response) throws HandshakeException {
+        if (getState() != HandshakeMessage.HandshakeState.IN_PROGRESS) {
+            throw new HandshakeException("not waiting for response but got one");
+        }
+
+        @HandshakeMessage.HandshakeState int newState =
+                HandshakeMessage.HandshakeState.OOB_VERIFICATION_NEEDED;
+        switch (getMode()) {
+            case Mode.SERVER:
+                if (!CLIENT_RESPONSE.equals(new String(response))) {
+                    throw new HandshakeException("unexpected response: " + new String(response));
+                }
+                setState(newState);
+                return HandshakeMessage.newBuilder()
+                        .setOobVerificationCode(VERIFICATION_CODE.getBytes())
+                        .setHandshakeState(newState)
+                        .build();
+            case Mode.CLIENT:
+                if (!INIT_RESPONSE.equals(new String(response))) {
+                    throw new HandshakeException("unexpected response: " + new String(response));
+                }
+                setState(newState);
+                return HandshakeMessage.newBuilder()
+                        .setHandshakeState(newState)
+                        .setNextMessage(CLIENT_RESPONSE.getBytes())
+                        .setOobVerificationCode(VERIFICATION_CODE.getBytes())
+                        .build();
+            default:
+                throw new IllegalStateException("unexpected role: " + getMode());
+        }
+    }
+
+    @Override
+    public HandshakeMessage verifyPin() throws HandshakeException {
+        @HandshakeMessage.HandshakeState int state = getState();
+        if (state != HandshakeMessage.HandshakeState.OOB_VERIFICATION_NEEDED) {
+            throw new IllegalStateException("asking to verify pin, state = " + state);
+        }
+        state = HandshakeMessage.HandshakeState.FINISHED;
+        return HandshakeMessage.newBuilder().setKey(new FakeKey()).setHandshakeState(
+                state).build();
+    }
+}
diff --git a/encryption-runner/src/android/car/encryptionrunner/OobUkey2EncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/OobUkey2EncryptionRunner.java
new file mode 100644
index 0000000..9474bd4
--- /dev/null
+++ b/encryption-runner/src/android/car/encryptionrunner/OobUkey2EncryptionRunner.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.encryptionrunner;
+
+import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake;
+
+/**
+ * An {@link EncryptionRunner} that uses Ukey2 as the underlying implementation, and generates a
+ * longer token for the out of band verification step.
+ *
+ * <p>To use this class:
+ *
+ * <p>1. As a client.
+ *
+ * <p>{@code
+ * HandshakeMessage initialClientMessage = clientRunner.initHandshake();
+ * sendToServer(initialClientMessage.getNextMessage());
+ * byte message = getServerResponse();
+ * HandshakeMessage message = clientRunner.continueHandshake(message);
+ * }
+ *
+ * <p>If it is a first-time connection,
+ *
+ * <p>{@code message.getHandshakeState()} should be OOB_VERIFICATION_NEEDED. Wait for an encrypted
+ * message sent from the server, and decrypt that message with an out of band key that was generated
+ * before the start of the handshake.
+ *
+ * <p>After confirming that the decrypted message matches the verification code, send an encrypted
+ * message back to the server, and call {@code HandshakeMessage lastMessage =
+ * clientRunner.verifyPin();} otherwise {@code clientRunner.invalidPin(); }
+ *
+ * <p>Use {@code lastMessage.getKey()} to get the key for encryption.
+ *
+ * <p>If it is a reconnection,
+ *
+ * <p>{@code message.getHandshakeState()} should be RESUMING_SESSION, PIN has been verified blindly,
+ * send the authentication message over to server, then authenticate the message from server.
+ *
+ * <p>{@code
+ * clientMessage = clientRunner.initReconnectAuthentication(previousKey)
+ * sendToServer(clientMessage.getNextMessage());
+ * HandshakeMessage lastMessage = clientRunner.authenticateReconnection(previousKey, message)
+ * }
+ *
+ * <p>{@code lastMessage.getHandshakeState()} should be FINISHED if reconnection handshake is done.
+ *
+ * <p>2. As a server.
+ *
+ * <p>{@code
+ * byte[] initialMessage = getClientMessageBytes();
+ * HandshakeMessage message = serverRunner.respondToInitRequest(initialMessage);
+ * sendToClient(message.getNextMessage());
+ * byte[] clientMessage = getClientResponse();
+ * HandshakeMessage message = serverRunner.continueHandshake(clientMessage);}
+ *
+ * <p>if it is a first-time connection,
+ *
+ * <p>{@code message.getHandshakeState()} should be OOB_VERIFICATION_NEEDED, send the verification
+ * code to the client, encrypted using an out of band key generated before the start of the
+ * handshake, and wait for a response from the client.
+ * If the decrypted message from the client matches the verification code, call {@code
+ * HandshakeMessage lastMessage = serverRunner.verifyPin}, otherwise
+ * {@code clientRunner.invalidPin(); }
+ * Use {@code lastMessage.getKey()} to get the key for encryption.
+ *
+ * <p>If it is a reconnection,
+ *
+ * <p>{@code message.getHandshakeState()} should be RESUMING_SESSION,PIN has been verified blindly,
+ * waiting for client message.
+ * After client message been received,
+ * {@code serverMessage = serverRunner.authenticateReconnection(previousKey, message);
+ * sendToClient(serverMessage.getNextMessage());}
+ * {@code serverMessage.getHandshakeState()} should be FINISHED if reconnection handshake is done.
+ *
+ * <p>Also see {@link EncryptionRunnerTest} for examples.
+ */
+public final class OobUkey2EncryptionRunner extends Ukey2EncryptionRunner {
+    // Choose max verification string length supported by Ukey2
+    private static final int VERIFICATION_STRING_LENGTH = 32;
+
+    @Override
+    public HandshakeMessage continueHandshake(byte[] response) throws HandshakeException {
+        checkInitialized();
+
+        Ukey2Handshake uKey2Client = getUkey2Client();
+
+        try {
+            if (uKey2Client.getHandshakeState() != Ukey2Handshake.State.IN_PROGRESS) {
+                throw new IllegalStateException(
+                        "handshake is not in progress, state =" + uKey2Client.getHandshakeState());
+            }
+            uKey2Client.parseHandshakeMessage(response);
+
+            // Not obvious from ukey2 api, but getting the next message can change the state.
+            // calling getNext message might go from in progress to verification needed, on
+            // the assumption that we already send this message to the peer.
+            byte[] nextMessage = null;
+            if (uKey2Client.getHandshakeState() == Ukey2Handshake.State.IN_PROGRESS) {
+                nextMessage = uKey2Client.getNextHandshakeMessage();
+            }
+
+            byte[] verificationCode = null;
+            if (uKey2Client.getHandshakeState() == Ukey2Handshake.State.VERIFICATION_NEEDED) {
+                // getVerificationString() needs to be called before notifyPinVerified().
+                verificationCode = uKey2Client.getVerificationString(VERIFICATION_STRING_LENGTH);
+                if (isReconnect()) {
+                    HandshakeMessage handshakeMessage = verifyPin();
+                    return HandshakeMessage.newBuilder()
+                            .setHandshakeState(handshakeMessage.getHandshakeState())
+                            .setNextMessage(nextMessage)
+                            .build();
+                }
+            }
+
+            return HandshakeMessage.newBuilder()
+                    .setHandshakeState(HandshakeMessage.HandshakeState.OOB_VERIFICATION_NEEDED)
+                    .setNextMessage(nextMessage)
+                    .setOobVerificationCode(verificationCode)
+                    .build();
+        } catch (com.google.security.cryptauth.lib.securegcm.HandshakeException
+                | Ukey2Handshake.AlertException e) {
+            throw new HandshakeException(e);
+        }
+    }
+}
diff --git a/encryption-runner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
index f4ee5a7..ac1bf91 100644
--- a/encryption-runner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
+++ b/encryption-runner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
@@ -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;