Merge "Merge identical phone numbers for the same contact" into pi-car-dev
diff --git a/car-apps-common/src/com/android/car/apps/common/CarUxRestrictionsUtil.java b/car-apps-common/src/com/android/car/apps/common/CarUxRestrictionsUtil.java
index ed219ae..219bf59 100644
--- a/car-apps-common/src/com/android/car/apps/common/CarUxRestrictionsUtil.java
+++ b/car-apps-common/src/com/android/car/apps/common/CarUxRestrictionsUtil.java
@@ -38,6 +38,8 @@
  * This class must be a singleton because only one listener can be registered with
  * {@link CarUxRestrictionsManager} at a time, as documented in
  * {@link CarUxRestrictionsManager#registerListener}.
+ *
+ * @deprecated Use {@link com.android.car.ui.utils.CarUxRestrictionsUtil} instead
  */
 public class CarUxRestrictionsUtil {
     private static final String TAG = "CarUxRestrictionsUtil";
@@ -63,7 +65,7 @@
             }
         };
 
-        mCarApi = Car.createCar(context);
+        mCarApi = Car.createCar(context.getApplicationContext());
         mObservers = Collections.newSetFromMap(new WeakHashMap<>());
 
         try {
diff --git a/car-apps-common/src/com/android/car/apps/common/ClickThroughToolbar.java b/car-apps-common/src/com/android/car/apps/common/ClickThroughToolbar.java
index 3a05111..7e5b79c 100644
--- a/car-apps-common/src/com/android/car/apps/common/ClickThroughToolbar.java
+++ b/car-apps-common/src/com/android/car/apps/common/ClickThroughToolbar.java
@@ -28,7 +28,7 @@
  * <p>By default, the {@link Toolbar} eats all touches on it. This view will override
  * {@link #onTouchEvent(MotionEvent)} and return {@code false} if configured to allow pass through.
  *
- * @deprecated Use {@link com.android.car.ui.Toolbar} instead
+ * @deprecated Use {@link com.android.car.ui.toolbar.Toolbar} instead
  */
 @Deprecated
 public class ClickThroughToolbar extends Toolbar {
diff --git a/car-apps-common/src/com/android/car/apps/common/imaging/ImageBinder.java b/car-apps-common/src/com/android/car/apps/common/imaging/ImageBinder.java
index 0e59a54..f640218 100644
--- a/car-apps-common/src/com/android/car/apps/common/imaging/ImageBinder.java
+++ b/car-apps-common/src/com/android/car/apps/common/imaging/ImageBinder.java
@@ -158,7 +158,7 @@
             getImageFetcher(context).cancelRequest(mCurrentKey, mFetchReceiver);
             onRequestFinished();
         }
-        setDrawable(getLoadingDrawable(context));
+        setDrawable(mPlaceholderType != PlaceholderType.NONE ? getLoadingDrawable(context) : null);
     }
 
     private void onRequestFinished() {
diff --git a/car-apps-common/src/com/android/car/apps/common/imaging/LocalImageFetcher.java b/car-apps-common/src/com/android/car/apps/common/imaging/LocalImageFetcher.java
index 185ec67..dc10caf 100644
--- a/car-apps-common/src/com/android/car/apps/common/imaging/LocalImageFetcher.java
+++ b/car-apps-common/src/com/android/car/apps/common/imaging/LocalImageFetcher.java
@@ -241,7 +241,8 @@
             }
         };
 
-        private @ImageDecoder.Allocator int mAllocatorMode = ImageDecoder.ALLOCATOR_HARDWARE;
+        // ALLOCATOR_HARDWARE causes crashes on some emulators (in media center's queue).
+        private @ImageDecoder.Allocator int mAllocatorMode = ImageDecoder.ALLOCATOR_SOFTWARE;
 
         @Override
         protected Drawable doInBackground(Void... voids) {
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 829b644..dea0d6a 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
@@ -17,7 +17,6 @@
 package com.android.car.media.common.browse;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.support.v4.media.MediaBrowserCompat;
@@ -53,7 +52,7 @@
 
     private ChildrenSubscription mSubscription;
 
-    BrowsedMediaItems(@NonNull MediaBrowserCompat mediaBrowser, @Nullable String parentId) {
+    BrowsedMediaItems(@NonNull MediaBrowserCompat mediaBrowser, @NonNull String parentId) {
         mBrowser = mediaBrowser;
         mParentId = parentId;
     }
@@ -61,10 +60,7 @@
     @Override
     protected void onActive() {
         super.onActive();
-        String rootNode = mBrowser.getRoot();
-        String itemId = mParentId != null ? mParentId : rootNode;
-
-        mSubscription = new ChildrenSubscription(itemId);
+        mSubscription = new ChildrenSubscription(mParentId);
         mSubscription.start(CHILDREN_SUBSCRIPTION_RETRIES, CHILDREN_SUBSCRIPTION_RETRY_TIME_MS);
     }
 
diff --git a/car-media-common/src/com/android/car/media/common/browse/MediaBrowserViewModel.java b/car-media-common/src/com/android/car/media/common/browse/MediaBrowserViewModel.java
index 74143c1..5a4626a 100644
--- a/car-media-common/src/com/android/car/media/common/browse/MediaBrowserViewModel.java
+++ b/car-media-common/src/com/android/car/media/common/browse/MediaBrowserViewModel.java
@@ -112,7 +112,7 @@
          * {@link #getBrowsedMediaItems()}.
          */
         @UiThread
-        void setCurrentBrowseId(@Nullable String browseId);
+        void setCurrentBrowseId(@NonNull String browseId);
 
         /**
          * Set the current item to be searched for. If available, the list of items will be emitted
@@ -137,61 +137,16 @@
          */
         @NonNull
         public static MediaBrowserViewModel.WithMutableBrowseId getInstanceWithMediaBrowser(
+                @NonNull String key,
                 @NonNull ViewModelProvider viewModelProvider,
                 @NonNull LiveData<MediaBrowserCompat> mediaBrowser) {
-            MediaBrowserViewModelImpl viewModel = viewModelProvider.get(
-                    MediaBrowserViewModelImpl.class);
+            MutableMediaBrowserViewModel viewModel =
+                    viewModelProvider.get(key, MutableMediaBrowserViewModel.class);
             initMediaBrowser(mediaBrowser, viewModel);
             return viewModel;
         }
 
         /**
-         * Fetch an initialized {@link MediaBrowserViewModel.WithMutableBrowseId}. It will get its
-         * media browser from the {@link MediaSourceViewModel} provided by {@code
-         * viewModelProvider}.
-         *
-         *
-         * @param mediaSourceVM     the {@link MediaSourceViewModel} singleton.
-         * @param viewModelProvider the ViewModelProvider to load ViewModels from.
-         * @param key               a key to decide which instance of the ViewModel to fetch.
-         *                          Subsequent calls with the same key will return the same
-         *                          instance.
-         * @return an initialized MediaBrowserViewModel.WithMutableBrowseId for the given key.
-         * @see ViewModelProvider#get(String, Class)
-         */
-        @NonNull
-        public static MediaBrowserViewModel.WithMutableBrowseId getInstanceForKey(
-                MediaSourceViewModel mediaSourceVM, @NonNull ViewModelProvider viewModelProvider,
-                @NonNull String key) {
-            MediaBrowserViewModelImpl viewModel = viewModelProvider.get(key,
-                    MediaBrowserViewModelImpl.class);
-            initMediaBrowser(mediaSourceVM.getConnectedMediaBrowser(), viewModel);
-            return viewModel;
-        }
-
-        /**
-         * Fetch an initialized {@link MediaBrowserViewModel}. It will get its media browser from
-         * the {@link MediaSourceViewModel} provided by {@code viewModelProvider}. It will already
-         * be configured to browse {@code browseId}.
-         *
-         *
-         * @param mediaSourceVM     the {@link MediaSourceViewModel} singleton.
-         * @param viewModelProvider the ViewModelProvider to load ViewModels from.
-         * @param browseId          the browseId to browse. This will also serve as the key for
-         *                          fetching the ViewModel.
-         * @return an initialized MediaBrowserViewModel configured to browse the specified browseId.
-         */
-        @NonNull
-        public static MediaBrowserViewModel getInstanceForBrowseId(
-                MediaSourceViewModel mediaSourceVM, @NonNull ViewModelProvider viewModelProvider,
-                @NonNull String browseId) {
-            MediaBrowserViewModel.WithMutableBrowseId viewModel =
-                    getInstanceForKey(mediaSourceVM, viewModelProvider, browseId);
-            viewModel.setCurrentBrowseId(browseId);
-            return viewModel;
-        }
-
-        /**
          * Fetch an initialized {@link MediaBrowserViewModel}. It will get its media browser from
          * the {@link MediaSourceViewModel} provided by {@code viewModelProvider}. It will already
          * be configured to browse the root of the browser.
@@ -203,9 +158,9 @@
         @NonNull
         public static MediaBrowserViewModel getInstanceForBrowseRoot(
                 MediaSourceViewModel mediaSourceVM, @NonNull ViewModelProvider viewModelProvider) {
-            MediaBrowserViewModel.WithMutableBrowseId viewModel =
-                    getInstanceForKey(mediaSourceVM, viewModelProvider, KEY_BROWSER_ROOT);
-            viewModel.setCurrentBrowseId(null);
+            RootMediaBrowserViewModel viewModel =
+                    viewModelProvider.get(KEY_BROWSER_ROOT, RootMediaBrowserViewModel.class);
+            initMediaBrowser(mediaSourceVM.getConnectedMediaBrowser(), viewModel);
             return viewModel;
         }
 
diff --git a/car-media-common/src/com/android/car/media/common/browse/MediaBrowserViewModelImpl.java b/car-media-common/src/com/android/car/media/common/browse/MediaBrowserViewModelImpl.java
index 71a1e56..7d47ba7 100644
--- a/car-media-common/src/com/android/car/media/common/browse/MediaBrowserViewModelImpl.java
+++ b/car-media-common/src/com/android/car/media/common/browse/MediaBrowserViewModelImpl.java
@@ -25,7 +25,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UiThread;
 import android.app.Application;
 import android.os.Bundle;
 import android.support.v4.media.MediaBrowserCompat;
@@ -49,14 +48,15 @@
  * obtained via {@link MediaBrowserViewModel.Factory}
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
-public class MediaBrowserViewModelImpl extends AndroidViewModel implements
-        MediaBrowserViewModel.WithMutableBrowseId {
+class MediaBrowserViewModelImpl extends AndroidViewModel implements MediaBrowserViewModel {
+
+    private final boolean mIsRoot;
 
     private final SwitchingLiveData<MediaBrowserCompat> mMediaBrowserSwitch =
             SwitchingLiveData.newInstance();
 
-    private final MutableLiveData<String> mCurrentBrowseId = new MutableLiveData<>();
-    private final MutableLiveData<String> mCurrentSearchQuery = dataOf(null);
+    final MutableLiveData<String> mCurrentBrowseId = dataOf(null);
+    final MutableLiveData<String> mCurrentSearchQuery = dataOf(null);
     private final LiveData<MediaBrowserCompat> mConnectedMediaBrowser =
             map(mMediaBrowserSwitch.asLiveData(), MediaBrowserViewModelImpl::requireConnected);
 
@@ -67,9 +67,11 @@
 
     private final LiveData<String> mPackageName;
 
-    public MediaBrowserViewModelImpl(@NonNull Application application) {
+    MediaBrowserViewModelImpl(@NonNull Application application, boolean isRoot) {
         super(application);
 
+        mIsRoot = isRoot;
+
         mPackageName = map(mConnectedMediaBrowser,
                 mediaBrowser -> {
                     if (mediaBrowser == null) return null;
@@ -78,10 +80,14 @@
 
         mBrowsedMediaItems =
                 loadingSwitchMap(pair(mConnectedMediaBrowser, mCurrentBrowseId),
-                        split((mediaBrowser, browseId) ->
-                                mediaBrowser == null
-                                        ? null
-                                        : new BrowsedMediaItems(mediaBrowser, browseId)));
+                        split((mediaBrowser, browseId) -> {
+                            if (mediaBrowser == null || (!mIsRoot && browseId == null)) {
+                                return null;
+                            }
+
+                            String parentId = (mIsRoot) ? mediaBrowser.getRoot() : browseId;
+                            return new BrowsedMediaItems(mediaBrowser, parentId);
+                        }));
         mSearchedMediaItems =
                 loadingSwitchMap(pair(mConnectedMediaBrowser, mCurrentSearchQuery),
                         split((mediaBrowser, query) ->
@@ -143,26 +149,6 @@
         return mMediaBrowserSwitch.getSource();
     }
 
-    /**
-     * Set the current item to be browsed. If available, the list of items will be emitted by {@link
-     * #getBrowsedMediaItems()}.
-     */
-    @UiThread
-    @Override
-    public void setCurrentBrowseId(@Nullable String browseId) {
-        mCurrentBrowseId.setValue(browseId);
-    }
-
-    /**
-     * Set the current item to be searched for. If available, the list of items will be emitted
-     * by {@link #getBrowsedMediaItems()}.
-     */
-    @UiThread
-    @Override
-    public void search(@Nullable String query) {
-        mCurrentSearchQuery.setValue(query);
-    }
-
     @Override
     public LiveData<String> getPackageName() {
         return mPackageName;
diff --git a/car-media-common/src/com/android/car/media/common/browse/MutableMediaBrowserViewModel.java b/car-media-common/src/com/android/car/media/common/browse/MutableMediaBrowserViewModel.java
new file mode 100644
index 0000000..482efdd
--- /dev/null
+++ b/car-media-common/src/com/android/car/media/common/browse/MutableMediaBrowserViewModel.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.media.common.browse;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.app.Application;
+
+import androidx.annotation.RestrictTo;
+
+/** This isn't a comment. */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class MutableMediaBrowserViewModel extends MediaBrowserViewModelImpl implements
+        MediaBrowserViewModel.WithMutableBrowseId {
+    public MutableMediaBrowserViewModel(@NonNull Application application) {
+        super(application, /*isRoot*/ false);
+    }
+
+    @UiThread
+    @Override
+    public void setCurrentBrowseId(@NonNull String browseId) {
+        super.mCurrentBrowseId.setValue(browseId);
+    }
+
+    @UiThread
+    @Override
+    public void search(@Nullable String query) {
+        super.mCurrentSearchQuery.setValue(query);
+    }
+}
diff --git a/car-media-common/src/com/android/car/media/common/browse/RootMediaBrowserViewModel.java b/car-media-common/src/com/android/car/media/common/browse/RootMediaBrowserViewModel.java
new file mode 100644
index 0000000..1edb484
--- /dev/null
+++ b/car-media-common/src/com/android/car/media/common/browse/RootMediaBrowserViewModel.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.media.common.browse;
+
+import android.annotation.NonNull;
+import android.app.Application;
+
+import androidx.annotation.RestrictTo;
+
+/** This isn't a comment. */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class RootMediaBrowserViewModel extends MediaBrowserViewModelImpl {
+    public RootMediaBrowserViewModel(@NonNull Application application) {
+        super(application, /*isRoot*/ true);
+    }
+}
diff --git a/car-media-common/src/com/android/car/media/common/source/MediaSourceViewModel.java b/car-media-common/src/com/android/car/media/common/source/MediaSourceViewModel.java
index d25ebe9..1d02209 100644
--- a/car-media-common/src/com/android/car/media/common/source/MediaSourceViewModel.java
+++ b/car-media-common/src/com/android/car/media/common/source/MediaSourceViewModel.java
@@ -217,20 +217,13 @@
             return;
         }
 
-        // Reset dependent values to avoid propagating inconsistencies.
-        mMediaController.setValue(null);
-        mConnectedMediaBrowser.setValue(null);
-        mBrowserConnector.connectTo(null);
-
         // Broadcast the new source
         mPrimaryMediaSource.setValue(newMediaSource);
 
         // Recompute dependent values
-        if (newMediaSource == null) {
-            return;
+        if (newMediaSource != null) {
+            ComponentName browseService = newMediaSource.getBrowseServiceComponentName();
+            mBrowserConnector.connectTo(browseService);
         }
-
-        ComponentName browseService = newMediaSource.getBrowseServiceComponentName();
-        mBrowserConnector.connectTo(browseService);
     }
 }
diff --git a/car-telephony-common/res/values/strings.xml b/car-telephony-common/res/values/strings.xml
index e4f7b52..24a3300 100644
--- a/car-telephony-common/res/values/strings.xml
+++ b/car-telephony-common/res/values/strings.xml
@@ -42,4 +42,9 @@
     <!-- Status label for phone state. &#8230; is an ellipsis. [CHAR LIMIT=25] -->
     <string name="call_state_call_ending">Disconnecting&#8230;</string>
 
+    <!-- String format used to format a address Uri. -->
+    <string name="address_uri_format" translatable="false">geo:0,0?q=%s</string>
+    <!-- String format used to format a navigation Uri. -->
+    <string name="navigation_uri_format" translatable="false">https://maps.google.com/maps?daddr=%s&amp;nav=1</string>
+
 </resources>
\ No newline at end of file
diff --git a/car-telephony-common/src/com/android/car/telephony/common/Contact.java b/car-telephony-common/src/com/android/car/telephony/common/Contact.java
index d61fbf9..f2b45f8 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
@@ -159,11 +159,6 @@
     private String mLookupKey;
 
     /**
-     * All phone numbers of this contact mapping to the unique primary key for the raw data entry.
-     */
-    private List<PhoneNumber> mPhoneNumbers = new ArrayList<>();
-
-    /**
      * A URI that can be used to retrieve a thumbnail of the contact's photo.
      */
     @Nullable
@@ -198,6 +193,17 @@
     private boolean mIsVoiceMail;
 
     /**
+     * All phone numbers of this contact mapping to the unique primary key for the raw data entry.
+     */
+    private final List<PhoneNumber> mPhoneNumbers = new ArrayList<>();
+
+    /**
+     * All postal addresses of this contact mapping to the unique primary key for the raw data
+     * entry
+     */
+    private final List<PostalAddress> mPostalAddresses = new ArrayList<>();
+
+    /**
      * Parses a contact entry for a Cursor loaded from the Contact Database. A new contact will be
      * created and returned.
      */
@@ -226,7 +232,8 @@
             contact.loadBasicInfo(cursor);
         }
 
-        if (!accountName.equals(contact.mAccountName) || !lookupKey.equals(contact.mLookupKey)) {
+        if (!TextUtils.equals(accountName, contact.mAccountName)
+                || !TextUtils.equals(lookupKey, contact.mLookupKey)) {
             Log.w(TAG, "A wrong contact is passed in. A new contact will be created.");
             contact = new Contact();
             contact.loadBasicInfo(cursor);
@@ -243,6 +250,9 @@
             case ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE:
                 contact.addPhoneNumber(context, cursor);
                 break;
+            case ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE:
+                contact.addPostalAddress(cursor);
+                break;
             default:
                 Log.d(TAG,
                         String.format("This mimetype %s will not be loaded right now.", mimeType));
@@ -340,6 +350,15 @@
         }
     }
 
+    /**
+     * Loads the data whose mimetype is
+     * {@link ContactsContract.CommonDataKinds.StructuredPostal#CONTENT_ITEM_TYPE}.
+     */
+    private void addPostalAddress(Cursor cursor) {
+        PostalAddress postalAddress = PostalAddress.fromCursor(cursor);
+        mPostalAddresses.add(postalAddress);
+    }
+
     @Override
     public boolean equals(Object obj) {
         return obj instanceof Contact && mLookupKey.equals(((Contact) obj).mLookupKey)
@@ -465,6 +484,13 @@
     }
 
     /**
+     * Return all postal addresses associated with this contact.
+     */
+    public List<PostalAddress> getPostalAddresses() {
+        return mPostalAddresses;
+    }
+
+    /**
      * Returns if this Contact represents a voice mail number.
      */
     public boolean isVoicemail() {
@@ -544,6 +570,11 @@
         for (PhoneNumber phoneNumber : mPhoneNumbers) {
             dest.writeParcelable(phoneNumber, flags);
         }
+
+        dest.writeInt(mPostalAddresses.size());
+        for (PostalAddress postalAddress : mPostalAddresses) {
+            dest.writeParcelable(postalAddress, flags);
+        }
     }
 
     public static final Creator<Contact> CREATOR = new Creator<Contact>() {
@@ -581,7 +612,6 @@
         contact.mIsVoiceMail = source.readBoolean();
         contact.mPrimaryPhoneNumber = source.readParcelable(PhoneNumber.class.getClassLoader());
         int phoneNumberListLength = source.readInt();
-        contact.mPhoneNumbers = new ArrayList<>();
         for (int i = 0; i < phoneNumberListLength; i++) {
             PhoneNumber phoneNumber = source.readParcelable(PhoneNumber.class.getClassLoader());
             contact.mPhoneNumbers.add(phoneNumber);
@@ -590,6 +620,12 @@
             }
         }
 
+        int postalAddressListLength = source.readInt();
+        for (int i = 0; i < postalAddressListLength; i++) {
+            PostalAddress address = source.readParcelable(PostalAddress.class.getClassLoader());
+            contact.mPostalAddresses.add(address);
+        }
+
         return contact;
     }
 
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 db95960..501fa02 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
@@ -22,11 +22,13 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.Observer;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -39,7 +41,6 @@
  */
 public class InMemoryPhoneBook implements Observer<List<Contact>> {
     private static final String TAG = "CD.InMemoryPhoneBook";
-    private static final String KEY_FORMAT = "%s %s";
     private static InMemoryPhoneBook sInMemoryPhoneBook;
 
     private final Context mContext;
@@ -49,9 +50,10 @@
      */
     private final Map<I18nPhoneNumberWrapper, Contact> mPhoneNumberContactMap = new HashMap<>();
     /**
-     * A map to look up contact by lookup key.
+     * A map to look up contact by account name and lookup key. Each entry presents a map of lookup
+     * key to contacts for one account.
      */
-    private final Map<String, Contact> mLookupKeyContactMap = new HashMap<>();
+    private final Map<String, Map<String, Contact>> mLookupKeyContactMap = new HashMap<>();
     private boolean mIsLoaded = false;
 
     /**
@@ -103,10 +105,12 @@
                 ContactsContract.Data.CONTENT_URI,
                 null,
                 ContactsContract.Data.MIMETYPE + " = ? OR "
+                        + ContactsContract.Data.MIMETYPE + " = ? OR "
                         + ContactsContract.Data.MIMETYPE + " = ?",
                 new String[]{
                         ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
-                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE},
+                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE,
+                        ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE},
                 ContactsContract.Contacts.DISPLAY_NAME + " ASC ");
         mContactListAsyncQueryLiveData = new AsyncQueryLiveData<List<Contact>>(mContext,
                 QueryParam.of(contactListQueryParam), Executors.newSingleThreadExecutor()) {
@@ -158,11 +162,11 @@
     }
 
     /**
-     * Looks up a {@link Contact} by the given lookup key and account name. Returns null if can't
-     * find the contact entry.
+     * Looks up a {@link Contact} by the given lookup key and account name. Account name could be
+     * null for locally added contacts. Returns null if can't find the contact entry.
      */
     @Nullable
-    public Contact lookupContactByKey(String lookupKey, String accountName) {
+    public Contact lookupContactByKey(String lookupKey, @Nullable String accountName) {
         if (!isLoaded()) {
             Log.w(TAG, "looking up a contact while loading.");
         }
@@ -170,16 +174,41 @@
             Log.w(TAG, "looking up an empty lookup key.");
             return null;
         }
-        if (TextUtils.isEmpty(accountName)) {
-            Log.w(TAG, "looking up an empty lookup account");
-            return null;
+        if (mLookupKeyContactMap.containsKey(accountName)) {
+            return mLookupKeyContactMap.get(accountName).get(lookupKey);
         }
 
-        return mLookupKeyContactMap.get(getContactKey(lookupKey, accountName));
+        return null;
+    }
+
+    /**
+     * Iterates all the accounts and returns a list of contacts that match the lookup key. This API
+     * is discouraged to use whenever the account name is available where {@link
+     * #lookupContactByKey(String, String)} should be used instead.
+     */
+    @NonNull
+    public List<Contact> lookupContactByKey(String lookupKey) {
+        if (!isLoaded()) {
+            Log.w(TAG, "looking up a contact while loading.");
+        }
+
+        if (TextUtils.isEmpty(lookupKey)) {
+            Log.w(TAG, "looking up an empty lookup key.");
+            return Collections.emptyList();
+        }
+        List<Contact> results = new ArrayList<>();
+        // Iterate all the accounts to get all the match contacts with given lookup key.
+        for (Map<String, Contact> subMap : mLookupKeyContactMap.values()) {
+            if (subMap.containsKey(lookupKey)) {
+                results.add(subMap.get(lookupKey));
+            }
+        }
+
+        return results;
     }
 
     private List<Contact> onCursorLoaded(Cursor cursor) {
-        Map<String, Contact> contactMap = new LinkedHashMap<>();
+        Map<String, Map<String, Contact>> contactMap = new LinkedHashMap<>();
         List<Contact> contactList = new ArrayList<>();
 
         while (cursor.moveToNext()) {
@@ -188,12 +217,18 @@
             int lookupKeyColumn = cursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY);
             String accountName = cursor.getString(accountNameColumn);
             String lookupKey = cursor.getString(lookupKeyColumn);
-            String key = getContactKey(lookupKey, accountName);
 
-            contactMap.put(key, Contact.fromCursor(mContext, cursor, contactMap.get(key)));
+            if (!contactMap.containsKey(accountName)) {
+                contactMap.put(accountName, new HashMap<>());
+            }
+
+            Map<String, Contact> subMap = contactMap.get(accountName);
+            subMap.put(lookupKey, Contact.fromCursor(mContext, cursor, subMap.get(lookupKey)));
         }
 
-        contactList.addAll(contactMap.values());
+        for (Map<String, Contact> subMap : contactMap.values()) {
+            contactList.addAll(subMap.values());
+        }
 
         mLookupKeyContactMap.clear();
         mLookupKeyContactMap.putAll(contactMap);
@@ -211,13 +246,4 @@
         Log.d(TAG, "Contacts loaded:" + (contacts == null ? 0 : contacts.size()));
         mIsLoaded = true;
     }
-
-    /**
-     * Formats a key to identify a contact based the lookup key and the account name.
-     */
-    private String getContactKey(String lookupKey, String accountName) {
-        String key = String.format(KEY_FORMAT, lookupKey, accountName);
-        Log.d(TAG, "Contact key is: " + key);
-        return key;
-    }
 }
diff --git a/car-telephony-common/src/com/android/car/telephony/common/PostalAddress.java b/car-telephony-common/src/com/android/car/telephony/common/PostalAddress.java
new file mode 100644
index 0000000..7d0baec
--- /dev/null
+++ b/car-telephony-common/src/com/android/car/telephony/common/PostalAddress.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.telephony.common;
+
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.ContactsContract;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Encapsulates data about an address entry. Typically loaded from the local Address store.
+ */
+public class PostalAddress implements Parcelable {
+    private static final String TAG = "CD.PostalAddress";
+
+    /**
+     * The formatted address.
+     */
+    private String mFormattedAddress;
+
+    /**
+     * The address type. See more at {@link ContactsContract.CommonDataKinds.StructuredPostal#TYPE}
+     */
+    private int mType;
+
+    /**
+     * The user defined label. See more at
+     * {@link ContactsContract.CommonDataKinds.StructuredPostal#LABEL}
+     */
+    @Nullable
+    private String mLabel;
+
+    /**
+     * Parses a PostalAddress entry for a Cursor loaded from the Address Database.
+     */
+    public static PostalAddress fromCursor(Cursor cursor) {
+        int formattedAddressColumn = cursor.getColumnIndex(
+                ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS);
+        int addressTypeColumn = cursor.getColumnIndex(
+                ContactsContract.CommonDataKinds.StructuredPostal.TYPE);
+        int labelColumn = cursor.getColumnIndex(
+                ContactsContract.CommonDataKinds.StructuredPostal.LABEL);
+
+        PostalAddress postalAddress = new PostalAddress();
+        postalAddress.mFormattedAddress = cursor.getString(formattedAddressColumn);
+        postalAddress.mType = cursor.getInt(addressTypeColumn);
+        postalAddress.mLabel = cursor.getString(labelColumn);
+
+        return postalAddress;
+    }
+
+    /**
+     * Returns {@link #mFormattedAddress}
+     */
+    public String getFormattedAddress() {
+        return mFormattedAddress;
+    }
+
+    /**
+     * Returns {@link #mType}
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns {@link #mLabel}
+     */
+    @Nullable
+    public String getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * Returns a human readable string label. For example, Home, Work, etc.
+     */
+    public CharSequence getReadableLabel(Resources res) {
+        return ContactsContract.CommonDataKinds.StructuredPostal.getTypeLabel(res, mType, mLabel);
+    }
+
+    /**
+     * Returns the address Uri for {@link #mFormattedAddress}.
+     */
+    public Uri getAddressUri(Resources res) {
+        String address = String.format(res.getString(R.string.address_uri_format),
+                Uri.encode(mFormattedAddress));
+        Log.d(TAG, "The address is: " + address);
+        return Uri.parse(address);
+    }
+
+    /**
+     * Returns the navigation Uri for {@link #mFormattedAddress}.
+     */
+    public Uri getNavigationUri(Resources res) {
+        String address = String.format(res.getString(R.string.navigation_uri_format),
+                Uri.encode(mFormattedAddress));
+        Log.d(TAG, "The address is: " + address);
+        return Uri.parse(address);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeString(mLabel);
+        dest.writeString(mFormattedAddress);
+    }
+
+    /**
+     * Create {@link PostalAddress} object from saved parcelable.
+     */
+    public static Creator<PostalAddress> CREATOR = new Creator<PostalAddress>() {
+        @Override
+        public PostalAddress createFromParcel(Parcel source) {
+            PostalAddress postalAddress = new PostalAddress();
+            postalAddress.mType = source.readInt();
+            postalAddress.mLabel = source.readString();
+            postalAddress.mFormattedAddress = source.readString();
+            return postalAddress;
+        }
+
+        @Override
+        public PostalAddress[] newArray(int size) {
+            return new PostalAddress[size];
+        }
+    };
+}
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 00c1e07..c9c2f8c 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
@@ -362,8 +362,8 @@
     }
 
     /**
-     * Sets a Contact avatar onto the provided {@code icon}. The first letter or both letters
-     * of the contact's initials.
+     * Sets a Contact avatar onto the provided {@code icon}. The first letter or both letters of the
+     * contact's initials.
      */
     public static void setContactBitmapAsync(
             Context context,
@@ -373,9 +373,9 @@
     }
 
     /**
-     * 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.
+     * 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.
      */
     public static void setContactBitmapAsync(
             Context context,
@@ -383,9 +383,9 @@
             @Nullable final Contact contact,
             @Nullable final String fallbackDisplayName) {
         Uri avatarUri = contact != null ? contact.getAvatarUri() : null;
-        String initials = contact != null
-                ? contact.getInitials() : fallbackDisplayName.substring(0, 1);
-        String identifier = TextUtils.isEmpty(initials) ? fallbackDisplayName : initials;
+        String initials = contact != null ? contact.getInitials()
+                : (fallbackDisplayName == null ? null : getInitials(fallbackDisplayName, null));
+        String identifier = contact == null ? fallbackDisplayName : contact.getDisplayName();
 
         setContactBitmapAsync(context, icon, avatarUri, initials, identifier);
     }
@@ -416,10 +416,10 @@
     /**
      * Create a {@link LetterTileDrawable} for the given initials.
      *
-     * @param initials   is the letters that will be drawn on the canvas. If it is null, then
-     *                   an avatar anonymous icon will be drawn
-     * @param identifier will decide the color for the drawable. If null, a default color will
-     *                   be used.
+     * @param initials   is the letters that will be drawn on the canvas. If it is null, then an
+     *                   avatar anonymous icon will be drawn
+     * @param identifier will decide the color for the drawable. If null, a default color will be
+     *                   used.
      */
     public static LetterTileDrawable createLetterTile(
             Context context,
@@ -506,7 +506,13 @@
         }
     }
 
-    static String getInitials(String name, String nameAlt) {
+    /**
+     * Returns the initials based on the name and nameAlt.
+     *
+     * @param name    should be the display name of a contact.
+     * @param nameAlt should be alternative display name of a contact.
+     */
+    public static String getInitials(String name, String nameAlt) {
         StringBuilder initials = new StringBuilder();
         if (!TextUtils.isEmpty(name) && Character.isLetter(name.charAt(0))) {
             initials.append(Character.toUpperCase(name.charAt(0)));
diff --git a/car-ui-lib/build.gradle b/car-ui-lib/build.gradle
index 7a8596f..aa720d4 100644
--- a/car-ui-lib/build.gradle
+++ b/car-ui-lib/build.gradle
@@ -23,7 +23,7 @@
 
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.5.0'
+        classpath 'com.android.tools.build:gradle:3.5.1'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
diff --git a/car-ui-lib/res/layout/car_ui_alert_dialog_title_with_subtitle.xml b/car-ui-lib/res/layout/car_ui_alert_dialog_title_with_subtitle.xml
new file mode 100644
index 0000000..389e511
--- /dev/null
+++ b/car-ui-lib/res/layout/car_ui_alert_dialog_title_with_subtitle.xml
@@ -0,0 +1,51 @@
+<?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:id="@+id/title_template"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    style="@style/Widget.CarUi.AlertDialog.HeaderContainer">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/car_ui_dialog_icon_size"
+        android:layout_height="@dimen/car_ui_dialog_icon_size"
+        style="@style/Widget.CarUi.AlertDialog.Icon"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/Widget.CarUi.AlertDialog.TitleContainer">
+        <com.android.internal.widget.DialogTitle
+            android:id="@+id/alertTitle"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAlignment="viewStart"
+            style="?android:attr/windowTitleStyle" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/alertSubtitle"
+            android:textAppearance="@style/TextAppearance.CarUi.AlertDialog.Subtitle"/>
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/car-ui-lib/res/layout/car_ui_list_preference_dialog.xml b/car-ui-lib/res/layout/car_ui_list_preference.xml
similarity index 100%
rename from car-ui-lib/res/layout/car_ui_list_preference_dialog.xml
rename to car-ui-lib/res/layout/car_ui_list_preference.xml
diff --git a/car-ui-lib/res/layout/car_ui_preference.xml b/car-ui-lib/res/layout/car_ui_preference.xml
index 2c2f4cf..03e101d 100644
--- a/car-ui-lib/res/layout/car_ui_preference.xml
+++ b/car-ui-lib/res/layout/car_ui_preference.xml
@@ -35,7 +35,7 @@
         android:layout_marginEnd="@dimen/car_ui_preference_icon_margin_end"
         android:layout_marginTop="@dimen/car_ui_preference_content_margin_top"
         android:scaleType="fitCenter"
-        android:tint="@color/car_ui_preference_icon_color"/>
+        style="@style/Preference.CarUi.Icon"/>
 
     <LinearLayout
         android:layout_width="match_parent"
diff --git a/car-ui-lib/res/layout/car_ui_radio_button_item.xml b/car-ui-lib/res/layout/car_ui_radio_button_item.xml
index e6733b1..f6e58ee 100644
--- a/car-ui-lib/res/layout/car_ui_radio_button_item.xml
+++ b/car-ui-lib/res/layout/car_ui_radio_button_item.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ Copyright 2019 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,10 +13,51 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-
-<RadioButton
+<androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/radio_button"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"/>
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/car_ui_list_item_radio_button_height">
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/car_ui_radio_button_start_guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="@dimen/car_ui_list_item_radio_button_start_inset" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/car_ui_radio_button_end_guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_end="@dimen/car_ui_list_item_radio_button_end_inset" />
+
+    <FrameLayout
+        android:id="@+id/radio_button_container"
+        android:layout_width="@dimen/car_ui_list_item_radio_button_icon_container_width"
+        android:layout_height="0dp"
+        android:background="?android:attr/selectableItemBackground"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/car_ui_radio_button_start_guideline"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <RadioButton
+            android:id="@+id/radio_button"
+            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_radio_button_end_guideline"
+        app:layout_constraintStart_toEndOf="@+id/radio_button_container"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/car-ui-lib/res/layout/car_ui_toolbar.xml b/car-ui-lib/res/layout/car_ui_toolbar.xml
index f22318d..2d54431 100644
--- a/car-ui-lib/res/layout/car_ui_toolbar.xml
+++ b/car-ui-lib/res/layout/car_ui_toolbar.xml
@@ -150,16 +150,28 @@
         android:layout_width="match_parent"
         android:layout_height="@dimen/car_ui_toolbar_separator_height"
         style="@style/Widget.CarUi.Toolbar.SeparatorView"
-        app:layout_constraintTop_toBottomOf="@id/car_ui_toolbar_bottom_guideline"
+        app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"/>
 
+    <ProgressBar
+        android:id="@+id/car_ui_toolbar_progress_bar"
+        style="@style/Widget.CarUi.Toolbar.ProgressBar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:indeterminate="true"
+        android:visibility="gone"/>
+
     <View
         android:id="@+id/car_ui_toolbar_bottom_styleable"
         android:layout_width="match_parent"
         android:layout_height="@dimen/car_ui_toolbar_bottom_view_height"
         style="@style/Widget.CarUi.Toolbar.BottomView"
-        app:layout_constraintTop_toBottomOf="@+id/car_ui_toolbar_row_separator"
+        app:layout_constraintBottom_toTopOf="@+id/car_ui_toolbar_progress_bar"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"/>
+
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml b/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml
index fe9eadc..bc334bf 100644
--- a/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml
+++ b/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml
@@ -164,4 +164,15 @@
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"/>
 
+    <ProgressBar
+        android:id="@+id/car_ui_toolbar_progress_bar"
+        style="@style/Widget.CarUi.Toolbar.ProgressBar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_styleable"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:indeterminate="true"
+        android:visibility="gone"/>
+
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-ui-lib/res/values-w1280dp/dimens.xml b/car-ui-lib/res/values-w1280dp/dimens.xml
new file mode 100644
index 0000000..a06df2b
--- /dev/null
+++ b/car-ui-lib/res/values-w1280dp/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Keylines -->
+    <dimen name="car_ui_keyline_1">32dp</dimen>
+    <dimen name="car_ui_keyline_2">108dp</dimen>
+    <dimen name="car_ui_keyline_3">128dp</dimen>
+    <dimen name="car_ui_keyline_4">168dp</dimen>
+</resources>
diff --git a/car-ui-lib/res/values/attrs.xml b/car-ui-lib/res/values/attrs.xml
index f6a60c9..71ad091 100644
--- a/car-ui-lib/res/values/attrs.xml
+++ b/car-ui-lib/res/values/attrs.xml
@@ -46,6 +46,10 @@
         <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 -->
@@ -109,6 +113,14 @@
         </attr>
     </declare-styleable>
 
+    <declare-styleable name="CarUiPreference">
+        <!-- Toggle for showing chevron -->
+        <attr name="showChevron" 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" />
 
diff --git a/car-ui-lib/res/values/dimens.xml b/car-ui-lib/res/values/dimens.xml
index 1ffa471..1679a1d 100644
--- a/car-ui-lib/res/values/dimens.xml
+++ b/car-ui-lib/res/values/dimens.xml
@@ -40,6 +40,12 @@
     <dimen name="car_ui_body2_size">28sp</dimen>
     <dimen name="car_ui_body3_size">24sp</dimen>
 
+    <!-- Keylines -->
+    <dimen name="car_ui_keyline_1">24dp</dimen>
+    <dimen name="car_ui_keyline_2">96dp</dimen>
+    <dimen name="car_ui_keyline_3">112dp</dimen>
+    <dimen name="car_ui_keyline_4">148dp</dimen>
+
     <!-- Tabs -->
 
     <!-- Exact size of the tab textbox. Use @dimen/wrap_content if this must be flexible -->
@@ -172,6 +178,8 @@
     <dimen name="car_ui_dialog_edittext_margin_bottom">10dp</dimen>
     <dimen name="car_ui_dialog_edittext_margin_start">22dp</dimen>
     <dimen name="car_ui_dialog_edittext_margin_end">22dp</dimen>
+    <dimen name="car_ui_dialog_icon_size">56dp</dimen>
+    <dimen name="car_ui_dialog_title_margin">@dimen/car_ui_keyline_1</dimen>
 
     <!-- List item  -->
 
@@ -187,4 +195,9 @@
     <dimen name="car_ui_list_item_icon_container_width">112dp</dimen>
     <dimen name="car_ui_list_item_action_divider_width">1dp</dimen>
     <dimen name="car_ui_list_item_action_divider_height">60dp</dimen>
+
+    <dimen name="car_ui_list_item_radio_button_height">@dimen/car_ui_list_item_height</dimen>
+    <dimen name="car_ui_list_item_radio_button_start_inset">@dimen/car_ui_list_item_start_inset</dimen>
+    <dimen name="car_ui_list_item_radio_button_end_inset">@dimen/car_ui_list_item_end_inset</dimen>
+    <dimen name="car_ui_list_item_radio_button_icon_container_width">@dimen/car_ui_list_item_icon_container_width</dimen>
 </resources>
diff --git a/car-ui-lib/res/values/styles.xml b/car-ui-lib/res/values/styles.xml
index 5881a63..cb11c1e 100644
--- a/car-ui-lib/res/values/styles.xml
+++ b/car-ui-lib/res/values/styles.xml
@@ -34,6 +34,10 @@
         <item name="android:paddingEnd">@dimen/car_ui_toolbar_title_logo_padding</item>
     </style>
 
+    <style name="Widget.CarUi.Toolbar.ProgressBar"
+           parent="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal">
+    </style>
+
     <style name="Widget.CarUi.Toolbar.NavIcon">
         <item name="android:tint">@color/car_ui_toolbar_nav_icon_color</item>
         <item name="android:src">@drawable/car_ui_icon_arrow_back</item>
@@ -110,6 +114,26 @@
         <item name="android:scrollbars">none</item>
     </style>
 
+    <style name="Widget.CarUi.AlertDialog"/>
+
+    <style name="Widget.CarUi.AlertDialog.HeaderContainer">
+        <item name="android:orientation">horizontal</item>
+        <item name="android:gravity">center_vertical|start</item>
+        <item name="android:paddingTop">18dp</item>
+        <item name="android:paddingBottom">18dp</item>
+    </style>
+
+    <style name="Widget.CarUi.AlertDialog.TitleContainer">
+        <item name="android:layout_marginStart">@dimen/car_ui_dialog_title_margin</item>
+        <item name="android:layout_marginEnd">@dimen/car_ui_dialog_title_margin</item>
+        <item name="android:orientation">vertical</item>
+    </style>
+
+    <style name="Widget.CarUi.AlertDialog.Icon">
+        <item name="android:layout_marginStart">@dimen/car_ui_dialog_title_margin</item>
+        <item name="android:scaleType">fitCenter</item>
+    </style>
+
     <style name="Preference.CarUi.ListPreference" parent="android:Theme.DeviceDefault.Dialog">
         <item name="android:windowNoTitle">true</item>
         <!-- Set this to true if you want Full Screen without status bar -->
@@ -147,11 +171,15 @@
         <item name="android:layout">@layout/car_ui_preference_dropdown</item>
     </style>
 
+    <style name="Preference.CarUi.Icon"/>
+
     <style name="Preference.CarUi.Information">
         <item name="android:enabled">false</item>
         <item name="android:shouldDisableView">false</item>
     </style>
 
+    <style name="Preference.CarUi.Preference"/>
+
     <style name="Preference.CarUi.PreferenceScreen"/>
 
     <style name="Preference.CarUi.SeekBarPreference">
@@ -207,13 +235,12 @@
     </style>
 
     <style name="TextAppearance.CarUi.PreferenceEditTextDialogMessage">
-        <item name="android:textColor">
-            @color/car_ui_preference_edit_text_dialog_message_text_color
-        </item>
-        <item name="android:textSize">@dimen/car_ui_preference_edit_text_dialog_message_text_size
-        </item>
+        <item name="android:textColor">@color/car_ui_preference_edit_text_dialog_message_text_color</item>
+        <item name="android:textSize">@dimen/car_ui_preference_edit_text_dialog_message_text_size</item>
     </style>
 
+    <style name="TextAppearance.CarUi.AlertDialog.Subtitle" parent="android:TextAppearance.DeviceDefault"/>
+
     <style name="TextAppearance.CarUi.Widget" parent="android:TextAppearance.DeviceDefault.Widget"/>
 
     <style name="TextAppearance.CarUi.Widget.Toolbar"/>
diff --git a/car-ui-lib/res/values/themes.xml b/car-ui-lib/res/values/themes.xml
index 70e6872..d40e13c 100644
--- a/car-ui-lib/res/values/themes.xml
+++ b/car-ui-lib/res/values/themes.xml
@@ -205,13 +205,10 @@
     </style>
 
     <style name="CarUiPreferenceTheme">
-        <item name="checkBoxPreferenceStyle">@style/Preference.CarUi.CheckBoxPreference
-        </item>
+        <item name="checkBoxPreferenceStyle">@style/Preference.CarUi.CheckBoxPreference</item>
         <item name="dialogPreferenceStyle">@style/Preference.CarUi.DialogPreference</item>
         <item name="dropdownPreferenceStyle">@style/Preference.CarUi.DropDown</item>
-        <item name="editTextPreferenceStyle">
-            @style/Preference.CarUi.DialogPreference.EditTextPreference
-        </item>
+        <item name="editTextPreferenceStyle">@style/Preference.CarUi.DialogPreference.EditTextPreference</item>
         <item name="preferenceCategoryStyle">@style/Preference.CarUi.Category</item>
         <item name="preferenceFragmentCompatStyle">@style/PreferenceFragment.CarUi</item>
         <item name="preferenceFragmentListStyle">@style/PreferenceFragmentList.CarUi</item>
diff --git a/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java b/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java
index 78d8d87..25431d8 100644
--- a/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java
+++ b/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java
@@ -27,7 +27,9 @@
 import android.view.View;
 import android.widget.AdapterView;
 import android.widget.EditText;
+import android.widget.ImageView;
 import android.widget.ListAdapter;
+import android.widget.TextView;
 
 import androidx.annotation.ArrayRes;
 import androidx.annotation.AttrRes;
@@ -44,7 +46,9 @@
     private boolean mPositiveButtonSet;
     private boolean mNeutralButtonSet;
     private boolean mNegativeButtonSet;
-    private String mDefaultButtonText;
+    private CharSequence mTitle;
+    private CharSequence mSubtitle;
+    private Drawable mIcon;
 
     public AlertDialogBuilder(Context context) {
         // Resource id specified as 0 uses the parent contexts resolved value for alertDialogTheme.
@@ -54,7 +58,6 @@
     public AlertDialogBuilder(Context context, int themeResId) {
         mBuilder = new AlertDialog.Builder(context, themeResId);
         mContext = context;
-        mDefaultButtonText = context.getString(R.string.car_ui_alert_dialog_default_button);
     }
 
     public Context getContext() {
@@ -67,8 +70,7 @@
      * @return This Builder object to allow for chaining of calls to set methods
      */
     public AlertDialogBuilder setTitle(@StringRes int titleId) {
-        mBuilder.setTitle(titleId);
-        return this;
+        return setTitle(mContext.getText(titleId));
     }
 
     /**
@@ -77,11 +79,31 @@
      * @return This Builder object to allow for chaining of calls to set methods
      */
     public AlertDialogBuilder setTitle(CharSequence title) {
+        mTitle = title;
         mBuilder.setTitle(title);
         return this;
     }
 
     /**
+     * Sets a subtitle to be displayed in the {@link Dialog}.
+     *
+     * @return This Builder object to allow for chaining of calls to set methods
+     */
+    public AlertDialogBuilder setSubtitle(@StringRes int subtitle) {
+        return setSubtitle(mContext.getString(subtitle));
+    }
+
+    /**
+     * Sets a subtitle to be displayed in the {@link Dialog}.
+     *
+     * @return This Builder object to allow for chaining of calls to set methods
+     */
+    public AlertDialogBuilder setSubtitle(CharSequence subtitle) {
+        mSubtitle = subtitle;
+        return this;
+    }
+
+    /**
      * Set the message to display using the given resource id.
      *
      * @return This Builder object to allow for chaining of calls to set methods
@@ -109,8 +131,7 @@
      * @return This Builder object to allow for chaining of calls to set methods
      */
     public AlertDialogBuilder setIcon(@DrawableRes int iconId) {
-        mBuilder.setIcon(iconId);
-        return this;
+        return setIcon(mContext.getDrawable(iconId));
     }
 
     /**
@@ -124,6 +145,7 @@
      * methods
      */
     public AlertDialogBuilder setIcon(Drawable icon) {
+        mIcon = icon;
         mBuilder.setIcon(icon);
         return this;
     }
@@ -510,10 +532,8 @@
      */
     public AlertDialogBuilder setEditBox(String prompt, TextWatcher textChangedListener,
             InputFilter[] inputFilters, int inputType) {
-        LayoutInflater layoutInflater =
-                (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        View contentView = layoutInflater.inflate(R.layout.car_ui_alert_dialog_edit_text,
-                null);
+        View contentView = LayoutInflater.from(mContext).inflate(
+                R.layout.car_ui_alert_dialog_edit_text, null);
 
         EditText editText = contentView.requireViewById(R.id.textbox);
         editText.setText(prompt);
@@ -548,6 +568,33 @@
         return setEditBox(prompt, textChangedListener, inputFilters, 0);
     }
 
+
+    /** Final steps common to both {@link #create()} and {@link #show()} */
+    private void prepareDialog() {
+        if (mSubtitle != null) {
+
+            View customTitle = LayoutInflater.from(mContext).inflate(
+                    R.layout.car_ui_alert_dialog_title_with_subtitle, null);
+
+            TextView mTitleView = customTitle.requireViewById(R.id.alertTitle);
+            TextView mSubtitleView = customTitle.requireViewById(R.id.alertSubtitle);
+            ImageView mIconView = customTitle.requireViewById(R.id.icon);
+
+            mTitleView.setText(mTitle);
+            mSubtitleView.setText(mSubtitle);
+            mIconView.setImageDrawable(mIcon);
+            mIconView.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
+            mBuilder.setCustomTitle(customTitle);
+        }
+
+        if (!mNeutralButtonSet && !mNegativeButtonSet && !mPositiveButtonSet) {
+            String mDefaultButtonText = mContext.getString(
+                    R.string.car_ui_alert_dialog_default_button);
+            mBuilder.setNegativeButton(mDefaultButtonText, (dialog, which) -> {
+            });
+        }
+    }
+
     /**
      * Creates an {@link AlertDialog} with the arguments supplied to this
      * builder.
@@ -557,6 +604,7 @@
      * create and display the dialog.
      */
     public AlertDialog create() {
+        prepareDialog();
         return mBuilder.create();
     }
 
@@ -571,12 +619,7 @@
      * </pre>
      */
     public AlertDialog show() {
-        if (mNeutralButtonSet || mNegativeButtonSet || mPositiveButtonSet) {
-            return mBuilder.show();
-        }
-
-        mBuilder.setNegativeButton(mDefaultButtonText, (dialog, which) -> {
-        });
+        prepareDialog();
         return mBuilder.show();
     }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiDialogFragment.java b/car-ui-lib/src/com/android/car/ui/preference/CarUiDialogFragment.java
new file mode 100644
index 0000000..6a848b8
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/preference/CarUiDialogFragment.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.preference;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.DialogPreference;
+
+/**
+ * Abstract base class which presents a dialog associated with a {@link
+ * androidx.preference.DialogPreference}. Since the preference object may not be available during
+ * fragment re-creation, the necessary information for displaying the dialog is read once during
+ * the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved instance state.
+ * Custom subclasses should also follow this pattern.
+ *
+ * <p>Note: this is borrowed as-is from {@link androidx.preference.PreferenceDialogFragmentCompat}
+ * with updates to formatting to match the project style and the removal of the {@link
+ * DialogPreference.TargetFragment} interface requirement. See {@link PreferenceDialogFragment}
+ * for a version of this class with the check preserved. Automotive applications should use
+ * children of this fragment in order to launch the system themed platform {@link AlertDialog}
+ * instead of the one in the support library.
+ */
+
+public abstract class CarUiDialogFragment extends DialogFragment implements
+        DialogInterface.OnClickListener {
+
+    private static final String SAVE_STATE_TITLE = "CarUiDialogFragment.title";
+    private static final String SAVE_STATE_POSITIVE_TEXT = "CarUiDialogFragment.positiveText";
+    private static final String SAVE_STATE_NEGATIVE_TEXT = "CarUiDialogFragment.negativeText";
+    private static final String SAVE_STATE_MESSAGE = "CarUiDialogFragment.message";
+    private static final String SAVE_STATE_LAYOUT = "CarUiDialogFragment.layout";
+    private static final String SAVE_STATE_ICON = "CarUiDialogFragment.icon";
+
+    protected CharSequence mDialogTitle;
+    protected CharSequence mPositiveButtonText;
+    protected CharSequence mNegativeButtonText;
+    protected CharSequence mDialogMessage;
+    @LayoutRes
+    protected int mDialogLayoutRes;
+
+    protected BitmapDrawable mDialogIcon;
+
+    /** Which button was clicked. */
+    private int mWhichButtonClicked;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (savedInstanceState != null) {
+            mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
+            mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
+            mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
+            mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
+            mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
+            Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
+            if (bitmap != null) {
+                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
+        outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
+        outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
+        outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
+        outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
+        if (mDialogIcon != null) {
+            outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
+        }
+    }
+
+    @Override
+    @NonNull
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        Context context = getActivity();
+        mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(context)
+                .setTitle(mDialogTitle)
+                .setIcon(mDialogIcon)
+                .setPositiveButton(mPositiveButtonText, this)
+                .setNegativeButton(mNegativeButtonText, this);
+
+        View contentView = onCreateDialogView(context);
+        if (contentView != null) {
+            onBindDialogView(contentView);
+            builder.setView(contentView);
+        } else {
+            builder.setMessage(mDialogMessage);
+        }
+
+        onPrepareDialogBuilder(builder);
+
+        // Create the dialog
+        Dialog dialog = builder.create();
+        if (needInputMethod()) {
+            // Request input only after the dialog is shown. This is to prevent an issue where the
+            // dialog view collapsed the content on small displays.
+            dialog.setOnShowListener(d -> requestInputMethod(dialog));
+        }
+
+        return dialog;
+    }
+
+    /**
+     * Prepares the dialog builder to be shown when the preference is clicked. Use this to set
+     * custom properties on the dialog.
+     *
+     * <p>Do not {@link AlertDialog.Builder#create()} or {@link AlertDialog.Builder#show()}.
+     */
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+    }
+
+    /**
+     * Returns whether the preference needs to display a soft input method when the dialog is
+     * displayed. Default is false. Subclasses should override this method if they need the soft
+     * input method brought up automatically.
+     *
+     * <p>Note: Ensure your subclass manually requests focus (ideally in {@link
+     * #onBindDialogView(View)}) for the input field in order to
+     * correctly attach the input method to the field.
+     */
+    protected boolean needInputMethod() {
+        return false;
+    }
+
+    /**
+     * Sets the required flags on the dialog window to enable input method window to show up.
+     */
+    private void requestInputMethod(Dialog dialog) {
+        Window window = dialog.getWindow();
+        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+    }
+
+    /**
+     * Creates the content view for the dialog (if a custom content view is required). By default,
+     * it inflates the dialog layout resource if it is set.
+     *
+     * @return the content View for the dialog.
+     * @see DialogPreference#setLayoutResource(int)
+     */
+    protected View onCreateDialogView(Context context) {
+        int resId = mDialogLayoutRes;
+        if (resId == 0) {
+            return null;
+        }
+
+        LayoutInflater inflater = LayoutInflater.from(context);
+        return inflater.inflate(resId, null);
+    }
+
+    /**
+     * Binds views in the content View of the dialog to data.
+     *
+     * <p>Make sure to call through to the superclass implementation.
+     *
+     * @param view the content View of the dialog, if it is custom.
+     */
+    @CallSuper
+    protected void onBindDialogView(View view) {
+        View dialogMessageView = view.findViewById(android.R.id.message);
+
+        if (dialogMessageView != null) {
+            CharSequence message = mDialogMessage;
+            int newVisibility = View.GONE;
+
+            if (!TextUtils.isEmpty(message)) {
+                if (dialogMessageView instanceof TextView) {
+                    ((TextView) dialogMessageView).setText(message);
+                }
+
+                newVisibility = View.VISIBLE;
+            }
+
+            if (dialogMessageView.getVisibility() != newVisibility) {
+                dialogMessageView.setVisibility(newVisibility);
+            }
+        }
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        mWhichButtonClicked = which;
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        super.onDismiss(dialog);
+        onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
+    }
+
+    /**
+     * Called when the dialog is dismissed.
+     *
+     * @param positiveResult {@code true} if the dialog was dismissed with {@link
+     *                       DialogInterface#BUTTON_POSITIVE}.
+     */
+    protected abstract void onDialogClosed(boolean positiveResult);
+}
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiEditTextPreference.java b/car-ui-lib/src/com/android/car/ui/preference/CarUiEditTextPreference.java
index 079ab39..4480714 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/CarUiEditTextPreference.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/CarUiEditTextPreference.java
@@ -30,6 +30,7 @@
 public class CarUiEditTextPreference extends EditTextPreference {
 
     private final Context mContext;
+    private boolean mShowChevron = true;
 
     public CarUiEditTextPreference(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
@@ -56,13 +57,17 @@
     public void onAttached() {
         super.onAttached();
 
-        boolean showChevron = mContext.getResources().getBoolean(
+        boolean allowChevron = mContext.getResources().getBoolean(
                 R.bool.car_ui_preference_show_chevron);
 
-        if (!showChevron) {
+        if (!allowChevron || !mShowChevron) {
             return;
         }
 
         setWidgetLayoutResource(R.layout.car_ui_preference_chevron);
     }
+
+    public void setShowChevron(boolean showChevron) {
+        mShowChevron = showChevron;
+    }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java b/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java
index 19b5d19..5db22cf 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java
@@ -17,6 +17,7 @@
 package com.android.car.ui.preference;
 
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.util.AttributeSet;
 
 import androidx.preference.Preference;
@@ -29,37 +30,47 @@
  */
 public class CarUiPreference extends Preference {
 
-    private final Context mContext;
+    private Context mContext;
+    private boolean mShowChevron;
 
     public CarUiPreference(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mContext = context;
+        init(context, attrs, defStyleAttr, defStyleRes);
     }
 
     public CarUiPreference(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        mContext = context;
+        this(context, attrs, defStyleAttr, R.style.Preference_CarUi_Preference);
     }
 
     public CarUiPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mContext = context;
+        this(context, attrs, R.attr.carUiPreferenceStyle);
     }
 
     public CarUiPreference(Context context) {
-        super(context);
+        this(context, null);
+    }
+
+    public void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         mContext = context;
+
+        TypedArray a = getContext().obtainStyledAttributes(
+                attrs,
+                R.styleable.CarUiPreference,
+                defStyleAttr,
+                defStyleRes);
+
+        mShowChevron = a.getBoolean(R.styleable.CarUiPreference_showChevron, true);
     }
 
     @Override
     public void onAttached() {
         super.onAttached();
 
-        boolean showChevron = mContext.getResources().getBoolean(
+        boolean allowChevron = mContext.getResources().getBoolean(
                 R.bool.car_ui_preference_show_chevron);
 
-        if (!showChevron || getWidgetLayoutResource() != 0) {
+        if (!allowChevron || !mShowChevron) {
             return;
         }
 
@@ -68,4 +79,8 @@
             setWidgetLayoutResource(R.layout.car_ui_preference_chevron);
         }
     }
+
+    public void setShowChevron(boolean showChevron) {
+        mShowChevron = showChevron;
+    }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiRecyclerViewRadioButtonAdapter.java b/car-ui-lib/src/com/android/car/ui/preference/CarUiRecyclerViewRadioButtonAdapter.java
index 7c6ab9e..41e1351 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/CarUiRecyclerViewRadioButtonAdapter.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/CarUiRecyclerViewRadioButtonAdapter.java
@@ -20,7 +20,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.RadioButton;
+import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.car.ui.R;
@@ -55,6 +57,7 @@
         mSelectedPosition = position;
     }
 
+    @NonNull
     @Override
     public CarUiRecyclerViewRadioButtonAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
             int viewType) {
@@ -70,7 +73,7 @@
         //since only one radio button is allowed to be selected,
         // this condition un-checks previous selections
         holder.mRadioButton.setChecked(mSelectedPosition == position);
-        holder.mRadioButton.setText(entry);
+        holder.mTextView.setText(entry);
     }
 
     @Override
@@ -89,15 +92,17 @@
     }
 
     /** The viewholder class for recyclerview containing radio buttons. */
-    public class ViewHolder extends RecyclerView.ViewHolder {
+    class ViewHolder extends RecyclerView.ViewHolder {
 
-        public RadioButton mRadioButton;
+        RadioButton mRadioButton;
+        TextView mTextView;
 
-        public ViewHolder(View view) {
+        ViewHolder(View view) {
             super(view);
-            mRadioButton = (RadioButton) view.findViewById(R.id.radio_button);
+            mRadioButton = view.findViewById(R.id.radio_button);
+            mTextView = view.findViewById(R.id.text);
 
-            mRadioButton.setOnClickListener(v -> {
+            view.setOnClickListener(v -> {
                 mSelectedPosition = getAdapterPosition();
                 notifyDataSetChanged();
                 if (mOnRadioButtonClickedListener != null) {
diff --git a/car-ui-lib/src/com/android/car/ui/preference/EditTextPreferenceDialogFragment.java b/car-ui-lib/src/com/android/car/ui/preference/EditTextPreferenceDialogFragment.java
index f2d7c3e..97a58d2 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/EditTextPreferenceDialogFragment.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/EditTextPreferenceDialogFragment.java
@@ -18,8 +18,12 @@
 
 import android.app.AlertDialog;
 import android.os.Bundle;
+import android.text.InputType;
+import android.view.KeyEvent;
 import android.view.View;
+import android.view.inputmethod.EditorInfo;
 import android.widget.EditText;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.preference.EditTextPreference;
@@ -32,13 +36,14 @@
  * implementations in order to launch the system themed platform {@link AlertDialog} instead of the
  * one in the support library.
  */
-public class EditTextPreferenceDialogFragment extends PreferenceDialogFragment {
+public class EditTextPreferenceDialogFragment extends PreferenceDialogFragment implements
+        TextView.OnEditorActionListener {
 
     private static final String SAVE_STATE_TEXT = "EditTextPreferenceDialogFragment.text";
 
     private EditText mEditText;
-
     private CharSequence mText;
+    private boolean mAllowEnterToSubmit = true;
 
     /**
      * Returns a new instance of {@link EditTextPreferenceDialogFragment} for the {@link
@@ -82,6 +87,10 @@
 
         mEditText.requestFocus();
         mEditText.setText(mText);
+        mEditText.setInputType(InputType.TYPE_CLASS_TEXT);
+        mEditText.setImeOptions(EditorInfo.IME_ACTION_DONE);
+        mEditText.setOnEditorActionListener(this);
+
         // Place cursor at the end
         mEditText.setSelection(mEditText.getText().length());
     }
@@ -105,4 +114,26 @@
         }
     }
 
+    /** Allows enabling and disabling the ability to press enter to dismiss the dialog. */
+    public void setAllowEnterToSubmit(boolean isAllowed) {
+        mAllowEnterToSubmit = isAllowed;
+    }
+
+    /** Allows verifying if enter to submit is currently enabled. */
+    public boolean getAllowEnterToSubmit() {
+        return mAllowEnterToSubmit;
+    }
+
+    @Override
+    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        if (actionId == EditorInfo.IME_ACTION_DONE && mAllowEnterToSubmit) {
+            CharSequence newValue = v.getText();
+
+            getEditTextPreference().callChangeListener(newValue);
+            dismiss();
+
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceDialogFragment.java b/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceDialogFragment.java
index 9395fbf..4af62ef 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceDialogFragment.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceDialogFragment.java
@@ -107,7 +107,7 @@
 
         setStyle(DialogFragment.STYLE_NORMAL, R.style.Preference_CarUi_ListPreference);
         LayoutInflater inflater = LayoutInflater.from(getContext());
-        mDialogView = inflater.inflate(R.layout.car_ui_list_preference_dialog, null);
+        mDialogView = inflater.inflate(R.layout.car_ui_list_preference, null);
 
         Toolbar toolbar = mDialogView.findViewById(R.id.toolbar);
         toolbar.registerOnBackListener(this);
diff --git a/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceFragment.java b/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceFragment.java
new file mode 100644
index 0000000..d0653dd
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/preference/ListPreferenceFragment.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.preference;
+
+import static com.android.car.ui.preference.PreferenceDialogFragment.ARG_KEY;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.preference.DialogPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.car.ui.R;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+import com.android.car.ui.toolbar.Toolbar;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A fragment that provides a layout with a list of options associated with a {@link
+ * ListPreference}.
+ */
+public class ListPreferenceFragment extends Fragment implements
+        CarUiRecyclerViewRadioButtonAdapter.OnRadioButtonClickedListener {
+
+    private CarUiRecyclerView mCarUiRecyclerView;
+    private ListPreference mPreference;
+    private int mClickedDialogEntryIndex;
+    private CharSequence[] mEntryValues;
+
+    /**
+     * Returns a new instance of {@link ListPreferenceFragment} for the {@link ListPreference} with
+     * the given {@code key}.
+     */
+    public static ListPreferenceFragment newInstance(String key) {
+        ListPreferenceFragment fragment = new ListPreferenceFragment();
+        Bundle b = new Bundle(/* capacity= */ 1);
+        b.putString(ARG_KEY, key);
+        fragment.setArguments(b);
+        return fragment;
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(
+            @NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.car_ui_list_preference, container, false);
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        mCarUiRecyclerView = view.findViewById(R.id.radio_list);
+        final Toolbar toolbar = view.findViewById(R.id.toolbar);
+        if (mCarUiRecyclerView == null) {
+            throw new IllegalStateException(
+                    "ListPreference layout did not contain recycler view with expected id.");
+        }
+
+        if (toolbar == null) {
+            throw new IllegalStateException(
+                    "ListPreference layout did not contain toolbar with expected id.");
+        }
+
+        mCarUiRecyclerView.setPadding(0, toolbar.getHeight(), 0, 0);
+        toolbar.registerToolbarHeightChangeListener(newHeight -> {
+            if (mCarUiRecyclerView.getPaddingTop() == newHeight) {
+                return;
+            }
+
+            int oldHeight = mCarUiRecyclerView.getPaddingTop();
+            mCarUiRecyclerView.setPadding(0, newHeight, 0, 0);
+            mCarUiRecyclerView.scrollBy(0, oldHeight - newHeight);
+        });
+
+        mCarUiRecyclerView.setClipToPadding(false);
+        ListPreference preference = getListPreference();
+        toolbar.setTitle(mPreference.getTitle());
+
+        CharSequence[] entries = preference.getEntries();
+        mEntryValues = preference.getEntryValues();
+
+        if (entries == null || mEntryValues == null) {
+            throw new IllegalStateException(
+                    "ListPreference requires an entries array and an entryValues array.");
+        }
+
+        if (entries.length != mEntryValues.length) {
+            throw new IllegalStateException(
+                    "ListPreference entries array length does not match entryValues array length.");
+        }
+
+        mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
+        List<String> entryStrings = new ArrayList<>(entries.length);
+        for (CharSequence entry : entries) {
+            entryStrings.add(entry.toString());
+        }
+
+        CarUiRecyclerViewRadioButtonAdapter adapter = new CarUiRecyclerViewRadioButtonAdapter(
+                entryStrings, mClickedDialogEntryIndex);
+        mCarUiRecyclerView.setAdapter(adapter);
+        adapter.registerListener(this);
+    }
+
+    private ListPreference getListPreference() {
+        if (mPreference == null && getArguments() != null) {
+            String key = getArguments().getString(ARG_KEY);
+            DialogPreference.TargetFragment fragment =
+                    (DialogPreference.TargetFragment) getTargetFragment();
+
+            if (key == null) {
+                throw new IllegalStateException(
+                        "ListPreference key not found in Fragment arguments");
+            }
+
+            if (fragment == null) {
+                throw new IllegalStateException(
+                        "Target fragment must be registered before displaying ListPreference "
+                                + "screen.");
+            }
+
+            Preference preference = fragment.findPreference(key);
+
+            if (!(preference instanceof ListPreference)) {
+                throw new IllegalStateException(
+                        "Cannot use ListPreferenceFragment with a preference that is not of type "
+                                + "ListPreference");
+            }
+
+            mPreference = (ListPreference) preference;
+        }
+        return mPreference;
+    }
+
+    @Override
+    public void onClick(int position) {
+        if (position < 0 || position > mEntryValues.length - 1) {
+            throw new IllegalStateException(
+                    "Clicked preference has invalid index.");
+        }
+
+        mClickedDialogEntryIndex = position;
+        String value = mEntryValues[mClickedDialogEntryIndex].toString();
+        ListPreference preference = getListPreference();
+        if (preference.callChangeListener(value)) {
+            preference.setValue(value);
+        }
+
+        if (getActivity() == null) {
+            throw new IllegalStateException(
+                    "ListPreference fragment is not attached to an Activity.");
+        }
+
+        getActivity().getSupportFragmentManager().popBackStack();
+    }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/preference/PreferenceDialogFragment.java b/car-ui-lib/src/com/android/car/ui/preference/PreferenceDialogFragment.java
index 9e87838..8b2e79e 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/PreferenceDialogFragment.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/PreferenceDialogFragment.java
@@ -17,25 +17,13 @@
 package com.android.car.ui.preference;
 
 import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.TextView;
 
-import androidx.annotation.CallSuper;
-import androidx.annotation.LayoutRes;
-import androidx.annotation.NonNull;
-import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
 import androidx.preference.DialogPreference;
 import androidx.preference.PreferenceFragmentCompat;
@@ -47,37 +35,20 @@
  * the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved instance state.
  * Custom subclasses should also follow this pattern.
  *
- * <p>Note: this is borrowed as-is from androidx.preference.PreferenceDialogFragmentCompat with
- * updates to formatting to match the project style. Automotive applications should use children of
- * this fragment in order to launch the system themed platform {@link AlertDialog} instead of the
- * one in the support library.
+ * <p>Note: this has the same functionality and interface as {@link
+ * androidx.preference.PreferenceDialogFragmentCompat} with updates to formatting to match the
+ * project style. This class preserves the {@link DialogPreference.TargetFragment} interface
+ * requirement that was removed in {@link CarUiDialogFragment}. Automotive applications should use
+ * children of this fragment in order to launch the system themed platform {@link AlertDialog}
+ * instead of the one in the support library.
  */
-public abstract class PreferenceDialogFragment extends DialogFragment implements
+public abstract class PreferenceDialogFragment extends CarUiDialogFragment implements
         DialogInterface.OnClickListener {
 
     protected static final String ARG_KEY = "key";
 
-    private static final String SAVE_STATE_TITLE = "PreferenceDialogFragment.title";
-    private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogFragment.positiveText";
-    private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogFragment.negativeText";
-    private static final String SAVE_STATE_MESSAGE = "PreferenceDialogFragment.message";
-    private static final String SAVE_STATE_LAYOUT = "PreferenceDialogFragment.layout";
-    private static final String SAVE_STATE_ICON = "PreferenceDialogFragment.icon";
-
     private DialogPreference mPreference;
 
-    private CharSequence mDialogTitle;
-    private CharSequence mPositiveButtonText;
-    private CharSequence mNegativeButtonText;
-    private CharSequence mDialogMessage;
-    @LayoutRes
-    private int mDialogLayoutRes;
-
-    private BitmapDrawable mDialogIcon;
-
-    /** Which button was clicked. */
-    private int mWhichButtonClicked;
-
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -111,66 +82,9 @@
                 icon.draw(canvas);
                 mDialogIcon = new BitmapDrawable(getResources(), bitmap);
             }
-        } else {
-            mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
-            mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
-            mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
-            mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
-            mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
-            Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
-            if (bitmap != null) {
-                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
-            }
         }
     }
 
-    @Override
-    public void onSaveInstanceState(@NonNull Bundle outState) {
-        super.onSaveInstanceState(outState);
-
-        outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
-        outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
-        outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
-        outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
-        outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
-        if (mDialogIcon != null) {
-            outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
-        }
-    }
-
-    @Override
-    @NonNull
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        Context context = getActivity();
-        mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
-
-        AlertDialog.Builder builder = new AlertDialog.Builder(context)
-                .setTitle(mDialogTitle)
-                .setIcon(mDialogIcon)
-                .setPositiveButton(mPositiveButtonText, this)
-                .setNegativeButton(mNegativeButtonText, this);
-
-        View contentView = onCreateDialogView(context);
-        if (contentView != null) {
-            onBindDialogView(contentView);
-            builder.setView(contentView);
-        } else {
-            builder.setMessage(mDialogMessage);
-        }
-
-        onPrepareDialogBuilder(builder);
-
-        // Create the dialog
-        Dialog dialog = builder.create();
-        if (needInputMethod()) {
-            // Request input only after the dialog is shown. This is to prevent an issue where the
-            // dialog view collapsed the content on small displays.
-            dialog.setOnShowListener(d -> requestInputMethod(dialog));
-        }
-
-        return dialog;
-    }
-
     /**
      * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
      * been called on the {@link PreferenceFragmentCompat} which launched this dialog.
@@ -186,99 +100,4 @@
         }
         return mPreference;
     }
-
-    /**
-     * Prepares the dialog builder to be shown when the preference is clicked. Use this to set
-     * custom properties on the dialog.
-     *
-     * <p>Do not {@link AlertDialog.Builder#create()} or {@link AlertDialog.Builder#show()}.
-     */
-    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
-    }
-
-    /**
-     * Returns whether the preference needs to display a soft input method when the dialog is
-     * displayed. Default is false. Subclasses should override this method if they need the soft
-     * input method brought up automatically.
-     *
-     * <p>Note: Ensure your subclass manually requests focus (ideally in {@link
-     * #onBindDialogView(View)}) for the input field in order to
-     * correctly attach the input method to the field.
-     */
-    protected boolean needInputMethod() {
-        return false;
-    }
-
-    /**
-     * Sets the required flags on the dialog window to enable input method window to show up.
-     */
-    private void requestInputMethod(Dialog dialog) {
-        Window window = dialog.getWindow();
-        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
-    }
-
-    /**
-     * Creates the content view for the dialog (if a custom content view is required). By default,
-     * it inflates the dialog layout resource if it is set.
-     *
-     * @return the content View for the dialog.
-     * @see DialogPreference#setLayoutResource(int)
-     */
-    protected View onCreateDialogView(Context context) {
-        int resId = mDialogLayoutRes;
-        if (resId == 0) {
-            return null;
-        }
-
-        LayoutInflater inflater = LayoutInflater.from(context);
-        return inflater.inflate(resId, null);
-    }
-
-    /**
-     * Binds views in the content View of the dialog to data.
-     *
-     * <p>Make sure to call through to the superclass implementation.
-     *
-     * @param view the content View of the dialog, if it is custom.
-     */
-    @CallSuper
-    protected void onBindDialogView(View view) {
-        View dialogMessageView = view.findViewById(android.R.id.message);
-
-        if (dialogMessageView != null) {
-            CharSequence message = mDialogMessage;
-            int newVisibility = View.GONE;
-
-            if (!TextUtils.isEmpty(message)) {
-                if (dialogMessageView instanceof TextView) {
-                    ((TextView) dialogMessageView).setText(message);
-                }
-
-                newVisibility = View.VISIBLE;
-            }
-
-            if (dialogMessageView.getVisibility() != newVisibility) {
-                dialogMessageView.setVisibility(newVisibility);
-            }
-        }
-    }
-
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        mWhichButtonClicked = which;
-    }
-
-    @Override
-    public void onDismiss(DialogInterface dialog) {
-        super.onDismiss(dialog);
-        onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
-    }
-
-    /**
-     * Called when the dialog is dismissed.
-     *
-     * @param positiveResult {@code true} if the dialog was dismissed with {@link
-     *                       DialogInterface#BUTTON_POSITIVE}.
-     */
-    protected abstract void onDialogClosed(boolean positiveResult);
 }
diff --git a/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java b/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java
index 5582ffc..1acb2d2 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java
@@ -16,31 +16,50 @@
 
 package com.android.car.ui.preference;
 
+import android.content.Context;
 import android.os.Bundle;
+import android.util.Log;
+import android.util.Pair;
 import android.view.View;
+import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.preference.DialogPreference;
+import androidx.preference.DropDownPreference;
 import androidx.preference.EditTextPreference;
 import androidx.preference.ListPreference;
 import androidx.preference.MultiSelectListPreference;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.car.ui.R;
 import com.android.car.ui.toolbar.Toolbar;
 
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 /**
  * A PreferenceFragmentCompat is the entry point to using the Preference library.
  *
- * <p>Note: this is borrowed as-is from androidx.preference.PreferenceFragmentCompat with updates to
- * launch Car UI library {@link DialogFragment}. Automotive applications should use children of
- * this fragment in order to launch the system themed {@link DialogFragment}.
+ * <p>Using this fragment will replace regular Preferences with CarUi equivalents. Because of this,
+ * certain properties that cannot be read out of Preferences will be lost upon calling
+ * {@link #setPreferenceScreen(PreferenceScreen)}. These include the preference viewId,
+ * defaultValue, and enabled state.
  */
 public abstract class PreferenceFragment extends PreferenceFragmentCompat {
 
+    private static final String TAG = "CarUiPreferenceFragment";
     private static final String DIALOG_FRAGMENT_TAG =
             "com.android.car.ui.PreferenceFragment.DIALOG";
 
@@ -92,11 +111,11 @@
             return;
         }
 
-        final DialogFragment f;
+        final Fragment f;
         if (preference instanceof EditTextPreference) {
             f = EditTextPreferenceDialogFragment.newInstance(preference.getKey());
         } else if (preference instanceof ListPreference) {
-            f = ListPreferenceDialogFragment.newInstance(preference.getKey());
+            f = ListPreferenceFragment.newInstance(preference.getKey());
         } else if (preference instanceof MultiSelectListPreference) {
             f = MultiSelectListPreferenceDialogFragment.newInstance(preference.getKey());
         } else {
@@ -106,7 +125,197 @@
                             + ". Make sure to implement onPreferenceDisplayDialog() to handle "
                             + "displaying a custom dialog for this Preference.");
         }
+
         f.setTargetFragment(this, 0);
-        f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+
+        if (f instanceof DialogFragment) {
+            ((DialogFragment) f).show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+        } else {
+            if (getActivity() == null) {
+                throw new IllegalStateException(
+                        "Preference fragment is not attached to an Activity.");
+            }
+
+            if (getView() == null) {
+                throw new IllegalStateException(
+                        "Preference fragment must have a layout.");
+            }
+
+            getActivity().getSupportFragmentManager().beginTransaction()
+                    .replace(((ViewGroup) getView().getParent()).getId(), f)
+                    .addToBackStack(null)
+                    .commit();
+        }
+    }
+
+    /**
+     * This override of setPreferenceScreen replaces preferences with their CarUi versions first.
+     */
+    @Override
+    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+        // We do a search of the tree and every time we see a PreferenceGroup we remove
+        // all it's children, replace them with CarUi versions, and then re-add them
+
+        Map<Preference, String> dependencies = new HashMap<>();
+        List<Preference> children = new ArrayList<>();
+
+        // Stack of preferences to process
+        Deque<Preference> stack = new ArrayDeque<>();
+        stack.addFirst(preferenceScreen);
+
+        while (!stack.isEmpty()) {
+            Preference preference = stack.removeFirst();
+
+            if (preference instanceof PreferenceGroup) {
+                PreferenceGroup pg = (PreferenceGroup) preference;
+
+                children.clear();
+                for (int i = 0; i < pg.getPreferenceCount(); i++) {
+                    children.add(pg.getPreference(i));
+                }
+
+                pg.removeAll();
+
+                for (Preference child : children) {
+                    Preference replacement = getReplacementFor(child);
+
+                    dependencies.put(replacement, child.getDependency());
+                    pg.addPreference(replacement);
+                    stack.addFirst(replacement);
+                }
+            }
+        }
+
+        super.setPreferenceScreen(preferenceScreen);
+
+        // Set the dependencies after all the swapping has been done and they've been
+        // associated with this fragment, or we could potentially fail to find preferences
+        // or use the wrong preferenceManager
+        for (Map.Entry<Preference, String> entry : dependencies.entrySet()) {
+            entry.getKey().setDependency(entry.getValue());
+        }
+    }
+
+    // Mapping from regular preferences to CarUi preferences.
+    // Order is important, subclasses must come before their base classes
+    private static final List<Pair<Class<? extends Preference>, Class<? extends Preference>>>
+            sPreferenceMapping = Arrays.asList(
+            new Pair<>(DropDownPreference.class, CarUiDropDownPreference.class),
+            new Pair<>(ListPreference.class, CarUiListPreference.class),
+            new Pair<>(MultiSelectListPreference.class, CarUiMultiSelectListPreference.class),
+            new Pair<>(EditTextPreference.class, CarUiEditTextPreference.class),
+            new Pair<>(Preference.class, CarUiPreference.class)
+    );
+
+    /**
+     * Gets the CarUi version of the passed in preference. If there is no suitable replacement, this
+     * method will return it's input.
+     *
+     * <p>When given a Preference that extends a replaceable preference, we log a warning instead
+     * of replacing it so that we don't remove any functionality.
+     */
+    private static Preference getReplacementFor(Preference preference) {
+        Class<? extends Preference> clazz = preference.getClass();
+
+        for (Pair<Class<? extends Preference>, Class<? extends Preference>> replacement
+                : sPreferenceMapping) {
+            Class<? extends Preference> source = replacement.first;
+            Class<? extends Preference> target = replacement.second;
+            if (source.isAssignableFrom(clazz)) {
+                if (clazz == source) {
+                    try {
+                        return copyPreference(preference, (Preference) target
+                                .getDeclaredConstructor(Context.class)
+                                .newInstance(preference.getContext()));
+                    } catch (ReflectiveOperationException e) {
+                        throw new RuntimeException(e);
+                    }
+                } else if (clazz == target || source == Preference.class) {
+                    // Don't warn about subclasses of Preference because there are many legitimate
+                    // uses for non-carui Preference subclasses, like Preference groups.
+                    return preference;
+                } else {
+                    Log.w(TAG, "Subclass of " + source.getSimpleName() + " was used, "
+                            + "preventing us from substituting it with " + target.getSimpleName());
+                    return preference;
+                }
+            }
+        }
+
+        return preference;
+    }
+
+    /**
+     * Copies all the properties of one preference to another.
+     *
+     * @return the {@code to} parameter
+     */
+    private static Preference copyPreference(Preference from, Preference to) {
+        // viewId and defaultValue don't have getters
+        // isEnabled() is not completely symmetrical with setEnabled(), so we can't use it.
+        to.setTitle(from.getTitle());
+        to.setOnPreferenceClickListener(from.getOnPreferenceClickListener());
+        to.setOnPreferenceChangeListener(from.getOnPreferenceChangeListener());
+        to.setIcon(from.getIcon());
+        to.setFragment(from.getFragment());
+        to.setIntent(from.getIntent());
+        to.setKey(from.getKey());
+        to.setOrder(from.getOrder());
+        to.setSelectable(from.isSelectable());
+        to.setPersistent(from.isPersistent());
+        to.setIconSpaceReserved(from.isIconSpaceReserved());
+        to.setWidgetLayoutResource(from.getWidgetLayoutResource());
+        to.setPreferenceDataStore(from.getPreferenceDataStore());
+        to.setShouldDisableView(from.getShouldDisableView());
+        to.setSingleLineTitle(from.isSingleLineTitle());
+        to.setVisible(from.isVisible());
+        to.setLayoutResource(from.getLayoutResource());
+        to.setCopyingEnabled(from.isCopyingEnabled());
+
+        if (from.getSummaryProvider() != null) {
+            to.setSummaryProvider(from.getSummaryProvider());
+        } else {
+            to.setSummary(from.getSummary());
+        }
+
+        if (from.peekExtras() != null) {
+            to.getExtras().putAll(from.peekExtras());
+        }
+
+        if (from instanceof DialogPreference) {
+            DialogPreference fromDialog = (DialogPreference) from;
+            DialogPreference toDialog = (DialogPreference) to;
+            toDialog.setDialogTitle(fromDialog.getDialogTitle());
+            toDialog.setDialogIcon(fromDialog.getDialogIcon());
+            toDialog.setDialogMessage(fromDialog.getDialogMessage());
+            toDialog.setDialogLayoutResource(fromDialog.getDialogLayoutResource());
+            toDialog.setNegativeButtonText(fromDialog.getNegativeButtonText());
+            toDialog.setPositiveButtonText(fromDialog.getPositiveButtonText());
+        }
+
+        // DropDownPreference extends ListPreference and doesn't add any extra api surface,
+        // so we don't need a case for it
+        if (from instanceof ListPreference) {
+            ListPreference fromList = (ListPreference) from;
+            ListPreference toList = (ListPreference) to;
+            toList.setEntries(fromList.getEntries());
+            toList.setEntryValues(fromList.getEntryValues());
+            toList.setValue(fromList.getValue());
+        } else if (from instanceof EditTextPreference) {
+            EditTextPreference fromText = (EditTextPreference) from;
+            EditTextPreference toText = (EditTextPreference) to;
+            toText.setText(fromText.getText());
+        } else if (from instanceof MultiSelectListPreference) {
+            MultiSelectListPreference fromMulti = (MultiSelectListPreference) from;
+            MultiSelectListPreference toMulti = (MultiSelectListPreference) to;
+            toMulti.setEntries(fromMulti.getEntries());
+            toMulti.setEntryValues(fromMulti.getEntryValues());
+            toMulti.setValues(fromMulti.getValues());
+        }
+
+        // We don't need to add checks for things that we will never replace,
+        // like PreferenceGroup or CheckBoxPreference
+
+        return to;
     }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemLayoutManager.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemLayoutManager.java
index c825052..80572a5 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemLayoutManager.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemLayoutManager.java
@@ -133,7 +133,7 @@
             }
         }
 
-        return (mListItemHeights.get(firstPos)) + heightOfScreen;
+        return mListItemHeights.get(firstPos) + heightOfScreen;
     }
 
     /**
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java
index 73f5498..0b608a2 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java
@@ -132,7 +132,7 @@
         int START = 0;
 
         /** Position scrollbar to the right of the screen. */
-        int END = 2;
+        int END = 1;
     }
 
     /**
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/MenuItem.java b/car-ui-lib/src/com/android/car/ui/toolbar/MenuItem.java
index fc27679..7bfad8d 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/MenuItem.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/MenuItem.java
@@ -26,6 +26,8 @@
 import com.android.car.ui.R;
 import com.android.car.ui.utils.CarUxRestrictionsUtil;
 
+import java.lang.ref.WeakReference;
+
 /**
  * Represents a button to display in the {@link Toolbar}.
  *
@@ -38,8 +40,8 @@
  * itself, or it's overflow menu.
  *
  * <p>If you require a search or settings button, you should use
- * {@link Builder#createSearch(Context, OnClickListener)} or
- * {@link Builder#createSettings(Context, OnClickListener)}.
+ * {@link Builder#setToSearch()} or
+ * {@link Builder#setToSettings()}.
  *
  * <p>Some properties can be changed after the creating a MenuItem, but others require being set
  * with a {@link Builder}.
@@ -57,7 +59,10 @@
 
     private int mId;
     private CarUxRestrictions mCurrentRestrictions;
-    private Listener mListener;
+    // This is a WeakReference to allow the Toolbar (and by extension, the whole screen
+    // the toolbar is on) to be garbage-collected if the MenuItem is held past the
+    // lifecycle of the toolbar.
+    private WeakReference<Listener> mListener = new WeakReference<>(null);
     private CharSequence mTitle;
     private Drawable mIcon;
     private OnClickListener mOnClickListener;
@@ -89,14 +94,16 @@
     }
 
     private void update() {
-        if (mListener != null) {
-            mListener.onMenuItemChanged();
+        Listener listener = mListener.get();
+        if (listener != null) {
+            listener.onMenuItemChanged();
         }
     }
 
     /** Sets the id, which is purely for the client to distinguish MenuItems with.  */
     public void setId(int id) {
         mId = id;
+        update();
     }
 
     /** Gets the id, which is purely for the client to distinguish MenuItems with. */
@@ -290,17 +297,15 @@
         return mIsSearch;
     }
 
-    /**
-     * Builder class.
-     *
-     * <p>Use the static {@link #createSearch(Context, OnClickListener)} or
-     * {@link #createSettings(Context, OnClickListener)} if you want one of those specialized
-     * buttons.
-     */
+    /** Builder class */
     public static final class Builder {
-        private Context mContext;
+        private final Context mContext;
+        private final CharSequence mSearchTitle;
+        private final CharSequence mSettingsTitle;
+        private final Drawable mSearchIcon;
+        private final Drawable mSettingsIcon;
 
-        private int mId;
+        private int mId = View.NO_ID;
         private CharSequence mTitle;
         private Drawable mIcon;
         private OnClickListener mOnClickListener;
@@ -314,11 +319,18 @@
         private boolean mIsActivatable = false;
         private boolean mIsActivated = false;
         private boolean mIsSearch = false;
+        private boolean mIsSettings = false;
         @CarUxRestrictions.CarUxRestrictionsInfo
         private int mUxRestrictions = CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
 
         public Builder(Context c) {
-            mContext = c;
+            // Must use getApplicationContext to avoid leaking activities when the MenuItem
+            // is held onto for longer than the Activity's lifecycle
+            mContext = c.getApplicationContext();
+            mSearchTitle = mContext.getString(R.string.car_ui_toolbar_menu_item_search_title);
+            mSettingsTitle = mContext.getString(R.string.car_ui_toolbar_menu_item_settings_title);
+            mSearchIcon = mContext.getDrawable(R.drawable.car_ui_icon_search);
+            mSettingsIcon = mContext.getDrawable(R.drawable.car_ui_icon_settings);
         }
 
         /** Builds a {@link MenuItem} from the current state of the Builder */
@@ -332,6 +344,30 @@
                     || mIsActivatable)) {
                 throw new IllegalStateException("Unsupported options for a checkable MenuItem");
             }
+            if (mIsSearch && mIsSettings) {
+                throw new IllegalStateException("Can't have both a search and settings MenuItem");
+            }
+
+            if (mIsSearch && (!mSearchTitle.equals(mTitle)
+                    || !mSearchIcon.equals(mIcon)
+                    || mIsCheckable
+                    || mIsActivatable
+                    || !mIsTinted
+                    || mShowIconAndTitle
+                    || mDisplayBehavior != DisplayBehavior.ALWAYS)) {
+                throw new IllegalStateException("Invalid search MenuItem");
+            }
+
+            if (mIsSettings && (!mSettingsTitle.equals(mTitle)
+                    || !mSettingsIcon.equals(mIcon)
+                    || mIsCheckable
+                    || mIsActivatable
+                    || !mIsTinted
+                    || mShowIconAndTitle
+                    || mDisplayBehavior != DisplayBehavior.ALWAYS
+                    || mUxRestrictions != CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP)) {
+                throw new IllegalStateException("Invalid settings MenuItem");
+            }
 
             return new MenuItem(this);
         }
@@ -477,26 +513,20 @@
             return this;
         }
 
-        /** Sets that this is the search MenuItem, which has special behavior while searching */
-        private Builder setSearch() {
-            mIsSearch = true;
-            return this;
-        }
-
         /**
          * Creates a search MenuItem.
          *
          * <p>The advantage of using this over creating your own is getting an OEM-styled search
          * icon, and this button will always disappear while searching, even when the
          * {@link Toolbar Toolbar's} showMenuItemsWhileSearching is true.
+         *
+         * <p>If using this, you should only change the id, visibility, or onClickListener.
          */
-        public static MenuItem createSearch(Context c, OnClickListener listener) {
-            return new Builder(c)
-                    .setTitle(R.string.car_ui_toolbar_menu_item_search_title)
-                    .setIcon(R.drawable.car_ui_icon_search)
-                    .setOnClickListener(listener)
-                    .setSearch()
-                    .build();
+        public Builder setToSearch() {
+            mIsSearch = true;
+            setTitle(mSearchTitle);
+            setIcon(mSearchIcon);
+            return this;
         }
 
         /**
@@ -505,13 +535,32 @@
          * <p>The advantage of this over creating your own is getting an OEM-styled settings icon,
          * and that the MenuItem will be restricted based on
          * {@link CarUxRestrictions#UX_RESTRICTIONS_NO_SETUP}
+         *
+         * <p>If using this, you should only change the id, visibility, or onClickListener.
          */
-        public static MenuItem createSettings(Context c, OnClickListener listener) {
-            return new Builder(c)
-                    .setTitle(R.string.car_ui_toolbar_menu_item_settings_title)
-                    .setIcon(R.drawable.car_ui_icon_settings)
+        public Builder setToSettings() {
+            mIsSettings = true;
+            setTitle(mSettingsTitle);
+            setIcon(mSettingsIcon);
+            setUxRestrictions(CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP);
+            return this;
+        }
+
+        /** @deprecated Use {@link #setToSearch()} instead. */
+        @Deprecated
+        public static MenuItem createSearch(Context c, OnClickListener listener) {
+            return MenuItem.builder(c)
+                    .setToSearch()
                     .setOnClickListener(listener)
-                    .setUxRestrictions(CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP)
+                    .build();
+        }
+
+        /** @deprecated Use {@link #setToSettings()} instead. */
+        @Deprecated
+        public static MenuItem createSettings(Context c, OnClickListener listener) {
+            return MenuItem.builder(c)
+                    .setToSettings()
+                    .setOnClickListener(listener)
                     .build();
         }
     }
@@ -545,7 +594,12 @@
         void onMenuItemChanged();
     }
 
+    /**
+     * Sets a listener for changes to this MenuItem. Note that the MenuItem will only hold
+     * weak references to the Listener, so that the listener is not held if the MenuItem
+     * outlives the toolbar.
+     */
     void setListener(Listener listener) {
-        mListener = listener;
+        mListener = new WeakReference<>(listener);
     }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/MenuItemRenderer.java b/car-ui-lib/src/com/android/car/ui/toolbar/MenuItemRenderer.java
index 890fa0b..4254bdf 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/MenuItemRenderer.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/MenuItemRenderer.java
@@ -110,6 +110,8 @@
             return;
         }
 
+        mView.setId(mMenuItem.getId());
+
         boolean hasIcon = mMenuItem.getIcon() != null;
         boolean hasText = !TextUtils.isEmpty(mMenuItem.getTitle());
         boolean textAndIcon = mMenuItem.isShowingIconAndTitle();
@@ -208,9 +210,11 @@
 
         TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CarUiToolbarMenuItem);
         try {
-            int id = a.getResourceId(R.styleable.CarUiToolbarMenuItem_id, 0);
+            int id = a.getResourceId(R.styleable.CarUiToolbarMenuItem_id, View.NO_ID);
             String title = a.getString(R.styleable.CarUiToolbarMenuItem_title);
             Drawable icon = a.getDrawable(R.styleable.CarUiToolbarMenuItem_icon);
+            boolean isSearch = a.getBoolean(R.styleable.CarUiToolbarMenuItem_search, false);
+            boolean isSettings = a.getBoolean(R.styleable.CarUiToolbarMenuItem_settings, false);
             boolean tinted = a.getBoolean(R.styleable.CarUiToolbarMenuItem_tinted, true);
             boolean visible = a.getBoolean(R.styleable.CarUiToolbarMenuItem_visible, true);
             boolean showIconAndTitle = a.getBoolean(
@@ -265,6 +269,14 @@
                     .setShowIconAndTitle(showIconAndTitle)
                     .setDisplayBehavior(displayBehavior);
 
+            if (isSearch) {
+                builder.setToSearch();
+            }
+
+            if (isSettings) {
+                builder.setToSettings();
+            }
+
             if (checkable || checkedExists) {
                 builder.setChecked(checked);
             }
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/TabLayout.java b/car-ui-lib/src/com/android/car/ui/toolbar/TabLayout.java
index 9bbf3c8..b30c370 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/TabLayout.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/TabLayout.java
@@ -328,7 +328,7 @@
             textView.setText(mText);
         }
 
-        /** Set icon drawable. */
+        /** Set icon drawable. TODO(b/139444064): revise this api.*/
         protected void bindIcon(ImageView imageView) {
             imageView.setImageDrawable(mIcon);
         }
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java b/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
index ea55518..c8899fb 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
@@ -32,6 +32,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 
 import androidx.annotation.DrawableRes;
@@ -147,6 +148,11 @@
     private boolean mLogoFillsNavIconSpace;
     private boolean mEatingTouch = false;
     private boolean mEatingHover = false;
+    private ProgressBar mProgressBar;
+    private MenuItem.Listener mOverflowItemListener = () -> {
+        createOverflowDialog();
+        setState(getState());
+    };
 
     public Toolbar(Context context) {
         this(context, null);
@@ -202,6 +208,7 @@
             mTitleLogoContainer = requireViewById(R.id.car_ui_toolbar_title_logo_container);
             mTitleLogo = requireViewById(R.id.car_ui_toolbar_title_logo);
             mSearchView = requireViewById(R.id.car_ui_toolbar_search_view);
+            mProgressBar = requireViewById(R.id.car_ui_toolbar_progress_bar);
 
             mTitle.setText(a.getString(R.styleable.CarUiToolbar_title));
             setLogo(a.getResourceId(R.styleable.CarUiToolbar_logo, 0));
@@ -600,10 +607,7 @@
         for (MenuItem item : mMenuItems) {
             if (item.getDisplayBehavior() == MenuItem.DisplayBehavior.NEVER) {
                 mOverflowItems.add(item);
-                item.setListener(() -> {
-                    createOverflowDialog();
-                    setState(getState());
-                });
+                item.setListener(mOverflowItemListener);
             } else {
                 MenuItemRenderer renderer = new MenuItemRenderer(item, mMenuItemsContainer);
                 mMenuItemRenderers.add(renderer);
@@ -717,8 +721,7 @@
     /**
      * Set whether or not to show the {@link MenuItem MenuItems} while searching. Default false.
      * Even if this is set to true, the {@link MenuItem} created by
-     * {@link MenuItem.Builder#createSearch(Context, MenuItem.OnClickListener)} will still be
-     * hidden.
+     * {@link MenuItem.Builder#setToSearch()} will still be hidden.
      */
     public void setShowMenuItemsWhileSearching(boolean showMenuItems) {
         mShowMenuItemsWhileSearching = showMenuItems;
@@ -919,4 +922,19 @@
     public boolean unregisterOnBackListener(OnBackListener listener) {
         return mOnBackListeners.remove(listener);
     }
+
+    /** Shows the progress bar */
+    public void showProgressBar() {
+        mProgressBar.setVisibility(View.VISIBLE);
+    }
+
+    /** Hides the progress bar */
+    public void hideProgressBar() {
+        mProgressBar.setVisibility(View.GONE);
+    }
+
+    /** Returns the progress bar */
+    public ProgressBar getProgressBar() {
+        return mProgressBar;
+    }
 }
diff --git a/car-ui-lib/src/com/android/car/ui/utils/CarUxRestrictionsUtil.java b/car-ui-lib/src/com/android/car/ui/utils/CarUxRestrictionsUtil.java
index af65780..31ac24a 100644
--- a/car-ui-lib/src/com/android/car/ui/utils/CarUxRestrictionsUtil.java
+++ b/car-ui-lib/src/com/android/car/ui/utils/CarUxRestrictionsUtil.java
@@ -27,6 +27,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.car.ui.R;
 
@@ -66,7 +67,7 @@
                     }
                 };
 
-        mCarApi = Car.createCar(context);
+        mCarApi = Car.createCar(context.getApplicationContext());
         mObservers = Collections.newSetFromMap(new WeakHashMap<>());
 
         try {
@@ -154,4 +155,10 @@
 
         return str;
     }
+
+    /** Sets car UX restrictions. Only used for testing. */
+    @VisibleForTesting
+    public void setUxRestrictions(CarUxRestrictions carUxRestrictions) {
+        mCarUxRestrictions = carUxRestrictions;
+    }
 }
diff --git a/car-ui-lib/tests/apitest/current.xml b/car-ui-lib/tests/apitest/current.xml
index 9bd897a..4180cae 100644
--- a/car-ui-lib/tests/apitest/current.xml
+++ b/car-ui-lib/tests/apitest/current.xml
@@ -2,6 +2,7 @@
 <!--This file is AUTO GENERATED, DO NOT EDIT MANUALLY.-->
 <resources>
   <public type="attr" name="CarUiToolbarStyle"/>
+  <public type="attr" name="carUiPreferenceStyle"/>
   <public type="attr" name="carUiRecyclerViewStyle"/>
   <public type="attr" name="state_ux_restricted"/>
   <public type="bool" name="car_ui_list_item_single_line_title"/>
@@ -44,7 +45,13 @@
   <public type="dimen" name="car_ui_dialog_edittext_margin_end"/>
   <public type="dimen" name="car_ui_dialog_edittext_margin_start"/>
   <public type="dimen" name="car_ui_dialog_edittext_margin_top"/>
+  <public type="dimen" name="car_ui_dialog_icon_size"/>
+  <public type="dimen" name="car_ui_dialog_title_margin"/>
   <public type="dimen" name="car_ui_header_list_item_height"/>
+  <public type="dimen" name="car_ui_keyline_1"/>
+  <public type="dimen" name="car_ui_keyline_2"/>
+  <public type="dimen" name="car_ui_keyline_3"/>
+  <public type="dimen" name="car_ui_keyline_4"/>
   <public type="dimen" name="car_ui_letter_spacing_body1"/>
   <public type="dimen" name="car_ui_letter_spacing_body3"/>
   <public type="dimen" name="car_ui_list_item_action_divider_height"/>
@@ -54,6 +61,10 @@
   <public type="dimen" name="car_ui_list_item_height"/>
   <public type="dimen" name="car_ui_list_item_icon_container_width"/>
   <public type="dimen" name="car_ui_list_item_icon_size"/>
+  <public type="dimen" name="car_ui_list_item_radio_button_end_inset"/>
+  <public type="dimen" name="car_ui_list_item_radio_button_height"/>
+  <public type="dimen" name="car_ui_list_item_radio_button_icon_container_width"/>
+  <public type="dimen" name="car_ui_list_item_radio_button_start_inset"/>
   <public type="dimen" name="car_ui_list_item_start_inset"/>
   <public type="dimen" name="car_ui_list_item_supplemental_icon_size"/>
   <public type="dimen" name="car_ui_list_item_text_no_icon_start_margin"/>
@@ -183,8 +194,10 @@
   <public type="style" name="Preference.CarUi.DialogPreference"/>
   <public type="style" name="Preference.CarUi.DialogPreference.EditTextPreference"/>
   <public type="style" name="Preference.CarUi.DropDown"/>
+  <public type="style" name="Preference.CarUi.Icon"/>
   <public type="style" name="Preference.CarUi.Information"/>
   <public type="style" name="Preference.CarUi.ListPreference"/>
+  <public type="style" name="Preference.CarUi.Preference"/>
   <public type="style" name="Preference.CarUi.PreferenceScreen"/>
   <public type="style" name="Preference.CarUi.SeekBarPreference"/>
   <public type="style" name="Preference.CarUi.SwitchPreference"/>
@@ -192,6 +205,7 @@
   <public type="style" name="PreferenceFragmentList.CarUi"/>
   <public type="style" name="RadioButton.CarUi"/>
   <public type="style" name="TextAppearance.CarUi"/>
+  <public type="style" name="TextAppearance.CarUi.AlertDialog.Subtitle"/>
   <public type="style" name="TextAppearance.CarUi.ListItem"/>
   <public type="style" name="TextAppearance.CarUi.ListItem.Body"/>
   <public type="style" name="TextAppearance.CarUi.PreferenceCategoryTitle"/>
@@ -205,6 +219,10 @@
   <public type="style" name="TextAppearance.CarUi.Widget.Toolbar.Title"/>
   <public type="style" name="Theme.CarUi"/>
   <public type="style" name="Widget.CarUi"/>
+  <public type="style" name="Widget.CarUi.AlertDialog"/>
+  <public type="style" name="Widget.CarUi.AlertDialog.HeaderContainer"/>
+  <public type="style" name="Widget.CarUi.AlertDialog.Icon"/>
+  <public type="style" name="Widget.CarUi.AlertDialog.TitleContainer"/>
   <public type="style" name="Widget.CarUi.Button.Borderless.Colored"/>
   <public type="style" name="Widget.CarUi.CarUiRecyclerView"/>
   <public type="style" name="Widget.CarUi.CarUiRecyclerView.NestedRecyclerView"/>
@@ -216,6 +234,7 @@
   <public type="style" name="Widget.CarUi.Toolbar.MenuItem.Container"/>
   <public type="style" name="Widget.CarUi.Toolbar.NavIcon"/>
   <public type="style" name="Widget.CarUi.Toolbar.NavIconContainer"/>
+  <public type="style" name="Widget.CarUi.Toolbar.ProgressBar"/>
   <public type="style" name="Widget.CarUi.Toolbar.Search.CloseIcon"/>
   <public type="style" name="Widget.CarUi.Toolbar.Search.EditText"/>
   <public type="style" name="Widget.CarUi.Toolbar.Search.SearchIcon"/>
diff --git a/car-ui-lib/tests/paintbooth/build.gradle b/car-ui-lib/tests/paintbooth/build.gradle
index d8f89f6..0ef3514 100644
--- a/car-ui-lib/tests/paintbooth/build.gradle
+++ b/car-ui-lib/tests/paintbooth/build.gradle
@@ -45,6 +45,7 @@
 
 dependencies {
     implementation project(':')
+    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0'
     api 'androidx.annotation:annotation:1.1.0'
     api 'androidx.constraintlayout:constraintlayout:1.1.3'
     api 'androidx.recyclerview:recyclerview:1.0.0'
diff --git a/car-ui-lib/tests/paintbooth/res/layout/dialogs_activity.xml b/car-ui-lib/tests/paintbooth/res/layout/dialogs_activity.xml
deleted file mode 100644
index d94a16a..0000000
--- a/car-ui-lib/tests/paintbooth/res/layout/dialogs_activity.xml
+++ /dev/null
@@ -1,102 +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:id="@+id/dialog_layout"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@drawable/car_ui_activity_background">
-
-  <com.android.car.ui.toolbar.Toolbar
-      android:id="@+id/toolbar"
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      app:layout_constraintTop_toTopOf="parent"
-      app:state="subpage"
-      app:title="@string/app_name" />
-
-  <Button
-      android:id="@+id/show_dialog_bt"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:text="@string/dialog_show_dialog"
-      app:layout_constraintTop_toBottomOf="@+id/toolbar"
-      app:layout_constraintBottom_toTopOf="@+id/show_dialog_with_textbox"
-      app:layout_constraintLeft_toLeftOf="parent"
-      app:layout_constraintRight_toRightOf="parent"/>
-
-  <Button
-      android:id="@+id/show_dialog_with_textbox"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:text="@string/dialog_show_dialog_edit"
-      app:layout_constraintTop_toBottomOf="@+id/show_dialog_bt"
-      app:layout_constraintBottom_toTopOf="@+id/show_dialog_only_positive_bt"
-      app:layout_constraintEnd_toEndOf="parent"
-      app:layout_constraintStart_toStartOf="parent" />
-
-  <Button
-      android:id="@+id/show_dialog_only_positive_bt"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:text="@string/dialog_show_dialog_only_positive"
-      app:layout_constraintTop_toBottomOf="@+id/show_dialog_with_textbox"
-      app:layout_constraintBottom_toTopOf="@+id/show_dialog_with_no_button_set"
-      app:layout_constraintEnd_toEndOf="parent"
-      app:layout_constraintStart_toStartOf="parent" />
-
-  <Button
-      android:id="@+id/show_dialog_with_no_button_set"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:text="@string/dialog_show_dialog_no_button"
-      app:layout_constraintBottom_toTopOf="@+id/show_dialog_with_checkbox_bt"
-      app:layout_constraintEnd_toEndOf="parent"
-      app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toBottomOf="@+id/show_dialog_only_positive_bt" />
-
-  <Button
-      android:id="@+id/show_dialog_with_checkbox_bt"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:text="@string/dialog_show_dialog_checkbox"
-      app:layout_constraintBottom_toTopOf="@+id/show_dialog_without_title"
-      app:layout_constraintEnd_toEndOf="parent"
-      app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toBottomOf="@+id/show_dialog_with_no_button_set" />
-
-  <Button
-      android:id="@+id/show_dialog_without_title"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:text="@string/dialog_show_dialog_no_title"
-      app:layout_constraintBottom_toTopOf="@+id/show_toast"
-      app:layout_constraintEnd_toEndOf="parent"
-      app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toBottomOf="@+id/show_dialog_with_checkbox_bt" />
-
-  <Button
-      android:id="@+id/show_toast"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:text="@string/dialog_show_toast"
-      app:layout_constraintBottom_toBottomOf="parent"
-      app:layout_constraintEnd_toEndOf="parent"
-      app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toBottomOf="@+id/show_dialog_without_title" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-ui-lib/tests/paintbooth/res/values/strings.xml b/car-ui-lib/tests/paintbooth/res/values/strings.xml
index 926e814..e2159c1 100644
--- a/car-ui-lib/tests/paintbooth/res/values/strings.xml
+++ b/car-ui-lib/tests/paintbooth/res/values/strings.xml
@@ -218,6 +218,9 @@
   <!-- Text for show dialog button [CHAR_LIMIT=18]-->
   <string name="dialog_show_dialog">Show Dialog</string>
 
+  <!-- Text for show dialog button [CHAR_LIMIT=30]-->
+  <string name="dialog_show_dialog_icon">Show Dialog with icon</string>
+
   <!-- Text for Dialog with edit text box button [CHAR_LIMIT=50]-->
   <string name="dialog_show_dialog_edit">Show Dialog with edit text box</string>
 
@@ -236,6 +239,12 @@
   <!-- Text for show Toast button [CHAR_LIMIT=16]-->
   <string name="dialog_show_toast">Show Toast</string>
 
+  <!-- Button that shows a dialog with a subtitle [CHAR_LIMIT=50]-->
+  <string name="dialog_show_subtitle">Show Dialog with title and subtitle</string>
+
+  <!-- Button that shows a dialog with a subtitle and icon [CHAR_LIMIT=50]-->
+  <string name="dialog_show_subtitle_and_icon">Show Dialog with title, subtitle, and icon</string>
+
   <!--This section is for widget attributes -->
   <eat-comment/>
   <!-- Text for checkbox [CHAR_LIMIT=16]-->
diff --git a/car-ui-lib/tests/paintbooth/res/xml/menuitems.xml b/car-ui-lib/tests/paintbooth/res/xml/menuitems.xml
index 7899572..4f22379 100644
--- a/car-ui-lib/tests/paintbooth/res/xml/menuitems.xml
+++ b/car-ui-lib/tests/paintbooth/res/xml/menuitems.xml
@@ -15,6 +15,8 @@
   ~ limitations under the License.
   -->
 <MenuItems xmlns:app="http://schemas.android.com/apk/res-auto">
+    <MenuItem app:search="true"/>
+    <MenuItem app:settings="true"/>
     <MenuItem
         app:title="@string/preferences_screen_title"/>
     <MenuItem
diff --git a/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml b/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
index 87e491a..b49dd0d 100644
--- a/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
+++ b/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
@@ -63,7 +63,7 @@
         android:title="@string/title_switch_preference"
         android:summary="@string/summary_switch_preference"/>
 
-    <com.android.car.ui.preference.CarUiDropDownPreference
+    <DropDownPreference
         android:key="dropdown"
         android:title="@string/title_dropdown_preference"
         android:entries="@array/entries"
@@ -80,13 +80,13 @@
   <PreferenceCategory
       android:title="@string/dialogs">
 
-    <com.android.car.ui.preference.CarUiEditTextPreference
+    <EditTextPreference
         android:key="edittext"
         android:title="@string/title_edittext_preference"
         app:useSimpleSummaryProvider="true"
         android:dialogTitle="@string/dialog_title_edittext_preference"/>
 
-    <com.android.car.ui.preference.CarUiListPreference
+    <ListPreference
         android:key="list"
         android:title="@string/title_list_preference"
         app:useSimpleSummaryProvider="true"
@@ -94,7 +94,7 @@
         android:entryValues="@array/entry_values"
         android:dialogTitle="@string/dialog_title_list_preference"/>
 
-    <com.android.car.ui.preference.CarUiMultiSelectListPreference
+    <MultiSelectListPreference
         android:key="multi_select_list"
         android:title="@string/title_multi_list_preference"
         android:summary="@string/summary_multi_list_preference"
@@ -108,19 +108,19 @@
       android:title="@string/advanced_attributes"
       app:initialExpandedChildrenCount="1">
 
-    <com.android.car.ui.preference.CarUiPreference
+    <Preference
         android:key="expandable"
         android:title="@string/title_expandable_preference"
         android:summary="@string/summary_expandable_preference"/>
 
-    <com.android.car.ui.preference.CarUiPreference
+    <Preference
         android:title="@string/title_intent_preference"
         android:summary="@string/summary_intent_preference">
 
       <intent android:action="android.intent.action.VIEW"
           android:data="http://www.android.com"/>
 
-    </com.android.car.ui.preference.CarUiPreference>
+    </Preference>
 
     <SwitchPreference
         android:key="parent"
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java
index 2ae5f2b..f05a827 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java
@@ -19,11 +19,13 @@
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
+import android.util.Log;
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
+import android.widget.Toast;
 
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
@@ -38,6 +40,7 @@
 import com.android.car.ui.paintbooth.widgets.WidgetActivity;
 import com.android.car.ui.recyclerview.CarUiRecyclerView;
 
+import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.List;
 
@@ -106,5 +109,84 @@
 
         CarUiRecyclerView prv = findViewById(R.id.activities);
         prv.setAdapter(mAdapter);
+
+        initLeakCanary();
+    }
+
+    private void initLeakCanary() {
+        // This sets LeakCanary to report errors after a single leak instead of 5, and to ask for
+        // permission to use storage, which it needs to work.
+        //
+        // Equivalent to this non-reflection code:
+        //
+        // Config config = LeakCanary.INSTANCE.getConfig();
+        // LeakCanary.INSTANCE.setConfig(config.copy(config.getDumpHeap(),
+        //     config.getDumpHeapWhenDebugging(),
+        //     1,
+        //     config.getReferenceMatchers(),
+        //     config.getObjectInspectors(),
+        //     config.getOnHeapAnalyzedListener(),
+        //     config.getMetatadaExtractor(),
+        //     config.getComputeRetainedHeapSize(),
+        //     config.getMaxStoredHeapDumps(),
+        //     true,
+        //     config.getUseExperimentalLeakFinders()));
+        try {
+            Class<?> canaryClass = Class.forName("leakcanary.LeakCanary");
+            try {
+                Class<?> onHeapAnalyzedListenerClass =
+                        Class.forName("leakcanary.OnHeapAnalyzedListener");
+                Class<?> metadataExtractorClass = Class.forName("shark.MetadataExtractor");
+                Method getConfig = canaryClass.getMethod("getConfig");
+                Class<?> configClass = getConfig.getReturnType();
+                Method setConfig = canaryClass.getMethod("setConfig", configClass);
+                Method copy = configClass.getMethod("copy", boolean.class, boolean.class,
+                        int.class, List.class, List.class, onHeapAnalyzedListenerClass,
+                        metadataExtractorClass, boolean.class, int.class, boolean.class,
+                        boolean.class);
+
+                Object canary = canaryClass.getField("INSTANCE").get(null);
+                Object currentConfig = getConfig.invoke(canary);
+
+                Boolean dumpHeap = (Boolean) configClass
+                        .getMethod("getDumpHeap").invoke(currentConfig);
+                Boolean dumpHeapWhenDebugging = (Boolean) configClass
+                        .getMethod("getDumpHeapWhenDebugging").invoke(currentConfig);
+                List<?> referenceMatchers = (List<?>) configClass
+                        .getMethod("getReferenceMatchers").invoke(currentConfig);
+                List<?> objectInspectors = (List<?>) configClass
+                        .getMethod("getObjectInspectors").invoke(currentConfig);
+                Object onHeapAnalyzedListener = configClass
+                        .getMethod("getOnHeapAnalyzedListener").invoke(currentConfig);
+                // Yes, LeakCanary misspelled metadata
+                Object metadataExtractor = configClass
+                        .getMethod("getMetatadaExtractor").invoke(currentConfig);
+                Boolean computeRetainedHeapSize = (Boolean) configClass
+                        .getMethod("getComputeRetainedHeapSize").invoke(currentConfig);
+                Integer maxStoredHeapDumps = (Integer) configClass
+                        .getMethod("getMaxStoredHeapDumps").invoke(currentConfig);
+                Boolean useExperimentalLeakFinders = (Boolean) configClass
+                        .getMethod("getUseExperimentalLeakFinders").invoke(currentConfig);
+
+                setConfig.invoke(canary, copy.invoke(currentConfig,
+                        dumpHeap,
+                        dumpHeapWhenDebugging,
+                        1,
+                        referenceMatchers,
+                        objectInspectors,
+                        onHeapAnalyzedListener,
+                        metadataExtractor,
+                        computeRetainedHeapSize,
+                        maxStoredHeapDumps,
+                        true,
+                        useExperimentalLeakFinders));
+
+            } catch (ReflectiveOperationException e) {
+                Log.e("paintbooth", "Error initializing LeakCanary", e);
+                Toast.makeText(this, "Error initializing LeakCanary", Toast.LENGTH_LONG).show();
+            }
+        } catch (ClassNotFoundException e) {
+            // LeakCanary is not used in this build, do nothing.
+        }
     }
 }
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
index 5b5c330..505a026 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
@@ -18,100 +18,181 @@
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
+
 import com.android.car.ui.AlertDialogBuilder;
 import com.android.car.ui.paintbooth.R;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Activity that shows different dialogs from the device default theme.
  */
 public class DialogsActivity extends Activity {
 
+    private final List<Pair<Integer, View.OnClickListener>> mButtons = new ArrayList<>();
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.dialogs_activity);
+        setContentView(R.layout.car_ui_recycler_view_activity);
 
-        Button showDialogButton = findViewById(R.id.show_dialog_bt);
-        showDialogButton.setOnClickListener(v -> openDialog(false));
+        mButtons.add(Pair.create(R.string.dialog_show_dialog,
+                v -> showDialog()));
+        mButtons.add(Pair.create(R.string.dialog_show_dialog_icon,
+                v -> showDialogWithIcon()));
+        mButtons.add(Pair.create(R.string.dialog_show_dialog_edit,
+                v -> showDialogWithTextBox()));
+        mButtons.add(Pair.create(R.string.dialog_show_dialog_only_positive,
+                v -> showDialogWithOnlyPositiveButton()));
+        mButtons.add(Pair.create(R.string.dialog_show_dialog_no_button,
+                v -> showDialogWithNoButtonProvided()));
+        mButtons.add(Pair.create(R.string.dialog_show_dialog_checkbox,
+                v -> showDialogWithCheckbox()));
+        mButtons.add(Pair.create(R.string.dialog_show_dialog_no_title,
+                v -> showDialogWithoutTitle()));
+        mButtons.add(Pair.create(R.string.dialog_show_toast,
+                v -> showToast()));
+        mButtons.add(Pair.create(R.string.dialog_show_subtitle,
+                v -> showDialogWithSubtitle()));
+        mButtons.add(Pair.create(R.string.dialog_show_subtitle_and_icon,
+                v -> showDialogWithSubtitleAndIcon()));
 
-        Button showDialogOnlyPositiveButton = findViewById(R.id.show_dialog_only_positive_bt);
-        showDialogOnlyPositiveButton.setOnClickListener(v -> openDialogWithOnlyPositiveButton());
-
-        Button showDialogWithoutTitleButton = findViewById(R.id.show_dialog_without_title);
-        showDialogWithoutTitleButton.setOnClickListener(v -> openDialogWithoutTitle());
-
-        Button showDialogWithNoButtonProvided = findViewById(R.id.show_dialog_with_no_button_set);
-        showDialogWithNoButtonProvided.setOnClickListener(v -> openDialogWithNoButtonProvided());
-
-        Button showDialogWithCheckboxButton = findViewById(R.id.show_dialog_with_checkbox_bt);
-        showDialogWithCheckboxButton.setOnClickListener(v -> openDialog(true));
-
-        Button showDialogWithTextbox = findViewById(R.id.show_dialog_with_textbox);
-        showDialogWithTextbox.setOnClickListener(v -> openDialogWithTextbox());
-
-        Button showToast = findViewById(R.id.show_toast);
-        showToast.setOnClickListener(v -> showToast());
+        CarUiRecyclerView recyclerView = requireViewById(R.id.list);
+        recyclerView.setAdapter(mAdapter);
     }
 
-    private void openDialog(boolean showCheckbox) {
-        AlertDialogBuilder builder = new AlertDialogBuilder(this);
-
-        if (showCheckbox) {
-            // Set Custom Title
-            builder.setTitle("Custom Dialog Box");
-            builder.setMultiChoiceItems(
-                    new CharSequence[]{"I am a checkbox"},
-                    new boolean[]{false},
-                    (dialog, which, isChecked) -> {
-                    });
-        } else {
-            builder.setTitle("Standard Alert Dialog").setMessage("With a message to show.");
-        }
-
-        builder
-                .setPositiveButton("OK", (dialoginterface, i) -> {
+    private void showDialog() {
+        new AlertDialogBuilder(this)
+                .setTitle("Standard Alert Dialog")
+                .setMessage("With a message to show.")
+                .setPositiveButton("OK", (dialogInterface, which) -> {
                 })
-                .setNegativeButton("CANCEL", (dialog, which) -> {
-                });
-        builder.show();
-    }
-
-    private void openDialogWithNoButtonProvided() {
-        AlertDialogBuilder builder = new AlertDialogBuilder(this);
-        builder.setTitle("Standard Alert Dialog").show();
-    }
-
-    private void openDialogWithTextbox() {
-        AlertDialogBuilder builder = new AlertDialogBuilder(this);
-        builder.setTitle("Standard Alert Dialog").setEditBox("Edit me please", null, null);
-        builder.setPositiveButton("OK", (dialoginterface, i) -> {
-        });
-        builder.show();
-    }
-
-    private void openDialogWithOnlyPositiveButton() {
-        AlertDialogBuilder builder = new AlertDialogBuilder(this);
-        builder.setTitle("Standard Alert Dialog").setMessage("With a message to show.");
-        builder.setPositiveButton("OK", (dialoginterface, i) -> {
-        });
-        builder.show();
-    }
-
-    private void openDialogWithoutTitle() {
-        AlertDialogBuilder builder = new AlertDialogBuilder(this);
-        builder.setMessage("I dont have a title.");
-        builder
-                .setPositiveButton("OK", (dialoginterface, i) -> {
+                .setNegativeButton("CANCEL", (dialogInterface, which) -> {
                 })
-                .setNegativeButton("CANCEL", (dialog, which) -> {
-                });
-        builder.show();
+                .show();
+    }
+
+    private void showDialogWithIcon() {
+        new AlertDialogBuilder(this)
+                .setTitle("Alert dialog with icon")
+                .setMessage("The message body of the alert")
+                .setIcon(R.drawable.ic_tracklist)
+                .show();
+    }
+
+    private void showDialogWithNoButtonProvided() {
+        new AlertDialogBuilder(this)
+                .setTitle("Standard Alert Dialog")
+                .show();
+    }
+
+    private void showDialogWithCheckbox() {
+        new AlertDialogBuilder(this)
+                .setTitle("Custom Dialog Box")
+                .setMultiChoiceItems(
+                        new CharSequence[]{"I am a checkbox"},
+                        new boolean[]{false},
+                        (dialog, which, isChecked) -> {
+                        })
+                .setPositiveButton("OK", (dialogInterface, which) -> {
+                })
+                .setNegativeButton("CANCEL", (dialogInterface, which) -> {
+                })
+                .show();
+    }
+
+    private void showDialogWithTextBox() {
+        new AlertDialogBuilder(this)
+                .setTitle("Standard Alert Dialog")
+                .setEditBox("Edit me please", null, null)
+                .setPositiveButton("OK", (dialogInterface, i) -> {
+                })
+                .show();
+    }
+
+    private void showDialogWithOnlyPositiveButton() {
+        new AlertDialogBuilder(this)
+                .setTitle("Standard Alert Dialog").setMessage("With a message to show.")
+                .setPositiveButton("OK", (dialogInterface, i) -> {
+                })
+                .show();
+    }
+
+    private void showDialogWithoutTitle() {
+        new AlertDialogBuilder(this)
+                .setMessage("I dont have a title.")
+                .setPositiveButton("OK", (dialogInterface, i) -> {
+                })
+                .setNegativeButton("CANCEL", (dialogInterface, which) -> {
+                })
+                .show();
     }
 
     private void showToast() {
         Toast.makeText(this, "Toast message looks like this", Toast.LENGTH_LONG).show();
     }
+
+    private void showDialogWithSubtitle() {
+        new AlertDialogBuilder(this)
+                .setTitle("My Title!")
+                .setSubtitle("My Subtitle!")
+                .setMessage("My Message!")
+                .show();
+    }
+
+    private void showDialogWithSubtitleAndIcon() {
+        new AlertDialogBuilder(this)
+                .setTitle("My Title!")
+                .setSubtitle("My Subtitle!")
+                .setMessage("My Message!")
+                .setIcon(R.drawable.ic_tracklist)
+                .show();
+    }
+
+    private static class ViewHolder extends CarUiRecyclerView.ViewHolder {
+
+        private final Button mButton;
+
+        ViewHolder(View itemView) {
+            super(itemView);
+            mButton = itemView.requireViewById(R.id.button);
+        }
+
+        public void bind(Integer title, View.OnClickListener listener) {
+            mButton.setText(title);
+            mButton.setOnClickListener(listener);
+        }
+    }
+
+    private final CarUiRecyclerView.Adapter<ViewHolder> mAdapter =
+            new CarUiRecyclerView.Adapter<ViewHolder>() {
+                @Override
+                public int getItemCount() {
+                    return mButtons.size();
+                }
+
+                @Override
+                public ViewHolder onCreateViewHolder(ViewGroup parent, int position) {
+                    View item =
+                            LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item,
+                                    parent, false);
+                    return new ViewHolder(item);
+                }
+
+                @Override
+                public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+                    Pair<Integer, View.OnClickListener> pair = mButtons.get(position);
+                    holder.bind(pair.first, pair.second);
+                }
+            };
 }
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
index b42a2ec..a5e7fa3 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
@@ -62,13 +62,25 @@
                     return false;
                 });
 
-        mMenuItems.add(
-                MenuItem.Builder.createSearch(this, i -> toolbar.setState(Toolbar.State.SEARCH)));
+        mMenuItems.add(MenuItem.builder(this)
+                .setToSearch()
+                .setOnClickListener(i -> toolbar.setState(Toolbar.State.SEARCH))
+                .build());
 
         toolbar.setMenuItems(mMenuItems);
 
-        mButtons.add(Pair.create(getString(R.string.toolbar_change_title),
-                v -> toolbar.setTitle(toolbar.getTitle() + " X")));
+        mButtons.add(Pair.create("Toggle progress bar", v -> {
+            if (toolbar.getProgressBar().getVisibility() == View.GONE) {
+                toolbar.showProgressBar();
+                Toast.makeText(this, "showing progress bar", Toast.LENGTH_SHORT).show();
+            } else {
+                toolbar.hideProgressBar();
+                Toast.makeText(this, "hiding progress bar", Toast.LENGTH_SHORT).show();
+            }
+        }));
+
+        mButtons.add(Pair.create("Change title", v ->
+                toolbar.setTitle(toolbar.getTitle() + " X")));
 
         mButtons.add(Pair.create(getString(R.string.toolbar_set_xml_resource), v -> {
             mMenuItems.clear();
@@ -76,9 +88,11 @@
         }));
 
         mButtons.add(Pair.create(getString(R.string.toolbar_add_icon), v -> {
-            mMenuItems.add(MenuItem.Builder.createSettings(
-                    this, i -> Toast.makeText(this, "Clicked",
-                            Toast.LENGTH_SHORT).show()));
+            mMenuItems.add(MenuItem.builder(this)
+                    .setToSettings()
+                    .setOnClickListener(i -> Toast.makeText(this, "Clicked",
+                            Toast.LENGTH_SHORT).show())
+                    .build());
             toolbar.setMenuItems(mMenuItems);
         }));
 
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java b/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
index 5b2a1e3..5cf9e85 100644
--- a/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
+++ b/car-ui-lib/tests/robotests/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
@@ -18,7 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
 import android.content.Context;
+import android.content.res.Resources;
 import android.view.LayoutInflater;
 import android.view.View;
 
@@ -34,6 +38,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
 import org.robolectric.RuntimeEnvironment;
 
 @RunWith(CarUiRobolectricTestRunner.class)
@@ -104,4 +109,24 @@
 
         assertThat(mCarUiRecyclerView.mNestedRecyclerView).isNotNull();
     }
+
+    @Test
+    public void init_shouldNotContainNestedRecyclerView() {
+        Context context = spy(mContext);
+        Resources resources = spy(mContext.getResources());
+        when(resources.getBoolean(R.bool.car_ui_scrollbar_enable)).thenReturn(false);
+        when(context.getResources()).thenReturn(resources);
+
+        mCarUiRecyclerView = new CarUiRecyclerView(context);
+
+        assertThat(mCarUiRecyclerView.mNestedRecyclerView).isNull();
+    }
+
+    @Test
+    public void init_shouldHaveGridLayout() {
+        mCarUiRecyclerView = new CarUiRecyclerView(mContext,
+                Robolectric.buildAttributeSet().addAttribute(R.attr.layoutStyle, "grid").build());
+        assertThat(mCarUiRecyclerView.getEffectiveLayoutManager()).isInstanceOf(
+                GridLayoutManager.class);
+    }
 }
diff --git a/car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/ToolbarTest.java b/car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/ToolbarTest.java
index 239caee..54eaee7 100644
--- a/car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/ToolbarTest.java
+++ b/car-ui-lib/tests/robotests/src/com/android/car/ui/toolbar/ToolbarTest.java
@@ -323,7 +323,7 @@
     public void menuItems_setId_shouldWork() {
         MenuItem item = MenuItem.builder(mContext).build();
 
-        assertThat(item.getId()).isEqualTo(0);
+        assertThat(item.getId()).isEqualTo(View.NO_ID);
 
         item.setId(7);
 
@@ -417,8 +417,7 @@
     @Test
     public void menuItems_searchScreen_shouldHideMenuItems() {
         mToolbar.setMenuItems(Arrays.asList(
-                MenuItem.Builder.createSearch(mContext, i -> {
-                }),
+                MenuItem.builder(mContext).setToSearch().build(),
                 createMenuItem(i -> {
                 })));
 
@@ -432,8 +431,7 @@
     @Test
     public void menuItems_showMenuItemsWhileSearching() {
         mToolbar.setMenuItems(Arrays.asList(
-                MenuItem.Builder.createSearch(mContext, i -> {
-                }),
+                MenuItem.builder(mContext).setToSearch().build(),
                 createMenuItem(i -> {
                 })));
 
@@ -445,7 +443,7 @@
     }
 
     private MenuItem createMenuItem(MenuItem.OnClickListener listener) {
-        return new MenuItem.Builder(mContext)
+        return MenuItem.builder(mContext)
                 .setTitle("Button!")
                 .setOnClickListener(listener)
                 .build();