Merge "Update look and feel of multi-select preference screen." into pi-car-dev
diff --git a/car-ui-lib/Android.mk b/car-ui-lib/Android.mk
index 28edddd..c67ea5c 100644
--- a/car-ui-lib/Android.mk
+++ b/car-ui-lib/Android.mk
@@ -62,10 +62,11 @@
     androidx.appcompat_appcompat \
     androidx-constraintlayout_constraintlayout \
     androidx.preference_preference \
-    androidx.recyclerview_recyclerview
+    androidx.recyclerview_recyclerview \
+    androidx.asynclayoutinflater_asynclayoutinflater \
 
 LOCAL_STATIC_JAVA_LIBRARIES += \
-    androidx-constraintlayout_constraintlayout-solver
+    androidx-constraintlayout_constraintlayout-solver \
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
diff --git a/car-ui-lib/res/layout/car_ui_toolbar.xml b/car-ui-lib/res/layout/car_ui_toolbar.xml
index 2d54431..187dc84 100644
--- a/car-ui-lib/res/layout/car_ui_toolbar.xml
+++ b/car-ui-lib/res/layout/car_ui_toolbar.xml
@@ -65,7 +65,6 @@
         android:id="@+id/car_ui_toolbar_nav_icon_container"
         android:layout_width="@dimen/car_ui_toolbar_margin"
         android:layout_height="0dp"
-        android:background="?android:attr/selectableItemBackground"
         style="@style/Widget.CarUi.Toolbar.NavIconContainer"
         app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
         app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
@@ -135,11 +134,10 @@
         app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
         app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_end_guideline"/>
 
-    <com.android.car.ui.toolbar.SearchView
-        android:id="@+id/car_ui_toolbar_search_view"
+    <FrameLayout
+        android:id="@+id/car_ui_toolbar_search_view_container"
         android:layout_width="0dp"
         android:layout_height="@dimen/car_ui_toolbar_search_height"
-        android:visibility="gone"
         app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
         app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
         app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_nav_icon_container"
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 bc334bf..efae70b 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
@@ -79,7 +79,6 @@
         android:id="@+id/car_ui_toolbar_nav_icon_container"
         android:layout_width="@dimen/car_ui_toolbar_margin"
         android:layout_height="0dp"
-        android:background="?android:attr/selectableItemBackground"
         style="@style/Widget.CarUi.Toolbar.NavIconContainer"
         app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
         app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
@@ -128,15 +127,14 @@
         app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_title_logo_container"
         app:layout_constraintEnd_toStartOf="@id/car_ui_toolbar_menu_items_container"/>
 
-    <com.android.car.ui.toolbar.SearchView
-        android:id="@+id/car_ui_toolbar_search_view"
+    <FrameLayout
+        android:id="@+id/car_ui_toolbar_search_view_container"
         android:layout_width="0dp"
         android:layout_height="@dimen/car_ui_toolbar_search_height"
-        android:visibility="gone"
         app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
         app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
-        app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_nav_icon_container"
-        app:layout_constraintEnd_toStartOf="@id/car_ui_toolbar_menu_items_container"/>
+        app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_nav_icon_container"
+        app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_menu_items_container"/>
 
     <LinearLayout
         android:id="@+id/car_ui_toolbar_menu_items_container"
diff --git a/car-ui-lib/res/values/styles.xml b/car-ui-lib/res/values/styles.xml
index 87ee6d6..9b9419a 100644
--- a/car-ui-lib/res/values/styles.xml
+++ b/car-ui-lib/res/values/styles.xml
@@ -43,6 +43,7 @@
     <style name="Widget.CarUi.Toolbar.NavIcon">
         <item name="android:tint">@color/car_ui_toolbar_nav_icon_color</item>
         <item name="android:src">@drawable/car_ui_icon_arrow_back</item>
+        <item name="android:background">@drawable/car_ui_toolbar_menu_item_icon_ripple</item>
     </style>
 
     <style name="Widget.CarUi.Toolbar.Title">
@@ -201,10 +202,6 @@
         <item name="android:layout">@layout/car_ui_preference_fragment</item>
     </style>
 
-    <style name="RadioButton.CarUi" parent="android:Widget.DeviceDefault.Light.CompoundButton.RadioButton">
-        <item name="android:textAppearance">?android:attr/textAppearanceLarge</item>
-    </style>
-
     <style name="PreferenceFragmentList.CarUi">
         <item name="android:paddingTop">0dp</item>
         <item name="android:paddingBottom">0dp</item>
diff --git a/car-ui-lib/res/values/themes.xml b/car-ui-lib/res/values/themes.xml
index d40e13c..91d26c6 100644
--- a/car-ui-lib/res/values/themes.xml
+++ b/car-ui-lib/res/values/themes.xml
@@ -200,8 +200,6 @@
 
         <!-- Used by CarUiRecyclerView -->
         <item name="carUiRecyclerViewStyle">@style/Widget.CarUi.CarUiRecyclerView</item>
-
-        <item name="radioButtonStyle">@style/RadioButton.CarUi</item>
     </style>
 
     <style name="CarUiPreferenceTheme">
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 45f34ec..ef142ec 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
@@ -308,10 +308,11 @@
     /** Builder class */
     public static final class Builder {
         private final Context mContext;
-        private final CharSequence mSearchTitle;
-        private final CharSequence mSettingsTitle;
-        private final Drawable mSearchIcon;
-        private final Drawable mSettingsIcon;
+
+        private CharSequence mSearchTitle;
+        private CharSequence mSettingsTitle;
+        private Drawable mSearchIcon;
+        private Drawable mSettingsIcon;
 
         private int mId = View.NO_ID;
         private CharSequence mTitle;
@@ -335,10 +336,6 @@
             // 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 */
@@ -531,6 +528,8 @@
          * <p>If using this, you should only change the id, visibility, or onClickListener.
          */
         public Builder setToSearch() {
+            mSearchTitle = mContext.getString(R.string.car_ui_toolbar_menu_item_search_title);
+            mSearchIcon = mContext.getDrawable(R.drawable.car_ui_icon_search);
             mIsSearch = true;
             setTitle(mSearchTitle);
             setIcon(mSearchIcon);
@@ -547,6 +546,8 @@
          * <p>If using this, you should only change the id, visibility, or onClickListener.
          */
         public Builder setToSettings() {
+            mSettingsTitle = mContext.getString(R.string.car_ui_toolbar_menu_item_settings_title);
+            mSettingsIcon = mContext.getDrawable(R.drawable.car_ui_icon_settings);
             mIsSettings = true;
             setTitle(mSettingsTitle);
             setIcon(mSettingsIcon);
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 a939c5d..b6ba3fc 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
@@ -24,7 +24,6 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Xml;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
@@ -32,6 +31,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.XmlRes;
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 
 import com.android.car.ui.R;
 import com.android.car.ui.utils.CarUiUtils;
@@ -45,6 +45,7 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 
 class MenuItemRenderer implements MenuItem.Listener {
 
@@ -89,20 +90,22 @@
         updateView();
     }
 
-    View createView() {
-        LayoutInflater inflater = (LayoutInflater) mParentView.getContext().getSystemService(
-                Context.LAYOUT_INFLATER_SERVICE);
+    CompletableFuture<View> createView() {
+        CompletableFuture<View> future = new CompletableFuture<>();
+        AsyncLayoutInflater inflater = new AsyncLayoutInflater(mParentView.getContext());
+        inflater.inflate(R.layout.car_ui_toolbar_menu_item, mParentView, (View view, int resid,
+                ViewGroup parent) -> {
+            mView = view;
+            mIconContainer = mView.requireViewById(R.id.car_ui_toolbar_menu_item_icon_container);
+            mIconView = mView.requireViewById(R.id.car_ui_toolbar_menu_item_icon);
+            mSwitch = mView.requireViewById(R.id.car_ui_toolbar_menu_item_switch);
+            mTextView = mView.requireViewById(R.id.car_ui_toolbar_menu_item_text);
+            mTextWithIconView = mView.requireViewById(R.id.car_ui_toolbar_menu_item_text_with_icon);
+            updateView();
+            future.complete(view);
+        });
 
-        mView = inflater.inflate(R.layout.car_ui_toolbar_menu_item, mParentView, false);
-
-        mIconContainer = mView.requireViewById(R.id.car_ui_toolbar_menu_item_icon_container);
-        mIconView = mView.requireViewById(R.id.car_ui_toolbar_menu_item_icon);
-        mSwitch = mView.requireViewById(R.id.car_ui_toolbar_menu_item_switch);
-        mTextView = mView.requireViewById(R.id.car_ui_toolbar_menu_item_text);
-        mTextWithIconView = mView.requireViewById(R.id.car_ui_toolbar_menu_item_text_with_icon);
-
-        updateView();
-        return mView;
+        return future;
     }
 
     private void updateView() {
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java b/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java
index 54048c6..d7240eb 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java
@@ -32,22 +32,23 @@
 
 import com.android.car.ui.R;
 
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.Set;
 
 /**
  * A search view used by {@link Toolbar}.
  */
 public class SearchView extends ConstraintLayout {
+    private final InputMethodManager mInputMethodManager;
     private final ImageView mIcon;
     private final EditText mSearchText;
     private final View mCloseIcon;
     private final int mStartPaddingWithoutIcon;
     private final int mStartPadding;
     private final int mEndPadding;
-    private final Set<Toolbar.OnSearchListener> mSearchListeners = new HashSet<>();
-    private final Set<Toolbar.OnSearchCompletedListener> mSearchCompletedListeners =
-            new HashSet<>();
+    private Set<Toolbar.OnSearchListener> mSearchListeners = Collections.emptySet();
+    private Set<Toolbar.OnSearchCompletedListener> mSearchCompletedListeners =
+            Collections.emptySet();
     private final TextWatcher mTextWatcher = new TextWatcher() {
         @Override
         public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
@@ -76,6 +77,9 @@
     public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
+        mInputMethodManager = (InputMethodManager)
+            getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+
         LayoutInflater inflater = LayoutInflater.from(context);
         inflater.inflate(R.layout.car_ui_toolbar_search_view, this, true);
 
@@ -83,6 +87,7 @@
         mIcon = requireViewById(R.id.car_ui_toolbar_search_icon);
         mCloseIcon = requireViewById(R.id.car_ui_toolbar_search_close);
         mCloseIcon.setOnClickListener(view -> mSearchText.getText().clear());
+        mCloseIcon.setVisibility(View.GONE);
 
         mStartPaddingWithoutIcon = mSearchText.getPaddingStart();
         mStartPadding = context.getResources().getDimensionPixelSize(
@@ -94,13 +99,10 @@
 
         mSearchText.setOnFocusChangeListener(
                 (view, hasFocus) -> {
-                    InputMethodManager manager = ((InputMethodManager)
-                            context.getSystemService(Context.INPUT_METHOD_SERVICE));
                     if (hasFocus) {
-                        manager.restartInput(view); // needed to detect changes to imeOptions
-                        manager.showSoftInput(view, 0);
+                        mInputMethodManager.showSoftInput(view, 0);
                     } else {
-                        manager.hideSoftInputFromWindow(view.getWindowToken(), 0);
+                        mInputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
                     }
                 });
 
@@ -138,32 +140,16 @@
      * Adds a listener for the search text changing.
      * See also {@link #unregisterOnSearchListener(Toolbar.OnSearchListener)}
      */
-    public void registerOnSearchListener(Toolbar.OnSearchListener listener) {
-        mSearchListeners.add(listener);
+    public void setSearchListeners(Set<Toolbar.OnSearchListener> listeners) {
+        mSearchListeners = listeners;
     }
 
     /**
      * Removes a search listener.
      * See also {@link #registerOnSearchListener(Toolbar.OnSearchListener)}
      */
-    public boolean unregisterOnSearchListener(Toolbar.OnSearchListener listener) {
-        return mSearchListeners.remove(listener);
-    }
-
-    /**
-     * Adds a search completed listener
-     * See also {@link #registerOnSearchCompletedListener(Toolbar.OnSearchCompletedListener)}
-     */
-    public void registerOnSearchCompletedListener(Toolbar.OnSearchCompletedListener listener) {
-        mSearchCompletedListeners.add(listener);
-    }
-
-    /**
-     * Removes a search completed listener.
-     * See also {@link #unregisterOnSearchCompletedListener(Toolbar.OnSearchCompletedListener)}
-     */
-    public boolean unregisterOnSearchCompletedListener(Toolbar.OnSearchCompletedListener listener) {
-        return mSearchCompletedListeners.remove(listener);
+    public void setSearchCompletedListeners(Set<Toolbar.OnSearchCompletedListener> listeners) {
+        mSearchCompletedListeners = listeners;
     }
 
     /**
@@ -227,6 +213,9 @@
                 mIcon.setVisibility(View.VISIBLE);
             }
             mIsPlainText = plainText;
+
+            // Needed to detect changes to imeOptions
+            mInputMethodManager.restartInput(mSearchText);
         }
     }
 
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 32b2ae1..7970789 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
@@ -49,7 +49,9 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * A toolbar for Android Automotive OS apps.
@@ -144,11 +146,21 @@
     private ViewGroup mTitleLogoContainer;
     private TabLayout mTabLayout;
     private LinearLayout mMenuItemsContainer;
-    private final MenuItem mOverflowButton;
+    private FrameLayout mSearchViewContainer;
+    private SearchView mSearchView;
+
+    // Cached values that we will send to views when they are inflated
+    private CharSequence mSearchHint;
+    private Drawable mSearchIcon;
+    private String mSearchQuery;
+    private final Set<OnSearchListener> mOnSearchListeners = new HashSet<>();
+    private final Set<OnSearchCompletedListener> mOnSearchCompletedListeners = new HashSet<>();
+
     private final Set<OnBackListener> mOnBackListeners = new HashSet<>();
     private final Set<OnTabSelectedListener> mOnTabSelectedListeners = new HashSet<>();
     private final Set<OnHeightChangedListener> mOnHeightChangedListeners = new HashSet<>();
-    private SearchView mSearchView;
+
+    private final MenuItem mOverflowButton;
     private boolean mHasLogo = false;
     private boolean mShowMenuItemsWhileSearching;
     private State mState = State.HOME;
@@ -157,6 +169,8 @@
     private List<MenuItem> mMenuItems = Collections.emptyList();
     private List<MenuItem> mOverflowItems = new ArrayList<>();
     private final List<MenuItemRenderer> mMenuItemRenderers = new ArrayList<>();
+    private CompletableFuture<Void> mMenuItemViewsFuture;
+    private int mMenuItemsXmlId = 0;
     private AlertDialog mOverflowDialog;
     private boolean mNavIconSpaceReserved;
     private boolean mLogoFillsNavIconSpace;
@@ -224,7 +238,7 @@
             mTitle = requireViewById(R.id.car_ui_toolbar_title);
             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);
+            mSearchViewContainer = requireViewById(R.id.car_ui_toolbar_search_view_container);
             mProgressBar = requireViewById(R.id.car_ui_toolbar_progress_bar);
 
             mTitle.setText(a.getString(R.styleable.CarUiToolbar_title));
@@ -511,18 +525,23 @@
     }
 
     /** Sets the hint for the search bar. */
-    public void setSearchHint(int resId) {
-        mSearchView.setHint(resId);
+    public void setSearchHint(@StringRes int resId) {
+        setSearchHint(getContext().getString(resId));
     }
 
     /** Sets the hint for the search bar. */
     public void setSearchHint(CharSequence hint) {
-        mSearchView.setHint(hint);
+        if (!Objects.equals(hint, mSearchHint)) {
+            mSearchHint = hint;
+            if (mSearchView != null) {
+                mSearchView.setHint(mSearchHint);
+            }
+        }
     }
 
     /** Gets the search hint */
     public CharSequence getSearchHint() {
-        return mSearchView.getHint();
+        return mSearchHint;
     }
 
     /**
@@ -531,8 +550,8 @@
      * <p>The icon will be lost on configuration change, make sure to set it in onCreate() or
      * a similar place.
      */
-    public void setSearchIcon(int resId) {
-        mSearchView.setIcon(resId);
+    public void setSearchIcon(@DrawableRes int resId) {
+        setSearchIcon(getContext().getDrawable(resId));
     }
 
     /**
@@ -542,7 +561,12 @@
      * a similar place.
      */
     public void setSearchIcon(Drawable d) {
-        mSearchView.setIcon(d);
+        if (!Objects.equals(d, mSearchIcon)) {
+            mSearchIcon = d;
+            if (mSearchView != null) {
+                mSearchView.setIcon(mSearchIcon);
+            }
+        }
     }
 
     /**
@@ -595,10 +619,7 @@
         return super.getBackground() != null;
     }
 
-    /**
-     * Sets the {@link MenuItem Menuitems} to display.
-     */
-    public void setMenuItems(@Nullable List<MenuItem> items) {
+    private void setMenuItemsInternal(@Nullable List<MenuItem> items) {
         if (items == null) {
             items = Collections.emptyList();
         }
@@ -615,6 +636,7 @@
         mMenuItemRenderers.clear();
         mMenuItemsContainer.removeAllViews();
 
+        List<CompletableFuture<View>> viewFutures = new ArrayList<>();
         for (MenuItem item : mMenuItems) {
             if (item.getDisplayBehavior() == MenuItem.DisplayBehavior.NEVER) {
                 mOverflowItems.add(item);
@@ -622,23 +644,48 @@
             } else {
                 MenuItemRenderer renderer = new MenuItemRenderer(item, mMenuItemsContainer);
                 mMenuItemRenderers.add(renderer);
-                mMenuItemsContainer.addView(renderer.createView());
+                viewFutures.add(renderer.createView());
             }
         }
 
-        MenuItemRenderer renderer = new MenuItemRenderer(mOverflowButton, mMenuItemsContainer);
-        mMenuItemRenderers.add(renderer);
-        mMenuItemsContainer.addView(renderer.createView());
+        if (!mOverflowItems.isEmpty()) {
+            MenuItemRenderer renderer = new MenuItemRenderer(mOverflowButton, mMenuItemsContainer);
+            mMenuItemRenderers.add(renderer);
+            viewFutures.add(renderer.createView());
+            createOverflowDialog();
+        }
 
-        createOverflowDialog();
+        if (mMenuItemViewsFuture != null) {
+            mMenuItemViewsFuture.cancel(false);
+        }
+
+        mMenuItemViewsFuture = CompletableFuture.allOf(
+            viewFutures.toArray(new CompletableFuture[0]));
+        mMenuItemViewsFuture.thenRunAsync(() -> {
+            for (CompletableFuture<View> future : viewFutures) {
+                mMenuItemsContainer.addView(future.join());
+            }
+            mMenuItemViewsFuture = null;
+        }, getContext().getMainExecutor());
 
         setState(mState);
     }
 
     /**
+     * Sets the {@link MenuItem Menuitems} to display.
+     */
+    public void setMenuItems(@Nullable List<MenuItem> items) {
+        mMenuItemsXmlId = 0;
+        setMenuItemsInternal(items);
+    }
+
+    /**
      * Sets the {@link MenuItem Menuitems} to display to a list defined in XML.
      *
-     * The XML file must have one <MenuItems> tag, with a variable number of <MenuItem>
+     * <p>If this method is called twice with the same argument (and {@link #setMenuItems(List)}
+     * wasn't called), nothing will happen the second time, even if the MenuItems were changed.
+     *
+     * <p>The XML file must have one <MenuItems> tag, with a variable number of <MenuItem>
      * child tags. See CarUiToolbarMenuItem in CarUi's attrs.xml for a list of available attributes.
      *
      * Example:
@@ -662,8 +709,13 @@
      * @return The MenuItems that were loaded from XML.
      */
     public List<MenuItem> setMenuItems(@XmlRes int resId) {
+        if (mMenuItemsXmlId != 0 && mMenuItemsXmlId == resId) {
+            return mMenuItems;
+        }
+
+        mMenuItemsXmlId = resId;
         List<MenuItem> menuItems = MenuItemRenderer.readMenuItemList(getContext(), resId);
-        setMenuItems(menuItems);
+        setMenuItemsInternal(menuItems);
         return menuItems;
     }
 
@@ -748,7 +800,16 @@
      * Sets the search query.
      */
     public void setSearchQuery(String query) {
-        mSearchView.setSearchQuery(query);
+        if (!Objects.equals(mSearchQuery, query)) {
+            mSearchQuery = query;
+            if (mSearchView != null) {
+                mSearchView.setSearchQuery(query);
+            } else {
+                for (OnSearchListener listener : mOnSearchListeners) {
+                    listener.onSearch(query);
+                }
+            }
+        }
     }
 
     /**
@@ -758,6 +819,23 @@
     public void setState(State state) {
         mState = state;
 
+        if (mSearchView == null && (state == State.SEARCH || state == State.EDIT)) {
+            SearchView searchView = new SearchView(getContext());
+            searchView.setHint(mSearchHint);
+            searchView.setIcon(mSearchIcon);
+            searchView.setSearchListeners(mOnSearchListeners);
+            searchView.setSearchCompletedListeners(mOnSearchCompletedListeners);
+            searchView.setSearchQuery(mSearchQuery);
+            searchView.setVisibility(View.GONE);
+
+            FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT);
+            mSearchViewContainer.addView(searchView, layoutParams);
+
+            mSearchView = searchView;
+        }
+
         for (MenuItemRenderer renderer : mMenuItemRenderers) {
             renderer.setToolbarState(mState);
         }
@@ -820,8 +898,14 @@
                 ? VISIBLE : GONE);
         mTabLayout.setVisibility(state == State.HOME && hasTabs ? VISIBLE : GONE);
 
-        mSearchView.setVisibility(state == State.SEARCH || state == State.EDIT ? VISIBLE : GONE);
-        mSearchView.setPlainText(state == State.EDIT);
+        if (mSearchView != null) {
+            if (state == State.SEARCH || state == State.EDIT) {
+                mSearchView.setPlainText(state == State.EDIT);
+                mSearchView.setVisibility(VISIBLE);
+            } else {
+                mSearchView.setVisibility(GONE);
+            }
+        }
 
         boolean showButtons = (state != State.SEARCH && state != State.EDIT)
                 || mShowMenuItemsWhileSearching;
@@ -918,22 +1002,22 @@
 
     /** Registers a new {@link OnSearchListener} to the list of listeners. */
     public void registerOnSearchListener(OnSearchListener listener) {
-        mSearchView.registerOnSearchListener(listener);
+        mOnSearchListeners.add(listener);
     }
 
     /** Unregisters an existing {@link OnSearchListener} from the list of listeners. */
     public boolean unregisterOnSearchListener(OnSearchListener listener) {
-        return mSearchView.unregisterOnSearchListener(listener);
+        return mOnSearchListeners.remove(listener);
     }
 
     /** Registers a new {@link OnSearchCompletedListener} to the list of listeners. */
     public void registerOnSearchCompletedListener(OnSearchCompletedListener listener) {
-        mSearchView.registerOnSearchCompletedListener(listener);
+        mOnSearchCompletedListeners.add(listener);
     }
 
     /** Unregisters an existing {@link OnSearchCompletedListener} from the list of listeners. */
     public boolean unregisterOnSearchCompletedListener(OnSearchCompletedListener listener) {
-        return mSearchView.unregisterOnSearchCompletedListener(listener);
+        return mOnSearchCompletedListeners.remove(listener);
     }
 
     /** Registers a new {@link OnBackListener} to the list of listeners. */
diff --git a/car-ui-lib/tests/apitest/current.xml b/car-ui-lib/tests/apitest/current.xml
index 21c0f66..c428165 100644
--- a/car-ui-lib/tests/apitest/current.xml
+++ b/car-ui-lib/tests/apitest/current.xml
@@ -215,7 +215,6 @@
   <public type="style" name="Preference.CarUi.SwitchPreference"/>
   <public type="style" name="PreferenceFragment.CarUi"/>
   <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"/>