blob: 2cc226d5a6019e3c6e4740c3255a0bea11dc54a6 [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 */
Abodunrinwa Toki008f3872017-11-27 19:32:35 +000064 String DEFAULT_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 Makc5a74322020-02-04 17:18:15 +000069 @IntDef(value = {LOCAL, SYSTEM, DEFAULT_SERVICE})
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 */
76 int DEFAULT_SERVICE = 2;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000077
Jan Althaus705b9e92018-01-22 18:22:29 +010078 /** The TextClassifier failed to run. */
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000079 String TYPE_UNKNOWN = "";
Jan Althaus705b9e92018-01-22 18:22:29 +010080 /** The classifier ran, but didn't recognize a known entity. */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000081 String TYPE_OTHER = "other";
Jan Althaus705b9e92018-01-22 18:22:29 +010082 /** E-mail address (e.g. "noreply@android.com"). */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000083 String TYPE_EMAIL = "email";
Jan Althaus705b9e92018-01-22 18:22:29 +010084 /** Phone number (e.g. "555-123 456"). */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000085 String TYPE_PHONE = "phone";
Jan Althaus705b9e92018-01-22 18:22:29 +010086 /** Physical address. */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000087 String TYPE_ADDRESS = "address";
Jan Althaus705b9e92018-01-22 18:22:29 +010088 /** Web URL. */
Abodunrinwa Toki9b4c82a2017-02-06 20:29:36 +000089 String TYPE_URL = "url";
Jan Althaus705b9e92018-01-22 18:22:29 +010090 /** Time reference that is no more specific than a date. May be absolute such as "01/01/2000" or
91 * relative like "tomorrow". **/
92 String TYPE_DATE = "date";
93 /** Time reference that includes a specific time. May be absolute such as "01/01/2000 5:30pm" or
94 * relative like "tomorrow at 5:30pm". **/
95 String TYPE_DATE_TIME = "datetime";
96 /** Flight number in IATA format. */
97 String TYPE_FLIGHT_NUMBER = "flight";
Tony Make1f3ac062018-11-27 14:30:21 +000098 /**
99 * Word that users may be interested to look up for meaning.
100 * @hide
101 */
102 String TYPE_DICTIONARY = "dictionary";
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000103
Abodunrinwa Toki45cb3e62017-03-29 21:51:45 +0100104 /** @hide */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000105 @Retention(RetentionPolicy.SOURCE)
Jeff Sharkey5db9a912017-12-08 17:32:32 -0700106 @StringDef(prefix = { "TYPE_" }, value = {
107 TYPE_UNKNOWN,
108 TYPE_OTHER,
109 TYPE_EMAIL,
110 TYPE_PHONE,
111 TYPE_ADDRESS,
112 TYPE_URL,
Jan Althaus705b9e92018-01-22 18:22:29 +0100113 TYPE_DATE,
114 TYPE_DATE_TIME,
115 TYPE_FLIGHT_NUMBER,
Tony Make1f3ac062018-11-27 14:30:21 +0000116 TYPE_DICTIONARY
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000117 })
118 @interface EntityType {}
119
Richard Ledley1fc998b2018-02-16 15:45:06 +0000120 /** Designates that the text in question is editable. **/
121 String HINT_TEXT_IS_EDITABLE = "android.text_is_editable";
122 /** Designates that the text in question is not editable. **/
123 String HINT_TEXT_IS_NOT_EDITABLE = "android.text_is_not_editable";
Richard Ledleydb18a572017-11-30 17:33:51 +0000124
125 /** @hide */
126 @Retention(RetentionPolicy.SOURCE)
Richard Ledley1fc998b2018-02-16 15:45:06 +0000127 @StringDef(prefix = { "HINT_" }, value = {HINT_TEXT_IS_EDITABLE, HINT_TEXT_IS_NOT_EDITABLE})
128 @interface Hints {}
Richard Ledleydb18a572017-11-30 17:33:51 +0000129
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000130 /** @hide */
131 @Retention(RetentionPolicy.SOURCE)
Tony Mak4dd43142018-05-02 16:49:01 +0100132 @StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_EDITTEXT, WIDGET_TYPE_UNSELECTABLE_TEXTVIEW,
133 WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW,
134 WIDGET_TYPE_CUSTOM_EDITTEXT, WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW,
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000135 WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_UNKNOWN})
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000136 @interface WidgetType {}
137
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000138 /** The widget involved in the text classification context is a standard
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000139 * {@link android.widget.TextView}. */
140 String WIDGET_TYPE_TEXTVIEW = "textview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000141 /** The widget involved in the text classification context is a standard
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000142 * {@link android.widget.EditText}. */
143 String WIDGET_TYPE_EDITTEXT = "edittext";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000144 /** The widget involved in the text classification context is a standard non-selectable
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000145 * {@link android.widget.TextView}. */
146 String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000147 /** The widget involved in the text classification context is a standard
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000148 * {@link android.webkit.WebView}. */
149 String WIDGET_TYPE_WEBVIEW = "webview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000150 /** The widget involved in the text classification context is a standard editable
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000151 * {@link android.webkit.WebView}. */
152 String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000153 /** The widget involved in the text classification context is a custom text widget. */
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000154 String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000155 /** The widget involved in the text classification context is a custom editable text widget. */
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000156 String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000157 /** The widget involved in the text classification context is a custom non-selectable text
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000158 * widget. */
159 String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000160 /** The widget involved in the text classification context is a notification */
161 String WIDGET_TYPE_NOTIFICATION = "notification";
162 /** The widget involved in the text classification context is of an unknown/unspecified type. */
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000163 String WIDGET_TYPE_UNKNOWN = "unknown";
164
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000165 /**
166 * No-op TextClassifier.
167 * This may be used to turn off TextClassifier features.
168 */
Abodunrinwa Toki520b2f82019-01-27 07:48:02 +0000169 TextClassifier NO_OP = new TextClassifier() {
170 @Override
171 public String toString() {
172 return "TextClassifier.NO_OP";
173 }
174 };
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000175
176 /**
Tony Mak63abbe22019-01-28 15:14:36 +0000177 * Extra that is included on activity intents coming from a TextClassifier when
178 * it suggests actions to its caller.
Tony Makc5a46122018-12-05 22:19:22 +0000179 * <p>
Tony Mak63abbe22019-01-28 15:14:36 +0000180 * All {@link TextClassifier} implementations should make sure this extra exists in their
Tony Makc5a46122018-12-05 22:19:22 +0000181 * generated intents.
182 */
183 String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER";
184
185 /**
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +0100186 * Returns suggested text selection start and end indices, recognized entity types, and their
187 * associated confidence scores. The entity types are ordered from highest to lowest scoring.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000188 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800189 * <p><strong>NOTE: </strong>Call on a worker thread.
190 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100191 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000192 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
193 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100194 * @param request the text selection request
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000195 */
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100196 @WorkerThread
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000197 @NonNull
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100198 default TextSelection suggestSelection(@NonNull TextSelection.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000199 Objects.requireNonNull(request);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100200 Utils.checkMainThread();
201 return new TextSelection.Builder(request.getStartIndex(), request.getEndIndex()).build();
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100202 }
203
204 /**
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000205 * Returns suggested text selection start and end indices, recognized entity types, and their
206 * associated confidence scores. The entity types are ordered from highest to lowest scoring.
207 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800208 * <p><strong>NOTE: </strong>Call on a worker thread.
209 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100210 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000211 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
212 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100213 * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
214 * {@link #suggestSelection(TextSelection.Request)}. If that method calls this method,
215 * a stack overflow error will happen.
216 *
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000217 * @param text text providing context for the selected text (which is specified
218 * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
219 * @param selectionStartIndex start index of the selected part of text
220 * @param selectionEndIndex end index of the selected part of text
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100221 * @param defaultLocales ordered list of locale preferences that may be used to
222 * disambiguate the provided text. If no locale preferences exist, set this to null
223 * or an empty locale list.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000224 *
225 * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
226 * selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
227 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100228 * @see #suggestSelection(TextSelection.Request)
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000229 */
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100230 @WorkerThread
231 @NonNull
232 default TextSelection suggestSelection(
233 @NonNull CharSequence text,
234 @IntRange(from = 0) int selectionStartIndex,
235 @IntRange(from = 0) int selectionEndIndex,
236 @Nullable LocaleList defaultLocales) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100237 final TextSelection.Request request = new TextSelection.Request.Builder(
238 text, selectionStartIndex, selectionEndIndex)
239 .setDefaultLocales(defaultLocales)
240 .build();
241 return suggestSelection(request);
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100242 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000243
244 /**
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100245 * Classifies the specified text and returns a {@link TextClassification} object that can be
246 * used to generate a widget for handling the classified text.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000247 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800248 * <p><strong>NOTE: </strong>Call on a worker thread.
249 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100250 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000251 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
252 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100253 * @param request the text classification request
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000254 */
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100255 @WorkerThread
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000256 @NonNull
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100257 default TextClassification classifyText(@NonNull TextClassification.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000258 Objects.requireNonNull(request);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100259 Utils.checkMainThread();
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100260 return TextClassification.EMPTY;
261 }
262
263 /**
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000264 * Classifies the specified text and returns a {@link TextClassification} object that can be
265 * used to generate a widget for handling the classified text.
266 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800267 * <p><strong>NOTE: </strong>Call on a worker thread.
268 *
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000269 * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100270 * {@link #classifyText(TextClassification.Request)}. If that method calls this method,
271 * a stack overflow error will happen.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000272 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100273 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000274 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
275 *
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000276 * @param text text providing context for the text to classify (which is specified
277 * by the sub sequence starting at startIndex and ending at endIndex)
278 * @param startIndex start index of the text to classify
279 * @param endIndex end index of the text to classify
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100280 * @param defaultLocales ordered list of locale preferences that may be used to
281 * disambiguate the provided text. If no locale preferences exist, set this to null
282 * or an empty locale list.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000283 *
284 * @throws IllegalArgumentException if text is null; startIndex is negative;
285 * endIndex is greater than text.length() or not greater than startIndex
286 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100287 * @see #classifyText(TextClassification.Request)
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000288 */
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100289 @WorkerThread
290 @NonNull
291 default TextClassification classifyText(
292 @NonNull CharSequence text,
293 @IntRange(from = 0) int startIndex,
294 @IntRange(from = 0) int endIndex,
295 @Nullable LocaleList defaultLocales) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100296 final TextClassification.Request request = new TextClassification.Request.Builder(
297 text, startIndex, endIndex)
298 .setDefaultLocales(defaultLocales)
299 .build();
300 return classifyText(request);
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100301 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000302
303 /**
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800304 * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with
305 * links information.
Richard Ledley68d94522017-10-05 10:52:19 +0100306 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800307 * <p><strong>NOTE: </strong>Call on a worker thread.
Richard Ledleydb18a572017-11-30 17:33:51 +0000308 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100309 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000310 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
311 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100312 * @param request the text links request
Richard Ledley68d94522017-10-05 10:52:19 +0100313 *
Jan Althaus108aad32018-01-30 15:26:55 +0100314 * @see #getMaxGenerateLinksTextLength()
Richard Ledley68d94522017-10-05 10:52:19 +0100315 */
316 @WorkerThread
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000317 @NonNull
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100318 default TextLinks generateLinks(@NonNull TextLinks.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000319 Objects.requireNonNull(request);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100320 Utils.checkMainThread();
321 return new TextLinks.Builder(request.getText().toString()).build();
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000322 }
323
324 /**
Jan Althaus108aad32018-01-30 15:26:55 +0100325 * Returns the maximal length of text that can be processed by generateLinks.
326 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100327 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000328 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
329 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100330 * @see #generateLinks(TextLinks.Request)
Jan Althaus108aad32018-01-30 15:26:55 +0100331 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000332 @WorkerThread
Jan Althaus108aad32018-01-30 15:26:55 +0100333 default int getMaxGenerateLinksTextLength() {
334 return Integer.MAX_VALUE;
335 }
336
337 /**
Tony Makae85aae2019-01-09 15:59:56 +0000338 * Detects the language of the text in the given request.
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100339 *
340 * <p><strong>NOTE: </strong>Call on a worker thread.
341 *
342 *
343 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
344 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
345 *
346 * @param request the {@link TextLanguage} request.
347 * @return the {@link TextLanguage} result.
348 */
349 @WorkerThread
350 @NonNull
351 default TextLanguage detectLanguage(@NonNull TextLanguage.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000352 Objects.requireNonNull(request);
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100353 Utils.checkMainThread();
354 return TextLanguage.EMPTY;
355 }
356
357 /**
Tony Makc9d31e22018-10-22 16:17:45 +0100358 * Suggests and returns a list of actions according to the given conversation.
359 */
360 @WorkerThread
Tony Mak42ab9842019-03-05 15:38:51 +0000361 @NonNull
Tony Makc9d31e22018-10-22 16:17:45 +0100362 default ConversationActions suggestConversationActions(
363 @NonNull ConversationActions.Request request) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000364 Objects.requireNonNull(request);
Tony Makc9d31e22018-10-22 16:17:45 +0100365 Utils.checkMainThread();
Tony Makc4359bf2018-12-11 19:38:53 +0800366 return new ConversationActions(Collections.emptyList(), null);
Tony Makc9d31e22018-10-22 16:17:45 +0100367 }
368
369 /**
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000370 * <strong>NOTE: </strong>Use {@link #onTextClassifierEvent(TextClassifierEvent)} instead.
371 * <p>
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000372 * Reports a selection event.
373 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100374 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000375 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
376 */
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000377 default void onSelectionEvent(@NonNull SelectionEvent event) {
378 // TODO: Consider rerouting to onTextClassifierEvent()
379 }
380
381 /**
382 * Reports a text classifier event.
383 * <p>
384 * <strong>NOTE: </strong>Call on a worker thread.
385 *
386 * @throws IllegalStateException if this TextClassifier has been destroyed.
387 * @see #isDestroyed()
388 */
389 default void onTextClassifierEvent(@NonNull TextClassifierEvent event) {}
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000390
391 /**
392 * Destroys this TextClassifier.
393 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100394 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to its methods should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000395 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
396 *
397 * <p>Subsequent calls to this method are no-ops.
398 */
399 default void destroy() {}
400
401 /**
402 * Returns whether or not this TextClassifier has been destroyed.
403 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100404 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, caller should not interact
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000405 * with the classifier and an attempt to do so would throw an {@link IllegalStateException}.
406 * However, this method should never throw an {@link IllegalStateException}.
407 *
408 * @see #destroy()
409 */
410 default boolean isDestroyed() {
411 return false;
412 }
413
Tony Makf93e9e52018-07-16 14:46:29 +0200414 /** @hide **/
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100415 default void dump(@NonNull IndentingPrintWriter printWriter) {}
Tony Makf93e9e52018-07-16 14:46:29 +0200416
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000417 /**
Tony Makae85aae2019-01-09 15:59:56 +0000418 * Configuration object for specifying what entity types to identify.
Richard Ledleydb18a572017-11-30 17:33:51 +0000419 *
420 * Configs are initially based on a predefined preset, and can be modified from there.
421 */
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100422 final class EntityConfig implements Parcelable {
Tony Makae85aae2019-01-09 15:59:56 +0000423 private final List<String> mIncludedTypes;
424 private final List<String> mExcludedTypes;
425 private final List<String> mHints;
426 private final boolean mIncludeTypesFromTextClassifier;
Richard Ledleydb18a572017-11-30 17:33:51 +0000427
Tony Makae85aae2019-01-09 15:59:56 +0000428 private EntityConfig(
429 List<String> includedEntityTypes,
430 List<String> excludedEntityTypes,
431 List<String> hints,
432 boolean includeTypesFromTextClassifier) {
Daulet Zhanguzine1559472019-12-18 14:17:56 +0000433 mIncludedTypes = Objects.requireNonNull(includedEntityTypes);
434 mExcludedTypes = Objects.requireNonNull(excludedEntityTypes);
435 mHints = Objects.requireNonNull(hints);
Tony Makae85aae2019-01-09 15:59:56 +0000436 mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
437 }
438
439 private EntityConfig(Parcel in) {
440 mIncludedTypes = new ArrayList<>();
441 in.readStringList(mIncludedTypes);
442 mExcludedTypes = new ArrayList<>();
443 in.readStringList(mExcludedTypes);
444 List<String> tmpHints = new ArrayList<>();
445 in.readStringList(tmpHints);
446 mHints = Collections.unmodifiableList(tmpHints);
447 mIncludeTypesFromTextClassifier = in.readByte() != 0;
448 }
449
450 @Override
451 public void writeToParcel(Parcel parcel, int flags) {
452 parcel.writeStringList(mIncludedTypes);
453 parcel.writeStringList(mExcludedTypes);
454 parcel.writeStringList(mHints);
455 parcel.writeByte((byte) (mIncludeTypesFromTextClassifier ? 1 : 0));
Richard Ledleydb18a572017-11-30 17:33:51 +0000456 }
457
458 /**
Richard Ledley1fc998b2018-02-16 15:45:06 +0000459 * Creates an EntityConfig.
460 *
461 * @param hints Hints for the TextClassifier to determine what types of entities to find.
Tony Makae85aae2019-01-09 15:59:56 +0000462 *
463 * @deprecated Use {@link Builder} instead.
Richard Ledley1fc998b2018-02-16 15:45:06 +0000464 */
Tony Makae85aae2019-01-09 15:59:56 +0000465 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100466 public static EntityConfig createWithHints(@Nullable Collection<String> hints) {
Tony Makae85aae2019-01-09 15:59:56 +0000467 return new EntityConfig.Builder()
468 .includeTypesFromTextClassifier(true)
469 .setHints(hints)
470 .build();
Abodunrinwa Tokiae82e7a2018-04-03 23:49:16 +0100471 }
472
Richard Ledley1fc998b2018-02-16 15:45:06 +0000473 /**
474 * Creates an EntityConfig.
475 *
476 * @param hints Hints for the TextClassifier to determine what types of entities to find
477 * @param includedEntityTypes Entity types, e.g. {@link #TYPE_EMAIL}, to explicitly include
478 * @param excludedEntityTypes Entity types, e.g. {@link #TYPE_PHONE}, to explicitly exclude
479 *
Richard Ledleydb18a572017-11-30 17:33:51 +0000480 *
481 * Note that if an entity has been excluded, the exclusion will take precedence.
Tony Makae85aae2019-01-09 15:59:56 +0000482 *
483 * @deprecated Use {@link Builder} instead.
Richard Ledleydb18a572017-11-30 17:33:51 +0000484 */
Tony Makae85aae2019-01-09 15:59:56 +0000485 @Deprecated
Richard Ledley1fc998b2018-02-16 15:45:06 +0000486 public static EntityConfig create(@Nullable Collection<String> hints,
487 @Nullable Collection<String> includedEntityTypes,
488 @Nullable Collection<String> excludedEntityTypes) {
Tony Makae85aae2019-01-09 15:59:56 +0000489 return new EntityConfig.Builder()
490 .setIncludedTypes(includedEntityTypes)
491 .setExcludedTypes(excludedEntityTypes)
492 .setHints(hints)
493 .includeTypesFromTextClassifier(true)
494 .build();
Richard Ledleydb18a572017-11-30 17:33:51 +0000495 }
496
497 /**
Richard Ledley1fc998b2018-02-16 15:45:06 +0000498 * Creates an EntityConfig with an explicit entity list.
499 *
500 * @param entityTypes Complete set of entities, e.g. {@link #TYPE_URL} to find.
501 *
Tony Makae85aae2019-01-09 15:59:56 +0000502 * @deprecated Use {@link Builder} instead.
Richard Ledleydb18a572017-11-30 17:33:51 +0000503 */
Tony Makae85aae2019-01-09 15:59:56 +0000504 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100505 public static EntityConfig createWithExplicitEntityList(
506 @Nullable Collection<String> entityTypes) {
Tony Makae85aae2019-01-09 15:59:56 +0000507 return new EntityConfig.Builder()
508 .setIncludedTypes(entityTypes)
509 .includeTypesFromTextClassifier(false)
510 .build();
Abodunrinwa Tokiae82e7a2018-04-03 23:49:16 +0100511 }
512
Richard Ledleydb18a572017-11-30 17:33:51 +0000513 /**
Tony Makae85aae2019-01-09 15:59:56 +0000514 * Returns a final list of entity types to find.
Richard Ledley1fc998b2018-02-16 15:45:06 +0000515 *
Tony Makae85aae2019-01-09 15:59:56 +0000516 * @param entityTypes Entity types we think should be found before factoring in
517 * includes/excludes
Richard Ledley1fc998b2018-02-16 15:45:06 +0000518 *
519 * This method is intended for use by TextClassifier implementations.
Richard Ledleydb18a572017-11-30 17:33:51 +0000520 */
Richard Ledleyab669a02018-04-03 15:15:43 +0100521 public Collection<String> resolveEntityListModifications(
Tony Makae85aae2019-01-09 15:59:56 +0000522 @NonNull Collection<String> entityTypes) {
523 final Set<String> finalSet = new HashSet<>();
524 if (mIncludeTypesFromTextClassifier) {
525 finalSet.addAll(entityTypes);
Richard Ledleydb18a572017-11-30 17:33:51 +0000526 }
Tony Makae85aae2019-01-09 15:59:56 +0000527 finalSet.addAll(mIncludedTypes);
528 finalSet.removeAll(mExcludedTypes);
Richard Ledleyab669a02018-04-03 15:15:43 +0100529 return finalSet;
Richard Ledley1fc998b2018-02-16 15:45:06 +0000530 }
531
532 /**
533 * Retrieves the list of hints.
534 *
535 * @return An unmodifiable collection of the hints.
536 */
537 public Collection<String> getHints() {
538 return mHints;
Richard Ledleydb18a572017-11-30 17:33:51 +0000539 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100540
Tony Makae85aae2019-01-09 15:59:56 +0000541 /**
542 * Return whether the client allows the text classifier to include its own list of
543 * default types. If this function returns {@code true}, a default list of types suggested
544 * from a text classifier will be taking into account.
545 *
546 * <p>NOTE: This method is intended for use by a text classifier.
547 *
548 * @see #resolveEntityListModifications(Collection)
549 */
550 public boolean shouldIncludeTypesFromTextClassifier() {
551 return mIncludeTypesFromTextClassifier;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100552 }
553
554 @Override
Tony Makae85aae2019-01-09 15:59:56 +0000555 public int describeContents() {
556 return 0;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100557 }
558
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700559 public static final @android.annotation.NonNull Parcelable.Creator<EntityConfig> CREATOR =
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100560 new Parcelable.Creator<EntityConfig>() {
561 @Override
562 public EntityConfig createFromParcel(Parcel in) {
563 return new EntityConfig(in);
564 }
565
566 @Override
567 public EntityConfig[] newArray(int size) {
568 return new EntityConfig[size];
569 }
570 };
571
Tony Makae85aae2019-01-09 15:59:56 +0000572
573
574 /** Builder class to construct the {@link EntityConfig} object. */
575 public static final class Builder {
576 @Nullable
577 private Collection<String> mIncludedTypes;
578 @Nullable
579 private Collection<String> mExcludedTypes;
580 @Nullable
581 private Collection<String> mHints;
582 private boolean mIncludeTypesFromTextClassifier = true;
583
584 /**
585 * Sets a collection of types that are explicitly included.
586 */
587 @NonNull
588 public Builder setIncludedTypes(@Nullable Collection<String> includedTypes) {
589 mIncludedTypes = includedTypes;
590 return this;
591 }
592
593 /**
594 * Sets a collection of types that are explicitly excluded.
595 */
596 @NonNull
597 public Builder setExcludedTypes(@Nullable Collection<String> excludedTypes) {
598 mExcludedTypes = excludedTypes;
599 return this;
600 }
601
602 /**
603 * Specifies whether or not to include the types suggested by the text classifier. By
604 * default, it is included.
605 */
606 @NonNull
607 public Builder includeTypesFromTextClassifier(boolean includeTypesFromTextClassifier) {
608 mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
609 return this;
610 }
611
612
613 /**
614 * Sets the hints for the TextClassifier to determine what types of entities to find.
615 * These hints will only be used if {@link #includeTypesFromTextClassifier} is
616 * set to be true.
617 */
Tony Mak42ab9842019-03-05 15:38:51 +0000618 @NonNull
619 public Builder setHints(@Nullable Collection<String> hints) {
Tony Makae85aae2019-01-09 15:59:56 +0000620 mHints = hints;
621 return this;
622 }
623
624 /**
625 * Combines all of the options that have been set and returns a new {@link EntityConfig}
626 * object.
627 */
628 @NonNull
629 public EntityConfig build() {
630 return new EntityConfig(
631 mIncludedTypes == null
632 ? Collections.emptyList()
633 : new ArrayList<>(mIncludedTypes),
634 mExcludedTypes == null
635 ? Collections.emptyList()
636 : new ArrayList<>(mExcludedTypes),
637 mHints == null
638 ? Collections.emptyList()
639 : Collections.unmodifiableList(new ArrayList<>(mHints)),
640 mIncludeTypesFromTextClassifier);
641 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100642 }
Richard Ledleydb18a572017-11-30 17:33:51 +0000643 }
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000644
645 /**
646 * Utility functions for TextClassifier methods.
647 *
648 * <ul>
649 * <li>Provides validation of input parameters to TextClassifier methods
650 * </ul>
651 *
Tony Mak72e17972019-03-16 10:28:42 +0000652 * Intended to be used only for TextClassifier purposes.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000653 * @hide
654 */
655 final class Utils {
656
Tony Mak72e17972019-03-16 10:28:42 +0000657 @GuardedBy("WORD_ITERATOR")
658 private static final BreakIterator WORD_ITERATOR = BreakIterator.getWordInstance();
659
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000660 /**
661 * @throws IllegalArgumentException if text is null; startIndex is negative;
662 * endIndex is greater than text.length() or is not greater than startIndex;
663 * options is null
664 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100665 static void checkArgument(@NonNull CharSequence text, int startIndex, int endIndex) {
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000666 Preconditions.checkArgument(text != null);
667 Preconditions.checkArgument(startIndex >= 0);
668 Preconditions.checkArgument(endIndex <= text.length());
669 Preconditions.checkArgument(endIndex > startIndex);
670 }
671
Tony Makc5a74322020-02-04 17:18:15 +0000672 /** Returns if the length of the text is within the range. */
673 static boolean checkTextLength(CharSequence text, int maxLength) {
674 int textLength = text.length();
675 return textLength >= 0 && textLength <= maxLength;
Jan Althaus108aad32018-01-30 15:26:55 +0100676 }
677
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000678 /**
Tony Mak72e17972019-03-16 10:28:42 +0000679 * Returns the substring of {@code text} that contains at least text from index
680 * {@code start} <i>(inclusive)</i> to index {@code end} <i><(exclusive)/i> with the goal of
681 * returning text that is at least {@code minimumLength}. If {@code text} is not long
682 * enough, this will return {@code text}. This method returns text at word boundaries.
683 *
684 * @param text the source text
685 * @param start the start index of text that must be included
686 * @param end the end index of text that must be included
687 * @param minimumLength minimum length of text to return if {@code text} is long enough
688 */
689 public static String getSubString(
690 String text, int start, int end, int minimumLength) {
691 Preconditions.checkArgument(start >= 0);
692 Preconditions.checkArgument(end <= text.length());
693 Preconditions.checkArgument(start <= end);
694
695 if (text.length() < minimumLength) {
696 return text;
697 }
698
699 final int length = end - start;
700 if (length >= minimumLength) {
701 return text.substring(start, end);
702 }
703
704 final int offset = (minimumLength - length) / 2;
705 int iterStart = Math.max(0, Math.min(start - offset, text.length() - minimumLength));
706 int iterEnd = Math.min(text.length(), iterStart + minimumLength);
707
708 synchronized (WORD_ITERATOR) {
709 WORD_ITERATOR.setText(text);
710 iterStart = WORD_ITERATOR.isBoundary(iterStart)
711 ? iterStart : Math.max(0, WORD_ITERATOR.preceding(iterStart));
712 iterEnd = WORD_ITERATOR.isBoundary(iterEnd)
713 ? iterEnd : Math.max(iterEnd, WORD_ITERATOR.following(iterEnd));
714 WORD_ITERATOR.setText("");
715 return text.substring(iterStart, iterEnd);
716 }
717 }
718
719 /**
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000720 * Generates links using legacy {@link Linkify}.
721 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100722 public static TextLinks generateLegacyLinks(@NonNull TextLinks.Request request) {
723 final String string = request.getText().toString();
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000724 final TextLinks.Builder links = new TextLinks.Builder(string);
725
Richard Ledleyab669a02018-04-03 15:15:43 +0100726 final Collection<String> entities = request.getEntityConfig()
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000727 .resolveEntityListModifications(Collections.emptyList());
728 if (entities.contains(TextClassifier.TYPE_URL)) {
729 addLinks(links, string, TextClassifier.TYPE_URL);
730 }
731 if (entities.contains(TextClassifier.TYPE_PHONE)) {
732 addLinks(links, string, TextClassifier.TYPE_PHONE);
733 }
734 if (entities.contains(TextClassifier.TYPE_EMAIL)) {
735 addLinks(links, string, TextClassifier.TYPE_EMAIL);
736 }
737 // NOTE: Do not support MAP_ADDRESSES. Legacy version does not work well.
738 return links.build();
739 }
740
741 private static void addLinks(
742 TextLinks.Builder links, String string, @EntityType String entityType) {
743 final Spannable spannable = new SpannableString(string);
744 if (Linkify.addLinks(spannable, linkMask(entityType))) {
745 final URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
746 for (URLSpan urlSpan : spans) {
747 links.addLink(
748 spannable.getSpanStart(urlSpan),
749 spannable.getSpanEnd(urlSpan),
750 entityScores(entityType),
751 urlSpan);
752 }
753 }
754 }
755
756 @LinkifyMask
757 private static int linkMask(@EntityType String entityType) {
758 switch (entityType) {
759 case TextClassifier.TYPE_URL:
760 return Linkify.WEB_URLS;
761 case TextClassifier.TYPE_PHONE:
762 return Linkify.PHONE_NUMBERS;
763 case TextClassifier.TYPE_EMAIL:
764 return Linkify.EMAIL_ADDRESSES;
765 default:
766 // NOTE: Do not support MAP_ADDRESSES. Legacy version does not work well.
767 return 0;
768 }
769 }
770
771 private static Map<String, Float> entityScores(@EntityType String entityType) {
772 final Map<String, Float> scores = new ArrayMap<>();
773 scores.put(entityType, 1f);
774 return scores;
775 }
776
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100777 static void checkMainThread() {
778 if (Looper.myLooper() == Looper.getMainLooper()) {
779 Log.w(DEFAULT_LOG_TAG, "TextClassifier called on main thread");
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800780 }
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000781 }
782 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000783}