Add MenuItem.setVisible(boolean)

It's a very common operation, but I had worked around it by calling
setMenuItems() repeatedly before. Now it's being requested for MediaCenter.

Fixes: 141574504
Test: Manually
Change-Id: Id58a041f8305932dcfea0bac0f6d87aaf3292f41
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 7a57e36..4d0c1da 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
@@ -67,6 +67,7 @@
     private DisplayBehavior mDisplayBehavior;
     private boolean mIsEnabled;
     private boolean mIsChecked;
+    private boolean mIsVisible;
     private View mView;
 
 
@@ -80,6 +81,7 @@
         mDisplayBehavior = builder.mDisplayBehavior;
         mIsEnabled = builder.mIsEnabled;
         mIsChecked = builder.mIsChecked;
+        mIsVisible = builder.mIsVisible;
         mId = builder.mId;
     }
 
@@ -129,6 +131,21 @@
         }
     }
 
+    /** Returns whether or not the MenuItem is visible */
+    public boolean isVisible() {
+        return mIsVisible;
+    }
+
+    /** Sets whether or not the MenuItem is visible */
+    public void setVisible(boolean visible) {
+        mIsVisible = visible;
+
+        // The toolbar will change the visibility of the view
+        if (mListener != null) {
+            mListener.onMenuItemChanged(this);
+        }
+    }
+
     /** Gets the title of this MenuItem. */
     public CharSequence getTitle() {
         return mTitle;
@@ -146,7 +163,7 @@
         }
 
         if (mListener != null) {
-            mListener.onMenuItemTitleChanged(this, mTitle);
+            mListener.onMenuItemChanged(this);
         }
     }
 
@@ -211,6 +228,7 @@
         private boolean mIsEnabled = true;
         private boolean mIsCheckable = false;
         private boolean mIsChecked = false;
+        private boolean mIsVisible = true;
         private int mCustomLayoutId;
         private int mId;
 
@@ -241,6 +259,12 @@
             return this;
         }
 
+        /** Sets whether the MenuItem is visible or not. Default true. */
+        public Builder setVisible(boolean visible) {
+            mIsVisible = visible;
+            return this;
+        }
+
         /**
          * Sets a custom layout to use for this MenuItem.
          *
@@ -367,8 +391,8 @@
 
     /** Listener for {@link Toolbar} to update when this MenuItem changes */
     interface Listener {
-        /** Called when the MenuItem's title is changed. For use only by {@link Toolbar} */
-        void onMenuItemTitleChanged(MenuItem item, CharSequence title);
+        /** Called when the MenuItem is changed. For use only by {@link Toolbar} */
+        void onMenuItemChanged(MenuItem item);
     }
 
     void setListener(Listener listener) {
@@ -404,6 +428,10 @@
             mView.setId(getId());
         }
 
+        if (!mIsVisible) {
+            mView.setVisibility(View.GONE);
+        }
+
         recursiveSetEnabled(mView, isEnabled());
         mView.setOnClickListener(mViewOnClickListener);
         return mView;
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 1659f36..e656aa0 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
@@ -134,16 +134,27 @@
     private SearchView mSearchView;
     private boolean mHasLogo = false;
     private boolean mShowMenuItemsWhileSearching;
-    private View mSearchButton;
+    private MenuItem mSearchMenuItem;
     private State mState = State.HOME;
     private NavButtonMode mNavButtonMode = NavButtonMode.BACK;
     @NonNull
     private List<MenuItem> mMenuItems = Collections.emptyList();
     private List<MenuItem> mOverflowItems = new ArrayList<>();
-    private MenuItem.Listener mMenuItemListener = (item, title) -> {
+    private MenuItem.Listener mMenuItemListener = (item) -> {
         if (item.getDisplayBehavior() == MenuItem.DisplayBehavior.NEVER) {
             createOverflowDialog();
+        } else {
+            View view = item.getView();
+            if (view != null) {
+                if (item.getId() == R.id.search) {
+                    view.setVisibility(mState != State.SEARCH && item.isVisible() ? VISIBLE : GONE);
+                } else {
+                    view.setVisibility(item.isVisible() ? VISIBLE : GONE);
+                }
+            }
         }
+
+        setState(getState());
     };
     private AlertDialog mOverflowDialog;
 
@@ -488,6 +499,7 @@
 
         mOverflowItems.clear();
         mMenuItemsContainer.removeAllViews();
+        mSearchMenuItem = null;
 
         for (MenuItem item : items) {
             item.setListener(mMenuItemListener);
@@ -498,13 +510,15 @@
 
                 // Add views with index 0 so that they are added right-to-left
                 mMenuItemsContainer.addView(menuItemView, 0);
+
+                if (item.getId() == R.id.search) {
+                    mSearchMenuItem = item;
+                }
             }
         }
 
         createOverflowDialog();
 
-        mSearchButton = mMenuItemsContainer.findViewById(R.id.search);
-
         setState(mState);
     }
 
@@ -514,13 +528,26 @@
         return mMenuItems;
     }
 
+    private int countVisibleOverflowItems() {
+        int numVisibleItems = 0;
+        for (MenuItem item : mOverflowItems) {
+            if (item.isVisible()) {
+                numVisibleItems++;
+            }
+        }
+        return numVisibleItems;
+    }
+
     private void createOverflowDialog() {
         // TODO(b/140564530) Use a carui alert with a (paged)recyclerview here
         // TODO(b/140563930) Support enabled/disabled overflow items
 
-        CharSequence[] itemTitles = new CharSequence[mOverflowItems.size()];
-        for (int i = 0; i < mOverflowItems.size(); i++) {
-            itemTitles[i] = mOverflowItems.get(i).getTitle();
+        CharSequence[] itemTitles = new CharSequence[countVisibleOverflowItems()];
+        int i = 0;
+        for (MenuItem item : mOverflowItems) {
+            if (item.isVisible()) {
+                itemTitles[i++] = item.getTitle();
+            }
         }
 
         mOverflowDialog = new AlertDialog.Builder(getContext())
@@ -622,9 +649,14 @@
         mSearchView.setVisibility(state == State.SEARCH ? VISIBLE : GONE);
         boolean showButtons = state != State.SEARCH || mShowMenuItemsWhileSearching;
         mMenuItemsContainer.setVisibility(showButtons ? VISIBLE : GONE);
-        mOverflowButton.setVisibility(showButtons && mOverflowItems.size() > 0 ? VISIBLE : GONE);
-        if (mSearchButton != null) {
-            mSearchButton.setVisibility(state != State.SEARCH ? VISIBLE : GONE);
+        mOverflowButton.setVisibility(showButtons && countVisibleOverflowItems() > 0
+                ? VISIBLE : GONE);
+        if (mSearchMenuItem != null) {
+            View searchView = mSearchMenuItem.getView();
+            if (searchView != null) {
+                searchView.setVisibility(mState != State.SEARCH && mSearchMenuItem.isVisible()
+                        ? VISIBLE : GONE);
+            }
         }
         mCustomViewContainer.setVisibility(state == State.SUBPAGE_CUSTOM ? VISIBLE : GONE);
         if (state != State.SUBPAGE_CUSTOM) {
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 a39dc89..531c3a8 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
@@ -16,11 +16,14 @@
 package com.android.car.ui.paintbooth.toolbar;
 
 import android.app.Activity;
+import android.app.AlertDialog;
 import android.os.Bundle;
+import android.text.InputType;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
+import android.widget.EditText;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
@@ -70,6 +73,39 @@
             toolbar.setMenuItems(mMenuItems);
         }));
 
+        Mutable<Integer> overflowCounter = new Mutable<>(1);
+        mButtons.add(Pair.create("Add overflow menu item", v -> {
+            mMenuItems.add(new MenuItem.Builder(this)
+                    .setTitle("Foo " + overflowCounter.value)
+                    .setOnClickListener(i ->
+                            Toast.makeText(this, "Clicked", Toast.LENGTH_SHORT).show())
+                    .setDisplayBehavior(MenuItem.DisplayBehavior.NEVER)
+                    .build());
+            toolbar.setMenuItems(mMenuItems);
+            overflowCounter.value++;
+        }));
+
+        mButtons.add(Pair.create("Toggle menu item visibility", v -> {
+            EditText textBox = new EditText(this);
+            textBox.setInputType(InputType.TYPE_CLASS_NUMBER);
+            new AlertDialog.Builder(this)
+                    .setView(textBox)
+                    .setTitle("Enter the index of the MenuItem to toggle")
+                    .setPositiveButton("Ok", ((dialog, which) -> {
+                        try {
+                            MenuItem item = mMenuItems.get(
+                                    Integer.parseInt(textBox.getText().toString()));
+                            item.setVisible(!item.isVisible());
+                        } catch (NumberFormatException | IndexOutOfBoundsException e) {
+                            Toast.makeText(this, "Invalid index \""
+                                    + textBox.getText().toString()
+                                    + "\", valid range is 0 to " + (mMenuItems.size() - 1),
+                                    Toast.LENGTH_LONG).show();
+                        }
+                    }))
+                    .show();
+        }));
+
         mButtons.add(Pair.create("Toggle nav button mode", v -> {
             if (toolbar.getNavButtonMode() == Toolbar.NavButtonMode.BACK) {
                 toolbar.setNavButtonMode(Toolbar.NavButtonMode.CLOSE);
@@ -143,4 +179,17 @@
             ((ViewHolder) holder).bind(pair.first, pair.second);
         }
     };
+
+    /** For changing values from lambdas */
+    public static final class Mutable<E> {
+        public E value;
+
+        public Mutable() {
+            value = null;
+        }
+
+        public Mutable(E value) {
+            this.value = value;
+        }
+    }
 }