blob: 052ee953e97116c75addd000b8bd66204f426d51 [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
19import android.annotation.FloatRange;
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +000020import android.annotation.IntDef;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000021import android.annotation.IntRange;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
Jan Althaus20d346e2018-03-23 14:03:52 +010024import android.app.PendingIntent;
25import android.app.RemoteAction;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000026import android.content.Context;
27import android.content.Intent;
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +000028import android.content.pm.PackageManager;
29import android.content.pm.ResolveInfo;
Abodunrinwa Tokief7cb2c2018-02-07 22:21:08 +000030import android.content.res.Resources;
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +010031import android.graphics.BitmapFactory;
32import android.graphics.drawable.AdaptiveIconDrawable;
Jan Althaus0d9fbb92017-11-28 12:19:33 +010033import android.graphics.drawable.BitmapDrawable;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000034import android.graphics.drawable.Drawable;
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +010035import android.graphics.drawable.Icon;
Tony Makd6f3fb42018-10-26 15:42:49 +010036import android.os.Bundle;
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +010037import android.os.LocaleList;
Jan Althaus0d9fbb92017-11-28 12:19:33 +010038import android.os.Parcel;
39import android.os.Parcelable;
Abodunrinwa Toki0f138962018-12-03 22:35:10 +000040import android.text.SpannedString;
Jan Althausbbe43df2017-11-30 15:01:40 +010041import android.util.ArrayMap;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000042import android.view.View.OnClickListener;
43import android.view.textclassifier.TextClassifier.EntityType;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010044import android.view.textclassifier.TextClassifier.Utils;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000045
Abodunrinwa Toki0f138962018-12-03 22:35:10 +000046import com.android.internal.annotations.VisibleForTesting;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000047import com.android.internal.util.Preconditions;
48
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +000049import java.lang.annotation.Retention;
50import java.lang.annotation.RetentionPolicy;
Jan Althausa1652cf2018-03-29 17:51:57 +020051import java.time.ZonedDateTime;
Jan Althaus92d76832017-09-27 18:14:35 +020052import java.util.ArrayList;
Jan Althaus20d346e2018-03-23 14:03:52 +010053import java.util.Collections;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000054import java.util.List;
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000055import java.util.Locale;
Jan Althausbbe43df2017-11-30 15:01:40 +010056import java.util.Map;
Abodunrinwa Toki25f7fdc2019-02-19 23:42:30 +000057import java.util.Objects;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000058
59/**
60 * Information for generating a widget to handle classified text.
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010061 *
62 * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may
Abodunrinwa Tokiba385622017-11-29 19:30:32 +000063 * be used to build a widget that can be used to act on classified text. There is the concept of a
64 * <i>primary action</i> and other <i>secondary actions</i>.
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010065 *
66 * <p>e.g. building a view that, when clicked, shares the classified text with the preferred app:
67 *
68 * <pre>{@code
69 * // Called preferably outside the UiThread.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000070 * TextClassification classification = textClassifier.classifyText(allText, 10, 25);
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010071 *
72 * // Called on the UiThread.
73 * Button button = new Button(context);
74 * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null);
75 * button.setText(classification.getLabel());
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +010076 * button.setOnClickListener(v -> classification.getActions().get(0).getActionIntent().send());
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010077 * }</pre>
78 *
79 * <p>e.g. starting an action mode with menu items that can handle the classified text:
80 *
81 * <pre>{@code
82 * // Called preferably outside the UiThread.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000083 * final TextClassification classification = textClassifier.classifyText(allText, 10, 25);
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010084 *
85 * // Called on the UiThread.
86 * view.startActionMode(new ActionMode.Callback() {
87 *
88 * public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Jan Althaus20d346e2018-03-23 14:03:52 +010089 * for (int i = 0; i < classification.getActions().size(); ++i) {
90 * RemoteAction action = classification.getActions().get(i);
91 * menu.add(Menu.NONE, i, 20, action.getTitle())
92 * .setIcon(action.getIcon());
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010093 * }
94 * return true;
95 * }
96 *
97 * public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Jan Althaus20d346e2018-03-23 14:03:52 +010098 * classification.getActions().get(item.getItemId()).getActionIntent().send();
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010099 * return true;
100 * }
101 *
102 * ...
103 * });
104 * }</pre>
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000105 */
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800106public final class TextClassification implements Parcelable {
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000107
108 /**
109 * @hide
110 */
Abodunrinwa Tokiadc19402018-11-22 17:10:25 +0000111 public static final TextClassification EMPTY = new TextClassification.Builder().build();
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000112
Jan Althaus20d346e2018-03-23 14:03:52 +0100113 private static final String LOG_TAG = "TextClassification";
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100114 // TODO(toki): investigate a way to derive this based on device properties.
Jan Althaus20d346e2018-03-23 14:03:52 +0100115 private static final int MAX_LEGACY_ICON_SIZE = 192;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100116
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000117 @Retention(RetentionPolicy.SOURCE)
118 @IntDef(value = {IntentType.UNSUPPORTED, IntentType.ACTIVITY, IntentType.SERVICE})
119 private @interface IntentType {
120 int UNSUPPORTED = -1;
121 int ACTIVITY = 0;
122 int SERVICE = 1;
123 }
124
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000125 @NonNull private final String mText;
Jan Althaus20d346e2018-03-23 14:03:52 +0100126 @Nullable private final Drawable mLegacyIcon;
127 @Nullable private final String mLegacyLabel;
128 @Nullable private final Intent mLegacyIntent;
129 @Nullable private final OnClickListener mLegacyOnClickListener;
130 @NonNull private final List<RemoteAction> mActions;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100131 @NonNull private final EntityConfidence mEntityConfidence;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100132 @Nullable private final String mId;
Tony Makd6f3fb42018-10-26 15:42:49 +0100133 @NonNull private final Bundle mExtras;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000134
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100135 private TextClassification(
Abodunrinwa Tokid44286f2017-07-12 19:38:54 +0100136 @Nullable String text,
Jan Althaus20d346e2018-03-23 14:03:52 +0100137 @Nullable Drawable legacyIcon,
138 @Nullable String legacyLabel,
139 @Nullable Intent legacyIntent,
140 @Nullable OnClickListener legacyOnClickListener,
141 @NonNull List<RemoteAction> actions,
Jan Althausbbe43df2017-11-30 15:01:40 +0100142 @NonNull Map<String, Float> entityConfidence,
Tony Makd6f3fb42018-10-26 15:42:49 +0100143 @Nullable String id,
144 @NonNull Bundle extras) {
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000145 mText = text;
Jan Althaus20d346e2018-03-23 14:03:52 +0100146 mLegacyIcon = legacyIcon;
147 mLegacyLabel = legacyLabel;
148 mLegacyIntent = legacyIntent;
149 mLegacyOnClickListener = legacyOnClickListener;
150 mActions = Collections.unmodifiableList(actions);
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100151 mEntityConfidence = new EntityConfidence(entityConfidence);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100152 mId = id;
Tony Makd6f3fb42018-10-26 15:42:49 +0100153 mExtras = extras;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000154 }
155
156 /**
157 * Gets the classified text.
158 */
Abodunrinwa Tokid44286f2017-07-12 19:38:54 +0100159 @Nullable
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000160 public String getText() {
161 return mText;
162 }
163
164 /**
165 * Returns the number of entities found in the classified text.
166 */
167 @IntRange(from = 0)
168 public int getEntityCount() {
Jan Althausbbe43df2017-11-30 15:01:40 +0100169 return mEntityConfidence.getEntities().size();
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000170 }
171
172 /**
173 * Returns the entity at the specified index. Entities are ordered from high confidence
174 * to low confidence.
175 *
176 * @throws IndexOutOfBoundsException if the specified index is out of range.
177 * @see #getEntityCount() for the number of entities available.
178 */
179 @NonNull
180 public @EntityType String getEntity(int index) {
Jan Althausbbe43df2017-11-30 15:01:40 +0100181 return mEntityConfidence.getEntities().get(index);
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000182 }
183
184 /**
185 * Returns the confidence score for the specified entity. The value ranges from
186 * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
187 * classified text.
188 */
189 @FloatRange(from = 0.0, to = 1.0)
190 public float getConfidenceScore(@EntityType String entity) {
191 return mEntityConfidence.getConfidenceScore(entity);
192 }
193
194 /**
Jan Althaus20d346e2018-03-23 14:03:52 +0100195 * Returns a list of actions that may be performed on the text. The list is ordered based on
196 * the likelihood that a user will use the action, with the most likely action appearing first.
Jan Althaus92d76832017-09-27 18:14:35 +0200197 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100198 public List<RemoteAction> getActions() {
199 return mActions;
Jan Althaus92d76832017-09-27 18:14:35 +0200200 }
201
202 /**
Jan Althaus20d346e2018-03-23 14:03:52 +0100203 * Returns an icon that may be rendered on a widget used to act on the classified text.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000204 *
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100205 * <p><strong>NOTE: </strong>This field is not parcelable and only represents the icon of the
206 * first {@link RemoteAction} (if one exists) when this object is read from a parcel.
207 *
Jan Althaus20d346e2018-03-23 14:03:52 +0100208 * @deprecated Use {@link #getActions()} instead.
Jan Althaus92d76832017-09-27 18:14:35 +0200209 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100210 @Deprecated
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000211 @Nullable
212 public Drawable getIcon() {
Jan Althaus20d346e2018-03-23 14:03:52 +0100213 return mLegacyIcon;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000214 }
215
216 /**
Jan Althaus20d346e2018-03-23 14:03:52 +0100217 * Returns a label that may be rendered on a widget used to act on the classified text.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000218 *
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100219 * <p><strong>NOTE: </strong>This field is not parcelable and only represents the label of the
220 * first {@link RemoteAction} (if one exists) when this object is read from a parcel.
221 *
Jan Althaus20d346e2018-03-23 14:03:52 +0100222 * @deprecated Use {@link #getActions()} instead.
Jan Althaus92d76832017-09-27 18:14:35 +0200223 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100224 @Deprecated
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000225 @Nullable
226 public CharSequence getLabel() {
Jan Althaus20d346e2018-03-23 14:03:52 +0100227 return mLegacyLabel;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000228 }
229
230 /**
Jan Althaus20d346e2018-03-23 14:03:52 +0100231 * Returns an intent that may be fired to act on the classified text.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000232 *
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100233 * <p><strong>NOTE: </strong>This field is not parcelled and will always return null when this
234 * object is read from a parcel.
235 *
Jan Althaus20d346e2018-03-23 14:03:52 +0100236 * @deprecated Use {@link #getActions()} instead.
Jan Althaus92d76832017-09-27 18:14:35 +0200237 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100238 @Deprecated
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000239 @Nullable
240 public Intent getIntent() {
Jan Althaus20d346e2018-03-23 14:03:52 +0100241 return mLegacyIntent;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000242 }
243
244 /**
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100245 * Returns the OnClickListener that may be triggered to act on the classified text.
246 *
247 * <p><strong>NOTE: </strong>This field is not parcelable and only represents the first
248 * {@link RemoteAction} (if one exists) when this object is read from a parcel.
Jan Althaus20d346e2018-03-23 14:03:52 +0100249 *
250 * @deprecated Use {@link #getActions()} instead.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000251 */
252 @Nullable
253 public OnClickListener getOnClickListener() {
Jan Althaus20d346e2018-03-23 14:03:52 +0100254 return mLegacyOnClickListener;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000255 }
256
Abodunrinwa Toki54486c12017-04-19 21:02:36 +0100257 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100258 * Returns the id, if one exists, for this object.
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100259 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100260 @Nullable
261 public String getId() {
262 return mId;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100263 }
264
Tony Makd6f3fb42018-10-26 15:42:49 +0100265 /**
266 * Returns the extended data.
267 *
268 * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
269 * prefer to hold a reference to the returned bundle rather than frequently calling this
270 * method.
271 */
272 @NonNull
273 public Bundle getExtras() {
274 return mExtras.deepCopy();
275 }
276
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000277 @Override
278 public String toString() {
Jan Althaus20d346e2018-03-23 14:03:52 +0100279 return String.format(Locale.US,
Abodunrinwa Toki25f7fdc2019-02-19 23:42:30 +0000280 "TextClassification {text=%s, entities=%s, actions=%s, id=%s, extras=%s}",
281 mText, mEntityConfidence, mActions, mId, mExtras);
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000282 }
283
284 /**
Jan Althaus20d346e2018-03-23 14:03:52 +0100285 * Creates an OnClickListener that triggers the specified PendingIntent.
286 *
287 * @hide
288 */
289 public static OnClickListener createIntentOnClickListener(@NonNull final PendingIntent intent) {
290 Preconditions.checkNotNull(intent);
291 return v -> {
292 try {
293 intent.send();
294 } catch (PendingIntent.CanceledException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100295 Log.e(LOG_TAG, "Error sending PendingIntent", e);
Jan Althaus20d346e2018-03-23 14:03:52 +0100296 }
297 };
298 }
299
300 /**
301 * Creates a PendingIntent for the specified intent.
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000302 * Returns null if the intent is not supported for the specified context.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000303 *
304 * @throws IllegalArgumentException if context or intent is null
305 * @hide
306 */
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000307 @Nullable
Jan Althaus20d346e2018-03-23 14:03:52 +0100308 public static PendingIntent createPendingIntent(
Abodunrinwa Toki904a9312018-04-18 21:21:27 +0100309 @NonNull final Context context, @NonNull final Intent intent, int requestCode) {
Abodunrinwa Tokiaec59812018-05-15 23:09:34 +0100310 final int flags = PendingIntent.FLAG_UPDATE_CURRENT;
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000311 switch (getIntentType(intent, context)) {
312 case IntentType.ACTIVITY:
Abodunrinwa Tokiaec59812018-05-15 23:09:34 +0100313 return PendingIntent.getActivity(context, requestCode, intent, flags);
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000314 case IntentType.SERVICE:
Abodunrinwa Tokiaec59812018-05-15 23:09:34 +0100315 return PendingIntent.getService(context, requestCode, intent, flags);
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000316 default:
317 return null;
318 }
319 }
320
321 @IntentType
322 private static int getIntentType(@NonNull Intent intent, @NonNull Context context) {
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000323 Preconditions.checkArgument(context != null);
324 Preconditions.checkArgument(intent != null);
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000325
326 final ResolveInfo activityRI = context.getPackageManager().resolveActivity(intent, 0);
327 if (activityRI != null) {
328 if (context.getPackageName().equals(activityRI.activityInfo.packageName)) {
329 return IntentType.ACTIVITY;
330 }
331 final boolean exported = activityRI.activityInfo.exported;
332 if (exported && hasPermission(context, activityRI.activityInfo.permission)) {
333 return IntentType.ACTIVITY;
334 }
335 }
336
337 final ResolveInfo serviceRI = context.getPackageManager().resolveService(intent, 0);
338 if (serviceRI != null) {
339 if (context.getPackageName().equals(serviceRI.serviceInfo.packageName)) {
340 return IntentType.SERVICE;
341 }
342 final boolean exported = serviceRI.serviceInfo.exported;
343 if (exported && hasPermission(context, serviceRI.serviceInfo.permission)) {
344 return IntentType.SERVICE;
345 }
346 }
347
348 return IntentType.UNSUPPORTED;
349 }
350
351 private static boolean hasPermission(@NonNull Context context, @NonNull String permission) {
352 return permission == null
353 || context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000354 }
355
356 /**
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100357 * Builder for building {@link TextClassification} objects.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000358 *
359 * <p>e.g.
360 *
361 * <pre>{@code
362 * TextClassification classification = new TextClassification.Builder()
363 * .setText(classifiedText)
364 * .setEntityType(TextClassifier.TYPE_EMAIL, 0.9)
365 * .setEntityType(TextClassifier.TYPE_OTHER, 0.1)
Jan Althaus20d346e2018-03-23 14:03:52 +0100366 * .addAction(remoteAction1)
367 * .addAction(remoteAction2)
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000368 * .build();
369 * }</pre>
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000370 */
371 public static final class Builder {
372
Jan Althaus20d346e2018-03-23 14:03:52 +0100373 @NonNull private List<RemoteAction> mActions = new ArrayList<>();
Jan Althausbbe43df2017-11-30 15:01:40 +0100374 @NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
Jan Althaus13a89c92018-04-09 16:14:36 +0200375 @Nullable private String mText;
376 @Nullable private Drawable mLegacyIcon;
377 @Nullable private String mLegacyLabel;
378 @Nullable private Intent mLegacyIntent;
379 @Nullable private OnClickListener mLegacyOnClickListener;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100380 @Nullable private String mId;
Tony Makd6f3fb42018-10-26 15:42:49 +0100381 @Nullable private Bundle mExtras;
Abodunrinwa Toki520b2f82019-01-27 07:48:02 +0000382 @NonNull private final ArrayList<Intent> mActionIntents = new ArrayList<>();
383 @Nullable private Bundle mForeignLanguageExtra;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000384
385 /**
386 * Sets the classified text.
387 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100388 @NonNull
Abodunrinwa Tokid44286f2017-07-12 19:38:54 +0100389 public Builder setText(@Nullable String text) {
390 mText = text;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000391 return this;
392 }
393
394 /**
395 * Sets an entity type for the classification result and assigns a confidence score.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000396 * If a confidence score had already been set for the specified entity type, this will
397 * override that score.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000398 *
399 * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
400 * 0 implies the entity does not exist for the classified text.
401 * Values greater than 1 are clamped to 1.
402 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100403 @NonNull
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000404 public Builder setEntityType(
405 @NonNull @EntityType String type,
Abodunrinwa Tokid44286f2017-07-12 19:38:54 +0100406 @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
Jan Althausbbe43df2017-11-30 15:01:40 +0100407 mEntityConfidence.put(type, confidenceScore);
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000408 return this;
409 }
410
411 /**
Jan Althaus20d346e2018-03-23 14:03:52 +0100412 * Adds an action that may be performed on the classified text. Actions should be added in
413 * order of likelihood that the user will use them, with the most likely action being added
414 * first.
Jan Althaus92d76832017-09-27 18:14:35 +0200415 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100416 @NonNull
Jan Althaus20d346e2018-03-23 14:03:52 +0100417 public Builder addAction(@NonNull RemoteAction action) {
Abodunrinwa Toki520b2f82019-01-27 07:48:02 +0000418 return addAction(action, null);
419 }
420
421 /**
422 * @param intent the intent in the remote action.
423 * @see #addAction(RemoteAction)
424 * @hide
425 */
426 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
427 public Builder addAction(RemoteAction action, @Nullable Intent intent) {
Jan Althaus20d346e2018-03-23 14:03:52 +0100428 Preconditions.checkArgument(action != null);
429 mActions.add(action);
Abodunrinwa Toki520b2f82019-01-27 07:48:02 +0000430 mActionIntents.add(intent);
Jan Althaus92d76832017-09-27 18:14:35 +0200431 return this;
432 }
433
434 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000435 * Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act
436 * on the classified text.
437 *
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100438 * <p><strong>NOTE: </strong>This field is not parcelled. If read from a parcel, the
439 * returned icon represents the icon of the first {@link RemoteAction} (if one exists).
440 *
Jan Althaus20d346e2018-03-23 14:03:52 +0100441 * @deprecated Use {@link #addAction(RemoteAction)} instead.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000442 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100443 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100444 @NonNull
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000445 public Builder setIcon(@Nullable Drawable icon) {
Jan Althaus20d346e2018-03-23 14:03:52 +0100446 mLegacyIcon = icon;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000447 return this;
448 }
449
450 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000451 * Sets the label for the <i>primary</i> action that may be rendered on a widget used to
452 * act on the classified text.
453 *
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100454 * <p><strong>NOTE: </strong>This field is not parcelled. If read from a parcel, the
455 * returned label represents the label of the first {@link RemoteAction} (if one exists).
456 *
Jan Althaus20d346e2018-03-23 14:03:52 +0100457 * @deprecated Use {@link #addAction(RemoteAction)} instead.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000458 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100459 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100460 @NonNull
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000461 public Builder setLabel(@Nullable String label) {
Jan Althaus20d346e2018-03-23 14:03:52 +0100462 mLegacyLabel = label;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000463 return this;
464 }
465
466 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000467 * Sets the intent for the <i>primary</i> action that may be fired to act on the classified
468 * text.
469 *
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100470 * <p><strong>NOTE: </strong>This field is not parcelled.
471 *
Jan Althaus20d346e2018-03-23 14:03:52 +0100472 * @deprecated Use {@link #addAction(RemoteAction)} instead.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000473 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100474 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100475 @NonNull
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000476 public Builder setIntent(@Nullable Intent intent) {
Jan Althaus20d346e2018-03-23 14:03:52 +0100477 mLegacyIntent = intent;
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000478 return this;
479 }
480
481 /**
482 * Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100483 * the classified text.
484 *
485 * <p><strong>NOTE: </strong>This field is not parcelable. If read from a parcel, the
486 * returned OnClickListener represents the first {@link RemoteAction} (if one exists).
Jan Althaus20d346e2018-03-23 14:03:52 +0100487 *
488 * @deprecated Use {@link #addAction(RemoteAction)} instead.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000489 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100490 @Deprecated
491 @NonNull
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000492 public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
Jan Althaus20d346e2018-03-23 14:03:52 +0100493 mLegacyOnClickListener = onClickListener;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000494 return this;
495 }
496
497 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100498 * Sets an id for the TextClassification object.
Abodunrinwa Toki54486c12017-04-19 21:02:36 +0100499 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100500 @NonNull
501 public Builder setId(@Nullable String id) {
502 mId = id;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100503 return this;
504 }
505
506 /**
Tony Makd6f3fb42018-10-26 15:42:49 +0100507 * Sets the extended data.
508 */
509 @NonNull
510 public Builder setExtras(@Nullable Bundle extras) {
511 mExtras = extras;
512 return this;
513 }
514
515 /**
Abodunrinwa Toki520b2f82019-01-27 07:48:02 +0000516 * @see #setExtras(Bundle)
517 * @hide
518 */
519 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
520 public Builder setForeignLanguageExtra(@Nullable Bundle extra) {
521 mForeignLanguageExtra = extra;
522 return this;
523 }
524
525 /**
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100526 * Builds and returns a {@link TextClassification} object.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000527 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100528 @NonNull
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100529 public TextClassification build() {
Jan Althaus20d346e2018-03-23 14:03:52 +0100530 return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent,
Abodunrinwa Toki520b2f82019-01-27 07:48:02 +0000531 mLegacyOnClickListener, mActions, mEntityConfidence, mId, buildExtras());
532 }
533
534 private Bundle buildExtras() {
535 final Bundle extras = mExtras == null ? new Bundle() : mExtras.deepCopy();
Abodunrinwa Toki25f7fdc2019-02-19 23:42:30 +0000536 if (mActionIntents.stream().anyMatch(Objects::nonNull)) {
Abodunrinwa Toki520b2f82019-01-27 07:48:02 +0000537 ExtrasUtils.putActionsIntents(extras, mActionIntents);
538 }
539 if (mForeignLanguageExtra != null) {
540 ExtrasUtils.putForeignLanguageExtra(extras, mForeignLanguageExtra);
541 }
542 return extras.isEmpty() ? Bundle.EMPTY : extras;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000543 }
544 }
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100545
546 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100547 * A request object for generating TextClassification.
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100548 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100549 public static final class Request implements Parcelable {
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100550
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100551 private final CharSequence mText;
552 private final int mStartIndex;
553 private final int mEndIndex;
554 @Nullable private final LocaleList mDefaultLocales;
555 @Nullable private final ZonedDateTime mReferenceTime;
Tony Makd6f3fb42018-10-26 15:42:49 +0100556 @NonNull private final Bundle mExtras;
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000557 @Nullable private String mCallingPackageName;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100558
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100559 private Request(
560 CharSequence text,
561 int startIndex,
562 int endIndex,
563 LocaleList defaultLocales,
Tony Makd6f3fb42018-10-26 15:42:49 +0100564 ZonedDateTime referenceTime,
565 Bundle extras) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100566 mText = text;
567 mStartIndex = startIndex;
568 mEndIndex = endIndex;
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100569 mDefaultLocales = defaultLocales;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100570 mReferenceTime = referenceTime;
Tony Makd6f3fb42018-10-26 15:42:49 +0100571 mExtras = extras;
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100572 }
573
574 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100575 * Returns the text providing context for the text to classify (which is specified
576 * by the sub sequence starting at startIndex and ending at endIndex)
Jan Althaus705b9e92018-01-22 18:22:29 +0100577 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100578 @NonNull
579 public CharSequence getText() {
580 return mText;
581 }
582
583 /**
584 * Returns start index of the text to classify.
585 */
586 @IntRange(from = 0)
587 public int getStartIndex() {
588 return mStartIndex;
589 }
590
591 /**
592 * Returns end index of the text to classify.
593 */
594 @IntRange(from = 0)
595 public int getEndIndex() {
596 return mEndIndex;
Jan Althaus705b9e92018-01-22 18:22:29 +0100597 }
598
599 /**
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100600 * @return ordered list of locale preferences that can be used to disambiguate
601 * the provided text.
602 */
603 @Nullable
604 public LocaleList getDefaultLocales() {
605 return mDefaultLocales;
606 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100607
Jan Althaus705b9e92018-01-22 18:22:29 +0100608 /**
609 * @return reference time based on which relative dates (e.g. "tomorrow") should be
610 * interpreted.
611 */
612 @Nullable
Jan Althausa1652cf2018-03-29 17:51:57 +0200613 public ZonedDateTime getReferenceTime() {
Jan Althaus705b9e92018-01-22 18:22:29 +0100614 return mReferenceTime;
615 }
616
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100617 /**
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000618 * Sets the name of the package that is sending this request.
619 * <p>
620 * For SystemTextClassifier's use.
621 * @hide
622 */
623 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
624 public void setCallingPackageName(@Nullable String callingPackageName) {
625 mCallingPackageName = callingPackageName;
626 }
627
628 /**
629 * Returns the name of the package that sent this request.
630 * This returns {@code null} if no calling package name is set.
631 */
632 @Nullable
633 public String getCallingPackageName() {
634 return mCallingPackageName;
635 }
636
637 /**
Tony Makd6f3fb42018-10-26 15:42:49 +0100638 * Returns the extended data.
639 *
640 * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
641 * prefer to hold a reference to the returned bundle rather than frequently calling this
642 * method.
643 */
644 @NonNull
645 public Bundle getExtras() {
646 return mExtras.deepCopy();
647 }
648
649 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100650 * A builder for building TextClassification requests.
651 */
652 public static final class Builder {
653
654 private final CharSequence mText;
655 private final int mStartIndex;
656 private final int mEndIndex;
Tony Makd6f3fb42018-10-26 15:42:49 +0100657 private Bundle mExtras;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100658
659 @Nullable private LocaleList mDefaultLocales;
660 @Nullable private ZonedDateTime mReferenceTime;
661
662 /**
663 * @param text text providing context for the text to classify (which is specified
664 * by the sub sequence starting at startIndex and ending at endIndex)
665 * @param startIndex start index of the text to classify
666 * @param endIndex end index of the text to classify
667 */
668 public Builder(
669 @NonNull CharSequence text,
670 @IntRange(from = 0) int startIndex,
671 @IntRange(from = 0) int endIndex) {
672 Utils.checkArgument(text, startIndex, endIndex);
673 mText = text;
674 mStartIndex = startIndex;
675 mEndIndex = endIndex;
676 }
677
678 /**
679 * @param defaultLocales ordered list of locale preferences that may be used to
680 * disambiguate the provided text. If no locale preferences exist, set this to null
681 * or an empty locale list.
682 *
683 * @return this builder
684 */
685 @NonNull
686 public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) {
687 mDefaultLocales = defaultLocales;
688 return this;
689 }
690
691 /**
692 * @param referenceTime reference time based on which relative dates (e.g. "tomorrow"
693 * should be interpreted. This should usually be the time when the text was
694 * originally composed. If no reference time is set, now is used.
695 *
696 * @return this builder
697 */
698 @NonNull
699 public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) {
700 mReferenceTime = referenceTime;
701 return this;
702 }
703
704 /**
Tony Makd6f3fb42018-10-26 15:42:49 +0100705 * Sets the extended data.
706 *
707 * @return this builder
708 */
709 @NonNull
710 public Builder setExtras(@Nullable Bundle extras) {
711 mExtras = extras;
712 return this;
713 }
714
715 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100716 * Builds and returns the request object.
717 */
718 @NonNull
719 public Request build() {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000720 return new Request(new SpannedString(mText), mStartIndex, mEndIndex,
721 mDefaultLocales, mReferenceTime,
Tony Makd6f3fb42018-10-26 15:42:49 +0100722 mExtras == null ? Bundle.EMPTY : mExtras.deepCopy());
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100723 }
724 }
725
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100726 @Override
727 public int describeContents() {
728 return 0;
729 }
730
731 @Override
732 public void writeToParcel(Parcel dest, int flags) {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000733 dest.writeCharSequence(mText);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100734 dest.writeInt(mStartIndex);
735 dest.writeInt(mEndIndex);
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000736 dest.writeParcelable(mDefaultLocales, flags);
737 dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString());
738 dest.writeString(mCallingPackageName);
Tony Makd6f3fb42018-10-26 15:42:49 +0100739 dest.writeBundle(mExtras);
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100740 }
741
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000742 private static Request readFromParcel(Parcel in) {
743 final CharSequence text = in.readCharSequence();
744 final int startIndex = in.readInt();
745 final int endIndex = in.readInt();
746 final LocaleList defaultLocales = in.readParcelable(null);
747 final String referenceTimeString = in.readString();
748 final ZonedDateTime referenceTime = referenceTimeString == null
749 ? null : ZonedDateTime.parse(referenceTimeString);
750 final String callingPackageName = in.readString();
751 final Bundle extras = in.readBundle();
752
753 final Request request = new Request(text, startIndex, endIndex,
754 defaultLocales, referenceTime, extras);
755 request.setCallingPackageName(callingPackageName);
756 return request;
757 }
758
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100759 public static final Parcelable.Creator<Request> CREATOR =
760 new Parcelable.Creator<Request>() {
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100761 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100762 public Request createFromParcel(Parcel in) {
Abodunrinwa Toki0f138962018-12-03 22:35:10 +0000763 return readFromParcel(in);
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100764 }
765
766 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100767 public Request[] newArray(int size) {
768 return new Request[size];
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100769 }
770 };
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100771 }
772
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800773 @Override
774 public int describeContents() {
775 return 0;
776 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100777
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800778 @Override
779 public void writeToParcel(Parcel dest, int flags) {
780 dest.writeString(mText);
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100781 // NOTE: legacy fields are not parcelled.
Jan Althaus20d346e2018-03-23 14:03:52 +0100782 dest.writeTypedList(mActions);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800783 mEntityConfidence.writeToParcel(dest, flags);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100784 dest.writeString(mId);
Tony Makd6f3fb42018-10-26 15:42:49 +0100785 dest.writeBundle(mExtras);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800786 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100787
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800788 public static final Parcelable.Creator<TextClassification> CREATOR =
789 new Parcelable.Creator<TextClassification>() {
790 @Override
791 public TextClassification createFromParcel(Parcel in) {
792 return new TextClassification(in);
793 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100794
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800795 @Override
796 public TextClassification[] newArray(int size) {
797 return new TextClassification[size];
798 }
799 };
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100800
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800801 private TextClassification(Parcel in) {
802 mText = in.readString();
Jan Althaus20d346e2018-03-23 14:03:52 +0100803 mActions = in.createTypedArrayList(RemoteAction.CREATOR);
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100804 if (!mActions.isEmpty()) {
805 final RemoteAction action = mActions.get(0);
806 mLegacyIcon = maybeLoadDrawable(action.getIcon());
807 mLegacyLabel = action.getTitle().toString();
808 mLegacyOnClickListener = createIntentOnClickListener(mActions.get(0).getActionIntent());
809 } else {
810 mLegacyIcon = null;
811 mLegacyLabel = null;
812 mLegacyOnClickListener = null;
813 }
814 mLegacyIntent = null; // mLegacyIntent is not parcelled.
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800815 mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100816 mId = in.readString();
Tony Makd6f3fb42018-10-26 15:42:49 +0100817 mExtras = in.readBundle();
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100818 }
Abodunrinwa Tokiae82e7a2018-04-03 23:49:16 +0100819
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100820 // Best effort attempt to try to load a drawable from the provided icon.
821 @Nullable
822 private static Drawable maybeLoadDrawable(Icon icon) {
823 if (icon == null) {
824 return null;
825 }
826 switch (icon.getType()) {
827 case Icon.TYPE_BITMAP:
828 return new BitmapDrawable(Resources.getSystem(), icon.getBitmap());
829 case Icon.TYPE_ADAPTIVE_BITMAP:
830 return new AdaptiveIconDrawable(null,
831 new BitmapDrawable(Resources.getSystem(), icon.getBitmap()));
832 case Icon.TYPE_DATA:
833 return new BitmapDrawable(
834 Resources.getSystem(),
835 BitmapFactory.decodeByteArray(
836 icon.getDataBytes(), icon.getDataOffset(), icon.getDataLength()));
837 }
838 return null;
839 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000840}