Merge "Implement Stateful TextClassifier APIs." into pi-dev am: 119d10deb9
am: 12dea84530
Change-Id: I248b3ffff26747f3cb95b6caf44bfcbe8dae10a3
This commit is contained in:
@@ -50317,38 +50317,13 @@ package android.view.inputmethod {
|
||||
|
||||
package android.view.textclassifier {
|
||||
|
||||
public abstract class Logger {
|
||||
ctor public Logger(android.view.textclassifier.Logger.Config);
|
||||
method public java.text.BreakIterator getTokenIterator(java.util.Locale);
|
||||
method public boolean isSmartSelection(java.lang.String);
|
||||
method public final void logSelectionActionEvent(int, int, int);
|
||||
method public final void logSelectionActionEvent(int, int, int, android.view.textclassifier.TextClassification);
|
||||
method public final void logSelectionModifiedEvent(int, int);
|
||||
method public final void logSelectionModifiedEvent(int, int, android.view.textclassifier.TextClassification);
|
||||
method public final void logSelectionModifiedEvent(int, int, android.view.textclassifier.TextSelection);
|
||||
method public final void logSelectionStartedEvent(int, int);
|
||||
method public abstract void writeEvent(android.view.textclassifier.SelectionEvent);
|
||||
field public static final int OUT_OF_BOUNDS = 2147483647; // 0x7fffffff
|
||||
field public static final int OUT_OF_BOUNDS_NEGATIVE = -2147483648; // 0x80000000
|
||||
field public static final java.lang.String WIDGET_CUSTOM_EDITTEXT = "customedit";
|
||||
field public static final java.lang.String WIDGET_CUSTOM_TEXTVIEW = "customview";
|
||||
field public static final java.lang.String WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
|
||||
field public static final java.lang.String WIDGET_EDITTEXT = "edittext";
|
||||
field public static final java.lang.String WIDGET_EDIT_WEBVIEW = "edit-webview";
|
||||
field public static final java.lang.String WIDGET_TEXTVIEW = "textview";
|
||||
field public static final java.lang.String WIDGET_UNKNOWN = "unknown";
|
||||
field public static final java.lang.String WIDGET_UNSELECTABLE_TEXTVIEW = "nosel-textview";
|
||||
field public static final java.lang.String WIDGET_WEBVIEW = "webview";
|
||||
}
|
||||
|
||||
public static final class Logger.Config {
|
||||
ctor public Logger.Config(android.content.Context, java.lang.String, java.lang.String);
|
||||
method public java.lang.String getPackageName();
|
||||
method public java.lang.String getWidgetType();
|
||||
method public java.lang.String getWidgetVersion();
|
||||
}
|
||||
|
||||
public final class SelectionEvent implements android.os.Parcelable {
|
||||
method public static android.view.textclassifier.SelectionEvent createSelectionActionEvent(int, int, int);
|
||||
method public static android.view.textclassifier.SelectionEvent createSelectionActionEvent(int, int, int, android.view.textclassifier.TextClassification);
|
||||
method public static android.view.textclassifier.SelectionEvent createSelectionModifiedEvent(int, int);
|
||||
method public static android.view.textclassifier.SelectionEvent createSelectionModifiedEvent(int, int, android.view.textclassifier.TextClassification);
|
||||
method public static android.view.textclassifier.SelectionEvent createSelectionModifiedEvent(int, int, android.view.textclassifier.TextSelection);
|
||||
method public static android.view.textclassifier.SelectionEvent createSelectionStartedEvent(int, int);
|
||||
method public int describeContents();
|
||||
method public long getDurationSincePreviousEvent();
|
||||
method public long getDurationSinceSessionStart();
|
||||
@@ -50359,13 +50334,14 @@ package android.view.textclassifier {
|
||||
method public int getEventType();
|
||||
method public int getInvocationMethod();
|
||||
method public java.lang.String getPackageName();
|
||||
method public java.lang.String getSessionId();
|
||||
method public android.view.textclassifier.TextClassificationSessionId getSessionId();
|
||||
method public java.lang.String getSignature();
|
||||
method public int getSmartEnd();
|
||||
method public int getSmartStart();
|
||||
method public int getStart();
|
||||
method public java.lang.String getWidgetType();
|
||||
method public java.lang.String getWidgetVersion();
|
||||
method public static boolean isTerminal(int);
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final int ACTION_ABANDON = 107; // 0x6b
|
||||
field public static final int ACTION_COPY = 101; // 0x65
|
||||
@@ -50386,6 +50362,7 @@ package android.view.textclassifier {
|
||||
field public static final int EVENT_SMART_SELECTION_SINGLE = 3; // 0x3
|
||||
field public static final int INVOCATION_LINK = 2; // 0x2
|
||||
field public static final int INVOCATION_MANUAL = 1; // 0x1
|
||||
field public static final int INVOCATION_UNKNOWN = 0; // 0x0
|
||||
}
|
||||
|
||||
public final class TextClassification implements android.os.Parcelable {
|
||||
@@ -50428,19 +50405,45 @@ package android.view.textclassifier {
|
||||
field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassification.Options> CREATOR;
|
||||
}
|
||||
|
||||
public final class TextClassificationContext {
|
||||
method public java.lang.String getPackageName();
|
||||
method public java.lang.String getWidgetType();
|
||||
method public java.lang.String getWidgetVersion();
|
||||
}
|
||||
|
||||
public static final class TextClassificationContext.Builder {
|
||||
ctor public TextClassificationContext.Builder(java.lang.String, java.lang.String);
|
||||
method public android.view.textclassifier.TextClassificationContext build();
|
||||
method public android.view.textclassifier.TextClassificationContext.Builder setWidgetVersion(java.lang.String);
|
||||
}
|
||||
|
||||
public final class TextClassificationManager {
|
||||
method public android.view.textclassifier.TextClassifier createTextClassificationSession(android.view.textclassifier.TextClassificationContext);
|
||||
method public android.view.textclassifier.TextClassifier getTextClassifier();
|
||||
method public void setTextClassificationSessionFactory(android.view.textclassifier.TextClassificationSessionFactory);
|
||||
method public void setTextClassifier(android.view.textclassifier.TextClassifier);
|
||||
}
|
||||
|
||||
public abstract interface TextClassificationSessionFactory {
|
||||
method public abstract android.view.textclassifier.TextClassifier createTextClassificationSession(android.view.textclassifier.TextClassificationContext);
|
||||
}
|
||||
|
||||
public final class TextClassificationSessionId implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassificationSessionId> CREATOR;
|
||||
}
|
||||
|
||||
public abstract interface TextClassifier {
|
||||
method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options);
|
||||
method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int);
|
||||
method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
|
||||
method public default void destroy();
|
||||
method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options);
|
||||
method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence);
|
||||
method public default android.view.textclassifier.Logger getLogger(android.view.textclassifier.Logger.Config);
|
||||
method public default int getMaxGenerateLinksTextLength();
|
||||
method public default boolean isDestroyed();
|
||||
method public default void onSelectionEvent(android.view.textclassifier.SelectionEvent);
|
||||
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options);
|
||||
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
|
||||
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
|
||||
@@ -50456,6 +50459,15 @@ package android.view.textclassifier {
|
||||
field public static final java.lang.String TYPE_PHONE = "phone";
|
||||
field public static final java.lang.String TYPE_UNKNOWN = "";
|
||||
field public static final java.lang.String TYPE_URL = "url";
|
||||
field public static final java.lang.String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit";
|
||||
field public static final java.lang.String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview";
|
||||
field public static final java.lang.String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
|
||||
field public static final java.lang.String WIDGET_TYPE_EDITTEXT = "edittext";
|
||||
field public static final java.lang.String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview";
|
||||
field public static final java.lang.String WIDGET_TYPE_TEXTVIEW = "textview";
|
||||
field public static final java.lang.String WIDGET_TYPE_UNKNOWN = "unknown";
|
||||
field public static final java.lang.String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview";
|
||||
field public static final java.lang.String WIDGET_TYPE_WEBVIEW = "webview";
|
||||
}
|
||||
|
||||
public static final class TextClassifier.EntityConfig implements android.os.Parcelable {
|
||||
|
||||
@@ -39,7 +39,7 @@ import java.util.StringJoiner;
|
||||
public final class DefaultLogger extends Logger {
|
||||
|
||||
private static final String LOG_TAG = "DefaultLogger";
|
||||
private static final String CLASSIFIER_ID = "androidtc";
|
||||
static final String CLASSIFIER_ID = "androidtc";
|
||||
|
||||
private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
|
||||
private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
|
||||
|
||||
@@ -18,56 +18,25 @@ package android.view.textclassifier;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.StringDef;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.text.BreakIterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A helper for logging TextClassifier related events.
|
||||
* @hide
|
||||
*/
|
||||
public abstract class Logger {
|
||||
|
||||
/**
|
||||
* Use this to specify an indeterminate positive index.
|
||||
*/
|
||||
public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* Use this to specify an indeterminate negative index.
|
||||
*/
|
||||
public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE;
|
||||
|
||||
private static final String LOG_TAG = "Logger";
|
||||
/* package */ static final boolean DEBUG_LOG_ENABLED = true;
|
||||
|
||||
private static final String NO_SIGNATURE = "";
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@StringDef({WIDGET_TEXTVIEW, WIDGET_WEBVIEW, WIDGET_EDITTEXT,
|
||||
WIDGET_EDIT_WEBVIEW, WIDGET_CUSTOM_TEXTVIEW, WIDGET_CUSTOM_EDITTEXT,
|
||||
WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW, WIDGET_UNKNOWN})
|
||||
public @interface WidgetType {}
|
||||
|
||||
public static final String WIDGET_TEXTVIEW = "textview";
|
||||
public static final String WIDGET_EDITTEXT = "edittext";
|
||||
public static final String WIDGET_UNSELECTABLE_TEXTVIEW = "nosel-textview";
|
||||
public static final String WIDGET_WEBVIEW = "webview";
|
||||
public static final String WIDGET_EDIT_WEBVIEW = "edit-webview";
|
||||
public static final String WIDGET_CUSTOM_TEXTVIEW = "customview";
|
||||
public static final String WIDGET_CUSTOM_EDITTEXT = "customedit";
|
||||
public static final String WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
|
||||
public static final String WIDGET_UNKNOWN = "unknown";
|
||||
|
||||
private @SelectionEvent.InvocationMethod int mInvocationMethod;
|
||||
private SelectionEvent mPrevEvent;
|
||||
private SelectionEvent mSmartEvent;
|
||||
@@ -108,7 +77,6 @@ public abstract class Logger {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a token iterator for tokenizing text for logging purposes.
|
||||
*/
|
||||
@@ -299,6 +267,9 @@ public abstract class Logger {
|
||||
// Selection did not change. Ignore event.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// do nothing.
|
||||
}
|
||||
|
||||
event.setEventTime(now);
|
||||
@@ -325,9 +296,9 @@ public abstract class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
private String startNewSession() {
|
||||
private TextClassificationSessionId startNewSession() {
|
||||
endSession();
|
||||
return UUID.randomUUID().toString();
|
||||
return new TextClassificationSessionId();
|
||||
}
|
||||
|
||||
private void endSession() {
|
||||
@@ -372,12 +343,12 @@ public abstract class Logger {
|
||||
/**
|
||||
* @param context Context of the widget the logger logs for
|
||||
* @param widgetType a name for the widget being logged for. e.g.
|
||||
* {@link #WIDGET_TEXTVIEW}
|
||||
* {@link TextClassifier#WIDGET_TYPE_TEXTVIEW}
|
||||
* @param widgetVersion a string version info for the widget the logger logs for
|
||||
*/
|
||||
public Config(
|
||||
@NonNull Context context,
|
||||
@WidgetType String widgetType,
|
||||
@TextClassifier.WidgetType String widgetType,
|
||||
@Nullable String widgetVersion) {
|
||||
mPackageName = Preconditions.checkNotNull(context).getPackageName();
|
||||
mWidgetType = widgetType;
|
||||
@@ -392,7 +363,8 @@ public abstract class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name for the widget being logged for. e.g. {@link #WIDGET_TEXTVIEW}.
|
||||
* Returns the name for the widget being logged for. e.g.
|
||||
* {@link TextClassifier#WIDGET_TYPE_TEXTVIEW}.
|
||||
*/
|
||||
public String getWidgetType() {
|
||||
return mWidgetType;
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
package android.view.textclassifier;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.view.textclassifier.TextClassifier.EntityType;
|
||||
import android.view.textclassifier.TextClassifier.WidgetType;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
@@ -103,30 +105,33 @@ public final class SelectionEvent implements Parcelable {
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({INVOCATION_MANUAL, INVOCATION_LINK})
|
||||
@IntDef({INVOCATION_MANUAL, INVOCATION_LINK, INVOCATION_UNKNOWN})
|
||||
public @interface InvocationMethod {}
|
||||
|
||||
/** Selection was invoked by the user long pressing, double tapping, or dragging to select. */
|
||||
public static final int INVOCATION_MANUAL = 1;
|
||||
/** Selection was invoked by the user tapping on a link. */
|
||||
public static final int INVOCATION_LINK = 2;
|
||||
/** Unknown invocation method */
|
||||
public static final int INVOCATION_UNKNOWN = 0;
|
||||
|
||||
private static final String NO_SIGNATURE = "";
|
||||
|
||||
private final int mAbsoluteStart;
|
||||
private final int mAbsoluteEnd;
|
||||
private final @EventType int mEventType;
|
||||
private final @EntityType String mEntityType;
|
||||
@Nullable private final String mWidgetVersion;
|
||||
private final String mPackageName;
|
||||
private final String mWidgetType;
|
||||
private final @InvocationMethod int mInvocationMethod;
|
||||
|
||||
// These fields should only be set by creator of a SelectionEvent.
|
||||
private String mSignature;
|
||||
private @EventType int mEventType;
|
||||
private String mPackageName = "";
|
||||
private String mWidgetType = TextClassifier.WIDGET_TYPE_UNKNOWN;
|
||||
private @InvocationMethod int mInvocationMethod;
|
||||
@Nullable private String mWidgetVersion;
|
||||
private String mSignature; // TODO: Rename to resultId.
|
||||
private long mEventTime;
|
||||
private long mDurationSinceSessionStart;
|
||||
private long mDurationSincePreviousEvent;
|
||||
private int mEventIndex;
|
||||
@Nullable private String mSessionId;
|
||||
@Nullable private TextClassificationSessionId mSessionId;
|
||||
private int mStart;
|
||||
private int mEnd;
|
||||
private int mSmartStart;
|
||||
@@ -135,20 +140,29 @@ public final class SelectionEvent implements Parcelable {
|
||||
SelectionEvent(
|
||||
int start, int end,
|
||||
@EventType int eventType, @EntityType String entityType,
|
||||
@InvocationMethod int invocationMethod, String signature, Logger.Config config) {
|
||||
@InvocationMethod int invocationMethod, String signature) {
|
||||
Preconditions.checkArgument(end >= start, "end cannot be less than start");
|
||||
mAbsoluteStart = start;
|
||||
mAbsoluteEnd = end;
|
||||
mEventType = eventType;
|
||||
mEntityType = Preconditions.checkNotNull(entityType);
|
||||
mSignature = Preconditions.checkNotNull(signature);
|
||||
Preconditions.checkNotNull(config);
|
||||
mWidgetVersion = config.getWidgetVersion();
|
||||
mPackageName = Preconditions.checkNotNull(config.getPackageName());
|
||||
mWidgetType = Preconditions.checkNotNull(config.getWidgetType());
|
||||
mInvocationMethod = invocationMethod;
|
||||
}
|
||||
|
||||
SelectionEvent(
|
||||
int start, int end,
|
||||
@EventType int eventType, @EntityType String entityType,
|
||||
@InvocationMethod int invocationMethod, String signature, Logger.Config config) {
|
||||
this(start, end, eventType, entityType, invocationMethod, signature);
|
||||
Preconditions.checkNotNull(config);
|
||||
setTextClassificationSessionContext(
|
||||
new TextClassificationContext.Builder(
|
||||
config.getPackageName(), config.getWidgetType())
|
||||
.setWidgetVersion(config.getWidgetVersion())
|
||||
.build());
|
||||
}
|
||||
|
||||
private SelectionEvent(Parcel in) {
|
||||
mAbsoluteStart = in.readInt();
|
||||
mAbsoluteEnd = in.readInt();
|
||||
@@ -163,7 +177,8 @@ public final class SelectionEvent implements Parcelable {
|
||||
mDurationSinceSessionStart = in.readLong();
|
||||
mDurationSincePreviousEvent = in.readLong();
|
||||
mEventIndex = in.readInt();
|
||||
mSessionId = in.readInt() > 0 ? in.readString() : null;
|
||||
mSessionId = in.readInt() > 0
|
||||
? TextClassificationSessionId.CREATOR.createFromParcel(in) : null;
|
||||
mStart = in.readInt();
|
||||
mEnd = in.readInt();
|
||||
mSmartStart = in.readInt();
|
||||
@@ -190,7 +205,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
dest.writeInt(mEventIndex);
|
||||
dest.writeInt(mSessionId != null ? 1 : 0);
|
||||
if (mSessionId != null) {
|
||||
dest.writeString(mSessionId);
|
||||
mSessionId.writeToParcel(dest, flags);
|
||||
}
|
||||
dest.writeInt(mStart);
|
||||
dest.writeInt(mEnd);
|
||||
@@ -203,6 +218,156 @@ public final class SelectionEvent implements Parcelable {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "selection started" event.
|
||||
*
|
||||
* @param invocationMethod the way the selection was triggered
|
||||
* @param start the index of the selected text
|
||||
*/
|
||||
@NonNull
|
||||
public static SelectionEvent createSelectionStartedEvent(
|
||||
@SelectionEvent.InvocationMethod int invocationMethod, int start) {
|
||||
return new SelectionEvent(
|
||||
start, start + 1, SelectionEvent.EVENT_SELECTION_STARTED,
|
||||
TextClassifier.TYPE_UNKNOWN, invocationMethod, NO_SIGNATURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "selection modified" event.
|
||||
* Use when the user modifies the selection.
|
||||
*
|
||||
* @param start the start (inclusive) index of the selection
|
||||
* @param end the end (exclusive) index of the selection
|
||||
*
|
||||
* @throws IllegalArgumentException if end is less than start
|
||||
*/
|
||||
@NonNull
|
||||
public static SelectionEvent createSelectionModifiedEvent(int start, int end) {
|
||||
Preconditions.checkArgument(end >= start, "end cannot be less than start");
|
||||
return new SelectionEvent(
|
||||
start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
|
||||
TextClassifier.TYPE_UNKNOWN, INVOCATION_UNKNOWN, NO_SIGNATURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "selection modified" event.
|
||||
* Use when the user modifies the selection and the selection's entity type is known.
|
||||
*
|
||||
* @param start the start (inclusive) index of the selection
|
||||
* @param end the end (exclusive) index of the selection
|
||||
* @param classification the TextClassification object returned by the TextClassifier that
|
||||
* classified the selected text
|
||||
*
|
||||
* @throws IllegalArgumentException if end is less than start
|
||||
*/
|
||||
@NonNull
|
||||
public static SelectionEvent createSelectionModifiedEvent(
|
||||
int start, int end, @NonNull TextClassification classification) {
|
||||
Preconditions.checkArgument(end >= start, "end cannot be less than start");
|
||||
Preconditions.checkNotNull(classification);
|
||||
final String entityType = classification.getEntityCount() > 0
|
||||
? classification.getEntity(0)
|
||||
: TextClassifier.TYPE_UNKNOWN;
|
||||
return new SelectionEvent(
|
||||
start, end, SelectionEvent.EVENT_SELECTION_MODIFIED,
|
||||
entityType, INVOCATION_UNKNOWN, classification.getSignature());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "selection modified" event.
|
||||
* Use when a TextClassifier modifies the selection.
|
||||
*
|
||||
* @param start the start (inclusive) index of the selection
|
||||
* @param end the end (exclusive) index of the selection
|
||||
* @param selection the TextSelection object returned by the TextClassifier for the
|
||||
* specified selection
|
||||
*
|
||||
* @throws IllegalArgumentException if end is less than start
|
||||
*/
|
||||
@NonNull
|
||||
public static SelectionEvent createSelectionModifiedEvent(
|
||||
int start, int end, @NonNull TextSelection selection) {
|
||||
Preconditions.checkArgument(end >= start, "end cannot be less than start");
|
||||
Preconditions.checkNotNull(selection);
|
||||
final String entityType = selection.getEntityCount() > 0
|
||||
? selection.getEntity(0)
|
||||
: TextClassifier.TYPE_UNKNOWN;
|
||||
return new SelectionEvent(
|
||||
start, end, SelectionEvent.EVENT_AUTO_SELECTION,
|
||||
entityType, INVOCATION_UNKNOWN, selection.getSignature());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event specifying an action taken on a selection.
|
||||
* Use when the user clicks on an action to act on the selected text.
|
||||
*
|
||||
* @param start the start (inclusive) index of the selection
|
||||
* @param end the end (exclusive) index of the selection
|
||||
* @param actionType the action that was performed on the selection
|
||||
*
|
||||
* @throws IllegalArgumentException if end is less than start
|
||||
*/
|
||||
@NonNull
|
||||
public static SelectionEvent createSelectionActionEvent(
|
||||
int start, int end, @SelectionEvent.ActionType int actionType) {
|
||||
Preconditions.checkArgument(end >= start, "end cannot be less than start");
|
||||
checkActionType(actionType);
|
||||
return new SelectionEvent(
|
||||
start, end, actionType, TextClassifier.TYPE_UNKNOWN, INVOCATION_UNKNOWN,
|
||||
NO_SIGNATURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event specifying an action taken on a selection.
|
||||
* Use when the user clicks on an action to act on the selected text and the selection's
|
||||
* entity type is known.
|
||||
*
|
||||
* @param start the start (inclusive) index of the selection
|
||||
* @param end the end (exclusive) index of the selection
|
||||
* @param actionType the action that was performed on the selection
|
||||
* @param classification the TextClassification object returned by the TextClassifier that
|
||||
* classified the selected text
|
||||
*
|
||||
* @throws IllegalArgumentException if end is less than start
|
||||
* @throws IllegalArgumentException If actionType is not a valid SelectionEvent actionType
|
||||
*/
|
||||
@NonNull
|
||||
public static SelectionEvent createSelectionActionEvent(
|
||||
int start, int end, @SelectionEvent.ActionType int actionType,
|
||||
@NonNull TextClassification classification) {
|
||||
Preconditions.checkArgument(end >= start, "end cannot be less than start");
|
||||
Preconditions.checkNotNull(classification);
|
||||
checkActionType(actionType);
|
||||
final String entityType = classification.getEntityCount() > 0
|
||||
? classification.getEntity(0)
|
||||
: TextClassifier.TYPE_UNKNOWN;
|
||||
return new SelectionEvent(start, end, actionType, entityType, INVOCATION_UNKNOWN,
|
||||
classification.getSignature());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException If eventType is not an {@link SelectionEvent.ActionType}
|
||||
*/
|
||||
private static void checkActionType(@SelectionEvent.EventType int eventType)
|
||||
throws IllegalArgumentException {
|
||||
switch (eventType) {
|
||||
case SelectionEvent.ACTION_OVERTYPE: // fall through
|
||||
case SelectionEvent.ACTION_COPY: // fall through
|
||||
case SelectionEvent.ACTION_PASTE: // fall through
|
||||
case SelectionEvent.ACTION_CUT: // fall through
|
||||
case SelectionEvent.ACTION_SHARE: // fall through
|
||||
case SelectionEvent.ACTION_SMART_SHARE: // fall through
|
||||
case SelectionEvent.ACTION_DRAG: // fall through
|
||||
case SelectionEvent.ACTION_ABANDON: // fall through
|
||||
case SelectionEvent.ACTION_SELECT_ALL: // fall through
|
||||
case SelectionEvent.ACTION_RESET: // fall through
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
String.format(Locale.US, "%d is not an eventType", eventType));
|
||||
}
|
||||
}
|
||||
|
||||
int getAbsoluteStart() {
|
||||
return mAbsoluteStart;
|
||||
}
|
||||
@@ -214,15 +379,24 @@ public final class SelectionEvent implements Parcelable {
|
||||
/**
|
||||
* Returns the type of event that was triggered. e.g. {@link #ACTION_COPY}.
|
||||
*/
|
||||
@EventType
|
||||
public int getEventType() {
|
||||
return mEventType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the event type.
|
||||
*/
|
||||
void setEventType(@EventType int eventType) {
|
||||
mEventType = eventType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of entity that is associated with this event. e.g.
|
||||
* {@link android.view.textclassifier.TextClassifier#TYPE_EMAIL}.
|
||||
*/
|
||||
@EntityType
|
||||
@NonNull
|
||||
public String getEntityType() {
|
||||
return mEntityType;
|
||||
}
|
||||
@@ -230,6 +404,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
/**
|
||||
* Returns the package name of the app that this event originated in.
|
||||
*/
|
||||
@NonNull
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
@@ -237,6 +412,8 @@ public final class SelectionEvent implements Parcelable {
|
||||
/**
|
||||
* Returns the type of widget that was involved in triggering this event.
|
||||
*/
|
||||
@WidgetType
|
||||
@NonNull
|
||||
public String getWidgetType() {
|
||||
return mWidgetType;
|
||||
}
|
||||
@@ -244,10 +421,20 @@ public final class SelectionEvent implements Parcelable {
|
||||
/**
|
||||
* Returns a string version info for the widget this event was triggered in.
|
||||
*/
|
||||
@Nullable
|
||||
public String getWidgetVersion() {
|
||||
return mWidgetVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link TextClassificationContext} for this event.
|
||||
*/
|
||||
void setTextClassificationSessionContext(TextClassificationContext context) {
|
||||
mPackageName = context.getPackageName();
|
||||
mWidgetType = context.getWidgetType();
|
||||
mWidgetVersion = context.getWidgetVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the way the selection mode was invoked.
|
||||
*/
|
||||
@@ -255,6 +442,13 @@ public final class SelectionEvent implements Parcelable {
|
||||
return mInvocationMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the invocationMethod for this event.
|
||||
*/
|
||||
void setInvocationMethod(@InvocationMethod int invocationMethod) {
|
||||
mInvocationMethod = invocationMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signature of the text classifier result associated with this event.
|
||||
*/
|
||||
@@ -320,17 +514,18 @@ public final class SelectionEvent implements Parcelable {
|
||||
/**
|
||||
* Returns the selection session id.
|
||||
*/
|
||||
public String getSessionId() {
|
||||
@Nullable
|
||||
public TextClassificationSessionId getSessionId() {
|
||||
return mSessionId;
|
||||
}
|
||||
|
||||
SelectionEvent setSessionId(String id) {
|
||||
SelectionEvent setSessionId(TextClassificationSessionId id) {
|
||||
mSessionId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start index of this events token relative to the index of the start selection
|
||||
* Returns the start index of this events relative to the index of the start selection
|
||||
* event in the selection session.
|
||||
*/
|
||||
public int getStart() {
|
||||
@@ -343,7 +538,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end index of this events token relative to the index of the start selection
|
||||
* Returns the end index of this events relative to the index of the start selection
|
||||
* event in the selection session.
|
||||
*/
|
||||
public int getEnd() {
|
||||
@@ -356,7 +551,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start index of this events token relative to the index of the smart selection
|
||||
* Returns the start index of this events relative to the index of the smart selection
|
||||
* event in the selection session.
|
||||
*/
|
||||
public int getSmartStart() {
|
||||
@@ -369,7 +564,7 @@ public final class SelectionEvent implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end index of this events token relative to the index of the smart selection
|
||||
* Returns the end index of this events relative to the index of the smart selection
|
||||
* event in the selection session.
|
||||
*/
|
||||
public int getSmartEnd() {
|
||||
@@ -382,7 +577,15 @@ public final class SelectionEvent implements Parcelable {
|
||||
}
|
||||
|
||||
boolean isTerminal() {
|
||||
switch (mEventType) {
|
||||
return isTerminal(mEventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the eventType is a terminal event type. Otherwise returns false.
|
||||
* A terminal event is an event that ends a selection interaction.
|
||||
*/
|
||||
public static boolean isTerminal(@EventType int eventType) {
|
||||
switch (eventType) {
|
||||
case ACTION_OVERTYPE: // fall through
|
||||
case ACTION_COPY: // fall through
|
||||
case ACTION_PASTE: // fall through
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.textclassifier;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.view.textclassifier.TextClassifier.WidgetType;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A representation of the context in which text classification would be performed.
|
||||
* @see TextClassificationManager#createTextClassificationSession(TextClassificationContext)
|
||||
*/
|
||||
public final class TextClassificationContext {
|
||||
|
||||
private final String mPackageName;
|
||||
private final String mWidgetType;
|
||||
@Nullable private final String mWidgetVersion;
|
||||
|
||||
private TextClassificationContext(
|
||||
String packageName,
|
||||
String widgetType,
|
||||
String widgetVersion) {
|
||||
mPackageName = Preconditions.checkNotNull(packageName);
|
||||
mWidgetType = Preconditions.checkNotNull(widgetType);
|
||||
mWidgetVersion = widgetVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package name for the calling package.
|
||||
*/
|
||||
@NonNull
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the widget type for this classification context.
|
||||
*/
|
||||
@NonNull
|
||||
@WidgetType
|
||||
public String getWidgetType() {
|
||||
return mWidgetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a custom version string for the widget type.
|
||||
*
|
||||
* @see #getWidgetType()
|
||||
*/
|
||||
@Nullable
|
||||
public String getWidgetVersion() {
|
||||
return mWidgetVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.US, "TextClassificationContext{"
|
||||
+ "packageName=%s, widgetType=%s, widgetVersion=%s}",
|
||||
mPackageName, mWidgetType, mWidgetVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for building a TextClassification context.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private final String mPackageName;
|
||||
private final String mWidgetType;
|
||||
|
||||
@Nullable private String mWidgetVersion;
|
||||
|
||||
/**
|
||||
* Initializes a new builder for text classification context objects.
|
||||
*
|
||||
* @param packageName the name of the calling package
|
||||
* @param widgetType the type of widget e.g. {@link TextClassifier#WIDGET_TYPE_TEXTVIEW}
|
||||
*
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder(@NonNull String packageName, @NonNull @WidgetType String widgetType) {
|
||||
mPackageName = Preconditions.checkNotNull(packageName);
|
||||
mWidgetType = Preconditions.checkNotNull(widgetType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an optional custom version string for the widget type.
|
||||
*
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder setWidgetVersion(@Nullable String widgetVersion) {
|
||||
mWidgetVersion = widgetVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the text classification context object.
|
||||
*
|
||||
* @return the built TextClassificationContext object
|
||||
*/
|
||||
@NonNull
|
||||
public TextClassificationContext build() {
|
||||
return new TextClassificationContext(mPackageName, mWidgetType, mWidgetVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.view.textclassifier;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemService;
|
||||
import android.content.Context;
|
||||
@@ -36,6 +37,9 @@ public final class TextClassificationManager {
|
||||
private static final String LOG_TAG = "TextClassificationManager";
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final TextClassificationSessionFactory mDefaultSessionFactory =
|
||||
classificationContext -> new TextClassificationSession(
|
||||
classificationContext, getTextClassifier());
|
||||
|
||||
private final Context mContext;
|
||||
private final TextClassificationConstants mSettings;
|
||||
@@ -46,12 +50,15 @@ public final class TextClassificationManager {
|
||||
private TextClassifier mLocalTextClassifier;
|
||||
@GuardedBy("mLock")
|
||||
private TextClassifier mSystemTextClassifier;
|
||||
@GuardedBy("mLock")
|
||||
private TextClassificationSessionFactory mSessionFactory;
|
||||
|
||||
/** @hide */
|
||||
public TextClassificationManager(Context context) {
|
||||
mContext = Preconditions.checkNotNull(context);
|
||||
mSettings = TextClassificationConstants.loadFromString(Settings.Global.getString(
|
||||
context.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
|
||||
mSessionFactory = mDefaultSessionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,6 +68,7 @@ public final class TextClassificationManager {
|
||||
*
|
||||
* @see #setTextClassifier(TextClassifier)
|
||||
*/
|
||||
@NonNull
|
||||
public TextClassifier getTextClassifier() {
|
||||
synchronized (mLock) {
|
||||
if (mTextClassifier == null) {
|
||||
@@ -93,7 +101,6 @@ public final class TextClassificationManager {
|
||||
* @see TextClassifier#SYSTEM
|
||||
* @hide
|
||||
*/
|
||||
// TODO: Expose as system API.
|
||||
public TextClassifier getTextClassifier(@TextClassifierType int type) {
|
||||
switch (type) {
|
||||
case TextClassifier.LOCAL:
|
||||
@@ -108,6 +115,61 @@ public final class TextClassificationManager {
|
||||
return mSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method to start a text classification session with the given context.
|
||||
* A session is created with a context helping the classifier better understand
|
||||
* what the user needs and consists of queries and feedback events. The queries
|
||||
* are directly related to providing useful functionality to the user and the events
|
||||
* are a feedback loop back to the classifier helping it learn and better serve
|
||||
* future queries.
|
||||
*
|
||||
* <p> All interactions with the returned classifier are considered part of a single
|
||||
* session and are logically grouped. For example, when a text widget is focused
|
||||
* all user interactions around text editing (selection, editing, etc) can be
|
||||
* grouped together to allow the classifier get better.
|
||||
*
|
||||
* @param classificationContext The context in which classification would occur
|
||||
*
|
||||
* @return An instance to perform classification in the given context
|
||||
*/
|
||||
@NonNull
|
||||
public TextClassifier createTextClassificationSession(
|
||||
@NonNull TextClassificationContext classificationContext) {
|
||||
Preconditions.checkNotNull(classificationContext);
|
||||
final TextClassifier textClassifier =
|
||||
mSessionFactory.createTextClassificationSession(classificationContext);
|
||||
Preconditions.checkNotNull(textClassifier, "Session Factory should never return null");
|
||||
return textClassifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #createTextClassificationSession(TextClassificationContext, TextClassifier)
|
||||
* @hide
|
||||
*/
|
||||
public TextClassifier createTextClassificationSession(
|
||||
TextClassificationContext classificationContext, TextClassifier textClassifier) {
|
||||
Preconditions.checkNotNull(classificationContext);
|
||||
Preconditions.checkNotNull(textClassifier);
|
||||
return new TextClassificationSession(classificationContext, textClassifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a TextClassificationSessionFactory to be used to create session-aware TextClassifiers.
|
||||
*
|
||||
* @param factory the textClassification session factory. If this is null, the default factory
|
||||
* will be used.
|
||||
*/
|
||||
public void setTextClassificationSessionFactory(
|
||||
@Nullable TextClassificationSessionFactory factory) {
|
||||
synchronized (mLock) {
|
||||
if (factory != null) {
|
||||
mSessionFactory = factory;
|
||||
} else {
|
||||
mSessionFactory = mDefaultSessionFactory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TextClassifier getSystemTextClassifier() {
|
||||
synchronized (mLock) {
|
||||
if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) {
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.textclassifier;
|
||||
|
||||
import android.annotation.WorkerThread;
|
||||
import android.view.textclassifier.DefaultLogger.SignatureParser;
|
||||
import android.view.textclassifier.SelectionEvent.InvocationMethod;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
/**
|
||||
* Session-aware TextClassifier.
|
||||
*/
|
||||
@WorkerThread
|
||||
final class TextClassificationSession implements TextClassifier {
|
||||
|
||||
/* package */ static final boolean DEBUG_LOG_ENABLED = true;
|
||||
private static final String LOG_TAG = "TextClassificationSession";
|
||||
|
||||
private final TextClassifier mDelegate;
|
||||
private final SelectionEventHelper mEventHelper;
|
||||
|
||||
private boolean mDestroyed;
|
||||
|
||||
TextClassificationSession(TextClassificationContext context, TextClassifier delegate) {
|
||||
mDelegate = Preconditions.checkNotNull(delegate);
|
||||
mEventHelper = new SelectionEventHelper(new TextClassificationSessionId(), context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextSelection suggestSelection(CharSequence text, int selectionStartIndex,
|
||||
int selectionEndIndex, TextSelection.Options options) {
|
||||
checkDestroyed();
|
||||
return mDelegate.suggestSelection(text, selectionStartIndex, selectionEndIndex, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextClassification classifyText(CharSequence text, int startIndex, int endIndex,
|
||||
TextClassification.Options options) {
|
||||
checkDestroyed();
|
||||
return mDelegate.classifyText(text, startIndex, endIndex, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextLinks generateLinks(CharSequence text, TextLinks.Options options) {
|
||||
checkDestroyed();
|
||||
return mDelegate.generateLinks(text, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionEvent(SelectionEvent event) {
|
||||
checkDestroyed();
|
||||
Preconditions.checkNotNull(event);
|
||||
if (mEventHelper.sanitizeEvent(event)) {
|
||||
mDelegate.onSelectionEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
mEventHelper.endSession();
|
||||
mDestroyed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyed() {
|
||||
return mDestroyed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if this TextClassification session has been destroyed.
|
||||
* @see #isDestroyed()
|
||||
* @see #destroy()
|
||||
*/
|
||||
private void checkDestroyed() {
|
||||
if (mDestroyed) {
|
||||
throw new IllegalStateException("This TextClassification session has been destroyed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for updating SelectionEvent fields.
|
||||
*/
|
||||
private static final class SelectionEventHelper {
|
||||
|
||||
private final TextClassificationSessionId mSessionId;
|
||||
private final TextClassificationContext mContext;
|
||||
|
||||
@InvocationMethod
|
||||
private int mInvocationMethod = SelectionEvent.INVOCATION_UNKNOWN;
|
||||
private SelectionEvent mPrevEvent;
|
||||
private SelectionEvent mSmartEvent;
|
||||
private SelectionEvent mStartEvent;
|
||||
|
||||
SelectionEventHelper(
|
||||
TextClassificationSessionId sessionId, TextClassificationContext context) {
|
||||
mSessionId = Preconditions.checkNotNull(sessionId);
|
||||
mContext = Preconditions.checkNotNull(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the necessary fields in the event for the current session.
|
||||
*
|
||||
* @return true if the event should be reported. false if the event should be ignored
|
||||
*/
|
||||
boolean sanitizeEvent(SelectionEvent event) {
|
||||
updateInvocationMethod(event);
|
||||
modifyAutoSelectionEventType(event);
|
||||
|
||||
if (event.getEventType() != SelectionEvent.EVENT_SELECTION_STARTED
|
||||
&& mStartEvent == null) {
|
||||
if (DEBUG_LOG_ENABLED) {
|
||||
Log.d(LOG_TAG, "Selection session not yet started. Ignoring event");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
switch (event.getEventType()) {
|
||||
case SelectionEvent.EVENT_SELECTION_STARTED:
|
||||
Preconditions.checkArgument(
|
||||
event.getAbsoluteEnd() == event.getAbsoluteStart() + 1);
|
||||
event.setSessionId(mSessionId);
|
||||
mStartEvent = event;
|
||||
break;
|
||||
case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: // fall through
|
||||
case SelectionEvent.EVENT_SMART_SELECTION_MULTI:
|
||||
mSmartEvent = event;
|
||||
break;
|
||||
case SelectionEvent.EVENT_SELECTION_MODIFIED: // fall through
|
||||
case SelectionEvent.EVENT_AUTO_SELECTION:
|
||||
if (mPrevEvent != null
|
||||
&& mPrevEvent.getAbsoluteStart() == event.getAbsoluteStart()
|
||||
&& mPrevEvent.getAbsoluteEnd() == event.getAbsoluteEnd()) {
|
||||
// Selection did not change. Ignore event.
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// do nothing.
|
||||
}
|
||||
|
||||
event.setEventTime(now);
|
||||
if (mStartEvent != null) {
|
||||
event.setSessionId(mStartEvent.getSessionId())
|
||||
.setDurationSinceSessionStart(now - mStartEvent.getEventTime())
|
||||
.setStart(event.getAbsoluteStart() - mStartEvent.getAbsoluteStart())
|
||||
.setEnd(event.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
|
||||
}
|
||||
if (mSmartEvent != null) {
|
||||
event.setSignature(mSmartEvent.getSignature())
|
||||
.setSmartStart(
|
||||
mSmartEvent.getAbsoluteStart() - mStartEvent.getAbsoluteStart())
|
||||
.setSmartEnd(mSmartEvent.getAbsoluteEnd() - mStartEvent.getAbsoluteStart());
|
||||
}
|
||||
if (mPrevEvent != null) {
|
||||
event.setDurationSincePreviousEvent(now - mPrevEvent.getEventTime())
|
||||
.setEventIndex(mPrevEvent.getEventIndex() + 1);
|
||||
}
|
||||
mPrevEvent = event;
|
||||
return true;
|
||||
}
|
||||
|
||||
void endSession() {
|
||||
mPrevEvent = null;
|
||||
mSmartEvent = null;
|
||||
mStartEvent = null;
|
||||
}
|
||||
|
||||
private void updateInvocationMethod(SelectionEvent event) {
|
||||
event.setTextClassificationSessionContext(mContext);
|
||||
if (event.getInvocationMethod() == SelectionEvent.INVOCATION_UNKNOWN) {
|
||||
event.setInvocationMethod(mInvocationMethod);
|
||||
} else {
|
||||
mInvocationMethod = event.getInvocationMethod();
|
||||
}
|
||||
}
|
||||
|
||||
private void modifyAutoSelectionEventType(SelectionEvent event) {
|
||||
switch (event.getEventType()) {
|
||||
case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: // fall through
|
||||
case SelectionEvent.EVENT_SMART_SELECTION_MULTI: // fall through
|
||||
case SelectionEvent.EVENT_AUTO_SELECTION:
|
||||
if (isPlatformLocalTextClassifierSmartSelection(event.getSignature())) {
|
||||
if (event.getAbsoluteEnd() - event.getAbsoluteStart() > 1) {
|
||||
event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_MULTI);
|
||||
} else {
|
||||
event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_SINGLE);
|
||||
}
|
||||
} else {
|
||||
event.setEventType(SelectionEvent.EVENT_AUTO_SELECTION);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPlatformLocalTextClassifierSmartSelection(String signature) {
|
||||
return DefaultLogger.CLASSIFIER_ID.equals(SignatureParser.getClassifierId(signature));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.textclassifier;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* An interface for creating a session-aware TextClassifier.
|
||||
*
|
||||
* @see TextClassificationManager#createTextClassificationSession(TextClassificationContext)
|
||||
*/
|
||||
public interface TextClassificationSessionFactory {
|
||||
|
||||
/**
|
||||
* Creates and returns a session-aware TextClassifier.
|
||||
*
|
||||
* @param classificationContext the classification context
|
||||
*/
|
||||
@NonNull
|
||||
TextClassifier createTextClassificationSession(
|
||||
@NonNull TextClassificationContext classificationContext);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.textclassifier;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This class represents the id of a text classification session.
|
||||
*/
|
||||
public final class TextClassificationSessionId implements Parcelable {
|
||||
private final @NonNull String mValue;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public TextClassificationSessionId() {
|
||||
this(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param value The internal value.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public TextClassificationSessionId(@NonNull String value) {
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + mValue.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TextClassificationSessionId other = (TextClassificationSessionId) obj;
|
||||
if (!mValue.equals(other.mValue)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeString(mValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens this id to a string.
|
||||
*
|
||||
* @return The flattened id.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public @NonNull String flattenToString() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unflattens a print job id from a string.
|
||||
*
|
||||
* @param string The string.
|
||||
* @return The unflattened id, or null if the string is malformed.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static @NonNull TextClassificationSessionId unflattenFromString(@NonNull String string) {
|
||||
return new TextClassificationSessionId(string);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<TextClassificationSessionId> CREATOR =
|
||||
new Parcelable.Creator<TextClassificationSessionId>() {
|
||||
@Override
|
||||
public TextClassificationSessionId createFromParcel(Parcel parcel) {
|
||||
return new TextClassificationSessionId(
|
||||
Preconditions.checkNotNull(parcel.readString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextClassificationSessionId[] newArray(int size) {
|
||||
return new TextClassificationSessionId[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -112,6 +112,38 @@ public interface TextClassifier {
|
||||
@StringDef(prefix = { "HINT_" }, value = {HINT_TEXT_IS_EDITABLE, HINT_TEXT_IS_NOT_EDITABLE})
|
||||
@interface Hints {}
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDITTEXT,
|
||||
WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW, WIDGET_TYPE_CUSTOM_EDITTEXT,
|
||||
WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW, WIDGET_TYPE_UNKNOWN})
|
||||
@interface WidgetType {}
|
||||
|
||||
/** The widget involved in the text classification session is a standard
|
||||
* {@link android.widget.TextView}. */
|
||||
String WIDGET_TYPE_TEXTVIEW = "textview";
|
||||
/** The widget involved in the text classification session is a standard
|
||||
* {@link android.widget.EditText}. */
|
||||
String WIDGET_TYPE_EDITTEXT = "edittext";
|
||||
/** The widget involved in the text classification session is a standard non-selectable
|
||||
* {@link android.widget.TextView}. */
|
||||
String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview";
|
||||
/** The widget involved in the text classification session is a standard
|
||||
* {@link android.webkit.WebView}. */
|
||||
String WIDGET_TYPE_WEBVIEW = "webview";
|
||||
/** The widget involved in the text classification session is a standard editable
|
||||
* {@link android.webkit.WebView}. */
|
||||
String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview";
|
||||
/** The widget involved in the text classification session is a custom text widget. */
|
||||
String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview";
|
||||
/** The widget involved in the text classification session is a custom editable text widget. */
|
||||
String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit";
|
||||
/** The widget involved in the text classification session is a custom non-selectable text
|
||||
* widget. */
|
||||
String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
|
||||
/** The widget involved in the text classification session is of an unknown/unspecified type. */
|
||||
String WIDGET_TYPE_UNKNOWN = "unknown";
|
||||
|
||||
/**
|
||||
* No-op TextClassifier.
|
||||
* This may be used to turn off TextClassifier features.
|
||||
@@ -124,6 +156,9 @@ public interface TextClassifier {
|
||||
*
|
||||
* <p><strong>NOTE: </strong>Call on a worker thread.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* @param text text providing context for the selected text (which is specified
|
||||
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
|
||||
* @param selectionStartIndex start index of the selected part of text
|
||||
@@ -156,6 +191,9 @@ public interface TextClassifier {
|
||||
*
|
||||
* <p><strong>NOTE: </strong>Call on a worker thread.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* @param text text providing context for the selected text (which is specified
|
||||
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
|
||||
* @param selectionStartIndex start index of the selected part of text
|
||||
@@ -185,6 +223,9 @@ public interface TextClassifier {
|
||||
* <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
|
||||
* {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method
|
||||
* calls this method, a stack overflow error will happen.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*/
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
@@ -205,6 +246,9 @@ public interface TextClassifier {
|
||||
*
|
||||
* <p><strong>NOTE: </strong>Call on a worker thread.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* @param text text providing context for the text to classify (which is specified
|
||||
* by the sub sequence starting at startIndex and ending at endIndex)
|
||||
* @param startIndex start index of the text to classify
|
||||
@@ -237,6 +281,9 @@ public interface TextClassifier {
|
||||
* {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method
|
||||
* calls this method, a stack overflow error will happen.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* @param text text providing context for the text to classify (which is specified
|
||||
* by the sub sequence starting at startIndex and ending at endIndex)
|
||||
* @param startIndex start index of the text to classify
|
||||
@@ -265,6 +312,9 @@ public interface TextClassifier {
|
||||
* <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
|
||||
* {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method
|
||||
* calls this method, a stack overflow error will happen.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*/
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
@@ -285,6 +335,9 @@ public interface TextClassifier {
|
||||
*
|
||||
* <p><strong>NOTE: </strong>Call on a worker thread.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* @param text the text to generate annotations for
|
||||
* @param options configuration for link generation
|
||||
*
|
||||
@@ -295,6 +348,7 @@ public interface TextClassifier {
|
||||
* @see #getMaxGenerateLinksTextLength()
|
||||
*/
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
default TextLinks generateLinks(
|
||||
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
|
||||
Utils.validate(text, false);
|
||||
@@ -311,6 +365,9 @@ public interface TextClassifier {
|
||||
* {@link #generateLinks(CharSequence, TextLinks.Options)}. If that method calls this method,
|
||||
* a stack overflow error will happen.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* @param text the text to generate annotations for
|
||||
*
|
||||
* @throws IllegalArgumentException if text is null or the text is too long for the
|
||||
@@ -320,6 +377,7 @@ public interface TextClassifier {
|
||||
* @see #getMaxGenerateLinksTextLength()
|
||||
*/
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
default TextLinks generateLinks(@NonNull CharSequence text) {
|
||||
return generateLinks(text, null);
|
||||
}
|
||||
@@ -327,6 +385,9 @@ public interface TextClassifier {
|
||||
/**
|
||||
* Returns the maximal length of text that can be processed by generateLinks.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* @see #generateLinks(CharSequence)
|
||||
* @see #generateLinks(CharSequence, TextLinks.Options)
|
||||
*/
|
||||
@@ -339,6 +400,7 @@ public interface TextClassifier {
|
||||
* Returns a helper for logging TextClassifier related events.
|
||||
*
|
||||
* @param config logger configuration
|
||||
* @hide
|
||||
*/
|
||||
@WorkerThread
|
||||
default Logger getLogger(@NonNull Logger.Config config) {
|
||||
@@ -346,6 +408,37 @@ public interface TextClassifier {
|
||||
return Logger.DISABLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a selection event.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*/
|
||||
default void onSelectionEvent(@NonNull SelectionEvent event) {}
|
||||
|
||||
/**
|
||||
* Destroys this TextClassifier.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to its methods should
|
||||
* throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
|
||||
*
|
||||
* <p>Subsequent calls to this method are no-ops.
|
||||
*/
|
||||
default void destroy() {}
|
||||
|
||||
/**
|
||||
* Returns whether or not this TextClassifier has been destroyed.
|
||||
*
|
||||
* <strong>NOTE: </strong>If a TextClassifier has been destroyed, caller should not interact
|
||||
* with the classifier and an attempt to do so would throw an {@link IllegalStateException}.
|
||||
* However, this method should never throw an {@link IllegalStateException}.
|
||||
*
|
||||
* @see #destroy()
|
||||
*/
|
||||
default boolean isDestroyed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration object for specifying what entities to identify.
|
||||
*
|
||||
|
||||
@@ -94,6 +94,8 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
private Logger.Config mLoggerConfig;
|
||||
@GuardedBy("mLoggerLock") // Do not access outside this lock.
|
||||
private Logger mLogger;
|
||||
@GuardedBy("mLoggerLock") // Do not access outside this lock.
|
||||
private Logger mLogger2; // This is the new logger. Will replace mLogger.
|
||||
|
||||
private final TextClassificationConstants mSettings;
|
||||
|
||||
@@ -300,6 +302,18 @@ public final class TextClassifierImpl implements TextClassifier {
|
||||
return mLogger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionEvent(SelectionEvent event) {
|
||||
Preconditions.checkNotNull(event);
|
||||
synchronized (mLoggerLock) {
|
||||
if (mLogger2 == null) {
|
||||
mLogger2 = new DefaultLogger(
|
||||
new Logger.Config(mContext, WIDGET_TYPE_UNKNOWN, null));
|
||||
}
|
||||
mLogger2.writeEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private TextClassifierImplNative getNative(LocaleList localeList)
|
||||
throws FileNotFoundException {
|
||||
synchronized (mLock) {
|
||||
|
||||
@@ -35,6 +35,7 @@ import android.util.Log;
|
||||
import android.view.ActionMode;
|
||||
import android.view.textclassifier.Logger;
|
||||
import android.view.textclassifier.SelectionEvent;
|
||||
import android.view.textclassifier.SelectionEvent.InvocationMethod;
|
||||
import android.view.textclassifier.TextClassification;
|
||||
import android.view.textclassifier.TextClassificationConstants;
|
||||
import android.view.textclassifier.TextClassificationManager;
|
||||
@@ -86,7 +87,7 @@ public final class SelectionActionModeHelper {
|
||||
mTextClassificationSettings = TextClassificationManager.getSettings(mTextView.getContext());
|
||||
mTextClassificationHelper = new TextClassificationHelper(
|
||||
mTextView.getContext(),
|
||||
mTextView.getTextClassifier(),
|
||||
mTextView::getTextClassifier,
|
||||
getText(mTextView),
|
||||
0, 1, mTextView.getTextLocales());
|
||||
mSelectionTracker = new SelectionTracker(mTextView);
|
||||
@@ -221,7 +222,7 @@ public final class SelectionActionModeHelper {
|
||||
|
||||
private boolean skipTextClassification() {
|
||||
// No need to make an async call for a no-op TextClassifier.
|
||||
final boolean noOpTextClassifier = mTextView.getTextClassifier() == TextClassifier.NO_OP;
|
||||
final boolean noOpTextClassifier = mTextView.usesNoOpTextClassifier();
|
||||
// Do not call the TextClassifier if there is no selection.
|
||||
final boolean noSelection = mTextView.getSelectionEnd() == mTextView.getSelectionStart();
|
||||
// Do not call the TextClassifier if this is a password field.
|
||||
@@ -447,7 +448,7 @@ public final class SelectionActionModeHelper {
|
||||
selectionEnd = mTextView.getSelectionEnd();
|
||||
}
|
||||
mTextClassificationHelper.init(
|
||||
mTextView.getTextClassifier(),
|
||||
mTextView::getTextClassifier,
|
||||
getText(mTextView),
|
||||
selectionStart, selectionEnd,
|
||||
mTextView.getTextLocales());
|
||||
@@ -660,6 +661,7 @@ public final class SelectionActionModeHelper {
|
||||
private static final String LOG_TAG = "SelectionMetricsLogger";
|
||||
private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s+");
|
||||
|
||||
private final Supplier<TextClassifier> mTextClassificationSession;
|
||||
private final Logger mLogger;
|
||||
private final boolean mEditTextLogger;
|
||||
private final BreakIterator mTokenIterator;
|
||||
@@ -668,26 +670,27 @@ public final class SelectionActionModeHelper {
|
||||
|
||||
SelectionMetricsLogger(TextView textView) {
|
||||
Preconditions.checkNotNull(textView);
|
||||
mTextClassificationSession = textView::getTextClassificationSession;
|
||||
mLogger = textView.getTextClassifier().getLogger(
|
||||
new Logger.Config(textView.getContext(), getWidetType(textView), null));
|
||||
mEditTextLogger = textView.isTextEditable();
|
||||
mTokenIterator = mLogger.getTokenIterator(textView.getTextLocale());
|
||||
}
|
||||
|
||||
@Logger.WidgetType
|
||||
@TextClassifier.WidgetType
|
||||
private static String getWidetType(TextView textView) {
|
||||
if (textView.isTextEditable()) {
|
||||
return Logger.WIDGET_EDITTEXT;
|
||||
return TextClassifier.WIDGET_TYPE_EDITTEXT;
|
||||
}
|
||||
if (textView.isTextSelectable()) {
|
||||
return Logger.WIDGET_TEXTVIEW;
|
||||
return TextClassifier.WIDGET_TYPE_TEXTVIEW;
|
||||
}
|
||||
return Logger.WIDGET_UNSELECTABLE_TEXTVIEW;
|
||||
return TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
|
||||
}
|
||||
|
||||
public void logSelectionStarted(
|
||||
CharSequence text, int index,
|
||||
@SelectionEvent.InvocationMethod int invocationMethod) {
|
||||
@InvocationMethod int invocationMethod) {
|
||||
try {
|
||||
Preconditions.checkNotNull(text);
|
||||
Preconditions.checkArgumentInRange(index, 0, text.length(), "index");
|
||||
@@ -697,9 +700,12 @@ public final class SelectionActionModeHelper {
|
||||
mTokenIterator.setText(mText);
|
||||
mStartIndex = index;
|
||||
mLogger.logSelectionStartedEvent(invocationMethod, 0);
|
||||
// TODO: Remove the above legacy logging.
|
||||
mTextClassificationSession.get().onSelectionEvent(
|
||||
SelectionEvent.createSelectionStartedEvent(invocationMethod, 0));
|
||||
} catch (Exception e) {
|
||||
// Avoid crashes due to logging.
|
||||
Log.d(LOG_TAG, e.getMessage());
|
||||
Log.e(LOG_TAG, "" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,16 +718,28 @@ public final class SelectionActionModeHelper {
|
||||
if (selection != null) {
|
||||
mLogger.logSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1], selection);
|
||||
// TODO: Remove the above legacy logging.
|
||||
mTextClassificationSession.get().onSelectionEvent(
|
||||
SelectionEvent.createSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1], selection));
|
||||
} else if (classification != null) {
|
||||
mLogger.logSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1], classification);
|
||||
// TODO: Remove the above legacy logging.
|
||||
mTextClassificationSession.get().onSelectionEvent(
|
||||
SelectionEvent.createSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1], classification));
|
||||
} else {
|
||||
mLogger.logSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1]);
|
||||
// TODO: Remove the above legacy logging.
|
||||
mTextClassificationSession.get().onSelectionEvent(
|
||||
SelectionEvent.createSelectionModifiedEvent(
|
||||
wordIndices[0], wordIndices[1]));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Avoid crashes due to logging.
|
||||
Log.d(LOG_TAG, e.getMessage());
|
||||
Log.e(LOG_TAG, "" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,13 +754,25 @@ public final class SelectionActionModeHelper {
|
||||
if (classification != null) {
|
||||
mLogger.logSelectionActionEvent(
|
||||
wordIndices[0], wordIndices[1], action, classification);
|
||||
// TODO: Remove the above legacy logging.
|
||||
mTextClassificationSession.get().onSelectionEvent(
|
||||
SelectionEvent.createSelectionActionEvent(
|
||||
wordIndices[0], wordIndices[1], action, classification));
|
||||
} else {
|
||||
mLogger.logSelectionActionEvent(
|
||||
wordIndices[0], wordIndices[1], action);
|
||||
// TODO: Remove the above legacy logging.
|
||||
mTextClassificationSession.get().onSelectionEvent(
|
||||
SelectionEvent.createSelectionActionEvent(
|
||||
wordIndices[0], wordIndices[1], action));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Avoid crashes due to logging.
|
||||
Log.d(LOG_TAG, e.getMessage());
|
||||
Log.e(LOG_TAG, "" + e.getMessage(), e);
|
||||
} finally {
|
||||
if (SelectionEvent.isTerminal(action)) {
|
||||
mTextClassificationSession.get().destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -887,7 +917,7 @@ public final class SelectionActionModeHelper {
|
||||
|
||||
private final Context mContext;
|
||||
private final boolean mDarkLaunchEnabled;
|
||||
private TextClassifier mTextClassifier;
|
||||
private Supplier<TextClassifier> mTextClassifier;
|
||||
|
||||
/** The original TextView text. **/
|
||||
private String mText;
|
||||
@@ -919,7 +949,7 @@ public final class SelectionActionModeHelper {
|
||||
/** Whether the TextClassifier has been initialized. */
|
||||
private boolean mHot;
|
||||
|
||||
TextClassificationHelper(Context context, TextClassifier textClassifier,
|
||||
TextClassificationHelper(Context context, Supplier<TextClassifier> textClassifier,
|
||||
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
|
||||
init(textClassifier, text, selectionStart, selectionEnd, locales);
|
||||
mContext = Preconditions.checkNotNull(context);
|
||||
@@ -928,7 +958,7 @@ public final class SelectionActionModeHelper {
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void init(TextClassifier textClassifier, CharSequence text,
|
||||
public void init(Supplier<TextClassifier> textClassifier, CharSequence text,
|
||||
int selectionStart, int selectionEnd, LocaleList locales) {
|
||||
mTextClassifier = Preconditions.checkNotNull(textClassifier);
|
||||
mText = Preconditions.checkNotNull(text).toString();
|
||||
@@ -953,11 +983,11 @@ public final class SelectionActionModeHelper {
|
||||
trimText();
|
||||
final TextSelection selection;
|
||||
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
|
||||
selection = mTextClassifier.suggestSelection(
|
||||
selection = mTextClassifier.get().suggestSelection(
|
||||
mTrimmedText, mRelativeStart, mRelativeEnd, mSelectionOptions);
|
||||
} else {
|
||||
// Use old APIs.
|
||||
selection = mTextClassifier.suggestSelection(
|
||||
selection = mTextClassifier.get().suggestSelection(
|
||||
mTrimmedText, mRelativeStart, mRelativeEnd,
|
||||
mSelectionOptions.getDefaultLocales());
|
||||
}
|
||||
@@ -1006,11 +1036,11 @@ public final class SelectionActionModeHelper {
|
||||
trimText();
|
||||
final TextClassification classification;
|
||||
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
|
||||
classification = mTextClassifier.classifyText(
|
||||
classification = mTextClassifier.get().classifyText(
|
||||
mTrimmedText, mRelativeStart, mRelativeEnd, mClassificationOptions);
|
||||
} else {
|
||||
// Use old APIs.
|
||||
classification = mTextClassifier.classifyText(
|
||||
classification = mTextClassifier.get().classifyText(
|
||||
mTrimmedText, mRelativeStart, mRelativeEnd,
|
||||
mClassificationOptions.getDefaultLocales());
|
||||
}
|
||||
|
||||
@@ -163,6 +163,7 @@ import android.view.inputmethod.ExtractedTextRequest;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.textclassifier.TextClassification;
|
||||
import android.view.textclassifier.TextClassificationContext;
|
||||
import android.view.textclassifier.TextClassificationManager;
|
||||
import android.view.textclassifier.TextClassifier;
|
||||
import android.view.textclassifier.TextLinks;
|
||||
@@ -427,6 +428,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
private boolean mPreDrawListenerDetached;
|
||||
|
||||
private TextClassifier mTextClassifier;
|
||||
private TextClassifier mTextClassificationSession;
|
||||
|
||||
// A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
|
||||
// that if a user is holding down a movement key to traverse text, we shouldn't also traverse
|
||||
@@ -11543,17 +11545,62 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
@NonNull
|
||||
public TextClassifier getTextClassifier() {
|
||||
if (mTextClassifier == null) {
|
||||
TextClassificationManager tcm =
|
||||
final TextClassificationManager tcm =
|
||||
mContext.getSystemService(TextClassificationManager.class);
|
||||
if (tcm != null) {
|
||||
mTextClassifier = tcm.getTextClassifier();
|
||||
} else {
|
||||
mTextClassifier = TextClassifier.NO_OP;
|
||||
return tcm.getTextClassifier();
|
||||
}
|
||||
return TextClassifier.NO_OP;
|
||||
}
|
||||
return mTextClassifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a session-aware text classifier.
|
||||
*/
|
||||
@NonNull
|
||||
TextClassifier getTextClassificationSession() {
|
||||
if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
|
||||
final TextClassificationManager tcm =
|
||||
mContext.getSystemService(TextClassificationManager.class);
|
||||
if (tcm != null) {
|
||||
final String widgetType;
|
||||
if (isTextEditable()) {
|
||||
widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
|
||||
} else if (isTextSelectable()) {
|
||||
widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
|
||||
} else {
|
||||
widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
|
||||
}
|
||||
// TODO: Tagged this widgetType with a * so it we can monitor if it reports
|
||||
// SelectionEvents exactly as the older Logger does. Remove once investigations
|
||||
// are complete.
|
||||
final TextClassificationContext textClassificationContext =
|
||||
new TextClassificationContext.Builder(
|
||||
mContext.getPackageName(), "*" + widgetType)
|
||||
.build();
|
||||
if (mTextClassifier != null) {
|
||||
mTextClassificationSession = tcm.createTextClassificationSession(
|
||||
textClassificationContext, mTextClassifier);
|
||||
} else {
|
||||
mTextClassificationSession = tcm.createTextClassificationSession(
|
||||
textClassificationContext);
|
||||
}
|
||||
} else {
|
||||
mTextClassificationSession = TextClassifier.NO_OP;
|
||||
}
|
||||
}
|
||||
return mTextClassificationSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this TextView uses a no-op TextClassifier.
|
||||
*/
|
||||
boolean usesNoOpTextClassifier() {
|
||||
return getTextClassifier() == TextClassifier.NO_OP;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts an ActionMode for the specified TextLinkSpan.
|
||||
*
|
||||
|
||||
@@ -33,8 +33,11 @@ public class SelectionEventTest {
|
||||
@Test
|
||||
public void testParcel() {
|
||||
final SelectionEvent[] captured = new SelectionEvent[1];
|
||||
final Logger logger = new Logger(new Logger.Config(
|
||||
InstrumentationRegistry.getTargetContext(), Logger.WIDGET_TEXTVIEW, null)) {
|
||||
final Logger logger = new Logger(
|
||||
new Logger.Config(
|
||||
InstrumentationRegistry.getTargetContext(),
|
||||
TextClassifier.WIDGET_TYPE_TEXTVIEW,
|
||||
null)) {
|
||||
@Override
|
||||
public void writeEvent(SelectionEvent event) {
|
||||
captured[0] = event;
|
||||
|
||||
Reference in New Issue
Block a user