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

Commit 5ad4308c authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Hide TextView context menu items when unavailable" into main

parents 00bff4f6 b4b98e34
Loading
Loading
Loading
Loading
+130 −56
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID;

import static com.android.graphics.hwui.flags.Flags.highContrastTextSmallTextRect;
import static com.android.text.flags.Flags.contextMenuHideUnavailableItems;

import android.R;
import android.animation.ValueAnimator;
@@ -3250,6 +3251,78 @@ public class Editor {
        final int menuItemOrderShare = 9;
        final int menuItemOrderAutofill = 10;

        if (contextMenuHideUnavailableItems()) {
            if (mTextView.canUndo()) {
                menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo,
                                com.android.internal.R.string.undo)
                        .setAlphabeticShortcut('z')
                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
                        .setIcon(a.getDrawable(0));
            }

            if (mTextView.canRedo()) {
                menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, menuItemOrderRedo,
                                com.android.internal.R.string.redo)
                        .setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
                        .setIcon(a.getDrawable(1));
            }

            if (mTextView.canCut()) {
                menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, menuItemOrderCut,
                                com.android.internal.R.string.cut)
                        .setAlphabeticShortcut('x')
                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
                        .setIcon(a.getDrawable(2));
            }

            if (mTextView.canCopy()) {
                menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, menuItemOrderCopy,
                                com.android.internal.R.string.copy)
                        .setAlphabeticShortcut('c')
                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
                        .setIcon(a.getDrawable(3));
            }

            if (mTextView.canPaste()) {
                menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, menuItemOrderPaste,
                                com.android.internal.R.string.paste)
                        .setAlphabeticShortcut('v')
                        .setIcon(a.getDrawable(4))
                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
            }

            if (mTextView.canPasteAsPlainText()) {
                menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT,
                                menuItemOrderPasteAsPlainText,
                                com.android.internal.R.string.paste_as_plain_text)
                        .setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
                        .setIcon(a.getDrawable(4))
                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
            }

            if (mTextView.canSelectAllText()) {
                menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL,
                                menuItemOrderSelectAll, com.android.internal.R.string.selectAll)
                        .setAlphabeticShortcut('a')
                        .setIcon(a.getDrawable(5))
                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
            }

            if (mTextView.canShare()) {
                menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, menuItemOrderShare,
                                com.android.internal.R.string.share)
                        .setIcon(a.getDrawable(6))
                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
            }

            final String selected = mTextView.getSelectedText();
            if (mTextView.canRequestAutofill() && (selected == null || selected.isEmpty())) {
                menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill,
                                android.R.string.autofill)
                        .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
            }
        } else {
            menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo,
                            com.android.internal.R.string.undo)
                    .setAlphabeticShortcut('z')
@@ -3306,6 +3379,7 @@ public class Editor {
                    .setEnabled(mTextView.canRequestAutofill()
                            && (selected == null || selected.isEmpty()))
                    .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
        }
        a.recycle();
    }

+24 −8
Original line number Diff line number Diff line
@@ -15552,15 +15552,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        }
    }
    boolean canUndo() {
    /** @hide */
    @VisibleForTesting
    public boolean canUndo() {
        return mEditor != null && mEditor.canUndo();
    }
    boolean canRedo() {
    /** @hide */
    @VisibleForTesting
    public boolean canRedo() {
        return mEditor != null && mEditor.canRedo();
    }
    boolean canCut() {
    /** @hide */
    @VisibleForTesting
    public boolean canCut() {
        if (hasPasswordTransformationMethod()) {
            return false;
        }
@@ -15573,7 +15579,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return false;
    }
    boolean canCopy() {
    /** @hide */
    @VisibleForTesting
    public boolean canCopy() {
        if (hasPasswordTransformationMethod()) {
            return false;
        }
@@ -15594,7 +15602,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions();
    }
    boolean canShare() {
    /** @hide */
    @VisibleForTesting
    public boolean canShare() {
        if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()
                || !getContext().getResources().getBoolean(
                com.android.internal.R.bool.config_textShareSupported)) {
@@ -15613,8 +15623,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
    }
    /** @hide */
    @VisibleForTesting
    @UnsupportedAppUsage
    boolean canPaste() {
    public boolean canPaste() {
        return (mText instanceof Editable
                && mEditor != null && mEditor.mKeyListener != null
                && getSelectionStart() >= 0
@@ -15622,7 +15634,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                && getClipboardManagerForUser().hasPrimaryClip());
    }
    boolean canPasteAsPlainText() {
    /** @hide */
    @VisibleForTesting
    public boolean canPasteAsPlainText() {
        if (!canPaste()) {
            return false;
        }
@@ -15644,7 +15658,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return canShare();
    }
    boolean canSelectAllText() {
    /** @hide */
    @VisibleForTesting
    public boolean canSelectAllText() {
        return canSelectText() && !hasPasswordTransformationMethod()
                && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
    }
+156 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -40,6 +41,9 @@ import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.textclassifier.TextClassification;
@@ -47,9 +51,12 @@ import android.view.textclassifier.TextClassification;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.verification.VerificationMode;

/**
 * TextViewTest tests {@link TextView}.
@@ -86,6 +93,10 @@ public class TextViewContextMenuTest {

    private SelectionActionModeHelper mMockHelper;

    @ClassRule public static final SetFlagsRule.ClassRule SET_FLAGS_CLASS_RULE =
            new SetFlagsRule.ClassRule();
    @Rule public final SetFlagsRule mSetFlagsRule = SET_FLAGS_CLASS_RULE.createSetFlagsRule();

    @Before
    public void setUp() {
        mMockHelper = mock(SelectionActionModeHelper.class);
@@ -234,6 +245,7 @@ public class TextViewContextMenuTest {
    }

    @Test
    @DisableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testAutofillMenuItemEnabledWhenNoTextSelected() {
        ContextMenu menu = mock(ContextMenu.class);
        MenuItem mockMenuItem = newMockMenuItem();
@@ -254,6 +266,7 @@ public class TextViewContextMenuTest {
    }

    @Test
    @DisableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testAutofillMenuItemNotEnabledWhenTextSelected() {
        ContextMenu menu = mock(ContextMenu.class);
        MenuItem mockMenuItem = newMockMenuItem();
@@ -271,4 +284,147 @@ public class TextViewContextMenuTest {
        verify(menu).add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt());
        verify(mockAutofillMenuItem).setEnabled(false);
    }

    private interface EditTextSetup {
        void run(EditText et);
    }

    private void verifyMenuItemNotAdded(EditTextSetup setup, int id, VerificationMode times) {
        ContextMenu menu = mock(ContextMenu.class);
        MenuItem mockMenuItem = newMockMenuItem();
        when(menu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mockMenuItem);
        EditText et = spy(new EditText(getInstrumentation().getContext()));
        setup.run(et);
        Editor editor = new Editor(et);
        editor.setTextContextMenuItems(menu);
        verify(menu, times).add(anyInt(), eq(id), anyInt(), anyInt());
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuUndoNotAddedWhenUnavailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canUndo(),
                TextView.ID_UNDO, never());
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuUndoAddedWhenAvailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canUndo(), TextView.ID_UNDO,
                times(1));
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuRedoNotAddedWhenUnavailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canRedo(), TextView.ID_REDO,
                never());
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuRedoAddedWhenUnavailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canRedo(), TextView.ID_REDO,
                times(1));
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuCutNotAddedWhenUnavailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canCut(), TextView.ID_CUT,
                never());
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuCutAddedWhenAvailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canCut(), TextView.ID_CUT,
                times(1));
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuCopyNotAddedWhenUnavailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canCopy(), TextView.ID_COPY,
                never());
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuCopyAddedWhenAvailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canCopy(), TextView.ID_COPY,
                times(1));
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuPasteNotAddedWhenUnavailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canPaste(), TextView.ID_PASTE,
                never());
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuPasteAddedWhenAvailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canPaste(), TextView.ID_PASTE,
                times(1));
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuPasteAsPlaintextNotAddedWhenUnavailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canPasteAsPlainText(),
                        TextView.ID_PASTE_AS_PLAIN_TEXT, never());
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuPasteAsPlaintextAddedWhenAvailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canPasteAsPlainText(),
                        TextView.ID_PASTE_AS_PLAIN_TEXT, times(1));
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuSelectAllNotAddedWhenUnavailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canSelectAllText(),
                        TextView.ID_SELECT_ALL, never());
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuSelectAllAddedWhenAvailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canSelectAllText(),
                        TextView.ID_SELECT_ALL, times(1));
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuShareNotAddedWhenUnavailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canShare(), TextView.ID_SHARE,
                never());
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuShareAddedWhenAvailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(true).when(spy).canShare(), TextView.ID_SHARE,
                times(1));
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuAutofillNotAddedWhenUnavailable() {
        verifyMenuItemNotAdded((spy) -> doReturn(false).when(spy).canRequestAutofill(),
                TextView.ID_AUTOFILL, never());
    }

    @Test
    @EnableFlags(com.android.text.flags.Flags.FLAG_CONTEXT_MENU_HIDE_UNAVAILABLE_ITEMS)
    public void testContextMenuAutofillNotAddedWhenUnavailableBecauseTextSelected() {
        verifyMenuItemNotAdded((spy) -> {
            doReturn(true).when(spy).canRequestAutofill();
            doReturn("test").when(spy).getSelectedText();
        }, TextView.ID_AUTOFILL, never());
    }
}