Defer loading SearchView until needed

And:
- Don't load Search/Settings assets on all MenuItems
- Don't restartInput() every time the keyboard is shown.

Bug: 146228321
Test: Manually
Change-Id: Iff1ce8f22677a9026dfa8fe821dd2b74b8be4fbf
diff --git a/car-ui-lib/res/layout/car_ui_toolbar.xml b/car-ui-lib/res/layout/car_ui_toolbar.xml
index 2d54431..dc5e997 100644
--- a/car-ui-lib/res/layout/car_ui_toolbar.xml
+++ b/car-ui-lib/res/layout/car_ui_toolbar.xml
@@ -135,11 +135,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..64f3ece 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
@@ -128,15 +128,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/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/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..d4b1f5d 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,6 +49,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -144,11 +145,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;
@@ -224,7 +235,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 +522,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 +547,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 +558,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);
+            }
+        }
     }
 
     /**
@@ -748,7 +769,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 +788,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 +867,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 +971,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. */