Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc9ba87f authored by Abodunrinwa Toki's avatar Abodunrinwa Toki Committed by Android (Google) Code Review
Browse files

Merge "Ensure the toolbar refreshes its PendingIntents when it changes."

parents e2fb6a64 c132a228
Loading
Loading
Loading
Loading
+125 −34
Original line number Diff line number Diff line
@@ -61,12 +61,17 @@ import android.widget.PopupWindow;
import android.widget.TextView;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
@@ -93,7 +98,6 @@ public final class FloatingToolbar {
    private final Rect mPreviousContentRect = new Rect();

    private Menu mMenu;
    private List<MenuItem> mShowingMenuItems = new ArrayList<>();
    private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER;

    private int mSuggestedWidth;
@@ -274,10 +278,11 @@ public final class FloatingToolbar {
    private void doShow() {
        List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
        menuItems.sort(mMenuItemComparator);
        if (!isCurrentlyShowing(menuItems) || mWidthChanged) {
        if (mPopup.isLayoutRequired(menuItems) || mWidthChanged) {
            mPopup.dismiss();
            mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
            mShowingMenuItems = menuItems;
        } else {
            mPopup.updateMenuItems(menuItems, mMenuItemClickListener);
        }
        if (!mPopup.isShowing()) {
            mPopup.show(mContentRect);
@@ -288,34 +293,11 @@ public final class FloatingToolbar {
        mPreviousContentRect.set(mContentRect);
    }

    /**
     * Returns true if this floating toolbar is currently showing the specified menu items.
     */
    private boolean isCurrentlyShowing(List<MenuItem> menuItems) {
        if (mShowingMenuItems == null || menuItems.size() != mShowingMenuItems.size()) {
            return false;
        }

        final int size = menuItems.size();
        for (int i = 0; i < size; i++) {
            final MenuItem menuItem = menuItems.get(i);
            final MenuItem showingItem = mShowingMenuItems.get(i);
            if (menuItem.getItemId() != showingItem.getItemId()
                    || !TextUtils.equals(menuItem.getTitle(), showingItem.getTitle())
                    || !Objects.equals(menuItem.getIcon(), showingItem.getIcon())
                    || menuItem.getGroupId() != showingItem.getGroupId()) {
                return false;
            }
        }

        return true;
    }

    /**
     * Returns the visible and enabled menu items in the specified menu.
     * This method is recursive.
     */
    private List<MenuItem> getVisibleAndEnabledMenuItems(Menu menu) {
    private static List<MenuItem> getVisibleAndEnabledMenuItems(Menu menu) {
        List<MenuItem> menuItems = new ArrayList<>();
        for (int i = 0; (menu != null) && (i < menu.size()); i++) {
            MenuItem menuItem = menu.getItem(i);
@@ -427,17 +409,25 @@ public final class FloatingToolbar {
        private Size mOverflowPanelSize;  // Should be null when there is no overflow.
        private Size mMainPanelSize;

        /* Item click listeners */
        /* Menu items and click listeners */
        private final Map<MenuItemRepr, MenuItem> mMenuItems = new LinkedHashMap<>();
        private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
        private final View.OnClickListener mMenuItemButtonOnClickListener =
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (v.getTag() instanceof MenuItem) {
                            if (mOnMenuItemClickListener != null) {
                                mOnMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
                        if (mOnMenuItemClickListener == null) {
                            return;
                        }
                        final Object tag = v.getTag();
                        if (!(tag instanceof MenuItemRepr)) {
                            return;
                        }
                        final MenuItem menuItem = mMenuItems.get((MenuItemRepr) tag);
                        if (menuItem == null) {
                            return;
                        }
                        mOnMenuItemClickListener.onMenuItemClick(menuItem);
                    }
                };

@@ -558,9 +548,9 @@ public final class FloatingToolbar {
                List<MenuItem> menuItems,
                MenuItem.OnMenuItemClickListener menuItemClickListener,
                int suggestedWidth) {
            mOnMenuItemClickListener = menuItemClickListener;
            cancelOverflowAnimations();
            clearPanels();
            updateMenuItems(menuItems, menuItemClickListener);
            menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth));
            if (!menuItems.isEmpty()) {
                // Add remaining items to the overflow.
@@ -569,6 +559,28 @@ public final class FloatingToolbar {
            updatePopupSize();
        }

        /**
         * Updates the popup's menu items without rebuilding the widget.
         * Use in place of layoutMenuItems() when the popup's views need not be reconstructed.
         *
         * @see isLayoutRequired(List<MenuItem>)
         */
        public void updateMenuItems(
                List<MenuItem> menuItems, MenuItem.OnMenuItemClickListener menuItemClickListener) {
            mMenuItems.clear();
            for (MenuItem menuItem : menuItems) {
                mMenuItems.put(MenuItemRepr.of(menuItem), menuItem);
            }
            mOnMenuItemClickListener = menuItemClickListener;
        }

        /**
         * Returns true if this popup needs a relayout to properly render the specified menu items.
         */
        public boolean isLayoutRequired(List<MenuItem> menuItems) {
            return !MenuItemRepr.reprEquals(menuItems, mMenuItems.values());
        }

        /**
         * Shows this popup at the specified coordinates.
         * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
@@ -1374,7 +1386,7 @@ public final class FloatingToolbar {
        }

        private void setButtonTagAndClickListener(View menuItemButton, MenuItem menuItem) {
            menuItemButton.setTag(menuItem);
            menuItemButton.setTag(MenuItemRepr.of(menuItem));
            menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
        }

@@ -1655,6 +1667,85 @@ public final class FloatingToolbar {
        }
    }

    /**
     * Represents the identity of a MenuItem that is rendered in a FloatingToolbarPopup.
     */
    @VisibleForTesting
    public static final class MenuItemRepr {

        public final int itemId;
        public final int groupId;
        @Nullable public final String title;
        @Nullable private final Drawable mIcon;

        private MenuItemRepr(
                int itemId, int groupId, @Nullable CharSequence title, @Nullable Drawable icon) {
            this.itemId = itemId;
            this.groupId = groupId;
            this.title = (title == null) ? null : title.toString();
            mIcon = icon;
        }

        /**
         * Creates an instance of MenuItemRepr for the specified menu item.
         */
        public static MenuItemRepr of(MenuItem menuItem) {
            return new MenuItemRepr(
                    menuItem.getItemId(),
                    menuItem.getGroupId(),
                    menuItem.getTitle(),
                    menuItem.getIcon());
        }

        /**
         * Returns this object's hashcode.
         */
        @Override
        public int hashCode() {
            return Objects.hash(itemId, groupId, title, mIcon);
        }

        /**
         * Returns true if this object is the same as the specified object.
         */
        @Override
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            }
            if (!(o instanceof MenuItemRepr)) {
                return false;
            }
            final MenuItemRepr other = (MenuItemRepr) o;
            return itemId == other.itemId
                    && groupId == other.groupId
                    && TextUtils.equals(title, other.title)
                    // Many Drawables (icons) do not implement equals(). Using equals() here instead
                    // of reference comparisons in case a Drawable subclass implements equals().
                    && Objects.equals(mIcon, other.mIcon);
        }

        /**
         * Returns true if the two menu item collections are the same based on MenuItemRepr.
         */
        public static boolean reprEquals(
                Collection<MenuItem> menuItems1, Collection<MenuItem> menuItems2) {
            if (menuItems1.size() != menuItems2.size()) {
                return false;
            }

            final Iterator<MenuItem> menuItems2Iter = menuItems2.iterator();
            for (MenuItem menuItem1 : menuItems1) {
                final MenuItem menuItem2 = menuItems2Iter.next();
                if (!MenuItemRepr.of(menuItem1).equals(MenuItemRepr.of(menuItem2))) {
                    return false;
                }
            }

            return true;
        }
    }

    /**
     * Creates and returns a menu button for the specified menu item.
     */
+50 −0
Original line number Diff line number Diff line
@@ -375,6 +375,56 @@ public class TextViewActivityTest {
        assertFloatingToolbarIsDisplayed();
    }

    @Test
    public void testToolbarMenuItemClickAfterSelectionChange() throws Throwable {
        final MenuItem[] latestItem = new MenuItem[1];
        final MenuItem[] clickedItem = new MenuItem[1];
        final String text = "abcd efg hijk";
        mActivityRule.runOnUiThread(() -> {
            final TextView textView = mActivity.findViewById(R.id.textview);
            textView.setText(text);
            textView.setCustomSelectionActionModeCallback(
                    new ActionMode.Callback() {
                        @Override
                        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                            menu.clear();
                            latestItem[0] = menu.add("Item");
                            return true;
                        }

                        @Override
                        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                            clickedItem[0] = item;
                            return true;
                        }

                        @Override
                        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                            return true;
                        }

                        @Override
                        public void onDestroyActionMode(ActionMode mode) {}
                    });
        });
        mInstrumentation.waitForIdleSync();

        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f")));
        sleepForFloatingToolbarPopup();

        // Change the selection so that the menu items are refreshed.
        final TextView textView = mActivity.findViewById(R.id.textview);
        onHandleView(com.android.internal.R.id.selection_start_handle)
                .perform(dragHandle(textView, Handle.SELECTION_START, 0));
        sleepForFloatingToolbarPopup();
        assertFloatingToolbarIsDisplayed();

        clickFloatingToolbarItem("Item");
        mInstrumentation.waitForIdleSync();

        assertEquals(latestItem[0], clickedItem[0]);
    }

    @Test
    public void testSelectionRemovedWhenNonselectableTextLosesFocus() throws Throwable {
        final TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
+8 −7
Original line number Diff line number Diff line
@@ -28,10 +28,11 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withTagValue;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import static com.android.internal.widget.FloatingToolbar.MenuItemRepr;

import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;

import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

@@ -158,8 +159,8 @@ public class FloatingToolbarEspressoUtils {
            public void describeTo(Description description) {}

            private void collectMenuItemIds(View view) {
                if (view.getTag() instanceof MenuItem) {
                    menuItemIds.add(((MenuItem) view.getTag()).getItemId());
                if (view.getTag() instanceof MenuItemRepr) {
                    menuItemIds.add(((MenuItemRepr) view.getTag()).itemId);
                } else if (view instanceof ViewGroup) {
                    ViewGroup viewGroup = (ViewGroup) view;
                    for (int i = 0; i < viewGroup.getChildCount(); i++) {
@@ -178,8 +179,8 @@ public class FloatingToolbarEspressoUtils {
     */
    public static void assertFloatingToolbarDoesNotContainItem(String itemLabel) {
        final Predicate<View> hasMenuItemLabel = view ->
                view.getTag() instanceof MenuItem
                        && itemLabel.equals(((MenuItem) view.getTag()).getTitle().toString());
                view.getTag() instanceof MenuItemRepr
                        && itemLabel.equals(((MenuItemRepr) view.getTag()).title);
        assertFloatingToolbarMenuItem(hasMenuItemLabel, false);
    }

@@ -191,8 +192,8 @@ public class FloatingToolbarEspressoUtils {
     */
    public static void assertFloatingToolbarDoesNotContainItem(final int menuItemId) {
        final Predicate<View> hasMenuItemId = view ->
                view.getTag() instanceof MenuItem
                        && ((MenuItem) view.getTag()).getItemId() == menuItemId;
                view.getTag() instanceof MenuItemRepr
                        && ((MenuItemRepr) view.getTag()).itemId == menuItemId;
        assertFloatingToolbarMenuItem(hasMenuItemId, false);
    }