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

This commit is contained in:
TreeHugger Robot
2017-05-02 16:21:10 +00:00
committed by Android (Google) Code Review
5 changed files with 140 additions and 23 deletions

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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
}
}

View File

@@ -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,22 +102,19 @@ 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) {
if (view instanceof TextView) {
TextView textView = (TextView) view;
int selectionStart = textView.getSelectionStart();
int selectionEnd = textView.getSelectionEnd();
try {
assertThat(selectionStart, index);
assertThat(selectionEnd, index);
} catch (IndexOutOfBoundsException e) {
throw new AssertionFailedError(e.getMessage());
}
} else {
throw new AssertionFailedError("TextView not found");
return (view, exception) -> {
if (view instanceof TextView) {
TextView textView = (TextView) view;
int selectionStart = textView.getSelectionStart();
int selectionEnd = textView.getSelectionEnd();
try {
assertThat(selectionStart, index);
assertThat(selectionEnd, index);
} catch (IndexOutOfBoundsException e) {
throw new AssertionFailedError(e.getMessage());
}
} 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}.
*/