Merge "Implement Stateful TextClassifier APIs." into pi-dev
am: 119d10deb9
Change-Id: Ia627d4c265a585b14beebd98a3f6fb20d5d36eca
This commit is contained in:
@@ -50315,38 +50315,13 @@ package android.view.inputmethod {
|
|||||||
|
|
||||||
package android.view.textclassifier {
|
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 {
|
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 int describeContents();
|
||||||
method public long getDurationSincePreviousEvent();
|
method public long getDurationSincePreviousEvent();
|
||||||
method public long getDurationSinceSessionStart();
|
method public long getDurationSinceSessionStart();
|
||||||
@@ -50357,13 +50332,14 @@ package android.view.textclassifier {
|
|||||||
method public int getEventType();
|
method public int getEventType();
|
||||||
method public int getInvocationMethod();
|
method public int getInvocationMethod();
|
||||||
method public java.lang.String getPackageName();
|
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 java.lang.String getSignature();
|
||||||
method public int getSmartEnd();
|
method public int getSmartEnd();
|
||||||
method public int getSmartStart();
|
method public int getSmartStart();
|
||||||
method public int getStart();
|
method public int getStart();
|
||||||
method public java.lang.String getWidgetType();
|
method public java.lang.String getWidgetType();
|
||||||
method public java.lang.String getWidgetVersion();
|
method public java.lang.String getWidgetVersion();
|
||||||
|
method public static boolean isTerminal(int);
|
||||||
method public void writeToParcel(android.os.Parcel, int);
|
method public void writeToParcel(android.os.Parcel, int);
|
||||||
field public static final int ACTION_ABANDON = 107; // 0x6b
|
field public static final int ACTION_ABANDON = 107; // 0x6b
|
||||||
field public static final int ACTION_COPY = 101; // 0x65
|
field public static final int ACTION_COPY = 101; // 0x65
|
||||||
@@ -50384,6 +50360,7 @@ package android.view.textclassifier {
|
|||||||
field public static final int EVENT_SMART_SELECTION_SINGLE = 3; // 0x3
|
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_LINK = 2; // 0x2
|
||||||
field public static final int INVOCATION_MANUAL = 1; // 0x1
|
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 {
|
public final class TextClassification implements android.os.Parcelable {
|
||||||
@@ -50426,19 +50403,45 @@ package android.view.textclassifier {
|
|||||||
field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassification.Options> CREATOR;
|
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 {
|
public final class TextClassificationManager {
|
||||||
|
method public android.view.textclassifier.TextClassifier createTextClassificationSession(android.view.textclassifier.TextClassificationContext);
|
||||||
method public android.view.textclassifier.TextClassifier getTextClassifier();
|
method public android.view.textclassifier.TextClassifier getTextClassifier();
|
||||||
|
method public void setTextClassificationSessionFactory(android.view.textclassifier.TextClassificationSessionFactory);
|
||||||
method public void setTextClassifier(android.view.textclassifier.TextClassifier);
|
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 {
|
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, 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);
|
||||||
method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList);
|
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, android.view.textclassifier.TextLinks.Options);
|
||||||
method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence);
|
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 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, 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);
|
||||||
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
|
method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
|
||||||
@@ -50454,6 +50457,15 @@ package android.view.textclassifier {
|
|||||||
field public static final java.lang.String TYPE_PHONE = "phone";
|
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_UNKNOWN = "";
|
||||||
field public static final java.lang.String TYPE_URL = "url";
|
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 {
|
public static final class TextClassifier.EntityConfig implements android.os.Parcelable {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import java.util.StringJoiner;
|
|||||||
public final class DefaultLogger extends Logger {
|
public final class DefaultLogger extends Logger {
|
||||||
|
|
||||||
private static final String LOG_TAG = "DefaultLogger";
|
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 START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
|
||||||
private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
|
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.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.annotation.StringDef;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.internal.util.Preconditions;
|
import com.android.internal.util.Preconditions;
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.text.BreakIterator;
|
import java.text.BreakIterator;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper for logging TextClassifier related events.
|
* A helper for logging TextClassifier related events.
|
||||||
|
* @hide
|
||||||
*/
|
*/
|
||||||
public abstract class Logger {
|
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";
|
private static final String LOG_TAG = "Logger";
|
||||||
/* package */ static final boolean DEBUG_LOG_ENABLED = true;
|
/* package */ static final boolean DEBUG_LOG_ENABLED = true;
|
||||||
|
|
||||||
private static final String NO_SIGNATURE = "";
|
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.InvocationMethod int mInvocationMethod;
|
||||||
private SelectionEvent mPrevEvent;
|
private SelectionEvent mPrevEvent;
|
||||||
private SelectionEvent mSmartEvent;
|
private SelectionEvent mSmartEvent;
|
||||||
@@ -108,7 +77,6 @@ public abstract class Logger {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a token iterator for tokenizing text for logging purposes.
|
* Returns a token iterator for tokenizing text for logging purposes.
|
||||||
*/
|
*/
|
||||||
@@ -299,6 +267,9 @@ public abstract class Logger {
|
|||||||
// Selection did not change. Ignore event.
|
// Selection did not change. Ignore event.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
event.setEventTime(now);
|
event.setEventTime(now);
|
||||||
@@ -325,9 +296,9 @@ public abstract class Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String startNewSession() {
|
private TextClassificationSessionId startNewSession() {
|
||||||
endSession();
|
endSession();
|
||||||
return UUID.randomUUID().toString();
|
return new TextClassificationSessionId();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endSession() {
|
private void endSession() {
|
||||||
@@ -372,12 +343,12 @@ public abstract class Logger {
|
|||||||
/**
|
/**
|
||||||
* @param context Context of the widget the logger logs for
|
* @param context Context of the widget the logger logs for
|
||||||
* @param widgetType a name for the widget being logged for. e.g.
|
* @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
|
* @param widgetVersion a string version info for the widget the logger logs for
|
||||||
*/
|
*/
|
||||||
public Config(
|
public Config(
|
||||||
@NonNull Context context,
|
@NonNull Context context,
|
||||||
@WidgetType String widgetType,
|
@TextClassifier.WidgetType String widgetType,
|
||||||
@Nullable String widgetVersion) {
|
@Nullable String widgetVersion) {
|
||||||
mPackageName = Preconditions.checkNotNull(context).getPackageName();
|
mPackageName = Preconditions.checkNotNull(context).getPackageName();
|
||||||
mWidgetType = widgetType;
|
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() {
|
public String getWidgetType() {
|
||||||
return mWidgetType;
|
return mWidgetType;
|
||||||
|
|||||||
@@ -17,10 +17,12 @@
|
|||||||
package android.view.textclassifier;
|
package android.view.textclassifier;
|
||||||
|
|
||||||
import android.annotation.IntDef;
|
import android.annotation.IntDef;
|
||||||
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.view.textclassifier.TextClassifier.EntityType;
|
import android.view.textclassifier.TextClassifier.EntityType;
|
||||||
|
import android.view.textclassifier.TextClassifier.WidgetType;
|
||||||
|
|
||||||
import com.android.internal.util.Preconditions;
|
import com.android.internal.util.Preconditions;
|
||||||
|
|
||||||
@@ -103,30 +105,33 @@ public final class SelectionEvent implements Parcelable {
|
|||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({INVOCATION_MANUAL, INVOCATION_LINK})
|
@IntDef({INVOCATION_MANUAL, INVOCATION_LINK, INVOCATION_UNKNOWN})
|
||||||
public @interface InvocationMethod {}
|
public @interface InvocationMethod {}
|
||||||
|
|
||||||
/** Selection was invoked by the user long pressing, double tapping, or dragging to select. */
|
/** Selection was invoked by the user long pressing, double tapping, or dragging to select. */
|
||||||
public static final int INVOCATION_MANUAL = 1;
|
public static final int INVOCATION_MANUAL = 1;
|
||||||
/** Selection was invoked by the user tapping on a link. */
|
/** Selection was invoked by the user tapping on a link. */
|
||||||
public static final int INVOCATION_LINK = 2;
|
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 mAbsoluteStart;
|
||||||
private final int mAbsoluteEnd;
|
private final int mAbsoluteEnd;
|
||||||
private final @EventType int mEventType;
|
|
||||||
private final @EntityType String mEntityType;
|
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 @EventType int mEventType;
|
||||||
private String mSignature;
|
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 mEventTime;
|
||||||
private long mDurationSinceSessionStart;
|
private long mDurationSinceSessionStart;
|
||||||
private long mDurationSincePreviousEvent;
|
private long mDurationSincePreviousEvent;
|
||||||
private int mEventIndex;
|
private int mEventIndex;
|
||||||
@Nullable private String mSessionId;
|
@Nullable private TextClassificationSessionId mSessionId;
|
||||||
private int mStart;
|
private int mStart;
|
||||||
private int mEnd;
|
private int mEnd;
|
||||||
private int mSmartStart;
|
private int mSmartStart;
|
||||||
@@ -135,20 +140,29 @@ public final class SelectionEvent implements Parcelable {
|
|||||||
SelectionEvent(
|
SelectionEvent(
|
||||||
int start, int end,
|
int start, int end,
|
||||||
@EventType int eventType, @EntityType String entityType,
|
@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");
|
Preconditions.checkArgument(end >= start, "end cannot be less than start");
|
||||||
mAbsoluteStart = start;
|
mAbsoluteStart = start;
|
||||||
mAbsoluteEnd = end;
|
mAbsoluteEnd = end;
|
||||||
mEventType = eventType;
|
mEventType = eventType;
|
||||||
mEntityType = Preconditions.checkNotNull(entityType);
|
mEntityType = Preconditions.checkNotNull(entityType);
|
||||||
mSignature = Preconditions.checkNotNull(signature);
|
mSignature = Preconditions.checkNotNull(signature);
|
||||||
Preconditions.checkNotNull(config);
|
|
||||||
mWidgetVersion = config.getWidgetVersion();
|
|
||||||
mPackageName = Preconditions.checkNotNull(config.getPackageName());
|
|
||||||
mWidgetType = Preconditions.checkNotNull(config.getWidgetType());
|
|
||||||
mInvocationMethod = invocationMethod;
|
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) {
|
private SelectionEvent(Parcel in) {
|
||||||
mAbsoluteStart = in.readInt();
|
mAbsoluteStart = in.readInt();
|
||||||
mAbsoluteEnd = in.readInt();
|
mAbsoluteEnd = in.readInt();
|
||||||
@@ -163,7 +177,8 @@ public final class SelectionEvent implements Parcelable {
|
|||||||
mDurationSinceSessionStart = in.readLong();
|
mDurationSinceSessionStart = in.readLong();
|
||||||
mDurationSincePreviousEvent = in.readLong();
|
mDurationSincePreviousEvent = in.readLong();
|
||||||
mEventIndex = in.readInt();
|
mEventIndex = in.readInt();
|
||||||
mSessionId = in.readInt() > 0 ? in.readString() : null;
|
mSessionId = in.readInt() > 0
|
||||||
|
? TextClassificationSessionId.CREATOR.createFromParcel(in) : null;
|
||||||
mStart = in.readInt();
|
mStart = in.readInt();
|
||||||
mEnd = in.readInt();
|
mEnd = in.readInt();
|
||||||
mSmartStart = in.readInt();
|
mSmartStart = in.readInt();
|
||||||
@@ -190,7 +205,7 @@ public final class SelectionEvent implements Parcelable {
|
|||||||
dest.writeInt(mEventIndex);
|
dest.writeInt(mEventIndex);
|
||||||
dest.writeInt(mSessionId != null ? 1 : 0);
|
dest.writeInt(mSessionId != null ? 1 : 0);
|
||||||
if (mSessionId != null) {
|
if (mSessionId != null) {
|
||||||
dest.writeString(mSessionId);
|
mSessionId.writeToParcel(dest, flags);
|
||||||
}
|
}
|
||||||
dest.writeInt(mStart);
|
dest.writeInt(mStart);
|
||||||
dest.writeInt(mEnd);
|
dest.writeInt(mEnd);
|
||||||
@@ -203,6 +218,156 @@ public final class SelectionEvent implements Parcelable {
|
|||||||
return 0;
|
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() {
|
int getAbsoluteStart() {
|
||||||
return mAbsoluteStart;
|
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}.
|
* Returns the type of event that was triggered. e.g. {@link #ACTION_COPY}.
|
||||||
*/
|
*/
|
||||||
|
@EventType
|
||||||
public int getEventType() {
|
public int getEventType() {
|
||||||
return mEventType;
|
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.
|
* Returns the type of entity that is associated with this event. e.g.
|
||||||
* {@link android.view.textclassifier.TextClassifier#TYPE_EMAIL}.
|
* {@link android.view.textclassifier.TextClassifier#TYPE_EMAIL}.
|
||||||
*/
|
*/
|
||||||
@EntityType
|
@EntityType
|
||||||
|
@NonNull
|
||||||
public String getEntityType() {
|
public String getEntityType() {
|
||||||
return mEntityType;
|
return mEntityType;
|
||||||
}
|
}
|
||||||
@@ -230,6 +404,7 @@ public final class SelectionEvent implements Parcelable {
|
|||||||
/**
|
/**
|
||||||
* Returns the package name of the app that this event originated in.
|
* Returns the package name of the app that this event originated in.
|
||||||
*/
|
*/
|
||||||
|
@NonNull
|
||||||
public String getPackageName() {
|
public String getPackageName() {
|
||||||
return mPackageName;
|
return mPackageName;
|
||||||
}
|
}
|
||||||
@@ -237,6 +412,8 @@ public final class SelectionEvent implements Parcelable {
|
|||||||
/**
|
/**
|
||||||
* Returns the type of widget that was involved in triggering this event.
|
* Returns the type of widget that was involved in triggering this event.
|
||||||
*/
|
*/
|
||||||
|
@WidgetType
|
||||||
|
@NonNull
|
||||||
public String getWidgetType() {
|
public String getWidgetType() {
|
||||||
return mWidgetType;
|
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.
|
* Returns a string version info for the widget this event was triggered in.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public String getWidgetVersion() {
|
public String getWidgetVersion() {
|
||||||
return mWidgetVersion;
|
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.
|
* Returns the way the selection mode was invoked.
|
||||||
*/
|
*/
|
||||||
@@ -255,6 +442,13 @@ public final class SelectionEvent implements Parcelable {
|
|||||||
return mInvocationMethod;
|
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.
|
* 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.
|
* Returns the selection session id.
|
||||||
*/
|
*/
|
||||||
public String getSessionId() {
|
@Nullable
|
||||||
|
public TextClassificationSessionId getSessionId() {
|
||||||
return mSessionId;
|
return mSessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectionEvent setSessionId(String id) {
|
SelectionEvent setSessionId(TextClassificationSessionId id) {
|
||||||
mSessionId = id;
|
mSessionId = id;
|
||||||
return this;
|
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.
|
* event in the selection session.
|
||||||
*/
|
*/
|
||||||
public int getStart() {
|
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.
|
* event in the selection session.
|
||||||
*/
|
*/
|
||||||
public int getEnd() {
|
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.
|
* event in the selection session.
|
||||||
*/
|
*/
|
||||||
public int getSmartStart() {
|
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.
|
* event in the selection session.
|
||||||
*/
|
*/
|
||||||
public int getSmartEnd() {
|
public int getSmartEnd() {
|
||||||
@@ -382,7 +577,15 @@ public final class SelectionEvent implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean isTerminal() {
|
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_OVERTYPE: // fall through
|
||||||
case ACTION_COPY: // fall through
|
case ACTION_COPY: // fall through
|
||||||
case ACTION_PASTE: // 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;
|
package android.view.textclassifier;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.annotation.SystemService;
|
import android.annotation.SystemService;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -36,6 +37,9 @@ public final class TextClassificationManager {
|
|||||||
private static final String LOG_TAG = "TextClassificationManager";
|
private static final String LOG_TAG = "TextClassificationManager";
|
||||||
|
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
|
private final TextClassificationSessionFactory mDefaultSessionFactory =
|
||||||
|
classificationContext -> new TextClassificationSession(
|
||||||
|
classificationContext, getTextClassifier());
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final TextClassificationConstants mSettings;
|
private final TextClassificationConstants mSettings;
|
||||||
@@ -46,12 +50,15 @@ public final class TextClassificationManager {
|
|||||||
private TextClassifier mLocalTextClassifier;
|
private TextClassifier mLocalTextClassifier;
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private TextClassifier mSystemTextClassifier;
|
private TextClassifier mSystemTextClassifier;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private TextClassificationSessionFactory mSessionFactory;
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public TextClassificationManager(Context context) {
|
public TextClassificationManager(Context context) {
|
||||||
mContext = Preconditions.checkNotNull(context);
|
mContext = Preconditions.checkNotNull(context);
|
||||||
mSettings = TextClassificationConstants.loadFromString(Settings.Global.getString(
|
mSettings = TextClassificationConstants.loadFromString(Settings.Global.getString(
|
||||||
context.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
|
context.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
|
||||||
|
mSessionFactory = mDefaultSessionFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,6 +68,7 @@ public final class TextClassificationManager {
|
|||||||
*
|
*
|
||||||
* @see #setTextClassifier(TextClassifier)
|
* @see #setTextClassifier(TextClassifier)
|
||||||
*/
|
*/
|
||||||
|
@NonNull
|
||||||
public TextClassifier getTextClassifier() {
|
public TextClassifier getTextClassifier() {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (mTextClassifier == null) {
|
if (mTextClassifier == null) {
|
||||||
@@ -93,7 +101,6 @@ public final class TextClassificationManager {
|
|||||||
* @see TextClassifier#SYSTEM
|
* @see TextClassifier#SYSTEM
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
// TODO: Expose as system API.
|
|
||||||
public TextClassifier getTextClassifier(@TextClassifierType int type) {
|
public TextClassifier getTextClassifier(@TextClassifierType int type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TextClassifier.LOCAL:
|
case TextClassifier.LOCAL:
|
||||||
@@ -108,6 +115,61 @@ public final class TextClassificationManager {
|
|||||||
return mSettings;
|
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() {
|
private TextClassifier getSystemTextClassifier() {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) {
|
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})
|
@StringDef(prefix = { "HINT_" }, value = {HINT_TEXT_IS_EDITABLE, HINT_TEXT_IS_NOT_EDITABLE})
|
||||||
@interface Hints {}
|
@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.
|
* No-op TextClassifier.
|
||||||
* This may be used to turn off TextClassifier features.
|
* 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.
|
* <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
|
* @param text text providing context for the selected text (which is specified
|
||||||
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
|
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
|
||||||
* @param selectionStartIndex start index of the selected part of text
|
* @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.
|
* <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
|
* @param text text providing context for the selected text (which is specified
|
||||||
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
|
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
|
||||||
* @param selectionStartIndex start index of the selected part of text
|
* @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
|
* <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
|
||||||
* {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method
|
* {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method
|
||||||
* calls this method, a stack overflow error will happen.
|
* 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
|
@WorkerThread
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -205,6 +246,9 @@ public interface TextClassifier {
|
|||||||
*
|
*
|
||||||
* <p><strong>NOTE: </strong>Call on a worker thread.
|
* <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
|
* @param text text providing context for the text to classify (which is specified
|
||||||
* by the sub sequence starting at startIndex and ending at endIndex)
|
* by the sub sequence starting at startIndex and ending at endIndex)
|
||||||
* @param startIndex start index of the text to classify
|
* @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
|
* {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method
|
||||||
* calls this method, a stack overflow error will happen.
|
* 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
|
* @param text text providing context for the text to classify (which is specified
|
||||||
* by the sub sequence starting at startIndex and ending at endIndex)
|
* by the sub sequence starting at startIndex and ending at endIndex)
|
||||||
* @param startIndex start index of the text to classify
|
* @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
|
* <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
|
||||||
* {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method
|
* {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method
|
||||||
* calls this method, a stack overflow error will happen.
|
* 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
|
@WorkerThread
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -285,6 +335,9 @@ public interface TextClassifier {
|
|||||||
*
|
*
|
||||||
* <p><strong>NOTE: </strong>Call on a worker thread.
|
* <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 text the text to generate annotations for
|
||||||
* @param options configuration for link generation
|
* @param options configuration for link generation
|
||||||
*
|
*
|
||||||
@@ -295,6 +348,7 @@ public interface TextClassifier {
|
|||||||
* @see #getMaxGenerateLinksTextLength()
|
* @see #getMaxGenerateLinksTextLength()
|
||||||
*/
|
*/
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
@NonNull
|
||||||
default TextLinks generateLinks(
|
default TextLinks generateLinks(
|
||||||
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
|
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
|
||||||
Utils.validate(text, false);
|
Utils.validate(text, false);
|
||||||
@@ -311,6 +365,9 @@ public interface TextClassifier {
|
|||||||
* {@link #generateLinks(CharSequence, TextLinks.Options)}. If that method calls this method,
|
* {@link #generateLinks(CharSequence, TextLinks.Options)}. If that method calls this method,
|
||||||
* a stack overflow error will happen.
|
* 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
|
* @param text the text to generate annotations for
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if text is null or the text is too long for the
|
* @throws IllegalArgumentException if text is null or the text is too long for the
|
||||||
@@ -320,6 +377,7 @@ public interface TextClassifier {
|
|||||||
* @see #getMaxGenerateLinksTextLength()
|
* @see #getMaxGenerateLinksTextLength()
|
||||||
*/
|
*/
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
@NonNull
|
||||||
default TextLinks generateLinks(@NonNull CharSequence text) {
|
default TextLinks generateLinks(@NonNull CharSequence text) {
|
||||||
return generateLinks(text, null);
|
return generateLinks(text, null);
|
||||||
}
|
}
|
||||||
@@ -327,6 +385,9 @@ public interface TextClassifier {
|
|||||||
/**
|
/**
|
||||||
* Returns the maximal length of text that can be processed by generateLinks.
|
* 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)
|
||||||
* @see #generateLinks(CharSequence, TextLinks.Options)
|
* @see #generateLinks(CharSequence, TextLinks.Options)
|
||||||
*/
|
*/
|
||||||
@@ -339,6 +400,7 @@ public interface TextClassifier {
|
|||||||
* Returns a helper for logging TextClassifier related events.
|
* Returns a helper for logging TextClassifier related events.
|
||||||
*
|
*
|
||||||
* @param config logger configuration
|
* @param config logger configuration
|
||||||
|
* @hide
|
||||||
*/
|
*/
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
default Logger getLogger(@NonNull Logger.Config config) {
|
default Logger getLogger(@NonNull Logger.Config config) {
|
||||||
@@ -346,6 +408,37 @@ public interface TextClassifier {
|
|||||||
return Logger.DISABLED;
|
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.
|
* Configuration object for specifying what entities to identify.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ public final class TextClassifierImpl implements TextClassifier {
|
|||||||
private Logger.Config mLoggerConfig;
|
private Logger.Config mLoggerConfig;
|
||||||
@GuardedBy("mLoggerLock") // Do not access outside this lock.
|
@GuardedBy("mLoggerLock") // Do not access outside this lock.
|
||||||
private Logger mLogger;
|
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;
|
private final TextClassificationConstants mSettings;
|
||||||
|
|
||||||
@@ -300,6 +302,18 @@ public final class TextClassifierImpl implements TextClassifier {
|
|||||||
return mLogger;
|
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)
|
private TextClassifierImplNative getNative(LocaleList localeList)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import android.util.Log;
|
|||||||
import android.view.ActionMode;
|
import android.view.ActionMode;
|
||||||
import android.view.textclassifier.Logger;
|
import android.view.textclassifier.Logger;
|
||||||
import android.view.textclassifier.SelectionEvent;
|
import android.view.textclassifier.SelectionEvent;
|
||||||
|
import android.view.textclassifier.SelectionEvent.InvocationMethod;
|
||||||
import android.view.textclassifier.TextClassification;
|
import android.view.textclassifier.TextClassification;
|
||||||
import android.view.textclassifier.TextClassificationConstants;
|
import android.view.textclassifier.TextClassificationConstants;
|
||||||
import android.view.textclassifier.TextClassificationManager;
|
import android.view.textclassifier.TextClassificationManager;
|
||||||
@@ -86,7 +87,7 @@ public final class SelectionActionModeHelper {
|
|||||||
mTextClassificationSettings = TextClassificationManager.getSettings(mTextView.getContext());
|
mTextClassificationSettings = TextClassificationManager.getSettings(mTextView.getContext());
|
||||||
mTextClassificationHelper = new TextClassificationHelper(
|
mTextClassificationHelper = new TextClassificationHelper(
|
||||||
mTextView.getContext(),
|
mTextView.getContext(),
|
||||||
mTextView.getTextClassifier(),
|
mTextView::getTextClassifier,
|
||||||
getText(mTextView),
|
getText(mTextView),
|
||||||
0, 1, mTextView.getTextLocales());
|
0, 1, mTextView.getTextLocales());
|
||||||
mSelectionTracker = new SelectionTracker(mTextView);
|
mSelectionTracker = new SelectionTracker(mTextView);
|
||||||
@@ -221,7 +222,7 @@ public final class SelectionActionModeHelper {
|
|||||||
|
|
||||||
private boolean skipTextClassification() {
|
private boolean skipTextClassification() {
|
||||||
// No need to make an async call for a no-op TextClassifier.
|
// 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.
|
// Do not call the TextClassifier if there is no selection.
|
||||||
final boolean noSelection = mTextView.getSelectionEnd() == mTextView.getSelectionStart();
|
final boolean noSelection = mTextView.getSelectionEnd() == mTextView.getSelectionStart();
|
||||||
// Do not call the TextClassifier if this is a password field.
|
// Do not call the TextClassifier if this is a password field.
|
||||||
@@ -447,7 +448,7 @@ public final class SelectionActionModeHelper {
|
|||||||
selectionEnd = mTextView.getSelectionEnd();
|
selectionEnd = mTextView.getSelectionEnd();
|
||||||
}
|
}
|
||||||
mTextClassificationHelper.init(
|
mTextClassificationHelper.init(
|
||||||
mTextView.getTextClassifier(),
|
mTextView::getTextClassifier,
|
||||||
getText(mTextView),
|
getText(mTextView),
|
||||||
selectionStart, selectionEnd,
|
selectionStart, selectionEnd,
|
||||||
mTextView.getTextLocales());
|
mTextView.getTextLocales());
|
||||||
@@ -660,6 +661,7 @@ public final class SelectionActionModeHelper {
|
|||||||
private static final String LOG_TAG = "SelectionMetricsLogger";
|
private static final String LOG_TAG = "SelectionMetricsLogger";
|
||||||
private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s+");
|
private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s+");
|
||||||
|
|
||||||
|
private final Supplier<TextClassifier> mTextClassificationSession;
|
||||||
private final Logger mLogger;
|
private final Logger mLogger;
|
||||||
private final boolean mEditTextLogger;
|
private final boolean mEditTextLogger;
|
||||||
private final BreakIterator mTokenIterator;
|
private final BreakIterator mTokenIterator;
|
||||||
@@ -668,26 +670,27 @@ public final class SelectionActionModeHelper {
|
|||||||
|
|
||||||
SelectionMetricsLogger(TextView textView) {
|
SelectionMetricsLogger(TextView textView) {
|
||||||
Preconditions.checkNotNull(textView);
|
Preconditions.checkNotNull(textView);
|
||||||
|
mTextClassificationSession = textView::getTextClassificationSession;
|
||||||
mLogger = textView.getTextClassifier().getLogger(
|
mLogger = textView.getTextClassifier().getLogger(
|
||||||
new Logger.Config(textView.getContext(), getWidetType(textView), null));
|
new Logger.Config(textView.getContext(), getWidetType(textView), null));
|
||||||
mEditTextLogger = textView.isTextEditable();
|
mEditTextLogger = textView.isTextEditable();
|
||||||
mTokenIterator = mLogger.getTokenIterator(textView.getTextLocale());
|
mTokenIterator = mLogger.getTokenIterator(textView.getTextLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Logger.WidgetType
|
@TextClassifier.WidgetType
|
||||||
private static String getWidetType(TextView textView) {
|
private static String getWidetType(TextView textView) {
|
||||||
if (textView.isTextEditable()) {
|
if (textView.isTextEditable()) {
|
||||||
return Logger.WIDGET_EDITTEXT;
|
return TextClassifier.WIDGET_TYPE_EDITTEXT;
|
||||||
}
|
}
|
||||||
if (textView.isTextSelectable()) {
|
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(
|
public void logSelectionStarted(
|
||||||
CharSequence text, int index,
|
CharSequence text, int index,
|
||||||
@SelectionEvent.InvocationMethod int invocationMethod) {
|
@InvocationMethod int invocationMethod) {
|
||||||
try {
|
try {
|
||||||
Preconditions.checkNotNull(text);
|
Preconditions.checkNotNull(text);
|
||||||
Preconditions.checkArgumentInRange(index, 0, text.length(), "index");
|
Preconditions.checkArgumentInRange(index, 0, text.length(), "index");
|
||||||
@@ -697,9 +700,12 @@ public final class SelectionActionModeHelper {
|
|||||||
mTokenIterator.setText(mText);
|
mTokenIterator.setText(mText);
|
||||||
mStartIndex = index;
|
mStartIndex = index;
|
||||||
mLogger.logSelectionStartedEvent(invocationMethod, 0);
|
mLogger.logSelectionStartedEvent(invocationMethod, 0);
|
||||||
|
// TODO: Remove the above legacy logging.
|
||||||
|
mTextClassificationSession.get().onSelectionEvent(
|
||||||
|
SelectionEvent.createSelectionStartedEvent(invocationMethod, 0));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Avoid crashes due to logging.
|
// 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) {
|
if (selection != null) {
|
||||||
mLogger.logSelectionModifiedEvent(
|
mLogger.logSelectionModifiedEvent(
|
||||||
wordIndices[0], wordIndices[1], selection);
|
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) {
|
} else if (classification != null) {
|
||||||
mLogger.logSelectionModifiedEvent(
|
mLogger.logSelectionModifiedEvent(
|
||||||
wordIndices[0], wordIndices[1], classification);
|
wordIndices[0], wordIndices[1], classification);
|
||||||
|
// TODO: Remove the above legacy logging.
|
||||||
|
mTextClassificationSession.get().onSelectionEvent(
|
||||||
|
SelectionEvent.createSelectionModifiedEvent(
|
||||||
|
wordIndices[0], wordIndices[1], classification));
|
||||||
} else {
|
} else {
|
||||||
mLogger.logSelectionModifiedEvent(
|
mLogger.logSelectionModifiedEvent(
|
||||||
wordIndices[0], wordIndices[1]);
|
wordIndices[0], wordIndices[1]);
|
||||||
|
// TODO: Remove the above legacy logging.
|
||||||
|
mTextClassificationSession.get().onSelectionEvent(
|
||||||
|
SelectionEvent.createSelectionModifiedEvent(
|
||||||
|
wordIndices[0], wordIndices[1]));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Avoid crashes due to logging.
|
// 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) {
|
if (classification != null) {
|
||||||
mLogger.logSelectionActionEvent(
|
mLogger.logSelectionActionEvent(
|
||||||
wordIndices[0], wordIndices[1], action, classification);
|
wordIndices[0], wordIndices[1], action, classification);
|
||||||
|
// TODO: Remove the above legacy logging.
|
||||||
|
mTextClassificationSession.get().onSelectionEvent(
|
||||||
|
SelectionEvent.createSelectionActionEvent(
|
||||||
|
wordIndices[0], wordIndices[1], action, classification));
|
||||||
} else {
|
} else {
|
||||||
mLogger.logSelectionActionEvent(
|
mLogger.logSelectionActionEvent(
|
||||||
wordIndices[0], wordIndices[1], action);
|
wordIndices[0], wordIndices[1], action);
|
||||||
|
// TODO: Remove the above legacy logging.
|
||||||
|
mTextClassificationSession.get().onSelectionEvent(
|
||||||
|
SelectionEvent.createSelectionActionEvent(
|
||||||
|
wordIndices[0], wordIndices[1], action));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Avoid crashes due to logging.
|
// 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 Context mContext;
|
||||||
private final boolean mDarkLaunchEnabled;
|
private final boolean mDarkLaunchEnabled;
|
||||||
private TextClassifier mTextClassifier;
|
private Supplier<TextClassifier> mTextClassifier;
|
||||||
|
|
||||||
/** The original TextView text. **/
|
/** The original TextView text. **/
|
||||||
private String mText;
|
private String mText;
|
||||||
@@ -919,7 +949,7 @@ public final class SelectionActionModeHelper {
|
|||||||
/** Whether the TextClassifier has been initialized. */
|
/** Whether the TextClassifier has been initialized. */
|
||||||
private boolean mHot;
|
private boolean mHot;
|
||||||
|
|
||||||
TextClassificationHelper(Context context, TextClassifier textClassifier,
|
TextClassificationHelper(Context context, Supplier<TextClassifier> textClassifier,
|
||||||
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
|
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
|
||||||
init(textClassifier, text, selectionStart, selectionEnd, locales);
|
init(textClassifier, text, selectionStart, selectionEnd, locales);
|
||||||
mContext = Preconditions.checkNotNull(context);
|
mContext = Preconditions.checkNotNull(context);
|
||||||
@@ -928,7 +958,7 @@ public final class SelectionActionModeHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
public void init(TextClassifier textClassifier, CharSequence text,
|
public void init(Supplier<TextClassifier> textClassifier, CharSequence text,
|
||||||
int selectionStart, int selectionEnd, LocaleList locales) {
|
int selectionStart, int selectionEnd, LocaleList locales) {
|
||||||
mTextClassifier = Preconditions.checkNotNull(textClassifier);
|
mTextClassifier = Preconditions.checkNotNull(textClassifier);
|
||||||
mText = Preconditions.checkNotNull(text).toString();
|
mText = Preconditions.checkNotNull(text).toString();
|
||||||
@@ -953,11 +983,11 @@ public final class SelectionActionModeHelper {
|
|||||||
trimText();
|
trimText();
|
||||||
final TextSelection selection;
|
final TextSelection selection;
|
||||||
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
|
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
|
||||||
selection = mTextClassifier.suggestSelection(
|
selection = mTextClassifier.get().suggestSelection(
|
||||||
mTrimmedText, mRelativeStart, mRelativeEnd, mSelectionOptions);
|
mTrimmedText, mRelativeStart, mRelativeEnd, mSelectionOptions);
|
||||||
} else {
|
} else {
|
||||||
// Use old APIs.
|
// Use old APIs.
|
||||||
selection = mTextClassifier.suggestSelection(
|
selection = mTextClassifier.get().suggestSelection(
|
||||||
mTrimmedText, mRelativeStart, mRelativeEnd,
|
mTrimmedText, mRelativeStart, mRelativeEnd,
|
||||||
mSelectionOptions.getDefaultLocales());
|
mSelectionOptions.getDefaultLocales());
|
||||||
}
|
}
|
||||||
@@ -1006,11 +1036,11 @@ public final class SelectionActionModeHelper {
|
|||||||
trimText();
|
trimText();
|
||||||
final TextClassification classification;
|
final TextClassification classification;
|
||||||
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
|
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
|
||||||
classification = mTextClassifier.classifyText(
|
classification = mTextClassifier.get().classifyText(
|
||||||
mTrimmedText, mRelativeStart, mRelativeEnd, mClassificationOptions);
|
mTrimmedText, mRelativeStart, mRelativeEnd, mClassificationOptions);
|
||||||
} else {
|
} else {
|
||||||
// Use old APIs.
|
// Use old APIs.
|
||||||
classification = mTextClassifier.classifyText(
|
classification = mTextClassifier.get().classifyText(
|
||||||
mTrimmedText, mRelativeStart, mRelativeEnd,
|
mTrimmedText, mRelativeStart, mRelativeEnd,
|
||||||
mClassificationOptions.getDefaultLocales());
|
mClassificationOptions.getDefaultLocales());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ import android.view.inputmethod.ExtractedTextRequest;
|
|||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.view.textclassifier.TextClassification;
|
import android.view.textclassifier.TextClassification;
|
||||||
|
import android.view.textclassifier.TextClassificationContext;
|
||||||
import android.view.textclassifier.TextClassificationManager;
|
import android.view.textclassifier.TextClassificationManager;
|
||||||
import android.view.textclassifier.TextClassifier;
|
import android.view.textclassifier.TextClassifier;
|
||||||
import android.view.textclassifier.TextLinks;
|
import android.view.textclassifier.TextLinks;
|
||||||
@@ -427,6 +428,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
|||||||
private boolean mPreDrawListenerDetached;
|
private boolean mPreDrawListenerDetached;
|
||||||
|
|
||||||
private TextClassifier mTextClassifier;
|
private TextClassifier mTextClassifier;
|
||||||
|
private TextClassifier mTextClassificationSession;
|
||||||
|
|
||||||
// A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
|
// 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
|
// that if a user is holding down a movement key to traverse text, we shouldn't also traverse
|
||||||
@@ -11527,17 +11529,62 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
|||||||
@NonNull
|
@NonNull
|
||||||
public TextClassifier getTextClassifier() {
|
public TextClassifier getTextClassifier() {
|
||||||
if (mTextClassifier == null) {
|
if (mTextClassifier == null) {
|
||||||
TextClassificationManager tcm =
|
final TextClassificationManager tcm =
|
||||||
mContext.getSystemService(TextClassificationManager.class);
|
mContext.getSystemService(TextClassificationManager.class);
|
||||||
if (tcm != null) {
|
if (tcm != null) {
|
||||||
mTextClassifier = tcm.getTextClassifier();
|
return tcm.getTextClassifier();
|
||||||
} else {
|
|
||||||
mTextClassifier = TextClassifier.NO_OP;
|
|
||||||
}
|
}
|
||||||
|
return TextClassifier.NO_OP;
|
||||||
}
|
}
|
||||||
return mTextClassifier;
|
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.
|
* Starts an ActionMode for the specified TextLinkSpan.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -33,8 +33,11 @@ public class SelectionEventTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testParcel() {
|
public void testParcel() {
|
||||||
final SelectionEvent[] captured = new SelectionEvent[1];
|
final SelectionEvent[] captured = new SelectionEvent[1];
|
||||||
final Logger logger = new Logger(new Logger.Config(
|
final Logger logger = new Logger(
|
||||||
InstrumentationRegistry.getTargetContext(), Logger.WIDGET_TEXTVIEW, null)) {
|
new Logger.Config(
|
||||||
|
InstrumentationRegistry.getTargetContext(),
|
||||||
|
TextClassifier.WIDGET_TYPE_TEXTVIEW,
|
||||||
|
null)) {
|
||||||
@Override
|
@Override
|
||||||
public void writeEvent(SelectionEvent event) {
|
public void writeEvent(SelectionEvent event) {
|
||||||
captured[0] = event;
|
captured[0] = event;
|
||||||
|
|||||||
Reference in New Issue
Block a user