Save toolbar state

Fixes: 141370968
Test: Manually
Change-Id: I0a982f675188b22bfd3c2e81485b848d380dab5f
diff --git a/car-ui-lib/res/values-port/config.xml b/car-ui-lib/res/values-port/config.xml
new file mode 100644
index 0000000..bb74d4c
--- /dev/null
+++ b/car-ui-lib/res/values-port/config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <!-- Whether or not the toolbar title and tabs can be shown at the same time -->
+    <bool name="car_ui_toolbar_title_and_tabs_are_mutually_exclusive">false</bool>
+</resources>
diff --git a/car-ui-lib/res/values/config.xml b/car-ui-lib/res/values/config.xml
index 7dc9c0e..047434a 100644
--- a/car-ui-lib/res/values/config.xml
+++ b/car-ui-lib/res/values/config.xml
@@ -44,4 +44,7 @@
     <!-- Width of the scrollbar container. -->
     <dimen name="car_ui_scrollbar_container_width">@dimen/car_ui_margin</dimen>
 
-</resources>
\ No newline at end of file
+    <!-- Whether or not the toolbar title and tabs can be shown at the same time -->
+    <bool name="car_ui_toolbar_title_and_tabs_are_mutually_exclusive">true</bool>
+
+</resources>
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 2e534f7..ef29354 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
@@ -130,6 +130,11 @@
         mSearchText.setHint(hint);
     }
 
+    /** Gets the search hint */
+    public CharSequence getHint() {
+        return mSearchText.getHint();
+    }
+
     /**
      * Sets a custom icon to display in the search box.
      */
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 ef3f4a8..1659f36 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
@@ -21,6 +21,8 @@
 import android.content.ContextWrapper;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -116,6 +118,8 @@
         SEARCH,
     }
 
+    private final boolean mTitleAndTabsAreMutuallyExclusive;
+
     private ImageView mNavIcon;
     private ImageView mLogo;
     private ViewGroup mNavIconContainer;
@@ -178,6 +182,9 @@
                 attrs, R.styleable.CarUiToolbar, defStyleAttr, defStyleRes);
 
         try {
+            mTitleAndTabsAreMutuallyExclusive = context.getResources().getBoolean(
+                    R.bool.car_ui_toolbar_title_and_tabs_are_mutually_exclusive);
+
             mTitle.setText(a.getString(R.styleable.CarUiToolbar_title));
             setLogo(a.getResourceId(R.styleable.CarUiToolbar_logo, 0));
             setBackgroundShown(a.getBoolean(R.styleable.CarUiToolbar_showBackground, true));
@@ -247,6 +254,81 @@
         handleToolbarHeightChangeListeners(getHeight());
     }
 
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState);
+        ss.mTitle = getTitle();
+        ss.mNavButtonMode = getNavButtonMode();
+        ss.mSearchHint = getSearchHint();
+        ss.mBackgroundShown = getBackgroundShown();
+        ss.mShowMenuItemsWhileSearching = getShowMenuItemsWhileSearching();
+        ss.mState = getState();
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (!(state instanceof SavedState)) {
+            Log.w(TAG, "onRestoreInstanceState called with an unsupported state");
+            super.onRestoreInstanceState(state);
+        } else {
+            SavedState ss = (SavedState) state;
+            super.onRestoreInstanceState(ss.getSuperState());
+            setTitle(ss.mTitle);
+            setNavButtonMode(ss.mNavButtonMode);
+            setSearchHint(ss.mSearchHint);
+            setBackgroundShown(ss.mBackgroundShown);
+            setShowMenuItemsWhileSearching(ss.mShowMenuItemsWhileSearching);
+            setState(ss.mState);
+        }
+    }
+
+    private static class SavedState extends BaseSavedState {
+        private CharSequence mTitle;
+        private State mState;
+        private NavButtonMode mNavButtonMode;
+        private CharSequence mSearchHint;
+        private boolean mBackgroundShown;
+        private boolean mShowMenuItemsWhileSearching;
+
+        SavedState(Parcelable in) {
+            super(in);
+        }
+
+        SavedState(Parcel in) {
+            super(in);
+            mTitle = in.readCharSequence();
+            mNavButtonMode = NavButtonMode.valueOf(in.readString());
+            mSearchHint = in.readCharSequence();
+            mBackgroundShown = in.readBoolean();
+            mShowMenuItemsWhileSearching = in.readBoolean();
+            mState = State.valueOf(in.readString());
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeCharSequence(mTitle);
+            out.writeString(mNavButtonMode.name());
+            out.writeCharSequence(mSearchHint);
+            out.writeBoolean(mBackgroundShown);
+            out.writeBoolean(mShowMenuItemsWhileSearching);
+            out.writeString(mState.name());
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
     /**
      * Sets the title of the toolbar to a string resource.
      *
@@ -265,6 +347,10 @@
         mTitle.setText(title);
     }
 
+    public CharSequence getTitle() {
+        return mTitle.getText();
+    }
+
     /**
      * Gets the {@link TabLayout} for this toolbar.
      */
@@ -278,6 +364,7 @@
      */
     public void addTab(TabLayout.Tab tab) {
         mTabLayout.addTab(tab);
+        setState(getState());
     }
 
     /**
@@ -310,20 +397,21 @@
         setState(mState);
     }
 
-    /**
-     * Sets the hint for the search bar.
-     */
+    /** Sets the hint for the search bar. */
     public void setSearchHint(int resId) {
         mSearchView.setHint(resId);
     }
 
-    /**
-     * Sets the hint for the search bar.
-     */
+    /** Sets the hint for the search bar. */
     public void setSearchHint(CharSequence hint) {
         mSearchView.setHint(hint);
     }
 
+    /** Gets the search hint */
+    public CharSequence getSearchHint() {
+        return mSearchView.getHint();
+    }
+
     /**
      * An enum of possible styles the nav button could be in. All styles will still call
      * {@link OnBackListener#onBack()}.
@@ -368,9 +456,7 @@
         }
     }
 
-    /**
-     * Show/hide the background. When hidden, the toolbar is completely transparent.
-     */
+    /** Show/hide the background. When hidden, the toolbar is completely transparent. */
     public void setBackgroundShown(boolean shown) {
         if (shown) {
             super.setBackground(getContext().getDrawable(R.color.car_ui_toolbar_background_color));
@@ -379,6 +465,11 @@
         }
     }
 
+    /** Returns true is the toolbar background is shown */
+    public boolean getBackgroundShown() {
+        return super.getBackground() != null;
+    }
+
     /**
      * Sets the {@link MenuItem Menuitems} to display.
      */
@@ -391,7 +482,9 @@
             return;
         }
 
-        mMenuItems = items;
+        // Copy the list so that if the list is modified and setMenuItems is called again,
+        // the equals() check will fail. Note that the MenuItems are not copied here.
+        mMenuItems = new ArrayList<>(items);
 
         mOverflowItems.clear();
         mMenuItemsContainer.removeAllViews();
@@ -452,6 +545,11 @@
         setState(mState);
     }
 
+    /** Returns if {@link MenuItem MenuItems} are shown while searching */
+    public boolean getShowMenuItemsWhileSearching() {
+        return mShowMenuItemsWhileSearching;
+    }
+
     /**
      * Sets the search query.
      */
@@ -507,6 +605,7 @@
                 }
             }
         };
+
         mNavIcon.setVisibility(state != State.HOME ? VISIBLE : INVISIBLE);
         mNavIcon.setImageResource(mNavButtonMode == NavButtonMode.BACK
                 ? R.drawable.car_ui_icon_arrow_back
@@ -515,8 +614,11 @@
         mNavIconContainer.setVisibility(state != State.HOME || mHasLogo ? VISIBLE : GONE);
         mNavIconContainer.setOnClickListener(state != State.HOME ? backClickListener : null);
         mNavIconContainer.setClickable(state != State.HOME);
-        mTitle.setVisibility(state == State.HOME || state == State.SUBPAGE ? VISIBLE : GONE);
-        mTabLayout.setVisibility(state == State.HOME ? VISIBLE : GONE);
+        boolean hasTabs = mTabLayout.getTabCount() > 0;
+        boolean showTitle = state == State.SUBPAGE || state == State.HOME
+                && (!mTitleAndTabsAreMutuallyExclusive || !hasTabs);
+        mTitle.setVisibility(showTitle ? VISIBLE : GONE);
+        mTabLayout.setVisibility(state == State.HOME && hasTabs ? VISIBLE : GONE);
         mSearchView.setVisibility(state == State.SEARCH ? VISIBLE : GONE);
         boolean showButtons = state != State.SEARCH || mShowMenuItemsWhileSearching;
         mMenuItemsContainer.setVisibility(showButtons ? VISIBLE : GONE);
diff --git a/car-ui-lib/tests/paintbooth/AndroidManifest.xml b/car-ui-lib/tests/paintbooth/AndroidManifest.xml
index 136cb5a..8b3cce7 100644
--- a/car-ui-lib/tests/paintbooth/AndroidManifest.xml
+++ b/car-ui-lib/tests/paintbooth/AndroidManifest.xml
@@ -51,5 +51,9 @@
         android:name=".preferences.PreferenceActivity"
         android:exported="false"
         android:parentActivityName=".MainActivity"/>
+    <activity
+        android:name=".toolbar.ToolbarActivity"
+        android:exported="false"
+        android:parentActivityName=".MainActivity"/>
   </application>
 </manifest>
diff --git a/car-ui-lib/tests/paintbooth/res/layout/grid_paged_recycler_view_activity.xml b/car-ui-lib/tests/paintbooth/res/layout/grid_paged_recycler_view_activity.xml
index 2e88b79..7c75f39 100644
--- a/car-ui-lib/tests/paintbooth/res/layout/grid_paged_recycler_view_activity.xml
+++ b/car-ui-lib/tests/paintbooth/res/layout/grid_paged_recycler_view_activity.xml
@@ -9,7 +9,8 @@
       android:id="@+id/toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
-      app:title="@string/app_name"/>
+      app:title="@string/app_name"
+      app:state="subpage"/>
 
   <com.android.car.ui.pagedrecyclerview.PagedRecyclerView
       android:id="@+id/grid_list"
@@ -17,4 +18,4 @@
       app:numOfColumns="4"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/car-ui-lib/tests/paintbooth/res/layout/paged_recycler_view_activity.xml b/car-ui-lib/tests/paintbooth/res/layout/paged_recycler_view_activity.xml
index 2c8b1c1..526cd0a 100644
--- a/car-ui-lib/tests/paintbooth/res/layout/paged_recycler_view_activity.xml
+++ b/car-ui-lib/tests/paintbooth/res/layout/paged_recycler_view_activity.xml
@@ -27,6 +27,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         app:title="@string/app_name"
+        app:logo="@drawable/ic_launcher"
         app:state="subpage"/>
 
     <com.android.car.ui.pagedrecyclerview.PagedRecyclerView
diff --git a/car-ui-lib/tests/paintbooth/res/layout/toolbar_custom_view.xml b/car-ui-lib/tests/paintbooth/res/layout/toolbar_custom_view.xml
new file mode 100644
index 0000000..67b584a
--- /dev/null
+++ b/car-ui-lib/tests/paintbooth/res/layout/toolbar_custom_view.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/text_box_1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Text Box 1"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/image_view"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+
+    <ImageView
+        android:id="@+id/image_view"
+        android:src="@drawable/ic_launcher"
+        android:layout_width="20dp"
+        android:layout_height="20dp"
+        app:layout_constraintStart_toEndOf="@id/text_box_1"
+        app:layout_constraintEnd_toStartOf="@id/text_box_2"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+
+    <TextView
+        android:id="@+id/text_box_2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Text Box 2"
+        app:layout_constraintStart_toEndOf="@id/image_view"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>
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 3c47f0b..ba7d1ec 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
@@ -33,6 +33,7 @@
 import com.android.car.ui.paintbooth.pagedrecyclerview.GridPagedRecyclerViewActivity;
 import com.android.car.ui.paintbooth.pagedrecyclerview.PagedRecyclerViewActivity;
 import com.android.car.ui.paintbooth.preferences.PreferenceActivity;
+import com.android.car.ui.paintbooth.toolbar.ToolbarActivity;
 
 import java.util.Arrays;
 import java.util.List;
@@ -48,7 +49,8 @@
             Pair.create("Dialogs sample", DialogsActivity.class),
             Pair.create("List sample", PagedRecyclerViewActivity.class),
             Pair.create("Grid sample", GridPagedRecyclerViewActivity.class),
-            Pair.create("Preferences sample", PreferenceActivity.class)
+            Pair.create("Preferences sample", PreferenceActivity.class),
+            Pair.create("Toolbar sample", ToolbarActivity.class)
     );
 
     private class ViewHolder extends RecyclerView.ViewHolder {
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
new file mode 100644
index 0000000..a39dc89
--- /dev/null
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.ui.paintbooth.toolbar;
+
+import android.app.Activity;
+import android.os.Bundle;
+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.core.util.Pair;
+
+import com.android.car.ui.pagedrecyclerview.PagedRecyclerView;
+import com.android.car.ui.paintbooth.R;
+import com.android.car.ui.toolbar.MenuItem;
+import com.android.car.ui.toolbar.TabLayout;
+import com.android.car.ui.toolbar.Toolbar;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ToolbarActivity extends Activity {
+
+    private List<MenuItem> mMenuItems = new ArrayList<>();
+    private List<Pair<CharSequence, View.OnClickListener>> mButtons = new ArrayList<>();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.paged_recycler_view_activity);
+
+        Toolbar toolbar = requireViewById(R.id.toolbar);
+        toolbar.registerOnBackListener(() -> {
+            if (toolbar.getState() == Toolbar.State.SEARCH) {
+                toolbar.setState(Toolbar.State.SUBPAGE);
+                return true;
+            }
+            return false;
+        });
+
+        toolbar.getRootView().setBackgroundColor(0xFFFFFF00);
+
+        mMenuItems.add(MenuItem.Builder.createSearch(this, i ->
+                toolbar.setState(Toolbar.State.SEARCH)));
+
+        toolbar.setMenuItems(mMenuItems);
+
+        mButtons.add(Pair.create("Change title", v ->
+                toolbar.setTitle(toolbar.getTitle() + " X")));
+
+        mButtons.add(Pair.create("Add menu item", v -> {
+            mMenuItems.add(MenuItem.Builder.createSettings(this, i ->
+                    Toast.makeText(this, "Clicked", Toast.LENGTH_SHORT).show()));
+            toolbar.setMenuItems(mMenuItems);
+        }));
+
+        mButtons.add(Pair.create("Toggle nav button mode", v -> {
+            if (toolbar.getNavButtonMode() == Toolbar.NavButtonMode.BACK) {
+                toolbar.setNavButtonMode(Toolbar.NavButtonMode.CLOSE);
+            } else {
+                toolbar.setNavButtonMode(Toolbar.NavButtonMode.BACK);
+            }
+        }));
+
+        mButtons.add(Pair.create("Toggle state", v -> {
+            if (toolbar.getState() == Toolbar.State.SUBPAGE) {
+                toolbar.setState(Toolbar.State.HOME);
+            } else {
+                toolbar.setState(Toolbar.State.SUBPAGE);
+            }
+        }));
+
+        mButtons.add(Pair.create("Toggle search hint", v -> {
+            if (toolbar.getSearchHint().equals("Foo")) {
+                toolbar.setSearchHint("Bar");
+            } else {
+                toolbar.setSearchHint("Foo");
+            }
+        }));
+
+        mButtons.add(Pair.create("Toggle background", v ->
+                toolbar.setBackgroundShown(!toolbar.getBackgroundShown())));
+
+        mButtons.add(Pair.create("Toggle show menu items while searching", v ->
+                toolbar.setShowMenuItemsWhileSearching(!toolbar.getShowMenuItemsWhileSearching())));
+
+        mButtons.add(Pair.create("Show custom view", v ->
+                toolbar.setCustomView(R.layout.toolbar_custom_view)));
+
+        mButtons.add(Pair.create("Add tab", v ->
+                toolbar.addTab(new TabLayout.Tab(getDrawable(R.drawable.ic_launcher), "Foo"))));
+
+        PagedRecyclerView prv = requireViewById(R.id.list);
+        prv.setAdapter(mAdapter);
+    }
+
+    private static class ViewHolder extends PagedRecyclerView.ViewHolder {
+        private final Button mButton;
+
+        ViewHolder(View itemView) {
+            super(itemView);
+            mButton = itemView.requireViewById(R.id.button);
+        }
+
+        public void bind(CharSequence title, View.OnClickListener listener) {
+            mButton.setText(title);
+            mButton.setOnClickListener(listener);
+        }
+    }
+
+    private PagedRecyclerView.Adapter mAdapter = new PagedRecyclerView.Adapter() {
+
+        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 PagedRecyclerView.ViewHolder holder, int position) {
+            Pair<CharSequence, View.OnClickListener> pair = mButtons.get(position);
+            ((ViewHolder) holder).bind(pair.first, pair.second);
+        }
+    };
+}