From 2e853aa13965b8b79d3bc76b326223bc24585c73 Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Tue, 17 Nov 2020 17:23:00 +0000 Subject: [PATCH] Introduce TextSelection.Builder.setIncludeTextClassification and TextSelection.getTextClassification() This small API change enables us to optimize the latnecy of smart selection. To enable smart selection, TextView/WebView always call textClassifier.suggestSelection and textClassifier.classifyText back to back. There are a lot of overlaps between suggestSelection and classifyText, e.g. both API calls classify the entity type of the text. This optimization is important to make smart selection working responsively, especially that we enforce a 200ms timeout in TextView. This small API change allows the TextClassifier to implement smart selection with just one single API call. This change is backward-compatible. If the text classifier is not updated to return the text classification result in suggestSelection yet, clients can learn that by checking if textSelection.getTextClassification() returns null. If so, the client can then fallback to call ClassifyText(). You may find the textview integration in ag/13092679 Design doc: go/android-s-suggestselection Bug: 173512834 Test: atest TextSelectionTest Change-Id: If4cc827aa19d6d14c885cf10cd5ba16aa3041cf6 --- core/api/current.txt | 4 ++ .../view/textclassifier/TextSelection.java | 71 ++++++++++++++++++- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index b61bc2fde0e9b..2e913b5542844 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -56539,6 +56539,7 @@ package android.view.textclassifier { method @Nullable public String getId(); method public int getSelectionEndIndex(); method public int getSelectionStartIndex(); + method @Nullable public android.view.textclassifier.TextClassification getTextClassification(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } @@ -56549,6 +56550,7 @@ package android.view.textclassifier { method @NonNull public android.view.textclassifier.TextSelection.Builder setEntityType(@NonNull String, @FloatRange(from=0.0, to=1.0) float); method @NonNull public android.view.textclassifier.TextSelection.Builder setExtras(@Nullable android.os.Bundle); method @NonNull public android.view.textclassifier.TextSelection.Builder setId(@Nullable String); + method @NonNull public android.view.textclassifier.TextSelection.Builder setTextClassification(@Nullable android.view.textclassifier.TextClassification); } public static final class TextSelection.Request implements android.os.Parcelable { @@ -56559,6 +56561,7 @@ package android.view.textclassifier { method @NonNull public android.os.Bundle getExtras(); method @IntRange(from=0) public int getStartIndex(); method @NonNull public CharSequence getText(); + method public boolean shouldIncludeTextClassification(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } @@ -56568,6 +56571,7 @@ package android.view.textclassifier { method @NonNull public android.view.textclassifier.TextSelection.Request build(); method @NonNull public android.view.textclassifier.TextSelection.Request.Builder setDefaultLocales(@Nullable android.os.LocaleList); method @NonNull public android.view.textclassifier.TextSelection.Request.Builder setExtras(@Nullable android.os.Bundle); + method @NonNull public android.view.textclassifier.TextSelection.Request.Builder setIncludeTextClassification(boolean); } } diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java index d8a632d10bc3d..5b32649ac55bf 100644 --- a/core/java/android/view/textclassifier/TextSelection.java +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -45,15 +45,19 @@ public final class TextSelection implements Parcelable { private final int mEndIndex; private final EntityConfidence mEntityConfidence; @Nullable private final String mId; + @Nullable + private final TextClassification mTextClassification; private final Bundle mExtras; private TextSelection( int startIndex, int endIndex, Map entityConfidence, String id, + @Nullable TextClassification textClassification, Bundle extras) { mStartIndex = startIndex; mEndIndex = endIndex; mEntityConfidence = new EntityConfidence(entityConfidence); mId = id; + mTextClassification = textClassification; mExtras = extras; } @@ -110,6 +114,19 @@ public final class TextSelection implements Parcelable { return mId; } + /** + * Returns the text classification result of the suggested selection span. Enables the text + * classification by calling + * {@link TextSelection.Request.Builder#setIncludeTextClassification(boolean)}. If the text + * classifier does not support it, a {@code null} is returned. + * + * @see TextSelection.Request.Builder#setIncludeTextClassification(boolean) + */ + @Nullable + public TextClassification getTextClassification() { + return mTextClassification; + } + /** * Returns the extended data. * @@ -138,6 +155,8 @@ public final class TextSelection implements Parcelable { private final Map mEntityConfidence = new ArrayMap<>(); @Nullable private String mId; @Nullable + private TextClassification mTextClassification; + @Nullable private Bundle mExtras; /** @@ -178,6 +197,21 @@ public final class TextSelection implements Parcelable { return this; } + /** + * Sets the text classification result of the suggested selection. If + * {@link Request#shouldIncludeTextClassification()} is {@code true}, set this value. + * Otherwise this value may be set to null. The intention of this method is to avoid + * doing expensive work if the client is not interested in such result. + * + * @return this builder + * @see Request#shouldIncludeTextClassification() + */ + @NonNull + public Builder setTextClassification(@Nullable TextClassification textClassification) { + mTextClassification = textClassification; + return this; + } + /** * Sets the extended data. * @@ -196,7 +230,7 @@ public final class TextSelection implements Parcelable { public TextSelection build() { return new TextSelection( mStartIndex, mEndIndex, mEntityConfidence, mId, - mExtras == null ? Bundle.EMPTY : mExtras); + mTextClassification, mExtras == null ? Bundle.EMPTY : mExtras); } } @@ -210,6 +244,7 @@ public final class TextSelection implements Parcelable { private final int mEndIndex; @Nullable private final LocaleList mDefaultLocales; private final boolean mDarkLaunchAllowed; + private final boolean mIncludeTextClassification; private final Bundle mExtras; @Nullable private SystemTextClassifierMetadata mSystemTcMetadata; @@ -219,12 +254,14 @@ public final class TextSelection implements Parcelable { int endIndex, LocaleList defaultLocales, boolean darkLaunchAllowed, + boolean includeTextClassification, Bundle extras) { mText = text; mStartIndex = startIndex; mEndIndex = endIndex; mDefaultLocales = defaultLocales; mDarkLaunchAllowed = darkLaunchAllowed; + mIncludeTextClassification = includeTextClassification; mExtras = extras; } @@ -302,6 +339,14 @@ public final class TextSelection implements Parcelable { return mSystemTcMetadata; } + /** + * Returns true if the client wants the text classifier to classify the text as well and + * include a {@link TextClassification} object in the result. + */ + public boolean shouldIncludeTextClassification() { + return mIncludeTextClassification; + } + /** * Returns the extended data. * @@ -320,9 +365,9 @@ public final class TextSelection implements Parcelable { private final CharSequence mText; private final int mStartIndex; private final int mEndIndex; - @Nullable private LocaleList mDefaultLocales; private boolean mDarkLaunchAllowed; + private boolean mIncludeTextClassification; private Bundle mExtras; /** @@ -371,6 +416,21 @@ public final class TextSelection implements Parcelable { return this; } + /** + * @param includeTextClassification If true, suggests the TextClassifier to classify the + * text in the suggested selection span and include a TextClassification object in + * the result. The TextClassifier may not support this and in which case, + * {@link TextSelection#getTextClassification()} returns {@code null}. + * + * @return this builder. + * @see TextSelection#getTextClassification() + */ + @NonNull + public Builder setIncludeTextClassification(boolean includeTextClassification) { + mIncludeTextClassification = includeTextClassification; + return this; + } + /** * Sets the extended data. * @@ -389,6 +449,7 @@ public final class TextSelection implements Parcelable { public Request build() { return new Request(new SpannedString(mText), mStartIndex, mEndIndex, mDefaultLocales, mDarkLaunchAllowed, + mIncludeTextClassification, mExtras == null ? Bundle.EMPTY : mExtras); } } @@ -406,6 +467,7 @@ public final class TextSelection implements Parcelable { dest.writeParcelable(mDefaultLocales, flags); dest.writeBundle(mExtras); dest.writeParcelable(mSystemTcMetadata, flags); + dest.writeBoolean(mIncludeTextClassification); } private static Request readFromParcel(Parcel in) { @@ -415,9 +477,10 @@ public final class TextSelection implements Parcelable { final LocaleList defaultLocales = in.readParcelable(null); final Bundle extras = in.readBundle(); final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null); + final boolean includeTextClassification = in.readBoolean(); final Request request = new Request(text, startIndex, endIndex, defaultLocales, - /* darkLaunchAllowed= */ false, extras); + /* darkLaunchAllowed= */ false, includeTextClassification, extras); request.setSystemTextClassifierMetadata(systemTcMetadata); return request; } @@ -448,6 +511,7 @@ public final class TextSelection implements Parcelable { mEntityConfidence.writeToParcel(dest, flags); dest.writeString(mId); dest.writeBundle(mExtras); + dest.writeParcelable(mTextClassification, flags); } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = @@ -469,5 +533,6 @@ public final class TextSelection implements Parcelable { mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); mId = in.readString(); mExtras = in.readBundle(); + mTextClassification = in.readParcelable(TextClassification.class.getClassLoader()); } }