Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package android.view.textclassifier; |
| 18 | |
Abodunrinwa Toki | c7073a4 | 2018-02-28 23:02:13 +0000 | [diff] [blame] | 19 | import android.annotation.IntDef; |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 20 | import android.annotation.IntRange; |
| 21 | import android.annotation.NonNull; |
Abodunrinwa Toki | 4cfda0b | 2017-02-28 18:56:47 +0000 | [diff] [blame] | 22 | import android.annotation.Nullable; |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 23 | import android.annotation.StringDef; |
Abodunrinwa Toki | e0b5789 | 2017-04-28 19:59:57 +0100 | [diff] [blame] | 24 | import android.annotation.WorkerThread; |
Abodunrinwa Toki | 4cfda0b | 2017-02-28 18:56:47 +0000 | [diff] [blame] | 25 | import android.os.LocaleList; |
Abodunrinwa Toki | d32906c | 2018-01-18 04:34:44 -0800 | [diff] [blame] | 26 | import android.os.Looper; |
Jan Althaus | 0d9fbb9 | 2017-11-28 12:19:33 +0100 | [diff] [blame] | 27 | import android.os.Parcel; |
| 28 | import android.os.Parcelable; |
Abodunrinwa Toki | 6563833 | 2018-03-16 21:08:50 +0000 | [diff] [blame] | 29 | import android.text.Spannable; |
| 30 | import android.text.SpannableString; |
| 31 | import android.text.style.URLSpan; |
| 32 | import android.text.util.Linkify; |
| 33 | import android.text.util.Linkify.LinkifyMask; |
| 34 | import android.util.ArrayMap; |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 35 | |
Tony Mak | 72e1797 | 2019-03-16 10:28:42 +0000 | [diff] [blame] | 36 | import com.android.internal.annotations.GuardedBy; |
Tony Mak | f93e9e5 | 2018-07-16 14:46:29 +0200 | [diff] [blame] | 37 | import com.android.internal.util.IndentingPrintWriter; |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 38 | import com.android.internal.util.Preconditions; |
| 39 | |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 40 | import java.lang.annotation.Retention; |
| 41 | import java.lang.annotation.RetentionPolicy; |
Tony Mak | 72e1797 | 2019-03-16 10:28:42 +0000 | [diff] [blame] | 42 | import java.text.BreakIterator; |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 43 | import java.util.ArrayList; |
| 44 | import java.util.Collection; |
| 45 | import java.util.Collections; |
Richard Ledley | ab669a0 | 2018-04-03 15:15:43 +0100 | [diff] [blame] | 46 | import java.util.HashSet; |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 47 | import java.util.List; |
Abodunrinwa Toki | 6563833 | 2018-03-16 21:08:50 +0000 | [diff] [blame] | 48 | import java.util.Map; |
Daulet Zhanguzin | e155947 | 2019-12-18 14:17:56 +0000 | [diff] [blame] | 49 | import java.util.Objects; |
Richard Ledley | ab669a0 | 2018-04-03 15:15:43 +0100 | [diff] [blame] | 50 | import java.util.Set; |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 51 | |
| 52 | /** |
| 53 | * Interface for providing text classification related features. |
Abodunrinwa Toki | aa750a4 | 2018-11-09 13:47:59 +0000 | [diff] [blame] | 54 | * <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 Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 57 | * |
Abodunrinwa Toki | d32906c | 2018-01-18 04:34:44 -0800 | [diff] [blame] | 58 | * <p><strong>NOTE: </strong>Unless otherwise stated, methods of this interface are blocking |
| 59 | * operations. Call on a worker thread. |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 60 | */ |
| 61 | public interface TextClassifier { |
| 62 | |
Abodunrinwa Toki | 692b196 | 2017-08-15 15:05:11 +0100 | [diff] [blame] | 63 | /** @hide */ |
Tony Mak | 293bdf3 | 2020-02-18 11:33:43 +0000 | [diff] [blame] | 64 | String LOG_TAG = "androidtc"; |
Abodunrinwa Toki | 692b196 | 2017-08-15 15:05:11 +0100 | [diff] [blame] | 65 | |
Abodunrinwa Toki | c7073a4 | 2018-02-28 23:02:13 +0000 | [diff] [blame] | 66 | |
| 67 | /** @hide */ |
| 68 | @Retention(RetentionPolicy.SOURCE) |
Tony Mak | 293bdf3 | 2020-02-18 11:33:43 +0000 | [diff] [blame] | 69 | @IntDef(value = {LOCAL, SYSTEM, DEFAULT_SYSTEM}) |
Abodunrinwa Toki | c7073a4 | 2018-02-28 23:02:13 +0000 | [diff] [blame] | 70 | @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 Mak | c5a7432 | 2020-02-04 17:18:15 +0000 | [diff] [blame] | 75 | /** Specifies the default TextClassifier that runs in the system process. @hide */ |
Tony Mak | 293bdf3 | 2020-02-18 11:33:43 +0000 | [diff] [blame] | 76 | int DEFAULT_SYSTEM = 2; |
| 77 | |
| 78 | /** @hide */ |
| 79 | static String typeToString(@TextClassifierType int type) { |
| 80 | switch (type) { |
| 81 | case LOCAL: |
| 82 | return "Local"; |
| 83 | case SYSTEM: |
| 84 | return "System"; |
| 85 | case DEFAULT_SYSTEM: |
| 86 | return "Default system"; |
| 87 | } |
| 88 | return "Unknown"; |
| 89 | } |
Abodunrinwa Toki | c7073a4 | 2018-02-28 23:02:13 +0000 | [diff] [blame] | 90 | |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 91 | /** The TextClassifier failed to run. */ |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 92 | String TYPE_UNKNOWN = ""; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 93 | /** The classifier ran, but didn't recognize a known entity. */ |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 94 | String TYPE_OTHER = "other"; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 95 | /** E-mail address (e.g. "noreply@android.com"). */ |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 96 | String TYPE_EMAIL = "email"; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 97 | /** Phone number (e.g. "555-123 456"). */ |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 98 | String TYPE_PHONE = "phone"; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 99 | /** Physical address. */ |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 100 | String TYPE_ADDRESS = "address"; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 101 | /** Web URL. */ |
Abodunrinwa Toki | 9b4c82a | 2017-02-06 20:29:36 +0000 | [diff] [blame] | 102 | String TYPE_URL = "url"; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 103 | /** Time reference that is no more specific than a date. May be absolute such as "01/01/2000" or |
| 104 | * relative like "tomorrow". **/ |
| 105 | String TYPE_DATE = "date"; |
| 106 | /** Time reference that includes a specific time. May be absolute such as "01/01/2000 5:30pm" or |
| 107 | * relative like "tomorrow at 5:30pm". **/ |
| 108 | String TYPE_DATE_TIME = "datetime"; |
| 109 | /** Flight number in IATA format. */ |
| 110 | String TYPE_FLIGHT_NUMBER = "flight"; |
Tony Mak | e1f3ac06 | 2018-11-27 14:30:21 +0000 | [diff] [blame] | 111 | /** |
| 112 | * Word that users may be interested to look up for meaning. |
| 113 | * @hide |
| 114 | */ |
| 115 | String TYPE_DICTIONARY = "dictionary"; |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 116 | |
Abodunrinwa Toki | 45cb3e6 | 2017-03-29 21:51:45 +0100 | [diff] [blame] | 117 | /** @hide */ |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 118 | @Retention(RetentionPolicy.SOURCE) |
Jeff Sharkey | 5db9a91 | 2017-12-08 17:32:32 -0700 | [diff] [blame] | 119 | @StringDef(prefix = { "TYPE_" }, value = { |
| 120 | TYPE_UNKNOWN, |
| 121 | TYPE_OTHER, |
| 122 | TYPE_EMAIL, |
| 123 | TYPE_PHONE, |
| 124 | TYPE_ADDRESS, |
| 125 | TYPE_URL, |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 126 | TYPE_DATE, |
| 127 | TYPE_DATE_TIME, |
| 128 | TYPE_FLIGHT_NUMBER, |
Tony Mak | e1f3ac06 | 2018-11-27 14:30:21 +0000 | [diff] [blame] | 129 | TYPE_DICTIONARY |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 130 | }) |
| 131 | @interface EntityType {} |
| 132 | |
Richard Ledley | 1fc998b | 2018-02-16 15:45:06 +0000 | [diff] [blame] | 133 | /** Designates that the text in question is editable. **/ |
| 134 | String HINT_TEXT_IS_EDITABLE = "android.text_is_editable"; |
| 135 | /** Designates that the text in question is not editable. **/ |
| 136 | String HINT_TEXT_IS_NOT_EDITABLE = "android.text_is_not_editable"; |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 137 | |
| 138 | /** @hide */ |
| 139 | @Retention(RetentionPolicy.SOURCE) |
Richard Ledley | 1fc998b | 2018-02-16 15:45:06 +0000 | [diff] [blame] | 140 | @StringDef(prefix = { "HINT_" }, value = {HINT_TEXT_IS_EDITABLE, HINT_TEXT_IS_NOT_EDITABLE}) |
| 141 | @interface Hints {} |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 142 | |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 143 | /** @hide */ |
| 144 | @Retention(RetentionPolicy.SOURCE) |
Tony Mak | 4dd4314 | 2018-05-02 16:49:01 +0100 | [diff] [blame] | 145 | @StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_EDITTEXT, WIDGET_TYPE_UNSELECTABLE_TEXTVIEW, |
| 146 | WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW, |
| 147 | WIDGET_TYPE_CUSTOM_EDITTEXT, WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW, |
Abodunrinwa Toki | 37ccedc | 2018-12-11 00:35:11 +0000 | [diff] [blame] | 148 | WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_UNKNOWN}) |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 149 | @interface WidgetType {} |
| 150 | |
Abodunrinwa Toki | 37ccedc | 2018-12-11 00:35:11 +0000 | [diff] [blame] | 151 | /** The widget involved in the text classification context is a standard |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 152 | * {@link android.widget.TextView}. */ |
| 153 | String WIDGET_TYPE_TEXTVIEW = "textview"; |
Abodunrinwa Toki | 37ccedc | 2018-12-11 00:35:11 +0000 | [diff] [blame] | 154 | /** The widget involved in the text classification context is a standard |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 155 | * {@link android.widget.EditText}. */ |
| 156 | String WIDGET_TYPE_EDITTEXT = "edittext"; |
Abodunrinwa Toki | 37ccedc | 2018-12-11 00:35:11 +0000 | [diff] [blame] | 157 | /** The widget involved in the text classification context is a standard non-selectable |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 158 | * {@link android.widget.TextView}. */ |
| 159 | String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview"; |
Abodunrinwa Toki | 37ccedc | 2018-12-11 00:35:11 +0000 | [diff] [blame] | 160 | /** The widget involved in the text classification context is a standard |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 161 | * {@link android.webkit.WebView}. */ |
| 162 | String WIDGET_TYPE_WEBVIEW = "webview"; |
Abodunrinwa Toki | 37ccedc | 2018-12-11 00:35:11 +0000 | [diff] [blame] | 163 | /** The widget involved in the text classification context is a standard editable |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 164 | * {@link android.webkit.WebView}. */ |
| 165 | String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview"; |
Abodunrinwa Toki | 37ccedc | 2018-12-11 00:35:11 +0000 | [diff] [blame] | 166 | /** The widget involved in the text classification context is a custom text widget. */ |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 167 | String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview"; |
Abodunrinwa Toki | 37ccedc | 2018-12-11 00:35:11 +0000 | [diff] [blame] | 168 | /** The widget involved in the text classification context is a custom editable text widget. */ |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 169 | String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit"; |
Abodunrinwa Toki | 37ccedc | 2018-12-11 00:35:11 +0000 | [diff] [blame] | 170 | /** The widget involved in the text classification context is a custom non-selectable text |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 171 | * widget. */ |
| 172 | String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; |
Abodunrinwa Toki | 37ccedc | 2018-12-11 00:35:11 +0000 | [diff] [blame] | 173 | /** The widget involved in the text classification context is a notification */ |
| 174 | String WIDGET_TYPE_NOTIFICATION = "notification"; |
| 175 | /** The widget involved in the text classification context is of an unknown/unspecified type. */ |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 176 | String WIDGET_TYPE_UNKNOWN = "unknown"; |
| 177 | |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 178 | /** |
| 179 | * No-op TextClassifier. |
| 180 | * This may be used to turn off TextClassifier features. |
| 181 | */ |
Abodunrinwa Toki | 520b2f8 | 2019-01-27 07:48:02 +0000 | [diff] [blame] | 182 | TextClassifier NO_OP = new TextClassifier() { |
| 183 | @Override |
| 184 | public String toString() { |
| 185 | return "TextClassifier.NO_OP"; |
| 186 | } |
| 187 | }; |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 188 | |
| 189 | /** |
Tony Mak | 63abbe2 | 2019-01-28 15:14:36 +0000 | [diff] [blame] | 190 | * Extra that is included on activity intents coming from a TextClassifier when |
| 191 | * it suggests actions to its caller. |
Tony Mak | c5a4612 | 2018-12-05 22:19:22 +0000 | [diff] [blame] | 192 | * <p> |
Tony Mak | 63abbe2 | 2019-01-28 15:14:36 +0000 | [diff] [blame] | 193 | * All {@link TextClassifier} implementations should make sure this extra exists in their |
Tony Mak | c5a4612 | 2018-12-05 22:19:22 +0000 | [diff] [blame] | 194 | * generated intents. |
| 195 | */ |
| 196 | String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER"; |
| 197 | |
| 198 | /** |
Abodunrinwa Toki | 33ff200 | 2017-10-24 00:49:27 +0100 | [diff] [blame] | 199 | * Returns suggested text selection start and end indices, recognized entity types, and their |
| 200 | * associated confidence scores. The entity types are ordered from highest to lowest scoring. |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 201 | * |
Abodunrinwa Toki | d32906c | 2018-01-18 04:34:44 -0800 | [diff] [blame] | 202 | * <p><strong>NOTE: </strong>Call on a worker thread. |
| 203 | * |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 204 | * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 205 | * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. |
| 206 | * |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 207 | * @param request the text selection request |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 208 | */ |
Abodunrinwa Toki | e0b5789 | 2017-04-28 19:59:57 +0100 | [diff] [blame] | 209 | @WorkerThread |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 210 | @NonNull |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 211 | default TextSelection suggestSelection(@NonNull TextSelection.Request request) { |
Daulet Zhanguzin | e155947 | 2019-12-18 14:17:56 +0000 | [diff] [blame] | 212 | Objects.requireNonNull(request); |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 213 | Utils.checkMainThread(); |
| 214 | return new TextSelection.Builder(request.getStartIndex(), request.getEndIndex()).build(); |
Abodunrinwa Toki | 2b6020f | 2017-10-28 02:28:45 +0100 | [diff] [blame] | 215 | } |
| 216 | |
| 217 | /** |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 218 | * Returns suggested text selection start and end indices, recognized entity types, and their |
| 219 | * associated confidence scores. The entity types are ordered from highest to lowest scoring. |
| 220 | * |
Abodunrinwa Toki | d32906c | 2018-01-18 04:34:44 -0800 | [diff] [blame] | 221 | * <p><strong>NOTE: </strong>Call on a worker thread. |
| 222 | * |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 223 | * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 224 | * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. |
| 225 | * |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 226 | * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls |
| 227 | * {@link #suggestSelection(TextSelection.Request)}. If that method calls this method, |
| 228 | * a stack overflow error will happen. |
| 229 | * |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 230 | * @param text text providing context for the selected text (which is specified |
| 231 | * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) |
| 232 | * @param selectionStartIndex start index of the selected part of text |
| 233 | * @param selectionEndIndex end index of the selected part of text |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 234 | * @param defaultLocales ordered list of locale preferences that may be used to |
| 235 | * disambiguate the provided text. If no locale preferences exist, set this to null |
| 236 | * or an empty locale list. |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 237 | * |
| 238 | * @throws IllegalArgumentException if text is null; selectionStartIndex is negative; |
| 239 | * selectionEndIndex is greater than text.length() or not greater than selectionStartIndex |
| 240 | * |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 241 | * @see #suggestSelection(TextSelection.Request) |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 242 | */ |
Abodunrinwa Toki | 2b6020f | 2017-10-28 02:28:45 +0100 | [diff] [blame] | 243 | @WorkerThread |
| 244 | @NonNull |
| 245 | default TextSelection suggestSelection( |
| 246 | @NonNull CharSequence text, |
| 247 | @IntRange(from = 0) int selectionStartIndex, |
| 248 | @IntRange(from = 0) int selectionEndIndex, |
| 249 | @Nullable LocaleList defaultLocales) { |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 250 | final TextSelection.Request request = new TextSelection.Request.Builder( |
| 251 | text, selectionStartIndex, selectionEndIndex) |
| 252 | .setDefaultLocales(defaultLocales) |
| 253 | .build(); |
| 254 | return suggestSelection(request); |
Abodunrinwa Toki | 2b6020f | 2017-10-28 02:28:45 +0100 | [diff] [blame] | 255 | } |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 256 | |
| 257 | /** |
Abodunrinwa Toki | e0b5789 | 2017-04-28 19:59:57 +0100 | [diff] [blame] | 258 | * Classifies the specified text and returns a {@link TextClassification} object that can be |
| 259 | * used to generate a widget for handling the classified text. |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 260 | * |
Abodunrinwa Toki | d32906c | 2018-01-18 04:34:44 -0800 | [diff] [blame] | 261 | * <p><strong>NOTE: </strong>Call on a worker thread. |
| 262 | * |
Abodunrinwa Toki | 7cefd4f | 2018-09-14 16:00:03 +0100 | [diff] [blame] | 263 | * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 264 | * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. |
| 265 | * |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 266 | * @param request the text classification request |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 267 | */ |
Abodunrinwa Toki | e0b5789 | 2017-04-28 19:59:57 +0100 | [diff] [blame] | 268 | @WorkerThread |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 269 | @NonNull |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 270 | default TextClassification classifyText(@NonNull TextClassification.Request request) { |
Daulet Zhanguzin | e155947 | 2019-12-18 14:17:56 +0000 | [diff] [blame] | 271 | Objects.requireNonNull(request); |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 272 | Utils.checkMainThread(); |
Abodunrinwa Toki | 2b6020f | 2017-10-28 02:28:45 +0100 | [diff] [blame] | 273 | return TextClassification.EMPTY; |
| 274 | } |
| 275 | |
| 276 | /** |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 277 | * Classifies the specified text and returns a {@link TextClassification} object that can be |
| 278 | * used to generate a widget for handling the classified text. |
| 279 | * |
Abodunrinwa Toki | d32906c | 2018-01-18 04:34:44 -0800 | [diff] [blame] | 280 | * <p><strong>NOTE: </strong>Call on a worker thread. |
| 281 | * |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 282 | * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 283 | * {@link #classifyText(TextClassification.Request)}. If that method calls this method, |
| 284 | * a stack overflow error will happen. |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 285 | * |
Abodunrinwa Toki | 7cefd4f | 2018-09-14 16:00:03 +0100 | [diff] [blame] | 286 | * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 287 | * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. |
| 288 | * |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 289 | * @param text text providing context for the text to classify (which is specified |
| 290 | * by the sub sequence starting at startIndex and ending at endIndex) |
| 291 | * @param startIndex start index of the text to classify |
| 292 | * @param endIndex end index of the text to classify |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 293 | * @param defaultLocales ordered list of locale preferences that may be used to |
| 294 | * disambiguate the provided text. If no locale preferences exist, set this to null |
| 295 | * or an empty locale list. |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 296 | * |
| 297 | * @throws IllegalArgumentException if text is null; startIndex is negative; |
| 298 | * endIndex is greater than text.length() or not greater than startIndex |
| 299 | * |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 300 | * @see #classifyText(TextClassification.Request) |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 301 | */ |
Abodunrinwa Toki | 2b6020f | 2017-10-28 02:28:45 +0100 | [diff] [blame] | 302 | @WorkerThread |
| 303 | @NonNull |
| 304 | default TextClassification classifyText( |
| 305 | @NonNull CharSequence text, |
| 306 | @IntRange(from = 0) int startIndex, |
| 307 | @IntRange(from = 0) int endIndex, |
| 308 | @Nullable LocaleList defaultLocales) { |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 309 | final TextClassification.Request request = new TextClassification.Request.Builder( |
| 310 | text, startIndex, endIndex) |
| 311 | .setDefaultLocales(defaultLocales) |
| 312 | .build(); |
| 313 | return classifyText(request); |
Abodunrinwa Toki | 2b6020f | 2017-10-28 02:28:45 +0100 | [diff] [blame] | 314 | } |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 315 | |
| 316 | /** |
Abodunrinwa Toki | d32906c | 2018-01-18 04:34:44 -0800 | [diff] [blame] | 317 | * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with |
| 318 | * links information. |
Richard Ledley | 68d9452 | 2017-10-05 10:52:19 +0100 | [diff] [blame] | 319 | * |
Abodunrinwa Toki | d32906c | 2018-01-18 04:34:44 -0800 | [diff] [blame] | 320 | * <p><strong>NOTE: </strong>Call on a worker thread. |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 321 | * |
Abodunrinwa Toki | 7cefd4f | 2018-09-14 16:00:03 +0100 | [diff] [blame] | 322 | * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 323 | * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. |
| 324 | * |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 325 | * @param request the text links request |
Richard Ledley | 68d9452 | 2017-10-05 10:52:19 +0100 | [diff] [blame] | 326 | * |
Jan Althaus | 108aad3 | 2018-01-30 15:26:55 +0100 | [diff] [blame] | 327 | * @see #getMaxGenerateLinksTextLength() |
Richard Ledley | 68d9452 | 2017-10-05 10:52:19 +0100 | [diff] [blame] | 328 | */ |
| 329 | @WorkerThread |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 330 | @NonNull |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 331 | default TextLinks generateLinks(@NonNull TextLinks.Request request) { |
Daulet Zhanguzin | e155947 | 2019-12-18 14:17:56 +0000 | [diff] [blame] | 332 | Objects.requireNonNull(request); |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 333 | Utils.checkMainThread(); |
| 334 | return new TextLinks.Builder(request.getText().toString()).build(); |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 335 | } |
| 336 | |
| 337 | /** |
Jan Althaus | 108aad3 | 2018-01-30 15:26:55 +0100 | [diff] [blame] | 338 | * Returns the maximal length of text that can be processed by generateLinks. |
| 339 | * |
Abodunrinwa Toki | 7cefd4f | 2018-09-14 16:00:03 +0100 | [diff] [blame] | 340 | * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 341 | * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. |
| 342 | * |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 343 | * @see #generateLinks(TextLinks.Request) |
Jan Althaus | 108aad3 | 2018-01-30 15:26:55 +0100 | [diff] [blame] | 344 | */ |
Abodunrinwa Toki | ad52f4b | 2018-02-06 23:32:41 +0000 | [diff] [blame] | 345 | @WorkerThread |
Jan Althaus | 108aad3 | 2018-01-30 15:26:55 +0100 | [diff] [blame] | 346 | default int getMaxGenerateLinksTextLength() { |
| 347 | return Integer.MAX_VALUE; |
| 348 | } |
| 349 | |
| 350 | /** |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 351 | * Detects the language of the text in the given request. |
Abodunrinwa Toki | 7cefd4f | 2018-09-14 16:00:03 +0100 | [diff] [blame] | 352 | * |
| 353 | * <p><strong>NOTE: </strong>Call on a worker thread. |
| 354 | * |
| 355 | * |
| 356 | * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should |
| 357 | * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. |
| 358 | * |
| 359 | * @param request the {@link TextLanguage} request. |
| 360 | * @return the {@link TextLanguage} result. |
| 361 | */ |
| 362 | @WorkerThread |
| 363 | @NonNull |
| 364 | default TextLanguage detectLanguage(@NonNull TextLanguage.Request request) { |
Daulet Zhanguzin | e155947 | 2019-12-18 14:17:56 +0000 | [diff] [blame] | 365 | Objects.requireNonNull(request); |
Abodunrinwa Toki | 7cefd4f | 2018-09-14 16:00:03 +0100 | [diff] [blame] | 366 | Utils.checkMainThread(); |
| 367 | return TextLanguage.EMPTY; |
| 368 | } |
| 369 | |
| 370 | /** |
Tony Mak | c9d31e2 | 2018-10-22 16:17:45 +0100 | [diff] [blame] | 371 | * Suggests and returns a list of actions according to the given conversation. |
| 372 | */ |
| 373 | @WorkerThread |
Tony Mak | 42ab984 | 2019-03-05 15:38:51 +0000 | [diff] [blame] | 374 | @NonNull |
Tony Mak | c9d31e2 | 2018-10-22 16:17:45 +0100 | [diff] [blame] | 375 | default ConversationActions suggestConversationActions( |
| 376 | @NonNull ConversationActions.Request request) { |
Daulet Zhanguzin | e155947 | 2019-12-18 14:17:56 +0000 | [diff] [blame] | 377 | Objects.requireNonNull(request); |
Tony Mak | c9d31e2 | 2018-10-22 16:17:45 +0100 | [diff] [blame] | 378 | Utils.checkMainThread(); |
Tony Mak | c4359bf | 2018-12-11 19:38:53 +0800 | [diff] [blame] | 379 | return new ConversationActions(Collections.emptyList(), null); |
Tony Mak | c9d31e2 | 2018-10-22 16:17:45 +0100 | [diff] [blame] | 380 | } |
| 381 | |
| 382 | /** |
Abodunrinwa Toki | 37ccedc | 2018-12-11 00:35:11 +0000 | [diff] [blame] | 383 | * <strong>NOTE: </strong>Use {@link #onTextClassifierEvent(TextClassifierEvent)} instead. |
| 384 | * <p> |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 385 | * Reports a selection event. |
| 386 | * |
Abodunrinwa Toki | 7cefd4f | 2018-09-14 16:00:03 +0100 | [diff] [blame] | 387 | * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 388 | * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. |
| 389 | */ |
Abodunrinwa Toki | 37ccedc | 2018-12-11 00:35:11 +0000 | [diff] [blame] | 390 | default void onSelectionEvent(@NonNull SelectionEvent event) { |
| 391 | // TODO: Consider rerouting to onTextClassifierEvent() |
| 392 | } |
| 393 | |
| 394 | /** |
| 395 | * Reports a text classifier event. |
| 396 | * <p> |
| 397 | * <strong>NOTE: </strong>Call on a worker thread. |
| 398 | * |
| 399 | * @throws IllegalStateException if this TextClassifier has been destroyed. |
| 400 | * @see #isDestroyed() |
| 401 | */ |
| 402 | default void onTextClassifierEvent(@NonNull TextClassifierEvent event) {} |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 403 | |
| 404 | /** |
| 405 | * Destroys this TextClassifier. |
| 406 | * |
Abodunrinwa Toki | 7cefd4f | 2018-09-14 16:00:03 +0100 | [diff] [blame] | 407 | * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to its methods should |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 408 | * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. |
| 409 | * |
| 410 | * <p>Subsequent calls to this method are no-ops. |
| 411 | */ |
| 412 | default void destroy() {} |
| 413 | |
| 414 | /** |
| 415 | * Returns whether or not this TextClassifier has been destroyed. |
| 416 | * |
Abodunrinwa Toki | 7cefd4f | 2018-09-14 16:00:03 +0100 | [diff] [blame] | 417 | * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, caller should not interact |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 418 | * with the classifier and an attempt to do so would throw an {@link IllegalStateException}. |
| 419 | * However, this method should never throw an {@link IllegalStateException}. |
| 420 | * |
| 421 | * @see #destroy() |
| 422 | */ |
| 423 | default boolean isDestroyed() { |
| 424 | return false; |
| 425 | } |
| 426 | |
Tony Mak | f93e9e5 | 2018-07-16 14:46:29 +0200 | [diff] [blame] | 427 | /** @hide **/ |
Abodunrinwa Toki | 7cefd4f | 2018-09-14 16:00:03 +0100 | [diff] [blame] | 428 | default void dump(@NonNull IndentingPrintWriter printWriter) {} |
Tony Mak | f93e9e5 | 2018-07-16 14:46:29 +0200 | [diff] [blame] | 429 | |
Abodunrinwa Toki | 88be5a6 | 2018-03-23 04:01:28 +0000 | [diff] [blame] | 430 | /** |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 431 | * Configuration object for specifying what entity types to identify. |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 432 | * |
| 433 | * Configs are initially based on a predefined preset, and can be modified from there. |
| 434 | */ |
Jan Althaus | 0d9fbb9 | 2017-11-28 12:19:33 +0100 | [diff] [blame] | 435 | final class EntityConfig implements Parcelable { |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 436 | private final List<String> mIncludedTypes; |
| 437 | private final List<String> mExcludedTypes; |
| 438 | private final List<String> mHints; |
| 439 | private final boolean mIncludeTypesFromTextClassifier; |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 440 | |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 441 | private EntityConfig( |
| 442 | List<String> includedEntityTypes, |
| 443 | List<String> excludedEntityTypes, |
| 444 | List<String> hints, |
| 445 | boolean includeTypesFromTextClassifier) { |
Daulet Zhanguzin | e155947 | 2019-12-18 14:17:56 +0000 | [diff] [blame] | 446 | mIncludedTypes = Objects.requireNonNull(includedEntityTypes); |
| 447 | mExcludedTypes = Objects.requireNonNull(excludedEntityTypes); |
| 448 | mHints = Objects.requireNonNull(hints); |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 449 | mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier; |
| 450 | } |
| 451 | |
| 452 | private EntityConfig(Parcel in) { |
| 453 | mIncludedTypes = new ArrayList<>(); |
| 454 | in.readStringList(mIncludedTypes); |
| 455 | mExcludedTypes = new ArrayList<>(); |
| 456 | in.readStringList(mExcludedTypes); |
| 457 | List<String> tmpHints = new ArrayList<>(); |
| 458 | in.readStringList(tmpHints); |
| 459 | mHints = Collections.unmodifiableList(tmpHints); |
| 460 | mIncludeTypesFromTextClassifier = in.readByte() != 0; |
| 461 | } |
| 462 | |
| 463 | @Override |
| 464 | public void writeToParcel(Parcel parcel, int flags) { |
| 465 | parcel.writeStringList(mIncludedTypes); |
| 466 | parcel.writeStringList(mExcludedTypes); |
| 467 | parcel.writeStringList(mHints); |
| 468 | parcel.writeByte((byte) (mIncludeTypesFromTextClassifier ? 1 : 0)); |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 469 | } |
| 470 | |
| 471 | /** |
Richard Ledley | 1fc998b | 2018-02-16 15:45:06 +0000 | [diff] [blame] | 472 | * Creates an EntityConfig. |
| 473 | * |
| 474 | * @param hints Hints for the TextClassifier to determine what types of entities to find. |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 475 | * |
| 476 | * @deprecated Use {@link Builder} instead. |
Richard Ledley | 1fc998b | 2018-02-16 15:45:06 +0000 | [diff] [blame] | 477 | */ |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 478 | @Deprecated |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 479 | public static EntityConfig createWithHints(@Nullable Collection<String> hints) { |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 480 | return new EntityConfig.Builder() |
| 481 | .includeTypesFromTextClassifier(true) |
| 482 | .setHints(hints) |
| 483 | .build(); |
Abodunrinwa Toki | ae82e7a | 2018-04-03 23:49:16 +0100 | [diff] [blame] | 484 | } |
| 485 | |
Richard Ledley | 1fc998b | 2018-02-16 15:45:06 +0000 | [diff] [blame] | 486 | /** |
| 487 | * Creates an EntityConfig. |
| 488 | * |
| 489 | * @param hints Hints for the TextClassifier to determine what types of entities to find |
| 490 | * @param includedEntityTypes Entity types, e.g. {@link #TYPE_EMAIL}, to explicitly include |
| 491 | * @param excludedEntityTypes Entity types, e.g. {@link #TYPE_PHONE}, to explicitly exclude |
| 492 | * |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 493 | * |
| 494 | * Note that if an entity has been excluded, the exclusion will take precedence. |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 495 | * |
| 496 | * @deprecated Use {@link Builder} instead. |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 497 | */ |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 498 | @Deprecated |
Richard Ledley | 1fc998b | 2018-02-16 15:45:06 +0000 | [diff] [blame] | 499 | public static EntityConfig create(@Nullable Collection<String> hints, |
| 500 | @Nullable Collection<String> includedEntityTypes, |
| 501 | @Nullable Collection<String> excludedEntityTypes) { |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 502 | return new EntityConfig.Builder() |
| 503 | .setIncludedTypes(includedEntityTypes) |
| 504 | .setExcludedTypes(excludedEntityTypes) |
| 505 | .setHints(hints) |
| 506 | .includeTypesFromTextClassifier(true) |
| 507 | .build(); |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 508 | } |
| 509 | |
| 510 | /** |
Richard Ledley | 1fc998b | 2018-02-16 15:45:06 +0000 | [diff] [blame] | 511 | * Creates an EntityConfig with an explicit entity list. |
| 512 | * |
| 513 | * @param entityTypes Complete set of entities, e.g. {@link #TYPE_URL} to find. |
| 514 | * |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 515 | * @deprecated Use {@link Builder} instead. |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 516 | */ |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 517 | @Deprecated |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 518 | public static EntityConfig createWithExplicitEntityList( |
| 519 | @Nullable Collection<String> entityTypes) { |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 520 | return new EntityConfig.Builder() |
| 521 | .setIncludedTypes(entityTypes) |
| 522 | .includeTypesFromTextClassifier(false) |
| 523 | .build(); |
Abodunrinwa Toki | ae82e7a | 2018-04-03 23:49:16 +0100 | [diff] [blame] | 524 | } |
| 525 | |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 526 | /** |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 527 | * Returns a final list of entity types to find. |
Richard Ledley | 1fc998b | 2018-02-16 15:45:06 +0000 | [diff] [blame] | 528 | * |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 529 | * @param entityTypes Entity types we think should be found before factoring in |
| 530 | * includes/excludes |
Richard Ledley | 1fc998b | 2018-02-16 15:45:06 +0000 | [diff] [blame] | 531 | * |
| 532 | * This method is intended for use by TextClassifier implementations. |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 533 | */ |
Richard Ledley | ab669a0 | 2018-04-03 15:15:43 +0100 | [diff] [blame] | 534 | public Collection<String> resolveEntityListModifications( |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 535 | @NonNull Collection<String> entityTypes) { |
| 536 | final Set<String> finalSet = new HashSet<>(); |
| 537 | if (mIncludeTypesFromTextClassifier) { |
| 538 | finalSet.addAll(entityTypes); |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 539 | } |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 540 | finalSet.addAll(mIncludedTypes); |
| 541 | finalSet.removeAll(mExcludedTypes); |
Richard Ledley | ab669a0 | 2018-04-03 15:15:43 +0100 | [diff] [blame] | 542 | return finalSet; |
Richard Ledley | 1fc998b | 2018-02-16 15:45:06 +0000 | [diff] [blame] | 543 | } |
| 544 | |
| 545 | /** |
| 546 | * Retrieves the list of hints. |
| 547 | * |
| 548 | * @return An unmodifiable collection of the hints. |
| 549 | */ |
| 550 | public Collection<String> getHints() { |
| 551 | return mHints; |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 552 | } |
Jan Althaus | 0d9fbb9 | 2017-11-28 12:19:33 +0100 | [diff] [blame] | 553 | |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 554 | /** |
| 555 | * Return whether the client allows the text classifier to include its own list of |
| 556 | * default types. If this function returns {@code true}, a default list of types suggested |
| 557 | * from a text classifier will be taking into account. |
| 558 | * |
| 559 | * <p>NOTE: This method is intended for use by a text classifier. |
| 560 | * |
| 561 | * @see #resolveEntityListModifications(Collection) |
| 562 | */ |
| 563 | public boolean shouldIncludeTypesFromTextClassifier() { |
| 564 | return mIncludeTypesFromTextClassifier; |
Jan Althaus | 0d9fbb9 | 2017-11-28 12:19:33 +0100 | [diff] [blame] | 565 | } |
| 566 | |
| 567 | @Override |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 568 | public int describeContents() { |
| 569 | return 0; |
Jan Althaus | 0d9fbb9 | 2017-11-28 12:19:33 +0100 | [diff] [blame] | 570 | } |
| 571 | |
Jeff Sharkey | 9e8f83d | 2019-02-28 12:06:45 -0700 | [diff] [blame] | 572 | public static final @android.annotation.NonNull Parcelable.Creator<EntityConfig> CREATOR = |
Jan Althaus | 0d9fbb9 | 2017-11-28 12:19:33 +0100 | [diff] [blame] | 573 | new Parcelable.Creator<EntityConfig>() { |
| 574 | @Override |
| 575 | public EntityConfig createFromParcel(Parcel in) { |
| 576 | return new EntityConfig(in); |
| 577 | } |
| 578 | |
| 579 | @Override |
| 580 | public EntityConfig[] newArray(int size) { |
| 581 | return new EntityConfig[size]; |
| 582 | } |
| 583 | }; |
| 584 | |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 585 | |
| 586 | |
| 587 | /** Builder class to construct the {@link EntityConfig} object. */ |
| 588 | public static final class Builder { |
| 589 | @Nullable |
| 590 | private Collection<String> mIncludedTypes; |
| 591 | @Nullable |
| 592 | private Collection<String> mExcludedTypes; |
| 593 | @Nullable |
| 594 | private Collection<String> mHints; |
| 595 | private boolean mIncludeTypesFromTextClassifier = true; |
| 596 | |
| 597 | /** |
| 598 | * Sets a collection of types that are explicitly included. |
| 599 | */ |
| 600 | @NonNull |
| 601 | public Builder setIncludedTypes(@Nullable Collection<String> includedTypes) { |
| 602 | mIncludedTypes = includedTypes; |
| 603 | return this; |
| 604 | } |
| 605 | |
| 606 | /** |
| 607 | * Sets a collection of types that are explicitly excluded. |
| 608 | */ |
| 609 | @NonNull |
| 610 | public Builder setExcludedTypes(@Nullable Collection<String> excludedTypes) { |
| 611 | mExcludedTypes = excludedTypes; |
| 612 | return this; |
| 613 | } |
| 614 | |
| 615 | /** |
| 616 | * Specifies whether or not to include the types suggested by the text classifier. By |
| 617 | * default, it is included. |
| 618 | */ |
| 619 | @NonNull |
| 620 | public Builder includeTypesFromTextClassifier(boolean includeTypesFromTextClassifier) { |
| 621 | mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier; |
| 622 | return this; |
| 623 | } |
| 624 | |
| 625 | |
| 626 | /** |
| 627 | * Sets the hints for the TextClassifier to determine what types of entities to find. |
| 628 | * These hints will only be used if {@link #includeTypesFromTextClassifier} is |
| 629 | * set to be true. |
| 630 | */ |
Tony Mak | 42ab984 | 2019-03-05 15:38:51 +0000 | [diff] [blame] | 631 | @NonNull |
| 632 | public Builder setHints(@Nullable Collection<String> hints) { |
Tony Mak | ae85aae | 2019-01-09 15:59:56 +0000 | [diff] [blame] | 633 | mHints = hints; |
| 634 | return this; |
| 635 | } |
| 636 | |
| 637 | /** |
| 638 | * Combines all of the options that have been set and returns a new {@link EntityConfig} |
| 639 | * object. |
| 640 | */ |
| 641 | @NonNull |
| 642 | public EntityConfig build() { |
| 643 | return new EntityConfig( |
| 644 | mIncludedTypes == null |
| 645 | ? Collections.emptyList() |
| 646 | : new ArrayList<>(mIncludedTypes), |
| 647 | mExcludedTypes == null |
| 648 | ? Collections.emptyList() |
| 649 | : new ArrayList<>(mExcludedTypes), |
| 650 | mHints == null |
| 651 | ? Collections.emptyList() |
| 652 | : Collections.unmodifiableList(new ArrayList<>(mHints)), |
| 653 | mIncludeTypesFromTextClassifier); |
| 654 | } |
Jan Althaus | 0d9fbb9 | 2017-11-28 12:19:33 +0100 | [diff] [blame] | 655 | } |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 656 | } |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 657 | |
| 658 | /** |
| 659 | * Utility functions for TextClassifier methods. |
| 660 | * |
| 661 | * <ul> |
| 662 | * <li>Provides validation of input parameters to TextClassifier methods |
| 663 | * </ul> |
| 664 | * |
Tony Mak | 72e1797 | 2019-03-16 10:28:42 +0000 | [diff] [blame] | 665 | * Intended to be used only for TextClassifier purposes. |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 666 | * @hide |
| 667 | */ |
| 668 | final class Utils { |
| 669 | |
Tony Mak | 72e1797 | 2019-03-16 10:28:42 +0000 | [diff] [blame] | 670 | @GuardedBy("WORD_ITERATOR") |
| 671 | private static final BreakIterator WORD_ITERATOR = BreakIterator.getWordInstance(); |
| 672 | |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 673 | /** |
| 674 | * @throws IllegalArgumentException if text is null; startIndex is negative; |
| 675 | * endIndex is greater than text.length() or is not greater than startIndex; |
| 676 | * options is null |
| 677 | */ |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 678 | static void checkArgument(@NonNull CharSequence text, int startIndex, int endIndex) { |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 679 | Preconditions.checkArgument(text != null); |
| 680 | Preconditions.checkArgument(startIndex >= 0); |
| 681 | Preconditions.checkArgument(endIndex <= text.length()); |
| 682 | Preconditions.checkArgument(endIndex > startIndex); |
| 683 | } |
| 684 | |
Tony Mak | c5a7432 | 2020-02-04 17:18:15 +0000 | [diff] [blame] | 685 | /** Returns if the length of the text is within the range. */ |
| 686 | static boolean checkTextLength(CharSequence text, int maxLength) { |
| 687 | int textLength = text.length(); |
| 688 | return textLength >= 0 && textLength <= maxLength; |
Jan Althaus | 108aad3 | 2018-01-30 15:26:55 +0100 | [diff] [blame] | 689 | } |
| 690 | |
Abodunrinwa Toki | 6563833 | 2018-03-16 21:08:50 +0000 | [diff] [blame] | 691 | /** |
Tony Mak | 72e1797 | 2019-03-16 10:28:42 +0000 | [diff] [blame] | 692 | * Returns the substring of {@code text} that contains at least text from index |
| 693 | * {@code start} <i>(inclusive)</i> to index {@code end} <i><(exclusive)/i> with the goal of |
| 694 | * returning text that is at least {@code minimumLength}. If {@code text} is not long |
| 695 | * enough, this will return {@code text}. This method returns text at word boundaries. |
| 696 | * |
| 697 | * @param text the source text |
| 698 | * @param start the start index of text that must be included |
| 699 | * @param end the end index of text that must be included |
| 700 | * @param minimumLength minimum length of text to return if {@code text} is long enough |
| 701 | */ |
| 702 | public static String getSubString( |
| 703 | String text, int start, int end, int minimumLength) { |
| 704 | Preconditions.checkArgument(start >= 0); |
| 705 | Preconditions.checkArgument(end <= text.length()); |
| 706 | Preconditions.checkArgument(start <= end); |
| 707 | |
| 708 | if (text.length() < minimumLength) { |
| 709 | return text; |
| 710 | } |
| 711 | |
| 712 | final int length = end - start; |
| 713 | if (length >= minimumLength) { |
| 714 | return text.substring(start, end); |
| 715 | } |
| 716 | |
| 717 | final int offset = (minimumLength - length) / 2; |
| 718 | int iterStart = Math.max(0, Math.min(start - offset, text.length() - minimumLength)); |
| 719 | int iterEnd = Math.min(text.length(), iterStart + minimumLength); |
| 720 | |
| 721 | synchronized (WORD_ITERATOR) { |
| 722 | WORD_ITERATOR.setText(text); |
| 723 | iterStart = WORD_ITERATOR.isBoundary(iterStart) |
| 724 | ? iterStart : Math.max(0, WORD_ITERATOR.preceding(iterStart)); |
| 725 | iterEnd = WORD_ITERATOR.isBoundary(iterEnd) |
| 726 | ? iterEnd : Math.max(iterEnd, WORD_ITERATOR.following(iterEnd)); |
| 727 | WORD_ITERATOR.setText(""); |
| 728 | return text.substring(iterStart, iterEnd); |
| 729 | } |
| 730 | } |
| 731 | |
| 732 | /** |
Abodunrinwa Toki | 6563833 | 2018-03-16 21:08:50 +0000 | [diff] [blame] | 733 | * Generates links using legacy {@link Linkify}. |
| 734 | */ |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 735 | public static TextLinks generateLegacyLinks(@NonNull TextLinks.Request request) { |
| 736 | final String string = request.getText().toString(); |
Abodunrinwa Toki | 6563833 | 2018-03-16 21:08:50 +0000 | [diff] [blame] | 737 | final TextLinks.Builder links = new TextLinks.Builder(string); |
| 738 | |
Richard Ledley | ab669a0 | 2018-04-03 15:15:43 +0100 | [diff] [blame] | 739 | final Collection<String> entities = request.getEntityConfig() |
Abodunrinwa Toki | 6563833 | 2018-03-16 21:08:50 +0000 | [diff] [blame] | 740 | .resolveEntityListModifications(Collections.emptyList()); |
| 741 | if (entities.contains(TextClassifier.TYPE_URL)) { |
| 742 | addLinks(links, string, TextClassifier.TYPE_URL); |
| 743 | } |
| 744 | if (entities.contains(TextClassifier.TYPE_PHONE)) { |
| 745 | addLinks(links, string, TextClassifier.TYPE_PHONE); |
| 746 | } |
| 747 | if (entities.contains(TextClassifier.TYPE_EMAIL)) { |
| 748 | addLinks(links, string, TextClassifier.TYPE_EMAIL); |
| 749 | } |
| 750 | // NOTE: Do not support MAP_ADDRESSES. Legacy version does not work well. |
| 751 | return links.build(); |
| 752 | } |
| 753 | |
| 754 | private static void addLinks( |
| 755 | TextLinks.Builder links, String string, @EntityType String entityType) { |
| 756 | final Spannable spannable = new SpannableString(string); |
| 757 | if (Linkify.addLinks(spannable, linkMask(entityType))) { |
| 758 | final URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class); |
| 759 | for (URLSpan urlSpan : spans) { |
| 760 | links.addLink( |
| 761 | spannable.getSpanStart(urlSpan), |
| 762 | spannable.getSpanEnd(urlSpan), |
| 763 | entityScores(entityType), |
| 764 | urlSpan); |
| 765 | } |
| 766 | } |
| 767 | } |
| 768 | |
| 769 | @LinkifyMask |
| 770 | private static int linkMask(@EntityType String entityType) { |
| 771 | switch (entityType) { |
| 772 | case TextClassifier.TYPE_URL: |
| 773 | return Linkify.WEB_URLS; |
| 774 | case TextClassifier.TYPE_PHONE: |
| 775 | return Linkify.PHONE_NUMBERS; |
| 776 | case TextClassifier.TYPE_EMAIL: |
| 777 | return Linkify.EMAIL_ADDRESSES; |
| 778 | default: |
| 779 | // NOTE: Do not support MAP_ADDRESSES. Legacy version does not work well. |
| 780 | return 0; |
| 781 | } |
| 782 | } |
| 783 | |
| 784 | private static Map<String, Float> entityScores(@EntityType String entityType) { |
| 785 | final Map<String, Float> scores = new ArrayMap<>(); |
| 786 | scores.put(entityType, 1f); |
| 787 | return scores; |
| 788 | } |
| 789 | |
Abodunrinwa Toki | 080c854 | 2018-03-27 00:04:06 +0100 | [diff] [blame] | 790 | static void checkMainThread() { |
| 791 | if (Looper.myLooper() == Looper.getMainLooper()) { |
Tony Mak | 293bdf3 | 2020-02-18 11:33:43 +0000 | [diff] [blame] | 792 | Log.w(LOG_TAG, "TextClassifier called on main thread"); |
Abodunrinwa Toki | d32906c | 2018-01-18 04:34:44 -0800 | [diff] [blame] | 793 | } |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 794 | } |
| 795 | } |
Abodunrinwa Toki | f001fef | 2017-01-04 23:51:42 +0000 | [diff] [blame] | 796 | } |