blob: e0101556910fd696635d507a4021e24bae2c7913 [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
356 default ConversationActions suggestConversationActions(
357 @NonNull ConversationActions.Request request) {
358 Preconditions.checkNotNull(request);
359 Utils.checkMainThread();
Tony Makc4359bf2018-12-11 19:38:53 +0800360 return new ConversationActions(Collections.emptyList(), null);
Tony Makc9d31e22018-10-22 16:17:45 +0100361 }
362
363 /**
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000364 * <strong>NOTE: </strong>Use {@link #onTextClassifierEvent(TextClassifierEvent)} instead.
365 * <p>
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000366 * Reports a selection event.
367 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100368 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000369 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
370 */
Abodunrinwa Toki37ccedc2018-12-11 00:35:11 +0000371 default void onSelectionEvent(@NonNull SelectionEvent event) {
372 // TODO: Consider rerouting to onTextClassifierEvent()
373 }
374
375 /**
376 * Reports a text classifier event.
377 * <p>
378 * <strong>NOTE: </strong>Call on a worker thread.
379 *
380 * @throws IllegalStateException if this TextClassifier has been destroyed.
381 * @see #isDestroyed()
382 */
383 default void onTextClassifierEvent(@NonNull TextClassifierEvent event) {}
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000384
385 /**
386 * Destroys this TextClassifier.
387 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100388 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to its methods should
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000389 * throw an {@link IllegalStateException}. See {@link #isDestroyed()}.
390 *
391 * <p>Subsequent calls to this method are no-ops.
392 */
393 default void destroy() {}
394
395 /**
396 * Returns whether or not this TextClassifier has been destroyed.
397 *
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100398 * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, caller should not interact
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000399 * with the classifier and an attempt to do so would throw an {@link IllegalStateException}.
400 * However, this method should never throw an {@link IllegalStateException}.
401 *
402 * @see #destroy()
403 */
404 default boolean isDestroyed() {
405 return false;
406 }
407
Tony Makf93e9e52018-07-16 14:46:29 +0200408 /** @hide **/
Abodunrinwa Toki7cefd4f2018-09-14 16:00:03 +0100409 default void dump(@NonNull IndentingPrintWriter printWriter) {}
Tony Makf93e9e52018-07-16 14:46:29 +0200410
Abodunrinwa Toki88be5a62018-03-23 04:01:28 +0000411 /**
Tony Makae85aae2019-01-09 15:59:56 +0000412 * Configuration object for specifying what entity types to identify.
Richard Ledleydb18a572017-11-30 17:33:51 +0000413 *
414 * Configs are initially based on a predefined preset, and can be modified from there.
415 */
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100416 final class EntityConfig implements Parcelable {
Tony Makae85aae2019-01-09 15:59:56 +0000417 private final List<String> mIncludedTypes;
418 private final List<String> mExcludedTypes;
419 private final List<String> mHints;
420 private final boolean mIncludeTypesFromTextClassifier;
Richard Ledleydb18a572017-11-30 17:33:51 +0000421
Tony Makae85aae2019-01-09 15:59:56 +0000422 private EntityConfig(
423 List<String> includedEntityTypes,
424 List<String> excludedEntityTypes,
425 List<String> hints,
426 boolean includeTypesFromTextClassifier) {
427 mIncludedTypes = Preconditions.checkNotNull(includedEntityTypes);
428 mExcludedTypes = Preconditions.checkNotNull(excludedEntityTypes);
429 mHints = Preconditions.checkNotNull(hints);
430 mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
431 }
432
433 private EntityConfig(Parcel in) {
434 mIncludedTypes = new ArrayList<>();
435 in.readStringList(mIncludedTypes);
436 mExcludedTypes = new ArrayList<>();
437 in.readStringList(mExcludedTypes);
438 List<String> tmpHints = new ArrayList<>();
439 in.readStringList(tmpHints);
440 mHints = Collections.unmodifiableList(tmpHints);
441 mIncludeTypesFromTextClassifier = in.readByte() != 0;
442 }
443
444 @Override
445 public void writeToParcel(Parcel parcel, int flags) {
446 parcel.writeStringList(mIncludedTypes);
447 parcel.writeStringList(mExcludedTypes);
448 parcel.writeStringList(mHints);
449 parcel.writeByte((byte) (mIncludeTypesFromTextClassifier ? 1 : 0));
Richard Ledleydb18a572017-11-30 17:33:51 +0000450 }
451
452 /**
Richard Ledley1fc998b2018-02-16 15:45:06 +0000453 * Creates an EntityConfig.
454 *
455 * @param hints Hints for the TextClassifier to determine what types of entities to find.
Tony Makae85aae2019-01-09 15:59:56 +0000456 *
457 * @deprecated Use {@link Builder} instead.
Richard Ledley1fc998b2018-02-16 15:45:06 +0000458 */
Tony Makae85aae2019-01-09 15:59:56 +0000459 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100460 public static EntityConfig createWithHints(@Nullable Collection<String> hints) {
Tony Makae85aae2019-01-09 15:59:56 +0000461 return new EntityConfig.Builder()
462 .includeTypesFromTextClassifier(true)
463 .setHints(hints)
464 .build();
Abodunrinwa Tokiae82e7a2018-04-03 23:49:16 +0100465 }
466
Richard Ledley1fc998b2018-02-16 15:45:06 +0000467 /**
468 * Creates an EntityConfig.
469 *
470 * @param hints Hints for the TextClassifier to determine what types of entities to find
471 * @param includedEntityTypes Entity types, e.g. {@link #TYPE_EMAIL}, to explicitly include
472 * @param excludedEntityTypes Entity types, e.g. {@link #TYPE_PHONE}, to explicitly exclude
473 *
Richard Ledleydb18a572017-11-30 17:33:51 +0000474 *
475 * Note that if an entity has been excluded, the exclusion will take precedence.
Tony Makae85aae2019-01-09 15:59:56 +0000476 *
477 * @deprecated Use {@link Builder} instead.
Richard Ledleydb18a572017-11-30 17:33:51 +0000478 */
Tony Makae85aae2019-01-09 15:59:56 +0000479 @Deprecated
Richard Ledley1fc998b2018-02-16 15:45:06 +0000480 public static EntityConfig create(@Nullable Collection<String> hints,
481 @Nullable Collection<String> includedEntityTypes,
482 @Nullable Collection<String> excludedEntityTypes) {
Tony Makae85aae2019-01-09 15:59:56 +0000483 return new EntityConfig.Builder()
484 .setIncludedTypes(includedEntityTypes)
485 .setExcludedTypes(excludedEntityTypes)
486 .setHints(hints)
487 .includeTypesFromTextClassifier(true)
488 .build();
Richard Ledleydb18a572017-11-30 17:33:51 +0000489 }
490
491 /**
Richard Ledley1fc998b2018-02-16 15:45:06 +0000492 * Creates an EntityConfig with an explicit entity list.
493 *
494 * @param entityTypes Complete set of entities, e.g. {@link #TYPE_URL} to find.
495 *
Tony Makae85aae2019-01-09 15:59:56 +0000496 * @deprecated Use {@link Builder} instead.
Richard Ledleydb18a572017-11-30 17:33:51 +0000497 */
Tony Makae85aae2019-01-09 15:59:56 +0000498 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100499 public static EntityConfig createWithExplicitEntityList(
500 @Nullable Collection<String> entityTypes) {
Tony Makae85aae2019-01-09 15:59:56 +0000501 return new EntityConfig.Builder()
502 .setIncludedTypes(entityTypes)
503 .includeTypesFromTextClassifier(false)
504 .build();
Abodunrinwa Tokiae82e7a2018-04-03 23:49:16 +0100505 }
506
Richard Ledleydb18a572017-11-30 17:33:51 +0000507 /**
Tony Makae85aae2019-01-09 15:59:56 +0000508 * Returns a final list of entity types to find.
Richard Ledley1fc998b2018-02-16 15:45:06 +0000509 *
Tony Makae85aae2019-01-09 15:59:56 +0000510 * @param entityTypes Entity types we think should be found before factoring in
511 * includes/excludes
Richard Ledley1fc998b2018-02-16 15:45:06 +0000512 *
513 * This method is intended for use by TextClassifier implementations.
Richard Ledleydb18a572017-11-30 17:33:51 +0000514 */
Richard Ledleyab669a02018-04-03 15:15:43 +0100515 public Collection<String> resolveEntityListModifications(
Tony Makae85aae2019-01-09 15:59:56 +0000516 @NonNull Collection<String> entityTypes) {
517 final Set<String> finalSet = new HashSet<>();
518 if (mIncludeTypesFromTextClassifier) {
519 finalSet.addAll(entityTypes);
Richard Ledleydb18a572017-11-30 17:33:51 +0000520 }
Tony Makae85aae2019-01-09 15:59:56 +0000521 finalSet.addAll(mIncludedTypes);
522 finalSet.removeAll(mExcludedTypes);
Richard Ledleyab669a02018-04-03 15:15:43 +0100523 return finalSet;
Richard Ledley1fc998b2018-02-16 15:45:06 +0000524 }
525
526 /**
527 * Retrieves the list of hints.
528 *
529 * @return An unmodifiable collection of the hints.
530 */
531 public Collection<String> getHints() {
532 return mHints;
Richard Ledleydb18a572017-11-30 17:33:51 +0000533 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100534
Tony Makae85aae2019-01-09 15:59:56 +0000535 /**
536 * Return whether the client allows the text classifier to include its own list of
537 * default types. If this function returns {@code true}, a default list of types suggested
538 * from a text classifier will be taking into account.
539 *
540 * <p>NOTE: This method is intended for use by a text classifier.
541 *
542 * @see #resolveEntityListModifications(Collection)
543 */
544 public boolean shouldIncludeTypesFromTextClassifier() {
545 return mIncludeTypesFromTextClassifier;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100546 }
547
548 @Override
Tony Makae85aae2019-01-09 15:59:56 +0000549 public int describeContents() {
550 return 0;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100551 }
552
553 public static final Parcelable.Creator<EntityConfig> CREATOR =
554 new Parcelable.Creator<EntityConfig>() {
555 @Override
556 public EntityConfig createFromParcel(Parcel in) {
557 return new EntityConfig(in);
558 }
559
560 @Override
561 public EntityConfig[] newArray(int size) {
562 return new EntityConfig[size];
563 }
564 };
565
Tony Makae85aae2019-01-09 15:59:56 +0000566
567
568 /** Builder class to construct the {@link EntityConfig} object. */
569 public static final class Builder {
570 @Nullable
571 private Collection<String> mIncludedTypes;
572 @Nullable
573 private Collection<String> mExcludedTypes;
574 @Nullable
575 private Collection<String> mHints;
576 private boolean mIncludeTypesFromTextClassifier = true;
577
578 /**
579 * Sets a collection of types that are explicitly included.
580 */
581 @NonNull
582 public Builder setIncludedTypes(@Nullable Collection<String> includedTypes) {
583 mIncludedTypes = includedTypes;
584 return this;
585 }
586
587 /**
588 * Sets a collection of types that are explicitly excluded.
589 */
590 @NonNull
591 public Builder setExcludedTypes(@Nullable Collection<String> excludedTypes) {
592 mExcludedTypes = excludedTypes;
593 return this;
594 }
595
596 /**
597 * Specifies whether or not to include the types suggested by the text classifier. By
598 * default, it is included.
599 */
600 @NonNull
601 public Builder includeTypesFromTextClassifier(boolean includeTypesFromTextClassifier) {
602 mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
603 return this;
604 }
605
606
607 /**
608 * Sets the hints for the TextClassifier to determine what types of entities to find.
609 * These hints will only be used if {@link #includeTypesFromTextClassifier} is
610 * set to be true.
611 */
612 public Builder setHints(Collection<String> hints) {
613 mHints = hints;
614 return this;
615 }
616
617 /**
618 * Combines all of the options that have been set and returns a new {@link EntityConfig}
619 * object.
620 */
621 @NonNull
622 public EntityConfig build() {
623 return new EntityConfig(
624 mIncludedTypes == null
625 ? Collections.emptyList()
626 : new ArrayList<>(mIncludedTypes),
627 mExcludedTypes == null
628 ? Collections.emptyList()
629 : new ArrayList<>(mExcludedTypes),
630 mHints == null
631 ? Collections.emptyList()
632 : Collections.unmodifiableList(new ArrayList<>(mHints)),
633 mIncludeTypesFromTextClassifier);
634 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100635 }
Richard Ledleydb18a572017-11-30 17:33:51 +0000636 }
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000637
638 /**
639 * Utility functions for TextClassifier methods.
640 *
641 * <ul>
642 * <li>Provides validation of input parameters to TextClassifier methods
643 * </ul>
644 *
645 * Intended to be used only in this package.
646 * @hide
647 */
648 final class Utils {
649
650 /**
651 * @throws IllegalArgumentException if text is null; startIndex is negative;
652 * endIndex is greater than text.length() or is not greater than startIndex;
653 * options is null
654 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100655 static void checkArgument(@NonNull CharSequence text, int startIndex, int endIndex) {
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000656 Preconditions.checkArgument(text != null);
657 Preconditions.checkArgument(startIndex >= 0);
658 Preconditions.checkArgument(endIndex <= text.length());
659 Preconditions.checkArgument(endIndex > startIndex);
660 }
661
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100662 static void checkTextLength(CharSequence text, int maxLength) {
Jan Althaus108aad32018-01-30 15:26:55 +0100663 Preconditions.checkArgumentInRange(text.length(), 0, maxLength, "text.length()");
664 }
665
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000666 /**
667 * Generates links using legacy {@link Linkify}.
668 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100669 public static TextLinks generateLegacyLinks(@NonNull TextLinks.Request request) {
670 final String string = request.getText().toString();
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000671 final TextLinks.Builder links = new TextLinks.Builder(string);
672
Richard Ledleyab669a02018-04-03 15:15:43 +0100673 final Collection<String> entities = request.getEntityConfig()
Abodunrinwa Toki65638332018-03-16 21:08:50 +0000674 .resolveEntityListModifications(Collections.emptyList());
675 if (entities.contains(TextClassifier.TYPE_URL)) {
676 addLinks(links, string, TextClassifier.TYPE_URL);
677 }
678 if (entities.contains(TextClassifier.TYPE_PHONE)) {
679 addLinks(links, string, TextClassifier.TYPE_PHONE);
680 }
681 if (entities.contains(TextClassifier.TYPE_EMAIL)) {
682 addLinks(links, string, TextClassifier.TYPE_EMAIL);
683 }
684 // NOTE: Do not support MAP_ADDRESSES. Legacy version does not work well.
685 return links.build();
686 }
687
688 private static void addLinks(
689 TextLinks.Builder links, String string, @EntityType String entityType) {
690 final Spannable spannable = new SpannableString(string);
691 if (Linkify.addLinks(spannable, linkMask(entityType))) {
692 final URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
693 for (URLSpan urlSpan : spans) {
694 links.addLink(
695 spannable.getSpanStart(urlSpan),
696 spannable.getSpanEnd(urlSpan),
697 entityScores(entityType),
698 urlSpan);
699 }
700 }
701 }
702
703 @LinkifyMask
704 private static int linkMask(@EntityType String entityType) {
705 switch (entityType) {
706 case TextClassifier.TYPE_URL:
707 return Linkify.WEB_URLS;
708 case TextClassifier.TYPE_PHONE:
709 return Linkify.PHONE_NUMBERS;
710 case TextClassifier.TYPE_EMAIL:
711 return Linkify.EMAIL_ADDRESSES;
712 default:
713 // NOTE: Do not support MAP_ADDRESSES. Legacy version does not work well.
714 return 0;
715 }
716 }
717
718 private static Map<String, Float> entityScores(@EntityType String entityType) {
719 final Map<String, Float> scores = new ArrayMap<>();
720 scores.put(entityType, 1f);
721 return scores;
722 }
723
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100724 static void checkMainThread() {
725 if (Looper.myLooper() == Looper.getMainLooper()) {
726 Log.w(DEFAULT_LOG_TAG, "TextClassifier called on main thread");
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800727 }
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000728 }
729 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000730}