blob: a4e550285c97dc7d5104dae1dc5462a8758decf1 [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 Makf93e9e52018-07-16 14:46:29 +020036import com.android.internal.util.IndentingPrintWriter;
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000037import com.android.internal.util.Preconditions;
38
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000039import java.lang.annotation.Retention;
40import java.lang.annotation.RetentionPolicy;
Richard Ledleydb18a572017-11-30 17:33:51 +000041import java.util.ArrayList;
42import java.util.Collection;
43import java.util.Collections;
Richard Ledleyab669a02018-04-03 15:15:43 +010044import java.util.HashSet;
Tony Makae85aae2019-01-09 15:59:56 +000045import java.util.List;
Abodunrinwa Toki65638332018-03-16 21:08:50 +000046import java.util.Map;
Richard Ledleyab669a02018-04-03 15:15:43 +010047import java.util.Set;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000048
49/**
50 * Interface for providing text classification related features.
Abodunrinwa Tokiaa750a42018-11-09 13:47:59 +000051 * <p>
52 * The TextClassifier may be used to understand the meaning of text, as well as generating predicted
53 * next actions based on the text.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000054 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -080055 * <p><strong>NOTE: </strong>Unless otherwise stated, methods of this interface are blocking
56 * operations. Call on a worker thread.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000057 */
58public interface TextClassifier {
59
Abodunrinwa Toki692b1962017-08-15 15:05:11 +010060 /** @hide */
Abodunrinwa Toki008f3872017-11-27 19:32:35 +000061 String DEFAULT_LOG_TAG = "androidtc";
Abodunrinwa Toki692b1962017-08-15 15:05:11 +010062
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000063
64 /** @hide */
65 @Retention(RetentionPolicy.SOURCE)
66 @IntDef(value = {LOCAL, SYSTEM})
67 @interface TextClassifierType {} // TODO: Expose as system APIs.
68 /** Specifies a TextClassifier that runs locally in the app's process. @hide */
69 int LOCAL = 0;
70 /** Specifies a TextClassifier that runs in the system process and serves all apps. @hide */
71 int SYSTEM = 1;
72
Jan Althaus705b9e92018-01-22 18:22:29 +010073 /** The TextClassifier failed to run. */
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000074 String TYPE_UNKNOWN = "";
Jan Althaus705b9e92018-01-22 18:22:29 +010075 /** The classifier ran, but didn't recognize a known entity. */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000076 String TYPE_OTHER = "other";
Jan Althaus705b9e92018-01-22 18:22:29 +010077 /** E-mail address (e.g. "noreply@android.com"). */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000078 String TYPE_EMAIL = "email";
Jan Althaus705b9e92018-01-22 18:22:29 +010079 /** Phone number (e.g. "555-123 456"). */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000080 String TYPE_PHONE = "phone";
Jan Althaus705b9e92018-01-22 18:22:29 +010081 /** Physical address. */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000082 String TYPE_ADDRESS = "address";
Jan Althaus705b9e92018-01-22 18:22:29 +010083 /** Web URL. */
Abodunrinwa Toki9b4c82a2017-02-06 20:29:36 +000084 String TYPE_URL = "url";
Jan Althaus705b9e92018-01-22 18:22:29 +010085 /** Time reference that is no more specific than a date. May be absolute such as "01/01/2000" or
86 * relative like "tomorrow". **/
87 String TYPE_DATE = "date";
88 /** Time reference that includes a specific time. May be absolute such as "01/01/2000 5:30pm" or
89 * relative like "tomorrow at 5:30pm". **/
90 String TYPE_DATE_TIME = "datetime";
91 /** Flight number in IATA format. */
92 String TYPE_FLIGHT_NUMBER = "flight";
Tony Make1f3ac062018-11-27 14:30:21 +000093 /**
94 * Word that users may be interested to look up for meaning.
95 * @hide
96 */
97 String TYPE_DICTIONARY = "dictionary";
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000098
Abodunrinwa Toki45cb3e62017-03-29 21:51:45 +010099 /** @hide */
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000100 @Retention(RetentionPolicy.SOURCE)
Jeff Sharkey5db9a912017-12-08 17:32:32 -0700101 @StringDef(prefix = { "TYPE_" }, value = {
102 TYPE_UNKNOWN,
103 TYPE_OTHER,
104 TYPE_EMAIL,
105 TYPE_PHONE,
106 TYPE_ADDRESS,
107 TYPE_URL,
Jan Althaus705b9e92018-01-22 18:22:29 +0100108 TYPE_DATE,
109 TYPE_DATE_TIME,
110 TYPE_FLIGHT_NUMBER,
Tony Make1f3ac062018-11-27 14:30:21 +0000111 TYPE_DICTIONARY
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000112 })
113 @interface EntityType {}
114
Richard Ledley1fc998b2018-02-16 15:45:06 +0000115 /** Designates that the text in question is editable. **/
116 String HINT_TEXT_IS_EDITABLE = "android.text_is_editable";
117 /** Designates that the text in question is not editable. **/
118 String HINT_TEXT_IS_NOT_EDITABLE = "android.text_is_not_editable";
Richard Ledleydb18a572017-11-30 17:33:51 +0000119
120 /** @hide */
121 @Retention(RetentionPolicy.SOURCE)
Richard Ledley1fc998b2018-02-16 15:45:06 +0000122 @StringDef(prefix = { "HINT_" }, value = {HINT_TEXT_IS_EDITABLE, HINT_TEXT_IS_NOT_EDITABLE})
123 @interface Hints {}
Richard Ledleydb18a572017-11-30 17:33:51 +0000124
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000125 /** @hide */
126 @Retention(RetentionPolicy.SOURCE)
Tony Mak4dd43142018-05-02 16:49:01 +0100127 @StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_EDITTEXT, WIDGET_TYPE_UNSELECTABLE_TEXTVIEW,
128 WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW,
129 WIDGET_TYPE_CUSTOM_EDITTEXT, WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW,
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000130 WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_UNKNOWN})
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000131 @interface WidgetType {}
132
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000133 /** The widget involved in the text classification context is a standard
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000134 * {@link android.widget.TextView}. */
135 String WIDGET_TYPE_TEXTVIEW = "textview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000136 /** The widget involved in the text classification context is a standard
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000137 * {@link android.widget.EditText}. */
138 String WIDGET_TYPE_EDITTEXT = "edittext";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000139 /** The widget involved in the text classification context is a standard non-selectable
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000140 * {@link android.widget.TextView}. */
141 String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000142 /** The widget involved in the text classification context is a standard
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000143 * {@link android.webkit.WebView}. */
144 String WIDGET_TYPE_WEBVIEW = "webview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000145 /** The widget involved in the text classification context is a standard editable
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000146 * {@link android.webkit.WebView}. */
147 String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000148 /** The widget involved in the text classification context is a custom text widget. */
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000149 String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000150 /** The widget involved in the text classification context is a custom editable text widget. */
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000151 String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000152 /** The widget involved in the text classification context is a custom non-selectable text
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000153 * widget. */
154 String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000155 /** The widget involved in the text classification context is a notification */
156 String WIDGET_TYPE_NOTIFICATION = "notification";
157 /** The widget involved in the text classification context is of an unknown/unspecified type. */
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000158 String WIDGET_TYPE_UNKNOWN = "unknown";
159
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000160 /**
161 * No-op TextClassifier.
162 * This may be used to turn off TextClassifier features.
163 */
Abodunrinwa Toki520b2f82019-01-27 07:48:02 +0000164 TextClassifier NO_OP = new TextClassifier() {
165 @Override
166 public String toString() {
167 return "TextClassifier.NO_OP";
168 }
169 };
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000170
171 /**
Tony Mak63abbe22019-01-28 15:14:36 +0000172 * Extra that is included on activity intents coming from a TextClassifier when
173 * it suggests actions to its caller.
Tony Makc5a46122018-12-05 22:19:22 +0000174 * <p>
Tony Mak63abbe22019-01-28 15:14:36 +0000175 * All {@link TextClassifier} implementations should make sure this extra exists in their
Tony Makc5a46122018-12-05 22:19:22 +0000176 * generated intents.
177 */
178 String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER";
179
180 /**
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +0100181 * Returns suggested text selection start and end indices, recognized entity types, and their
182 * associated confidence scores. The entity types are ordered from highest to lowest scoring.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000183 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800184 * <p><strong>NOTE: </strong>Call on a worker thread.
185 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100186 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000187 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
188 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100189 * @param request the text selection request
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000190 */
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100191 @WorkerThread
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000192 @NonNull
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100193 default TextSelection suggestSelection(@NonNull TextSelection.Request request) {
194 Preconditions.checkNotNull(request);
195 Utils.checkMainThread();
196 return new TextSelection.Builder(request.getStartIndex(), request.getEndIndex()).build();
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100197 }
198
199 /**
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000200 * Returns suggested text selection start and end indices, recognized entity types, and their
201 * associated confidence scores. The entity types are ordered from highest to lowest scoring.
202 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800203 * <p><strong>NOTE: </strong>Call on a worker thread.
204 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100205 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000206 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
207 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100208 * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
209 * {@link #suggestSelection(TextSelection.Request)}. If that method calls this method,
210 * a stack overflow error will happen.
211 *
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000212 * @param text text providing context for the selected text (which is specified
213 * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
214 * @param selectionStartIndex start index of the selected part of text
215 * @param selectionEndIndex end index of the selected part of text
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100216 * @param defaultLocales ordered list of locale preferences that may be used to
217 * disambiguate the provided text. If no locale preferences exist, set this to null
218 * or an empty locale list.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000219 *
220 * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
221 * selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
222 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100223 * @see #suggestSelection(TextSelection.Request)
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000224 */
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100225 @WorkerThread
226 @NonNull
227 default TextSelection suggestSelection(
228 @NonNull CharSequence text,
229 @IntRange(from = 0) int selectionStartIndex,
230 @IntRange(from = 0) int selectionEndIndex,
231 @Nullable LocaleList defaultLocales) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100232 final TextSelection.Request request = new TextSelection.Request.Builder(
233 text, selectionStartIndex, selectionEndIndex)
234 .setDefaultLocales(defaultLocales)
235 .build();
236 return suggestSelection(request);
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100237 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000238
239 /**
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100240 * Classifies the specified text and returns a {@link TextClassification} object that can be
241 * used to generate a widget for handling the classified text.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000242 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800243 * <p><strong>NOTE: </strong>Call on a worker thread.
244 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100245 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000246 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
247 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100248 * @param request the text classification request
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000249 */
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100250 @WorkerThread
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000251 @NonNull
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100252 default TextClassification classifyText(@NonNull TextClassification.Request request) {
253 Preconditions.checkNotNull(request);
254 Utils.checkMainThread();
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100255 return TextClassification.EMPTY;
256 }
257
258 /**
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000259 * Classifies the specified text and returns a {@link TextClassification} object that can be
260 * used to generate a widget for handling the classified text.
261 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800262 * <p><strong>NOTE: </strong>Call on a worker thread.
263 *
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000264 * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100265 * {@link #classifyText(TextClassification.Request)}. If that method calls this method,
266 * a stack overflow error will happen.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000267 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100268 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000269 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
270 *
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000271 * @param text text providing context for the text to classify (which is specified
272 * by the sub sequence starting at startIndex and ending at endIndex)
273 * @param startIndex start index of the text to classify
274 * @param endIndex end index of the text to classify
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100275 * @param defaultLocales ordered list of locale preferences that may be used to
276 * disambiguate the provided text. If no locale preferences exist, set this to null
277 * or an empty locale list.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000278 *
279 * @throws IllegalArgumentException if text is null; startIndex is negative;
280 * endIndex is greater than text.length() or not greater than startIndex
281 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100282 * @see #classifyText(TextClassification.Request)
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000283 */
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100284 @WorkerThread
285 @NonNull
286 default TextClassification classifyText(
287 @NonNull CharSequence text,
288 @IntRange(from = 0) int startIndex,
289 @IntRange(from = 0) int endIndex,
290 @Nullable LocaleList defaultLocales) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100291 final TextClassification.Request request = new TextClassification.Request.Builder(
292 text, startIndex, endIndex)
293 .setDefaultLocales(defaultLocales)
294 .build();
295 return classifyText(request);
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100296 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000297
298 /**
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800299 * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with
300 * links information.
Richard Ledley68d94522017-10-05 10:52:19 +0100301 *
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800302 * <p><strong>NOTE: </strong>Call on a worker thread.
Richard Ledleydb18a572017-11-30 17:33:51 +0000303 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100304 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000305 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
306 *
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100307 * @param request the text links request
Richard Ledley68d94522017-10-05 10:52:19 +0100308 *
Jan Althaus108aad32018-01-30 15:26:55 +0100309 * @see #getMaxGenerateLinksTextLength()
Richard Ledley68d94522017-10-05 10:52:19 +0100310 */
311 @WorkerThread
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000312 @NonNull
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100313 default TextLinks generateLinks(@NonNull TextLinks.Request request) {
314 Preconditions.checkNotNull(request);
315 Utils.checkMainThread();
316 return new TextLinks.Builder(request.getText().toString()).build();
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000317 }
318
319 /**
Jan Althaus108aad32018-01-30 15:26:55 +0100320 * Returns the maximal length of text that can be processed by generateLinks.
321 *
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 * @see #generateLinks(TextLinks.Request)
Jan Althaus108aad32018-01-30 15:26:55 +0100326 */
Abodunrinwa Tokiad52f4b2018-02-06 23:32:41 +0000327 @WorkerThread
Jan Althaus108aad32018-01-30 15:26:55 +0100328 default int getMaxGenerateLinksTextLength() {
329 return Integer.MAX_VALUE;
330 }
331
332 /**
Tony Makae85aae2019-01-09 15:59:56 +0000333 * Detects the language of the text in the given request.
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100334 *
335 * <p><strong>NOTE: </strong>Call on a worker thread.
336 *
337 *
338 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
339 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
340 *
341 * @param request the {@link TextLanguage} request.
342 * @return the {@link TextLanguage} result.
343 */
344 @WorkerThread
345 @NonNull
346 default TextLanguage detectLanguage(@NonNull TextLanguage.Request request) {
347 Preconditions.checkNotNull(request);
348 Utils.checkMainThread();
349 return TextLanguage.EMPTY;
350 }
351
352 /**
Tony Makc9d31e22018-10-22 16:17:45 +0100353 * Suggests and returns a list of actions according to the given conversation.
354 */
355 @WorkerThread
Tony Mak42ab9842019-03-05 15:38:51 +0000356 @NonNull
Tony Makc9d31e22018-10-22 16:17:45 +0100357 default ConversationActions suggestConversationActions(
358 @NonNull ConversationActions.Request request) {
359 Preconditions.checkNotNull(request);
360 Utils.checkMainThread();
Tony Makc4359bf2018-12-11 19:38:53 +0800361 return new ConversationActions(Collections.emptyList(), null);
Tony Makc9d31e22018-10-22 16:17:45 +0100362 }
363
364 /**
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000365 * <strong>NOTE: </strong>Use {@link #onTextClassifierEvent(TextClassifierEvent)} instead.
366 * <p>
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000367 * Reports a selection event.
368 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100369 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000370 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
371 */
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000372 default void onSelectionEvent(@NonNull SelectionEvent event) {
373 // TODO: Consider rerouting to onTextClassifierEvent()
374 }
375
376 /**
377 * Reports a text classifier event.
378 * <p>
379 * <strong>NOTE: </strong>Call on a worker thread.
380 *
381 * @throws IllegalStateException if this TextClassifier has been destroyed.
382 * @see #isDestroyed()
383 */
384 default void onTextClassifierEvent(@NonNull TextClassifierEvent event) {}
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000385
386 /**
387 * Destroys this TextClassifier.
388 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100389 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to its methods should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000390 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
391 *
392 * <p>Subsequent calls to this method are no-ops.
393 */
394 default void destroy() {}
395
396 /**
397 * Returns whether or not this TextClassifier has been destroyed.
398 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100399 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, caller should not interact
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000400 * with the classifier and an attempt to do so would throw an {@link IllegalStateException}.
401 * However, this method should never throw an {@link IllegalStateException}.
402 *
403 * @see #destroy()
404 */
405 default boolean isDestroyed() {
406 return false;
407 }
408
Tony Makf93e9e52018-07-16 14:46:29 +0200409 /** @hide **/
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100410 default void dump(@NonNull IndentingPrintWriter printWriter) {}
Tony Makf93e9e52018-07-16 14:46:29 +0200411
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000412 /**
Tony Makae85aae2019-01-09 15:59:56 +0000413 * Configuration object for specifying what entity types to identify.
Richard Ledleydb18a572017-11-30 17:33:51 +0000414 *
415 * Configs are initially based on a predefined preset, and can be modified from there.
416 */
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100417 final class EntityConfig implements Parcelable {
Tony Makae85aae2019-01-09 15:59:56 +0000418 private final List<String> mIncludedTypes;
419 private final List<String> mExcludedTypes;
420 private final List<String> mHints;
421 private final boolean mIncludeTypesFromTextClassifier;
Richard Ledleydb18a572017-11-30 17:33:51 +0000422
Tony Makae85aae2019-01-09 15:59:56 +0000423 private EntityConfig(
424 List<String> includedEntityTypes,
425 List<String> excludedEntityTypes,
426 List<String> hints,
427 boolean includeTypesFromTextClassifier) {
428 mIncludedTypes = Preconditions.checkNotNull(includedEntityTypes);
429 mExcludedTypes = Preconditions.checkNotNull(excludedEntityTypes);
430 mHints = Preconditions.checkNotNull(hints);
431 mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
432 }
433
434 private EntityConfig(Parcel in) {
435 mIncludedTypes = new ArrayList<>();
436 in.readStringList(mIncludedTypes);
437 mExcludedTypes = new ArrayList<>();
438 in.readStringList(mExcludedTypes);
439 List<String> tmpHints = new ArrayList<>();
440 in.readStringList(tmpHints);
441 mHints = Collections.unmodifiableList(tmpHints);
442 mIncludeTypesFromTextClassifier = in.readByte() != 0;
443 }
444
445 @Override
446 public void writeToParcel(Parcel parcel, int flags) {
447 parcel.writeStringList(mIncludedTypes);
448 parcel.writeStringList(mExcludedTypes);
449 parcel.writeStringList(mHints);
450 parcel.writeByte((byte) (mIncludeTypesFromTextClassifier ? 1 : 0));
Richard Ledleydb18a572017-11-30 17:33:51 +0000451 }
452
453 /**
Richard Ledley1fc998b2018-02-16 15:45:06 +0000454 * Creates an EntityConfig.
455 *
456 * @param hints Hints for the TextClassifier to determine what types of entities to find.
Tony Makae85aae2019-01-09 15:59:56 +0000457 *
458 * @deprecated Use {@link Builder} instead.
Richard Ledley1fc998b2018-02-16 15:45:06 +0000459 */
Tony Makae85aae2019-01-09 15:59:56 +0000460 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100461 public static EntityConfig createWithHints(@Nullable Collection<String> hints) {
Tony Makae85aae2019-01-09 15:59:56 +0000462 return new EntityConfig.Builder()
463 .includeTypesFromTextClassifier(true)
464 .setHints(hints)
465 .build();
Abodunrinwa Tokiae82e7a2018-04-03 23:49:16 +0100466 }
467
Richard Ledley1fc998b2018-02-16 15:45:06 +0000468 /**
469 * Creates an EntityConfig.
470 *
471 * @param hints Hints for the TextClassifier to determine what types of entities to find
472 * @param includedEntityTypes Entity types, e.g. {@link #TYPE_EMAIL}, to explicitly include
473 * @param excludedEntityTypes Entity types, e.g. {@link #TYPE_PHONE}, to explicitly exclude
474 *
Richard Ledleydb18a572017-11-30 17:33:51 +0000475 *
476 * Note that if an entity has been excluded, the exclusion will take precedence.
Tony Makae85aae2019-01-09 15:59:56 +0000477 *
478 * @deprecated Use {@link Builder} instead.
Richard Ledleydb18a572017-11-30 17:33:51 +0000479 */
Tony Makae85aae2019-01-09 15:59:56 +0000480 @Deprecated
Richard Ledley1fc998b2018-02-16 15:45:06 +0000481 public static EntityConfig create(@Nullable Collection<String> hints,
482 @Nullable Collection<String> includedEntityTypes,
483 @Nullable Collection<String> excludedEntityTypes) {
Tony Makae85aae2019-01-09 15:59:56 +0000484 return new EntityConfig.Builder()
485 .setIncludedTypes(includedEntityTypes)
486 .setExcludedTypes(excludedEntityTypes)
487 .setHints(hints)
488 .includeTypesFromTextClassifier(true)
489 .build();
Richard Ledleydb18a572017-11-30 17:33:51 +0000490 }
491
492 /**
Richard Ledley1fc998b2018-02-16 15:45:06 +0000493 * Creates an EntityConfig with an explicit entity list.
494 *
495 * @param entityTypes Complete set of entities, e.g. {@link #TYPE_URL} to find.
496 *
Tony Makae85aae2019-01-09 15:59:56 +0000497 * @deprecated Use {@link Builder} instead.
Richard Ledleydb18a572017-11-30 17:33:51 +0000498 */
Tony Makae85aae2019-01-09 15:59:56 +0000499 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100500 public static EntityConfig createWithExplicitEntityList(
501 @Nullable Collection<String> entityTypes) {
Tony Makae85aae2019-01-09 15:59:56 +0000502 return new EntityConfig.Builder()
503 .setIncludedTypes(entityTypes)
504 .includeTypesFromTextClassifier(false)
505 .build();
Abodunrinwa Tokiae82e7a2018-04-03 23:49:16 +0100506 }
507
Richard Ledleydb18a572017-11-30 17:33:51 +0000508 /**
Tony Makae85aae2019-01-09 15:59:56 +0000509 * Returns a final list of entity types to find.
Richard Ledley1fc998b2018-02-16 15:45:06 +0000510 *
Tony Makae85aae2019-01-09 15:59:56 +0000511 * @param entityTypes Entity types we think should be found before factoring in
512 * includes/excludes
Richard Ledley1fc998b2018-02-16 15:45:06 +0000513 *
514 * This method is intended for use by TextClassifier implementations.
Richard Ledleydb18a572017-11-30 17:33:51 +0000515 */
Richard Ledleyab669a02018-04-03 15:15:43 +0100516 public Collection<String> resolveEntityListModifications(
Tony Makae85aae2019-01-09 15:59:56 +0000517 @NonNull Collection<String> entityTypes) {
518 final Set<String> finalSet = new HashSet<>();
519 if (mIncludeTypesFromTextClassifier) {
520 finalSet.addAll(entityTypes);
Richard Ledleydb18a572017-11-30 17:33:51 +0000521 }
Tony Makae85aae2019-01-09 15:59:56 +0000522 finalSet.addAll(mIncludedTypes);
523 finalSet.removeAll(mExcludedTypes);
Richard Ledleyab669a02018-04-03 15:15:43 +0100524 return finalSet;
Richard Ledley1fc998b2018-02-16 15:45:06 +0000525 }
526
527 /**
528 * Retrieves the list of hints.
529 *
530 * @return An unmodifiable collection of the hints.
531 */
532 public Collection<String> getHints() {
533 return mHints;
Richard Ledleydb18a572017-11-30 17:33:51 +0000534 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100535
Tony Makae85aae2019-01-09 15:59:56 +0000536 /**
537 * Return whether the client allows the text classifier to include its own list of
538 * default types. If this function returns {@code true}, a default list of types suggested
539 * from a text classifier will be taking into account.
540 *
541 * <p>NOTE: This method is intended for use by a text classifier.
542 *
543 * @see #resolveEntityListModifications(Collection)
544 */
545 public boolean shouldIncludeTypesFromTextClassifier() {
546 return mIncludeTypesFromTextClassifier;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100547 }
548
549 @Override
Tony Makae85aae2019-01-09 15:59:56 +0000550 public int describeContents() {
551 return 0;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100552 }
553
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700554 public static final @android.annotation.NonNull Parcelable.Creator<EntityConfig> CREATOR =
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100555 new Parcelable.Creator<EntityConfig>() {
556 @Override
557 public EntityConfig createFromParcel(Parcel in) {
558 return new EntityConfig(in);
559 }
560
561 @Override
562 public EntityConfig[] newArray(int size) {
563 return new EntityConfig[size];
564 }
565 };
566
Tony Makae85aae2019-01-09 15:59:56 +0000567
568
569 /** Builder class to construct the {@link EntityConfig} object. */
570 public static final class Builder {
571 @Nullable
572 private Collection<String> mIncludedTypes;
573 @Nullable
574 private Collection<String> mExcludedTypes;
575 @Nullable
576 private Collection<String> mHints;
577 private boolean mIncludeTypesFromTextClassifier = true;
578
579 /**
580 * Sets a collection of types that are explicitly included.
581 */
582 @NonNull
583 public Builder setIncludedTypes(@Nullable Collection<String> includedTypes) {
584 mIncludedTypes = includedTypes;
585 return this;
586 }
587
588 /**
589 * Sets a collection of types that are explicitly excluded.
590 */
591 @NonNull
592 public Builder setExcludedTypes(@Nullable Collection<String> excludedTypes) {
593 mExcludedTypes = excludedTypes;
594 return this;
595 }
596
597 /**
598 * Specifies whether or not to include the types suggested by the text classifier. By
599 * default, it is included.
600 */
601 @NonNull
602 public Builder includeTypesFromTextClassifier(boolean includeTypesFromTextClassifier) {
603 mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
604 return this;
605 }
606
607
608 /**
609 * Sets the hints for the TextClassifier to determine what types of entities to find.
610 * These hints will only be used if {@link #includeTypesFromTextClassifier} is
611 * set to be true.
612 */
Tony Mak42ab9842019-03-05 15:38:51 +0000613 @NonNull
614 public Builder setHints(@Nullable Collection<String> hints) {
Tony Makae85aae2019-01-09 15:59:56 +0000615 mHints = hints;
616 return this;
617 }
618
619 /**
620 * Combines all of the options that have been set and returns a new {@link EntityConfig}
621 * object.
622 */
623 @NonNull
624 public EntityConfig build() {
625 return new EntityConfig(
626 mIncludedTypes == null
627 ? Collections.emptyList()
628 : new ArrayList<>(mIncludedTypes),
629 mExcludedTypes == null
630 ? Collections.emptyList()
631 : new ArrayList<>(mExcludedTypes),
632 mHints == null
633 ? Collections.emptyList()
634 : Collections.unmodifiableList(new ArrayList<>(mHints)),
635 mIncludeTypesFromTextClassifier);
636 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100637 }
Richard Ledleydb18a572017-11-30 17:33:51 +0000638 }
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000639
640 /**
641 * Utility functions for TextClassifier methods.
642 *
643 * <ul>
644 * <li>Provides validation of input parameters to TextClassifier methods
645 * </ul>
646 *
647 * Intended to be used only in this package.
648 * @hide
649 */
650 final class Utils {
651
652 /**
653 * @throws IllegalArgumentException if text is null; startIndex is negative;
654 * endIndex is greater than text.length() or is not greater than startIndex;
655 * options is null
656 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100657 static void checkArgument(@NonNull CharSequence text, int startIndex, int endIndex) {
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000658 Preconditions.checkArgument(text != null);
659 Preconditions.checkArgument(startIndex >= 0);
660 Preconditions.checkArgument(endIndex <= text.length());
661 Preconditions.checkArgument(endIndex > startIndex);
662 }
663
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100664 static void checkTextLength(CharSequence text, int maxLength) {
Jan Althaus108aad32018-01-30 15:26:55 +0100665 Preconditions.checkArgumentInRange(text.length(), 0, maxLength, "text.length()");
666 }
667
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000668 /**
669 * Generates links using legacy {@link Linkify}.
670 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100671 public static TextLinks generateLegacyLinks(@NonNull TextLinks.Request request) {
672 final String string = request.getText().toString();
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000673 final TextLinks.Builder links = new TextLinks.Builder(string);
674
Richard Ledleyab669a02018-04-03 15:15:43 +0100675 final Collection<String> entities = request.getEntityConfig()
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000676 .resolveEntityListModifications(Collections.emptyList());
677 if (entities.contains(TextClassifier.TYPE_URL)) {
678 addLinks(links, string, TextClassifier.TYPE_URL);
679 }
680 if (entities.contains(TextClassifier.TYPE_PHONE)) {
681 addLinks(links, string, TextClassifier.TYPE_PHONE);
682 }
683 if (entities.contains(TextClassifier.TYPE_EMAIL)) {
684 addLinks(links, string, TextClassifier.TYPE_EMAIL);
685 }
686 // NOTE: Do not support MAP_ADDRESSES. Legacy version does not work well.
687 return links.build();
688 }
689
690 private static void addLinks(
691 TextLinks.Builder links, String string, @EntityType String entityType) {
692 final Spannable spannable = new SpannableString(string);
693 if (Linkify.addLinks(spannable, linkMask(entityType))) {
694 final URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
695 for (URLSpan urlSpan : spans) {
696 links.addLink(
697 spannable.getSpanStart(urlSpan),
698 spannable.getSpanEnd(urlSpan),
699 entityScores(entityType),
700 urlSpan);
701 }
702 }
703 }
704
705 @LinkifyMask
706 private static int linkMask(@EntityType String entityType) {
707 switch (entityType) {
708 case TextClassifier.TYPE_URL:
709 return Linkify.WEB_URLS;
710 case TextClassifier.TYPE_PHONE:
711 return Linkify.PHONE_NUMBERS;
712 case TextClassifier.TYPE_EMAIL:
713 return Linkify.EMAIL_ADDRESSES;
714 default:
715 // NOTE: Do not support MAP_ADDRESSES. Legacy version does not work well.
716 return 0;
717 }
718 }
719
720 private static Map<String, Float> entityScores(@EntityType String entityType) {
721 final Map<String, Float> scores = new ArrayMap<>();
722 scores.put(entityType, 1f);
723 return scores;
724 }
725
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100726 static void checkMainThread() {
727 if (Looper.myLooper() == Looper.getMainLooper()) {
728 Log.w(DEFAULT_LOG_TAG, "TextClassifier called on main thread");
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800729 }
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000730 }
731 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000732}