From 4d5c48d9880d6bb0d5c2d55a145f435d725ebbdc Mon Sep 17 00:00:00 2001 From: Oli Lan Date: Thu, 4 Feb 2021 18:06:16 +0000 Subject: [PATCH] Add ClipDescription method to determine if the clip is styled text. This adds a new method to ClipDescription to allow callers to determine if the associated ClipData is styled text, without accessing the ClipData directly. This information is used by editors such as TextView or WebView when they show a selection toolbar, in order to determine if a "Paste as plain text" option should be shown. Currently, ClipData must be accessed in order to do this, so a clipboard access occurs whenever text is selected. Avoiding these clipboard accesses is desirable because a user-visible message may in future be shown whenever ClipData is accessed (see b/167676460), which may cause user confusion. Bug: 167660455 Test: new tests added to ClipDescriptionTest in CTS Change-Id: I48bea4d11a68b198d60016b364a3cfa0b0bf277e --- core/api/current.txt | 1 + core/java/android/content/ClipData.java | 19 +++++++++++++++ .../java/android/content/ClipDescription.java | 23 +++++++++++++++++++ core/java/android/widget/TextView.java | 14 ++++------- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index f82e00035e65e..51fb13a68c329 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9815,6 +9815,7 @@ package android.content { method public int getMimeTypeCount(); method public long getTimestamp(); method public boolean hasMimeType(String); + method public boolean isStyledText(); method public void setExtras(android.os.PersistableBundle); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index 0188637741848..f3ecbf6c08f34 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -740,6 +740,7 @@ public class ClipData implements Parcelable { mIcon = null; mItems = new ArrayList(); mItems.add(item); + mClipDescription.setIsStyledText(isStyledText()); } /** @@ -756,6 +757,7 @@ public class ClipData implements Parcelable { mIcon = null; mItems = new ArrayList(); mItems.add(item); + mClipDescription.setIsStyledText(isStyledText()); } /** @@ -914,6 +916,9 @@ public class ClipData implements Parcelable { throw new NullPointerException("item is null"); } mItems.add(item); + if (mItems.size() == 1) { + mClipDescription.setIsStyledText(isStyledText()); + } } /** @@ -1049,6 +1054,20 @@ public class ClipData implements Parcelable { } } + private boolean isStyledText() { + if (mItems.isEmpty()) { + return false; + } + final CharSequence text = mItems.get(0).getText(); + if (text instanceof Spanned) { + Spanned spanned = (Spanned) text; + if (TextUtils.hasStyleSpan(spanned)) { + return true; + } + } + return false; + } + @Override public String toString() { StringBuilder b = new StringBuilder(128); diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java index e3395e20947d1..d48f83223d3e2 100644 --- a/core/java/android/content/ClipDescription.java +++ b/core/java/android/content/ClipDescription.java @@ -120,6 +120,7 @@ public class ClipDescription implements Parcelable { private final ArrayList mMimeTypes; private PersistableBundle mExtras; private long mTimeStamp; + private boolean mIsStyledText; /** * Create a new clip. @@ -325,6 +326,26 @@ public class ClipDescription implements Parcelable { } } + /** + * Returns true if the first item of the associated {@link ClipData} contains styled text, i.e. + * if it contains spans such as {@link android.text.style.CharacterStyle CharacterStyle}, {@link + * android.text.style.ParagraphStyle ParagraphStyle}, or {@link + * android.text.style.UpdateAppearance UpdateAppearance}. Returns false if it does not, or if + * there is no associated clip data. + */ + public boolean isStyledText() { + return mIsStyledText; + } + + /** + * Sets whether the associated {@link ClipData} contains styled text in its first item. This + * should be called when this description is associated with clip data or when the first item + * is added to the associated clip data. + */ + void setIsStyledText(boolean isStyledText) { + mIsStyledText = isStyledText; + } + @Override public String toString() { StringBuilder b = new StringBuilder(128); @@ -429,6 +450,7 @@ public class ClipDescription implements Parcelable { dest.writeStringList(mMimeTypes); dest.writePersistableBundle(mExtras); dest.writeLong(mTimeStamp); + dest.writeBoolean(mIsStyledText); } ClipDescription(Parcel in) { @@ -436,6 +458,7 @@ public class ClipDescription implements Parcelable { mMimeTypes = in.createStringArrayList(); mExtras = in.readPersistableBundle(); mTimeStamp = in.readLong(); + mIsStyledText = in.readBoolean(); } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index fe37c53505119..0f2089a5463f7 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -12940,17 +12940,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - final ClipData clipData = getClipboardManagerForUser().getPrimaryClip(); - final ClipDescription description = clipData.getDescription(); + final ClipDescription description = + getClipboardManagerForUser().getPrimaryClipDescription(); 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); + return (isPlainType && description.isStyledText()) + || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); } boolean canProcessText() {