blob: 6d5077a99c62d1ae8fb1ea9c77ef529b9bfe1970 [file] [log] [blame]
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +00001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view.textclassifier;
18
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000019import android.annotation.IntDef;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000020import android.annotation.IntRange;
21import android.annotation.NonNull;
Abodunrinwa Toki4cfda0b2017-02-28 18:56:47 +000022import android.annotation.Nullable;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000023import android.annotation.StringDef;
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +010024import android.annotation.WorkerThread;
Abodunrinwa Toki4cfda0b2017-02-28 18:56:47 +000025import android.os.LocaleList;
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080026import android.os.Looper;
Jan Althaus0d9fbb92017-11-28 12:19:33 +010027import android.os.Parcel;
28import android.os.Parcelable;
Abodunrinwa Toki65638332018-03-16 21:08:50 +000029import android.text.Spannable;
30import android.text.SpannableString;
31import android.text.style.URLSpan;
32import android.text.util.Linkify;
33import android.text.util.Linkify.LinkifyMask;
34import android.util.ArrayMap;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000035
Tony Mak72e17972019-03-16 10:28:42 +000036import com.android.internal.annotations.GuardedBy;
Tony Makf93e9e52018-07-16 14:46:29 +020037import com.android.internal.util.IndentingPrintWriter;
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000038import com.android.internal.util.Preconditions;
39
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000040import java.lang.annotation.Retention;
41import java.lang.annotation.RetentionPolicy;
Tony Mak72e17972019-03-16 10:28:42 +000042import java.text.BreakIterator;
Richard Ledleydb18a572017-11-30 17:33:51 +000043import java.util.ArrayList;
44import java.util.Collection;
45import java.util.Collections;
Richard Ledleyab669a02018-04-03 15:15:43 +010046import java.util.HashSet;
Tony Makae85aae2019-01-09 15:59:56 +000047import java.util.List;
Abodunrinwa Toki65638332018-03-16 21:08:50 +000048import java.util.Map;
Daulet Zhanguzine1559472019-12-18 14:17:56 +000049import java.util.Objects;
Richard Ledleyab669a02018-04-03 15:15:43 +010050import java.util.Set;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000051
52/**
53 * Interface for providing text classification related features.
Abodunrinwa Tokiaa750a42018-11-09 13:47:59 +000054 * <p>
55 * The TextClassifier may be used to understand the meaning of text, as well as generating predicted
56 * next actions based on the text.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000057 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080058 * <p><strong>NOTE: </strong>Unless otherwise stated, methods of this interface are blocking
59 * operations. Call on a worker thread.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000060 */
61public interface TextClassifier {
62
Abodunrinwa Toki692b1962017-08-15 15:05:11 +010063 /** @hide */
Tony Mak293bdf32020-02-18 11:33:43 +000064 String LOG_TAG = "androidtc";
Abodunrinwa Toki692b1962017-08-15 15:05:11 +010065
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000066
67 /** @hide */
68 @Retention(RetentionPolicy.SOURCE)
Tony Mak293bdf32020-02-18 11:33:43 +000069 @IntDef(value = {LOCAL, SYSTEM, DEFAULT_SYSTEM})
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000070 @interface TextClassifierType {} // TODO: Expose as system APIs.
71 /** Specifies a TextClassifier that runs locally in the app's process. @hide */
72 int LOCAL = 0;
73 /** Specifies a TextClassifier that runs in the system process and serves all apps. @hide */
74 int SYSTEM = 1;
Tony Makc5a74322020-02-04 17:18:15 +000075 /** Specifies the default TextClassifier that runs in the system process. @hide */
Tony Mak293bdf32020-02-18 11:33:43 +000076 int DEFAULT_SYSTEM = 2;
77
78 /** @hide */
79 static String typeToString(@TextClassifierType int type) {
80 switch (type) {
81 case LOCAL:
82 return "Local";
83 case SYSTEM:
84 return "System";
85 case DEFAULT_SYSTEM:
86 return "Default system";
87 }
88 return "Unknown";
89 }
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000090
Jan Althaus705b9e92018-01-22 18:22:29 +010091 /** The TextClassifier failed to run. */
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000092 String TYPE_UNKNOWN = "";
Jan Althaus705b9e92018-01-22 18:22:29 +010093 /** The classifier ran, but didn't recognize a known entity. */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000094 String TYPE_OTHER = "other";
Jan Althaus705b9e92018-01-22 18:22:29 +010095 /** E-mail address (e.g. "noreply@android.com"). */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000096 String TYPE_EMAIL = "email";
Jan Althaus705b9e92018-01-22 18:22:29 +010097 /** Phone number (e.g. "555-123 456"). */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000098 String TYPE_PHONE = "phone";
Jan Althaus705b9e92018-01-22 18:22:29 +010099 /** Physical address. */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000100 String TYPE_ADDRESS = "address";
Jan Althaus705b9e92018-01-22 18:22:29 +0100101 /** Web URL. */
Abodunrinwa Toki9b4c82a2017-02-06 20:29:36 +0000102 String TYPE_URL = "url";
Jan Althaus705b9e92018-01-22 18:22:29 +0100103 /** Time reference that is no more specific than a date. May be absolute such as "01/01/2000" or
104 * relative like "tomorrow". **/
105 String TYPE_DATE = "date";
106 /** Time reference that includes a specific time. May be absolute such as "01/01/2000 5:30pm" or
107 * relative like "tomorrow at 5:30pm". **/
108 String TYPE_DATE_TIME = "datetime";
109 /** Flight number in IATA format. */
110 String TYPE_FLIGHT_NUMBER = "flight";
Tony Make1f3ac062018-11-27 14:30:21 +0000111 /**
112 * Word that users may be interested to look up for meaning.
113 * @hide
114 */
115 String TYPE_DICTIONARY = "dictionary";
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000116
Abodunrinwa Toki45cb3e62017-03-29 21:51:45 +0100117 /** @hide */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000118 @Retention(RetentionPolicy.SOURCE)
Jeff Sharkey5db9a912017-12-08 17:32:32 -0700119 @StringDef(prefix = { "TYPE_" }, value = {
120 TYPE_UNKNOWN,
121 TYPE_OTHER,
122 TYPE_EMAIL,
123 TYPE_PHONE,
124 TYPE_ADDRESS,
125 TYPE_URL,
Jan Althaus705b9e92018-01-22 18:22:29 +0100126 TYPE_DATE,
127 TYPE_DATE_TIME,
128 TYPE_FLIGHT_NUMBER,
Tony Make1f3ac062018-11-27 14:30:21 +0000129 TYPE_DICTIONARY
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000130 })
131 @interface EntityType {}
132
Richard Ledley1fc998b2018-02-16 15:45:06 +0000133 /** Designates that the text in question is editable. **/
134 String HINT_TEXT_IS_EDITABLE = "android.text_is_editable";
135 /** Designates that the text in question is not editable. **/
136 String HINT_TEXT_IS_NOT_EDITABLE = "android.text_is_not_editable";
Richard Ledleydb18a572017-11-30 17:33:51 +0000137
138 /** @hide */
139 @Retention(RetentionPolicy.SOURCE)
Richard Ledley1fc998b2018-02-16 15:45:06 +0000140 @StringDef(prefix = { "HINT_" }, value = {HINT_TEXT_IS_EDITABLE, HINT_TEXT_IS_NOT_EDITABLE})
141 @interface Hints {}
Richard Ledleydb18a572017-11-30 17:33:51 +0000142
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000143 /** @hide */
144 @Retention(RetentionPolicy.SOURCE)
Tony Mak4dd43142018-05-02 16:49:01 +0100145 @StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_EDITTEXT, WIDGET_TYPE_UNSELECTABLE_TEXTVIEW,
146 WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW,
147 WIDGET_TYPE_CUSTOM_EDITTEXT, WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW,
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000148 WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_UNKNOWN})
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000149 @interface WidgetType {}
150
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000151 /** The widget involved in the text classification context is a standard
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000152 * {@link android.widget.TextView}. */
153 String WIDGET_TYPE_TEXTVIEW = "textview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000154 /** The widget involved in the text classification context is a standard
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000155 * {@link android.widget.EditText}. */
156 String WIDGET_TYPE_EDITTEXT = "edittext";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000157 /** The widget involved in the text classification context is a standard non-selectable
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000158 * {@link android.widget.TextView}. */
159 String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000160 /** The widget involved in the text classification context is a standard
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000161 * {@link android.webkit.WebView}. */
162 String WIDGET_TYPE_WEBVIEW = "webview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000163 /** The widget involved in the text classification context is a standard editable
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000164 * {@link android.webkit.WebView}. */
165 String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000166 /** The widget involved in the text classification context is a custom text widget. */
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000167 String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000168 /** The widget involved in the text classification context is a custom editable text widget. */
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000169 String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000170 /** The widget involved in the text classification context is a custom non-selectable text
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000171 * widget. */
172 String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000173 /** The widget involved in the text classification context is a notification */
174 String WIDGET_TYPE_NOTIFICATION = "notification";
175 /** The widget involved in the text classification context is of an unknown/unspecified type. */
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000176 String WIDGET_TYPE_UNKNOWN = "unknown";
177
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000178 /**
179 * No-op TextClassifier.
180 * This may be used to turn off TextClassifier features.
181 */
Abodunrinwa Toki520b2f82019-01-27 07:48:02 +0000182 TextClassifier NO_OP = new TextClassifier() {
183 @Override
184 public String toString() {
185 return "TextClassifier.NO_OP";
186 }
187 };
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000188
189 /**
Tony Mak63abbe22019-01-28 15:14:36 +0000190 * Extra that is included on activity intents coming from a TextClassifier when
191 * it suggests actions to its caller.
Tony Makc5a46122018-12-05 22:19:22 +0000192 * <p>
Tony Mak63abbe22019-01-28 15:14:36 +0000193 * All {@link TextClassifier} implementations should make sure this extra exists in their
Tony Makc5a46122018-12-05 22:19:22 +0000194 * generated intents.
195 */
196 String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER";
197
198 /**
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +0100199 * Returns suggested text selection start and end indices, recognized entity types, and their
200 * associated confidence scores. The entity types are ordered from highest to lowest scoring.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000201 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800202 * <p><strong>NOTE: </strong>Call on a worker thread.
203 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100204 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000205 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
206 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100207 * @param request the text selection request
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000208 */
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100209 @WorkerThread
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000210 @NonNull
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100211 default TextSelection suggestSelection(@NonNull TextSelection.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000212 Objects.requireNonNull(request);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100213 Utils.checkMainThread();
214 return new TextSelection.Builder(request.getStartIndex(), request.getEndIndex()).build();
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100215 }
216
217 /**
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000218 * Returns suggested text selection start and end indices, recognized entity types, and their
219 * associated confidence scores. The entity types are ordered from highest to lowest scoring.
220 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800221 * <p><strong>NOTE: </strong>Call on a worker thread.
222 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100223 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000224 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
225 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100226 * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
227 * {@link #suggestSelection(TextSelection.Request)}. If that method calls this method,
228 * a stack overflow error will happen.
229 *
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000230 * @param text text providing context for the selected text (which is specified
231 * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
232 * @param selectionStartIndex start index of the selected part of text
233 * @param selectionEndIndex end index of the selected part of text
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100234 * @param defaultLocales ordered list of locale preferences that may be used to
235 * disambiguate the provided text. If no locale preferences exist, set this to null
236 * or an empty locale list.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000237 *
238 * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
239 * selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
240 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100241 * @see #suggestSelection(TextSelection.Request)
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000242 */
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100243 @WorkerThread
244 @NonNull
245 default TextSelection suggestSelection(
246 @NonNull CharSequence text,
247 @IntRange(from = 0) int selectionStartIndex,
248 @IntRange(from = 0) int selectionEndIndex,
249 @Nullable LocaleList defaultLocales) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100250 final TextSelection.Request request = new TextSelection.Request.Builder(
251 text, selectionStartIndex, selectionEndIndex)
252 .setDefaultLocales(defaultLocales)
253 .build();
254 return suggestSelection(request);
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100255 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000256
257 /**
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100258 * Classifies the specified text and returns a {@link TextClassification} object that can be
259 * used to generate a widget for handling the classified text.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000260 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800261 * <p><strong>NOTE: </strong>Call on a worker thread.
262 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100263 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000264 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
265 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100266 * @param request the text classification request
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000267 */
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100268 @WorkerThread
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000269 @NonNull
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100270 default TextClassification classifyText(@NonNull TextClassification.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000271 Objects.requireNonNull(request);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100272 Utils.checkMainThread();
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100273 return TextClassification.EMPTY;
274 }
275
276 /**
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000277 * Classifies the specified text and returns a {@link TextClassification} object that can be
278 * used to generate a widget for handling the classified text.
279 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800280 * <p><strong>NOTE: </strong>Call on a worker thread.
281 *
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000282 * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100283 * {@link #classifyText(TextClassification.Request)}. If that method calls this method,
284 * a stack overflow error will happen.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000285 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100286 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000287 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
288 *
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000289 * @param text text providing context for the text to classify (which is specified
290 * by the sub sequence starting at startIndex and ending at endIndex)
291 * @param startIndex start index of the text to classify
292 * @param endIndex end index of the text to classify
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100293 * @param defaultLocales ordered list of locale preferences that may be used to
294 * disambiguate the provided text. If no locale preferences exist, set this to null
295 * or an empty locale list.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000296 *
297 * @throws IllegalArgumentException if text is null; startIndex is negative;
298 * endIndex is greater than text.length() or not greater than startIndex
299 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100300 * @see #classifyText(TextClassification.Request)
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000301 */
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100302 @WorkerThread
303 @NonNull
304 default TextClassification classifyText(
305 @NonNull CharSequence text,
306 @IntRange(from = 0) int startIndex,
307 @IntRange(from = 0) int endIndex,
308 @Nullable LocaleList defaultLocales) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100309 final TextClassification.Request request = new TextClassification.Request.Builder(
310 text, startIndex, endIndex)
311 .setDefaultLocales(defaultLocales)
312 .build();
313 return classifyText(request);
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100314 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000315
316 /**
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800317 * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with
318 * links information.
Richard Ledley68d94522017-10-05 10:52:19 +0100319 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800320 * <p><strong>NOTE: </strong>Call on a worker thread.
Richard Ledleydb18a572017-11-30 17:33:51 +0000321 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100322 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000323 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
324 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100325 * @param request the text links request
Richard Ledley68d94522017-10-05 10:52:19 +0100326 *
Jan Althaus108aad32018-01-30 15:26:55 +0100327 * @see #getMaxGenerateLinksTextLength()
Richard Ledley68d94522017-10-05 10:52:19 +0100328 */
329 @WorkerThread
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000330 @NonNull
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100331 default TextLinks generateLinks(@NonNull TextLinks.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000332 Objects.requireNonNull(request);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100333 Utils.checkMainThread();
334 return new TextLinks.Builder(request.getText().toString()).build();
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000335 }
336
337 /**
Jan Althaus108aad32018-01-30 15:26:55 +0100338 * Returns the maximal length of text that can be processed by generateLinks.
339 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100340 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000341 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
342 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100343 * @see #generateLinks(TextLinks.Request)
Jan Althaus108aad32018-01-30 15:26:55 +0100344 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000345 @WorkerThread
Jan Althaus108aad32018-01-30 15:26:55 +0100346 default int getMaxGenerateLinksTextLength() {
347 return Integer.MAX_VALUE;
348 }
349
350 /**
Tony Makae85aae2019-01-09 15:59:56 +0000351 * Detects the language of the text in the given request.
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100352 *
353 * <p><strong>NOTE: </strong>Call on a worker thread.
354 *
355 *
356 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
357 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
358 *
359 * @param request the {@link TextLanguage} request.
360 * @return the {@link TextLanguage} result.
361 */
362 @WorkerThread
363 @NonNull
364 default TextLanguage detectLanguage(@NonNull TextLanguage.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000365 Objects.requireNonNull(request);
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100366 Utils.checkMainThread();
367 return TextLanguage.EMPTY;
368 }
369
370 /**
Tony Makc9d31e22018-10-22 16:17:45 +0100371 * Suggests and returns a list of actions according to the given conversation.
372 */
373 @WorkerThread
Tony Mak42ab9842019-03-05 15:38:51 +0000374 @NonNull
Tony Makc9d31e22018-10-22 16:17:45 +0100375 default ConversationActions suggestConversationActions(
376 @NonNull ConversationActions.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000377 Objects.requireNonNull(request);
Tony Makc9d31e22018-10-22 16:17:45 +0100378 Utils.checkMainThread();
Tony Makc4359bf2018-12-11 19:38:53 +0800379 return new ConversationActions(Collections.emptyList(), null);
Tony Makc9d31e22018-10-22 16:17:45 +0100380 }
381
382 /**
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000383 * <strong>NOTE: </strong>Use {@link #onTextClassifierEvent(TextClassifierEvent)} instead.
384 * <p>
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000385 * Reports a selection event.
386 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100387 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000388 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
389 */
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000390 default void onSelectionEvent(@NonNull SelectionEvent event) {
391 // TODO: Consider rerouting to onTextClassifierEvent()
392 }
393
394 /**
395 * Reports a text classifier event.
396 * <p>
397 * <strong>NOTE: </strong>Call on a worker thread.
398 *
399 * @throws IllegalStateException if this TextClassifier has been destroyed.
400 * @see #isDestroyed()
401 */
402 default void onTextClassifierEvent(@NonNull TextClassifierEvent event) {}
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000403
404 /**
405 * Destroys this TextClassifier.
406 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100407 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to its methods should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000408 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
409 *
410 * <p>Subsequent calls to this method are no-ops.
411 */
412 default void destroy() {}
413
414 /**
415 * Returns whether or not this TextClassifier has been destroyed.
416 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100417 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, caller should not interact
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000418 * with the classifier and an attempt to do so would throw an {@link IllegalStateException}.
419 * However, this method should never throw an {@link IllegalStateException}.
420 *
421 * @see #destroy()
422 */
423 default boolean isDestroyed() {
424 return false;
425 }
426
Tony Makf93e9e52018-07-16 14:46:29 +0200427 /** @hide **/
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100428 default void dump(@NonNull IndentingPrintWriter printWriter) {}
Tony Makf93e9e52018-07-16 14:46:29 +0200429
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000430 /**
Tony Makae85aae2019-01-09 15:59:56 +0000431 * Configuration object for specifying what entity types to identify.
Richard Ledleydb18a572017-11-30 17:33:51 +0000432 *
433 * Configs are initially based on a predefined preset, and can be modified from there.
434 */
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100435 final class EntityConfig implements Parcelable {
Tony Makae85aae2019-01-09 15:59:56 +0000436 private final List<String> mIncludedTypes;
437 private final List<String> mExcludedTypes;
438 private final List<String> mHints;
439 private final boolean mIncludeTypesFromTextClassifier;
Richard Ledleydb18a572017-11-30 17:33:51 +0000440
Tony Makae85aae2019-01-09 15:59:56 +0000441 private EntityConfig(
442 List<String> includedEntityTypes,
443 List<String> excludedEntityTypes,
444 List<String> hints,
445 boolean includeTypesFromTextClassifier) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000446 mIncludedTypes = Objects.requireNonNull(includedEntityTypes);
447 mExcludedTypes = Objects.requireNonNull(excludedEntityTypes);
448 mHints = Objects.requireNonNull(hints);
Tony Makae85aae2019-01-09 15:59:56 +0000449 mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
450 }
451
452 private EntityConfig(Parcel in) {
453 mIncludedTypes = new ArrayList<>();
454 in.readStringList(mIncludedTypes);
455 mExcludedTypes = new ArrayList<>();
456 in.readStringList(mExcludedTypes);
457 List<String> tmpHints = new ArrayList<>();
458 in.readStringList(tmpHints);
459 mHints = Collections.unmodifiableList(tmpHints);
460 mIncludeTypesFromTextClassifier = in.readByte() != 0;
461 }
462
463 @Override
464 public void writeToParcel(Parcel parcel, int flags) {
465 parcel.writeStringList(mIncludedTypes);
466 parcel.writeStringList(mExcludedTypes);
467 parcel.writeStringList(mHints);
468 parcel.writeByte((byte) (mIncludeTypesFromTextClassifier ? 1 : 0));
Richard Ledleydb18a572017-11-30 17:33:51 +0000469 }
470
471 /**
Richard Ledley1fc998b2018-02-16 15:45:06 +0000472 * Creates an EntityConfig.
473 *
474 * @param hints Hints for the TextClassifier to determine what types of entities to find.
Tony Makae85aae2019-01-09 15:59:56 +0000475 *
476 * @deprecated Use {@link Builder} instead.
Richard Ledley1fc998b2018-02-16 15:45:06 +0000477 */
Tony Makae85aae2019-01-09 15:59:56 +0000478 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100479 public static EntityConfig createWithHints(@Nullable Collection<String> hints) {
Tony Makae85aae2019-01-09 15:59:56 +0000480 return new EntityConfig.Builder()
481 .includeTypesFromTextClassifier(true)
482 .setHints(hints)
483 .build();
Abodunrinwa Tokiae82e7a2018-04-03 23:49:16 +0100484 }
485
Richard Ledley1fc998b2018-02-16 15:45:06 +0000486 /**
487 * Creates an EntityConfig.
488 *
489 * @param hints Hints for the TextClassifier to determine what types of entities to find
490 * @param includedEntityTypes Entity types, e.g. {@link #TYPE_EMAIL}, to explicitly include
491 * @param excludedEntityTypes Entity types, e.g. {@link #TYPE_PHONE}, to explicitly exclude
492 *
Richard Ledleydb18a572017-11-30 17:33:51 +0000493 *
494 * Note that if an entity has been excluded, the exclusion will take precedence.
Tony Makae85aae2019-01-09 15:59:56 +0000495 *
496 * @deprecated Use {@link Builder} instead.
Richard Ledleydb18a572017-11-30 17:33:51 +0000497 */
Tony Makae85aae2019-01-09 15:59:56 +0000498 @Deprecated
Richard Ledley1fc998b2018-02-16 15:45:06 +0000499 public static EntityConfig create(@Nullable Collection<String> hints,
500 @Nullable Collection<String> includedEntityTypes,
501 @Nullable Collection<String> excludedEntityTypes) {
Tony Makae85aae2019-01-09 15:59:56 +0000502 return new EntityConfig.Builder()
503 .setIncludedTypes(includedEntityTypes)
504 .setExcludedTypes(excludedEntityTypes)
505 .setHints(hints)
506 .includeTypesFromTextClassifier(true)
507 .build();
Richard Ledleydb18a572017-11-30 17:33:51 +0000508 }
509
510 /**
Richard Ledley1fc998b2018-02-16 15:45:06 +0000511 * Creates an EntityConfig with an explicit entity list.
512 *
513 * @param entityTypes Complete set of entities, e.g. {@link #TYPE_URL} to find.
514 *
Tony Makae85aae2019-01-09 15:59:56 +0000515 * @deprecated Use {@link Builder} instead.
Richard Ledleydb18a572017-11-30 17:33:51 +0000516 */
Tony Makae85aae2019-01-09 15:59:56 +0000517 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100518 public static EntityConfig createWithExplicitEntityList(
519 @Nullable Collection<String> entityTypes) {
Tony Makae85aae2019-01-09 15:59:56 +0000520 return new EntityConfig.Builder()
521 .setIncludedTypes(entityTypes)
522 .includeTypesFromTextClassifier(false)
523 .build();
Abodunrinwa Tokiae82e7a2018-04-03 23:49:16 +0100524 }
525
Richard Ledleydb18a572017-11-30 17:33:51 +0000526 /**
Tony Makae85aae2019-01-09 15:59:56 +0000527 * Returns a final list of entity types to find.
Richard Ledley1fc998b2018-02-16 15:45:06 +0000528 *
Tony Makae85aae2019-01-09 15:59:56 +0000529 * @param entityTypes Entity types we think should be found before factoring in
530 * includes/excludes
Richard Ledley1fc998b2018-02-16 15:45:06 +0000531 *
532 * This method is intended for use by TextClassifier implementations.
Richard Ledleydb18a572017-11-30 17:33:51 +0000533 */
Richard Ledleyab669a02018-04-03 15:15:43 +0100534 public Collection<String> resolveEntityListModifications(
Tony Makae85aae2019-01-09 15:59:56 +0000535 @NonNull Collection<String> entityTypes) {
536 final Set<String> finalSet = new HashSet<>();
537 if (mIncludeTypesFromTextClassifier) {
538 finalSet.addAll(entityTypes);
Richard Ledleydb18a572017-11-30 17:33:51 +0000539 }
Tony Makae85aae2019-01-09 15:59:56 +0000540 finalSet.addAll(mIncludedTypes);
541 finalSet.removeAll(mExcludedTypes);
Richard Ledleyab669a02018-04-03 15:15:43 +0100542 return finalSet;
Richard Ledley1fc998b2018-02-16 15:45:06 +0000543 }
544
545 /**
546 * Retrieves the list of hints.
547 *
548 * @return An unmodifiable collection of the hints.
549 */
550 public Collection<String> getHints() {
551 return mHints;
Richard Ledleydb18a572017-11-30 17:33:51 +0000552 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100553
Tony Makae85aae2019-01-09 15:59:56 +0000554 /**
555 * Return whether the client allows the text classifier to include its own list of
556 * default types. If this function returns {@code true}, a default list of types suggested
557 * from a text classifier will be taking into account.
558 *
559 * <p>NOTE: This method is intended for use by a text classifier.
560 *
561 * @see #resolveEntityListModifications(Collection)
562 */
563 public boolean shouldIncludeTypesFromTextClassifier() {
564 return mIncludeTypesFromTextClassifier;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100565 }
566
567 @Override
Tony Makae85aae2019-01-09 15:59:56 +0000568 public int describeContents() {
569 return 0;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100570 }
571
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700572 public static final @android.annotation.NonNull Parcelable.Creator<EntityConfig> CREATOR =
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100573 new Parcelable.Creator<EntityConfig>() {
574 @Override
575 public EntityConfig createFromParcel(Parcel in) {
576 return new EntityConfig(in);
577 }
578
579 @Override
580 public EntityConfig[] newArray(int size) {
581 return new EntityConfig[size];
582 }
583 };
584
Tony Makae85aae2019-01-09 15:59:56 +0000585
586
587 /** Builder class to construct the {@link EntityConfig} object. */
588 public static final class Builder {
589 @Nullable
590 private Collection<String> mIncludedTypes;
591 @Nullable
592 private Collection<String> mExcludedTypes;
593 @Nullable
594 private Collection<String> mHints;
595 private boolean mIncludeTypesFromTextClassifier = true;
596
597 /**
598 * Sets a collection of types that are explicitly included.
599 */
600 @NonNull
601 public Builder setIncludedTypes(@Nullable Collection<String> includedTypes) {
602 mIncludedTypes = includedTypes;
603 return this;
604 }
605
606 /**
607 * Sets a collection of types that are explicitly excluded.
608 */
609 @NonNull
610 public Builder setExcludedTypes(@Nullable Collection<String> excludedTypes) {
611 mExcludedTypes = excludedTypes;
612 return this;
613 }
614
615 /**
616 * Specifies whether or not to include the types suggested by the text classifier. By
617 * default, it is included.
618 */
619 @NonNull
620 public Builder includeTypesFromTextClassifier(boolean includeTypesFromTextClassifier) {
621 mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
622 return this;
623 }
624
625
626 /**
627 * Sets the hints for the TextClassifier to determine what types of entities to find.
628 * These hints will only be used if {@link #includeTypesFromTextClassifier} is
629 * set to be true.
630 */
Tony Mak42ab9842019-03-05 15:38:51 +0000631 @NonNull
632 public Builder setHints(@Nullable Collection<String> hints) {
Tony Makae85aae2019-01-09 15:59:56 +0000633 mHints = hints;
634 return this;
635 }
636
637 /**
638 * Combines all of the options that have been set and returns a new {@link EntityConfig}
639 * object.
640 */
641 @NonNull
642 public EntityConfig build() {
643 return new EntityConfig(
644 mIncludedTypes == null
645 ? Collections.emptyList()
646 : new ArrayList<>(mIncludedTypes),
647 mExcludedTypes == null
648 ? Collections.emptyList()
649 : new ArrayList<>(mExcludedTypes),
650 mHints == null
651 ? Collections.emptyList()
652 : Collections.unmodifiableList(new ArrayList<>(mHints)),
653 mIncludeTypesFromTextClassifier);
654 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100655 }
Richard Ledleydb18a572017-11-30 17:33:51 +0000656 }
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000657
658 /**
659 * Utility functions for TextClassifier methods.
660 *
661 * <ul>
662 * <li>Provides validation of input parameters to TextClassifier methods
663 * </ul>
664 *
Tony Mak72e17972019-03-16 10:28:42 +0000665 * Intended to be used only for TextClassifier purposes.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000666 * @hide
667 */
668 final class Utils {
669
Tony Mak72e17972019-03-16 10:28:42 +0000670 @GuardedBy("WORD_ITERATOR")
671 private static final BreakIterator WORD_ITERATOR = BreakIterator.getWordInstance();
672
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000673 /**
674 * @throws IllegalArgumentException if text is null; startIndex is negative;
675 * endIndex is greater than text.length() or is not greater than startIndex;
676 * options is null
677 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100678 static void checkArgument(@NonNull CharSequence text, int startIndex, int endIndex) {
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000679 Preconditions.checkArgument(text != null);
680 Preconditions.checkArgument(startIndex >= 0);
681 Preconditions.checkArgument(endIndex <= text.length());
682 Preconditions.checkArgument(endIndex > startIndex);
683 }
684
Tony Makc5a74322020-02-04 17:18:15 +0000685 /** Returns if the length of the text is within the range. */
686 static boolean checkTextLength(CharSequence text, int maxLength) {
687 int textLength = text.length();
688 return textLength >= 0 && textLength <= maxLength;
Jan Althaus108aad32018-01-30 15:26:55 +0100689 }
690
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000691 /**
Tony Mak72e17972019-03-16 10:28:42 +0000692 * Returns the substring of {@code text} that contains at least text from index
693 * {@code start} <i>(inclusive)</i> to index {@code end} <i><(exclusive)/i> with the goal of
694 * returning text that is at least {@code minimumLength}. If {@code text} is not long
695 * enough, this will return {@code text}. This method returns text at word boundaries.
696 *
697 * @param text the source text
698 * @param start the start index of text that must be included
699 * @param end the end index of text that must be included
700 * @param minimumLength minimum length of text to return if {@code text} is long enough
701 */
702 public static String getSubString(
703 String text, int start, int end, int minimumLength) {
704 Preconditions.checkArgument(start >= 0);
705 Preconditions.checkArgument(end <= text.length());
706 Preconditions.checkArgument(start <= end);
707
708 if (text.length() < minimumLength) {
709 return text;
710 }
711
712 final int length = end - start;
713 if (length >= minimumLength) {
714 return text.substring(start, end);
715 }
716
717 final int offset = (minimumLength - length) / 2;
718 int iterStart = Math.max(0, Math.min(start - offset, text.length() - minimumLength));
719 int iterEnd = Math.min(text.length(), iterStart + minimumLength);
720
721 synchronized (WORD_ITERATOR) {
722 WORD_ITERATOR.setText(text);
723 iterStart = WORD_ITERATOR.isBoundary(iterStart)
724 ? iterStart : Math.max(0, WORD_ITERATOR.preceding(iterStart));
725 iterEnd = WORD_ITERATOR.isBoundary(iterEnd)
726 ? iterEnd : Math.max(iterEnd, WORD_ITERATOR.following(iterEnd));
727 WORD_ITERATOR.setText("");
728 return text.substring(iterStart, iterEnd);
729 }
730 }
731
732 /**
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000733 * Generates links using legacy {@link Linkify}.
734 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100735 public static TextLinks generateLegacyLinks(@NonNull TextLinks.Request request) {
736 final String string = request.getText().toString();
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000737 final TextLinks.Builder links = new TextLinks.Builder(string);
738
Richard Ledleyab669a02018-04-03 15:15:43 +0100739 final Collection<String> entities = request.getEntityConfig()
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000740 .resolveEntityListModifications(Collections.emptyList());
741 if (entities.contains(TextClassifier.TYPE_URL)) {
742 addLinks(links, string, TextClassifier.TYPE_URL);
743 }
744 if (entities.contains(TextClassifier.TYPE_PHONE)) {
745 addLinks(links, string, TextClassifier.TYPE_PHONE);
746 }
747 if (entities.contains(TextClassifier.TYPE_EMAIL)) {
748 addLinks(links, string, TextClassifier.TYPE_EMAIL);
749 }
750 // NOTE: Do not support MAP_ADDRESSES. Legacy version does not work well.
751 return links.build();
752 }
753
754 private static void addLinks(
755 TextLinks.Builder links, String string, @EntityType String entityType) {
756 final Spannable spannable = new SpannableString(string);
757 if (Linkify.addLinks(spannable, linkMask(entityType))) {
758 final URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
759 for (URLSpan urlSpan : spans) {
760 links.addLink(
761 spannable.getSpanStart(urlSpan),
762 spannable.getSpanEnd(urlSpan),
763 entityScores(entityType),
764 urlSpan);
765 }
766 }
767 }
768
769 @LinkifyMask
770 private static int linkMask(@EntityType String entityType) {
771 switch (entityType) {
772 case TextClassifier.TYPE_URL:
773 return Linkify.WEB_URLS;
774 case TextClassifier.TYPE_PHONE:
775 return Linkify.PHONE_NUMBERS;
776 case TextClassifier.TYPE_EMAIL:
777 return Linkify.EMAIL_ADDRESSES;
778 default:
779 // NOTE: Do not support MAP_ADDRESSES. Legacy version does not work well.
780 return 0;
781 }
782 }
783
784 private static Map<String, Float> entityScores(@EntityType String entityType) {
785 final Map<String, Float> scores = new ArrayMap<>();
786 scores.put(entityType, 1f);
787 return scores;
788 }
789
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100790 static void checkMainThread() {
791 if (Looper.myLooper() == Looper.getMainLooper()) {
Tony Mak293bdf32020-02-18 11:33:43 +0000792 Log.w(LOG_TAG, "TextClassifier called on main thread");
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800793 }
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000794 }
795 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000796}