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

Commit 0fe5a2d5 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add "Paste as plain text" in TextView's toolbar." into oc-dev

parents 550e1c9a ea6cb121
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.LeadingMarginSpan;
import android.text.style.LocaleSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.ParagraphStyle;
import android.text.style.QuoteSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.ReplacementSpan;
@@ -56,6 +57,7 @@ import android.text.style.TtsSpan;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.text.style.UpdateAppearance;
import android.util.Log;
import android.util.Printer;
import android.view.View;
@@ -1903,6 +1905,22 @@ public class TextUtils {
        return Resources.getSystem().getQuantityString(R.plurals.selected_count, count, count);
    }

    /**
     * Returns whether or not the specified spanned text has a style span.
     * @hide
     */
    public static boolean hasStyleSpan(@NonNull Spanned spanned) {
        Preconditions.checkArgument(spanned != null);
        final Class<?>[] styleClasses = {
                CharacterStyle.class, ParagraphStyle.class, UpdateAppearance.class};
        for (Class<?> clazz : styleClasses) {
            if (spanned.nextSpanTransition(-1, spanned.length(), clazz) < spanned.length()) {
                return true;
            }
        }
        return false;
    }

    private static Object sLock = new Object();

    private static char[] sTemp = null;
+16 −7
Original line number Diff line number Diff line
@@ -154,10 +154,10 @@ public class Editor {
    private static final int MENU_ITEM_ORDER_COPY = 5;
    private static final int MENU_ITEM_ORDER_PASTE = 6;
    private static final int MENU_ITEM_ORDER_SHARE = 7;
    private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 8;
    private static final int MENU_ITEM_ORDER_SELECT_ALL = 9;
    private static final int MENU_ITEM_ORDER_REPLACE = 10;
    private static final int MENU_ITEM_ORDER_AUTOFILL = 11;
    private static final int MENU_ITEM_ORDER_SELECT_ALL = 8;
    private static final int MENU_ITEM_ORDER_REPLACE = 9;
    private static final int MENU_ITEM_ORDER_AUTOFILL = 10;
    private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
    private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;

    // Each Editor manages its own undo stack.
@@ -2634,9 +2634,9 @@ public class Editor {
                .setAlphabeticShortcut('v')
                .setEnabled(mTextView.canPaste())
                .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
        menu.add(Menu.NONE, TextView.ID_PASTE, MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
        menu.add(Menu.NONE, TextView.ID_PASTE_AS_PLAIN_TEXT, MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
                com.android.internal.R.string.paste_as_plain_text)
                .setEnabled(mTextView.canPaste())
                .setEnabled(mTextView.canPasteAsPlainText())
                .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
        menu.add(Menu.NONE, TextView.ID_SHARE, MENU_ITEM_ORDER_SHARE,
                com.android.internal.R.string.share)
@@ -3775,7 +3775,6 @@ public class Editor {
            mode.setSubtitle(null);
            mode.setTitleOptionalHint(true);
            populateMenuWithItems(menu);
            updateAssistMenuItem(menu);

            Callback customCallback = getCustomCallback();
            if (customCallback != null) {
@@ -3843,8 +3842,18 @@ public class Editor {
                        .setShowAsAction(mode);
            }

            if (mTextView.canPasteAsPlainText()) {
                menu.add(
                        Menu.NONE,
                        TextView.ID_PASTE_AS_PLAIN_TEXT,
                        MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
                        com.android.internal.R.string.paste_as_plain_text)
                        .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
            }

            updateSelectAllItem(menu);
            updateReplaceItem(menu);
            updateAssistMenuItem(menu);
        }

        @Override
+21 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.annotation.XmlRes;
import android.app.Activity;
import android.app.assist.AssistStructure;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
@@ -11042,6 +11043,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                        .hasPrimaryClip());
    }

    boolean canPasteAsPlainText() {
        if (!canPaste()) {
            return false;
        }

        final ClipData clipData =
                ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
                        .getPrimaryClip();
        final ClipDescription description = clipData.getDescription();
        final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
        final CharSequence text = clipData.getItemAt(0).getText();
        if (isPlainType && (text instanceof Spanned)) {
            Spanned spanned = (Spanned) text;
            if (TextUtils.hasStyleSpan(spanned)) {
                return true;
            }
        }
        return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
    }

    boolean canProcessText() {
        if (getId() == View.NO_ID) {
            return false;
+58 −1
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static android.widget.espresso.TextViewActions.longPressAndDragOnText;
import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
import static android.widget.espresso.TextViewAssertions.hasSelection;
import static android.widget.espresso.TextViewAssertions.doesNotHaveStyledText;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsNotDisplayed;
@@ -47,9 +48,16 @@ import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.is;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.text.TextUtils;
import android.text.Spanned;
import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.ViewAssertion;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.widget.espresso.CustomViewActions.RelativeCoordinatesProvider;
@@ -64,6 +72,8 @@ import android.view.KeyEvent;

import com.android.frameworks.coretests.R;

import junit.framework.AssertionFailedError;

/**
 * Tests the TextView widget from an Activity
 */
@@ -708,7 +718,8 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV
                    }

                    @Override
                    public void onDestroyActionMode(ActionMode actionMode) {}
                    public void onDestroyActionMode(ActionMode actionMode) {
                    }
                }));
        final String text = "droid@android.com";

@@ -717,4 +728,50 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV
        sleepForFloatingToolbarPopup();
        assertFloatingToolbarItemIndex(android.R.id.textAssist, 0);
    }

    public void testPastePlainText_menuAction() throws Exception {
        initializeClipboardWithText(TextStyle.STYLED);

        onView(withId(R.id.textview)).perform(replaceText(""));
        onView(withId(R.id.textview)).perform(longClick());
        sleepForFloatingToolbarPopup();
        clickFloatingToolbarItem(
                getActivity().getString(com.android.internal.R.string.paste_as_plain_text));
        getInstrumentation().waitForIdleSync();

        onView(withId(R.id.textview)).check(matches(withText("styledtext")));
        onView(withId(R.id.textview)).check(doesNotHaveStyledText());
    }

    public void testPastePlainText_noMenuItemForPlainText() {
        initializeClipboardWithText(TextStyle.PLAIN);

        onView(withId(R.id.textview)).perform(replaceText(""));
        onView(withId(R.id.textview)).perform(longClick());
        sleepForFloatingToolbarPopup();

        assertFloatingToolbarDoesNotContainItem(
                getActivity().getString(com.android.internal.R.string.paste_as_plain_text));
    }

    private void initializeClipboardWithText(TextStyle textStyle) {
        final ClipData clip;
        switch (textStyle) {
            case STYLED:
                clip = ClipData.newHtmlText("html", "styledtext", "<b>styledtext</b>");
                break;
            case PLAIN:
                clip = ClipData.newPlainText("plain", "plaintext");
                break;
            default:
                throw new IllegalArgumentException("Invalid text style");
        }
        getActivity().getWindow().getDecorView().post(() ->
            getActivity().getSystemService(ClipboardManager.class).setPrimaryClip( clip));
        getInstrumentation().waitForIdleSync();
    }

    private enum TextStyle {
        PLAIN, STYLED
    }
}
+27 −15
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.ViewAssertion;
import android.text.Spanned;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
@@ -100,9 +102,7 @@ public final class TextViewAssertions {
     * @param index  A matcher representing the expected index.
     */
    public static ViewAssertion hasInsertionPointerAtIndex(final Matcher<Integer> index) {
        return new ViewAssertion() {
            @Override
            public void check(View view, NoMatchingViewException exception) {
        return (view, exception) -> {
            if (view instanceof TextView) {
                TextView textView = (TextView) view;
                int selectionStart = textView.getSelectionStart();
@@ -116,7 +116,6 @@ public final class TextViewAssertions {
            } else {
                throw new AssertionFailedError("TextView not found");
            }
            }
        };
    }

@@ -136,6 +135,19 @@ public final class TextViewAssertions {
        return new CursorPositionAssertion(CursorPositionAssertion.RIGHT);
    }

    /**
     * Returns a {@link ViewAssertion} that asserts that the TextView does not contain styled text.
     */
    public static ViewAssertion doesNotHaveStyledText() {
        return (view, exception) -> {
            final CharSequence text = ((TextView) view).getText();
            if (text instanceof Spanned && !TextUtils.hasStyleSpan((Spanned) text)) {
                return;
            }
            throw new AssertionFailedError("TextView has styled text");
        };
    }

    /**
     * A {@link ViewAssertion} to check the selected text in a {@link TextView}.
     */