Merge "Add "Paste as plain text" in TextView's toolbar." into oc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
0fe5a2d5b3
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user