blob: 9511a9efb3d5dad948c8e6151fdb20163814582e [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;
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +010036import android.os.LocaleList;
Jan Althaus0d9fbb92017-11-28 12:19:33 +010037import android.os.Parcel;
38import android.os.Parcelable;
Jan Althausbbe43df2017-11-30 15:01:40 +010039import android.util.ArrayMap;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000040import android.view.View.OnClickListener;
41import android.view.textclassifier.TextClassifier.EntityType;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010042import android.view.textclassifier.TextClassifier.Utils;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000043
44import com.android.internal.util.Preconditions;
45
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +000046import java.lang.annotation.Retention;
47import java.lang.annotation.RetentionPolicy;
Jan Althausa1652cf2018-03-29 17:51:57 +020048import java.time.ZonedDateTime;
Jan Althaus92d76832017-09-27 18:14:35 +020049import java.util.ArrayList;
Jan Althaus20d346e2018-03-23 14:03:52 +010050import java.util.Collections;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000051import java.util.List;
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000052import java.util.Locale;
Jan Althausbbe43df2017-11-30 15:01:40 +010053import java.util.Map;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000054
55/**
56 * Information for generating a widget to handle classified text.
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010057 *
58 * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may
Abodunrinwa Tokiba385622017-11-29 19:30:32 +000059 * be used to build a widget that can be used to act on classified text. There is the concept of a
60 * <i>primary action</i> and other <i>secondary actions</i>.
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010061 *
62 * <p>e.g. building a view that, when clicked, shares the classified text with the preferred app:
63 *
64 * <pre>{@code
65 * // Called preferably outside the UiThread.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000066 * TextClassification classification = textClassifier.classifyText(allText, 10, 25);
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010067 *
68 * // Called on the UiThread.
69 * Button button = new Button(context);
70 * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null);
71 * button.setText(classification.getLabel());
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +010072 * button.setOnClickListener(v -> classification.getActions().get(0).getActionIntent().send());
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010073 * }</pre>
74 *
75 * <p>e.g. starting an action mode with menu items that can handle the classified text:
76 *
77 * <pre>{@code
78 * // Called preferably outside the UiThread.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000079 * final TextClassification classification = textClassifier.classifyText(allText, 10, 25);
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010080 *
81 * // Called on the UiThread.
82 * view.startActionMode(new ActionMode.Callback() {
83 *
84 * public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Jan Althaus20d346e2018-03-23 14:03:52 +010085 * for (int i = 0; i < classification.getActions().size(); ++i) {
86 * RemoteAction action = classification.getActions().get(i);
87 * menu.add(Menu.NONE, i, 20, action.getTitle())
88 * .setIcon(action.getIcon());
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010089 * }
90 * return true;
91 * }
92 *
93 * public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Jan Althaus20d346e2018-03-23 14:03:52 +010094 * classification.getActions().get(item.getItemId()).getActionIntent().send();
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010095 * return true;
96 * }
97 *
98 * ...
99 * });
100 * }</pre>
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000101 */
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800102public final class TextClassification implements Parcelable {
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000103
104 /**
105 * @hide
106 */
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100107 static final TextClassification EMPTY = new TextClassification.Builder().build();
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000108
Jan Althaus20d346e2018-03-23 14:03:52 +0100109 private static final String LOG_TAG = "TextClassification";
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100110 // TODO(toki): investigate a way to derive this based on device properties.
Jan Althaus20d346e2018-03-23 14:03:52 +0100111 private static final int MAX_LEGACY_ICON_SIZE = 192;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100112
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000113 @Retention(RetentionPolicy.SOURCE)
114 @IntDef(value = {IntentType.UNSUPPORTED, IntentType.ACTIVITY, IntentType.SERVICE})
115 private @interface IntentType {
116 int UNSUPPORTED = -1;
117 int ACTIVITY = 0;
118 int SERVICE = 1;
119 }
120
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000121 @NonNull private final String mText;
Jan Althaus20d346e2018-03-23 14:03:52 +0100122 @Nullable private final Drawable mLegacyIcon;
123 @Nullable private final String mLegacyLabel;
124 @Nullable private final Intent mLegacyIntent;
125 @Nullable private final OnClickListener mLegacyOnClickListener;
126 @NonNull private final List<RemoteAction> mActions;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100127 @NonNull private final EntityConfidence mEntityConfidence;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100128 @Nullable private final String mId;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000129
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100130 private TextClassification(
Abodunrinwa Tokid44286f2017-07-12 19:38:54 +0100131 @Nullable String text,
Jan Althaus20d346e2018-03-23 14:03:52 +0100132 @Nullable Drawable legacyIcon,
133 @Nullable String legacyLabel,
134 @Nullable Intent legacyIntent,
135 @Nullable OnClickListener legacyOnClickListener,
136 @NonNull List<RemoteAction> actions,
Jan Althausbbe43df2017-11-30 15:01:40 +0100137 @NonNull Map<String, Float> entityConfidence,
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100138 @Nullable String id) {
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000139 mText = text;
Jan Althaus20d346e2018-03-23 14:03:52 +0100140 mLegacyIcon = legacyIcon;
141 mLegacyLabel = legacyLabel;
142 mLegacyIntent = legacyIntent;
143 mLegacyOnClickListener = legacyOnClickListener;
144 mActions = Collections.unmodifiableList(actions);
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100145 mEntityConfidence = new EntityConfidence(entityConfidence);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100146 mId = id;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000147 }
148
149 /**
150 * Gets the classified text.
151 */
Abodunrinwa Tokid44286f2017-07-12 19:38:54 +0100152 @Nullable
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000153 public String getText() {
154 return mText;
155 }
156
157 /**
158 * Returns the number of entities found in the classified text.
159 */
160 @IntRange(from = 0)
161 public int getEntityCount() {
Jan Althausbbe43df2017-11-30 15:01:40 +0100162 return mEntityConfidence.getEntities().size();
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000163 }
164
165 /**
166 * Returns the entity at the specified index. Entities are ordered from high confidence
167 * to low confidence.
168 *
169 * @throws IndexOutOfBoundsException if the specified index is out of range.
170 * @see #getEntityCount() for the number of entities available.
171 */
172 @NonNull
173 public @EntityType String getEntity(int index) {
Jan Althausbbe43df2017-11-30 15:01:40 +0100174 return mEntityConfidence.getEntities().get(index);
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000175 }
176
177 /**
178 * Returns the confidence score for the specified entity. The value ranges from
179 * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
180 * classified text.
181 */
182 @FloatRange(from = 0.0, to = 1.0)
183 public float getConfidenceScore(@EntityType String entity) {
184 return mEntityConfidence.getConfidenceScore(entity);
185 }
186
187 /**
Jan Althaus20d346e2018-03-23 14:03:52 +0100188 * Returns a list of actions that may be performed on the text. The list is ordered based on
189 * the likelihood that a user will use the action, with the most likely action appearing first.
Jan Althaus92d76832017-09-27 18:14:35 +0200190 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100191 public List<RemoteAction> getActions() {
192 return mActions;
Jan Althaus92d76832017-09-27 18:14:35 +0200193 }
194
195 /**
Jan Althaus20d346e2018-03-23 14:03:52 +0100196 * Returns an icon that may be rendered on a widget used to act on the classified text.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000197 *
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100198 * <p><strong>NOTE: </strong>This field is not parcelable and only represents the icon of the
199 * first {@link RemoteAction} (if one exists) when this object is read from a parcel.
200 *
Jan Althaus20d346e2018-03-23 14:03:52 +0100201 * @deprecated Use {@link #getActions()} instead.
Jan Althaus92d76832017-09-27 18:14:35 +0200202 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100203 @Deprecated
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000204 @Nullable
205 public Drawable getIcon() {
Jan Althaus20d346e2018-03-23 14:03:52 +0100206 return mLegacyIcon;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000207 }
208
209 /**
Jan Althaus20d346e2018-03-23 14:03:52 +0100210 * Returns a label that may be rendered on a widget used to act on the classified text.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000211 *
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100212 * <p><strong>NOTE: </strong>This field is not parcelable and only represents the label of the
213 * first {@link RemoteAction} (if one exists) when this object is read from a parcel.
214 *
Jan Althaus20d346e2018-03-23 14:03:52 +0100215 * @deprecated Use {@link #getActions()} instead.
Jan Althaus92d76832017-09-27 18:14:35 +0200216 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100217 @Deprecated
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000218 @Nullable
219 public CharSequence getLabel() {
Jan Althaus20d346e2018-03-23 14:03:52 +0100220 return mLegacyLabel;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000221 }
222
223 /**
Jan Althaus20d346e2018-03-23 14:03:52 +0100224 * Returns an intent that may be fired to act on the classified text.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000225 *
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100226 * <p><strong>NOTE: </strong>This field is not parcelled and will always return null when this
227 * object is read from a parcel.
228 *
Jan Althaus20d346e2018-03-23 14:03:52 +0100229 * @deprecated Use {@link #getActions()} instead.
Jan Althaus92d76832017-09-27 18:14:35 +0200230 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100231 @Deprecated
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000232 @Nullable
233 public Intent getIntent() {
Jan Althaus20d346e2018-03-23 14:03:52 +0100234 return mLegacyIntent;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000235 }
236
237 /**
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100238 * Returns the OnClickListener that may be triggered to act on the classified text.
239 *
240 * <p><strong>NOTE: </strong>This field is not parcelable and only represents the first
241 * {@link RemoteAction} (if one exists) when this object is read from a parcel.
Jan Althaus20d346e2018-03-23 14:03:52 +0100242 *
243 * @deprecated Use {@link #getActions()} instead.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000244 */
245 @Nullable
246 public OnClickListener getOnClickListener() {
Jan Althaus20d346e2018-03-23 14:03:52 +0100247 return mLegacyOnClickListener;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000248 }
249
Abodunrinwa Toki54486c12017-04-19 21:02:36 +0100250 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100251 * Returns the id, if one exists, for this object.
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100252 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100253 @Nullable
254 public String getId() {
255 return mId;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100256 }
257
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000258 @Override
259 public String toString() {
Jan Althaus20d346e2018-03-23 14:03:52 +0100260 return String.format(Locale.US,
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100261 "TextClassification {text=%s, entities=%s, actions=%s, id=%s}",
262 mText, mEntityConfidence, mActions, mId);
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000263 }
264
265 /**
Jan Althaus20d346e2018-03-23 14:03:52 +0100266 * Creates an OnClickListener that triggers the specified PendingIntent.
267 *
268 * @hide
269 */
270 public static OnClickListener createIntentOnClickListener(@NonNull final PendingIntent intent) {
271 Preconditions.checkNotNull(intent);
272 return v -> {
273 try {
274 intent.send();
275 } catch (PendingIntent.CanceledException e) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100276 Log.e(LOG_TAG, "Error sending PendingIntent", e);
Jan Althaus20d346e2018-03-23 14:03:52 +0100277 }
278 };
279 }
280
281 /**
282 * Creates a PendingIntent for the specified intent.
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000283 * Returns null if the intent is not supported for the specified context.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000284 *
285 * @throws IllegalArgumentException if context or intent is null
286 * @hide
287 */
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000288 @Nullable
Jan Althaus20d346e2018-03-23 14:03:52 +0100289 public static PendingIntent createPendingIntent(
Abodunrinwa Toki904a9312018-04-18 21:21:27 +0100290 @NonNull final Context context, @NonNull final Intent intent, int requestCode) {
Abodunrinwa Tokiaec59812018-05-15 23:09:34 +0100291 final int flags = PendingIntent.FLAG_UPDATE_CURRENT;
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000292 switch (getIntentType(intent, context)) {
293 case IntentType.ACTIVITY:
Abodunrinwa Tokiaec59812018-05-15 23:09:34 +0100294 return PendingIntent.getActivity(context, requestCode, intent, flags);
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000295 case IntentType.SERVICE:
Abodunrinwa Tokiaec59812018-05-15 23:09:34 +0100296 return PendingIntent.getService(context, requestCode, intent, flags);
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000297 default:
298 return null;
299 }
300 }
301
302 @IntentType
303 private static int getIntentType(@NonNull Intent intent, @NonNull Context context) {
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000304 Preconditions.checkArgument(context != null);
305 Preconditions.checkArgument(intent != null);
Abodunrinwa Toki2f19b922018-02-12 19:59:28 +0000306
307 final ResolveInfo activityRI = context.getPackageManager().resolveActivity(intent, 0);
308 if (activityRI != null) {
309 if (context.getPackageName().equals(activityRI.activityInfo.packageName)) {
310 return IntentType.ACTIVITY;
311 }
312 final boolean exported = activityRI.activityInfo.exported;
313 if (exported && hasPermission(context, activityRI.activityInfo.permission)) {
314 return IntentType.ACTIVITY;
315 }
316 }
317
318 final ResolveInfo serviceRI = context.getPackageManager().resolveService(intent, 0);
319 if (serviceRI != null) {
320 if (context.getPackageName().equals(serviceRI.serviceInfo.packageName)) {
321 return IntentType.SERVICE;
322 }
323 final boolean exported = serviceRI.serviceInfo.exported;
324 if (exported && hasPermission(context, serviceRI.serviceInfo.permission)) {
325 return IntentType.SERVICE;
326 }
327 }
328
329 return IntentType.UNSUPPORTED;
330 }
331
332 private static boolean hasPermission(@NonNull Context context, @NonNull String permission) {
333 return permission == null
334 || context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000335 }
336
337 /**
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100338 * Builder for building {@link TextClassification} objects.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000339 *
340 * <p>e.g.
341 *
342 * <pre>{@code
343 * TextClassification classification = new TextClassification.Builder()
344 * .setText(classifiedText)
345 * .setEntityType(TextClassifier.TYPE_EMAIL, 0.9)
346 * .setEntityType(TextClassifier.TYPE_OTHER, 0.1)
Jan Althaus20d346e2018-03-23 14:03:52 +0100347 * .addAction(remoteAction1)
348 * .addAction(remoteAction2)
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000349 * .build();
350 * }</pre>
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000351 */
352 public static final class Builder {
353
Jan Althaus20d346e2018-03-23 14:03:52 +0100354 @NonNull private List<RemoteAction> mActions = new ArrayList<>();
Jan Althausbbe43df2017-11-30 15:01:40 +0100355 @NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
Jan Althaus13a89c92018-04-09 16:14:36 +0200356 @Nullable private String mText;
357 @Nullable private Drawable mLegacyIcon;
358 @Nullable private String mLegacyLabel;
359 @Nullable private Intent mLegacyIntent;
360 @Nullable private OnClickListener mLegacyOnClickListener;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100361 @Nullable private String mId;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000362
363 /**
364 * Sets the classified text.
365 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100366 @NonNull
Abodunrinwa Tokid44286f2017-07-12 19:38:54 +0100367 public Builder setText(@Nullable String text) {
368 mText = text;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000369 return this;
370 }
371
372 /**
373 * Sets an entity type for the classification result and assigns a confidence score.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000374 * If a confidence score had already been set for the specified entity type, this will
375 * override that score.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000376 *
377 * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
378 * 0 implies the entity does not exist for the classified text.
379 * Values greater than 1 are clamped to 1.
380 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100381 @NonNull
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000382 public Builder setEntityType(
383 @NonNull @EntityType String type,
Abodunrinwa Tokid44286f2017-07-12 19:38:54 +0100384 @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
Jan Althausbbe43df2017-11-30 15:01:40 +0100385 mEntityConfidence.put(type, confidenceScore);
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000386 return this;
387 }
388
389 /**
Jan Althaus20d346e2018-03-23 14:03:52 +0100390 * Adds an action that may be performed on the classified text. Actions should be added in
391 * order of likelihood that the user will use them, with the most likely action being added
392 * first.
Jan Althaus92d76832017-09-27 18:14:35 +0200393 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100394 @NonNull
Jan Althaus20d346e2018-03-23 14:03:52 +0100395 public Builder addAction(@NonNull RemoteAction action) {
396 Preconditions.checkArgument(action != null);
397 mActions.add(action);
Jan Althaus92d76832017-09-27 18:14:35 +0200398 return this;
399 }
400
401 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000402 * Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act
403 * on the classified text.
404 *
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100405 * <p><strong>NOTE: </strong>This field is not parcelled. If read from a parcel, the
406 * returned icon represents the icon of the first {@link RemoteAction} (if one exists).
407 *
Jan Althaus20d346e2018-03-23 14:03:52 +0100408 * @deprecated Use {@link #addAction(RemoteAction)} instead.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000409 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100410 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100411 @NonNull
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000412 public Builder setIcon(@Nullable Drawable icon) {
Jan Althaus20d346e2018-03-23 14:03:52 +0100413 mLegacyIcon = icon;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000414 return this;
415 }
416
417 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000418 * Sets the label for the <i>primary</i> action that may be rendered on a widget used to
419 * act on the classified text.
420 *
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100421 * <p><strong>NOTE: </strong>This field is not parcelled. If read from a parcel, the
422 * returned label represents the label of the first {@link RemoteAction} (if one exists).
423 *
Jan Althaus20d346e2018-03-23 14:03:52 +0100424 * @deprecated Use {@link #addAction(RemoteAction)} instead.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000425 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100426 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100427 @NonNull
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000428 public Builder setLabel(@Nullable String label) {
Jan Althaus20d346e2018-03-23 14:03:52 +0100429 mLegacyLabel = label;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000430 return this;
431 }
432
433 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000434 * Sets the intent for the <i>primary</i> action that may be fired to act on the classified
435 * text.
436 *
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100437 * <p><strong>NOTE: </strong>This field is not parcelled.
438 *
Jan Althaus20d346e2018-03-23 14:03:52 +0100439 * @deprecated Use {@link #addAction(RemoteAction)} instead.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000440 */
Jan Althaus20d346e2018-03-23 14:03:52 +0100441 @Deprecated
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100442 @NonNull
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000443 public Builder setIntent(@Nullable Intent intent) {
Jan Althaus20d346e2018-03-23 14:03:52 +0100444 mLegacyIntent = intent;
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000445 return this;
446 }
447
448 /**
449 * Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100450 * the classified text.
451 *
452 * <p><strong>NOTE: </strong>This field is not parcelable. If read from a parcel, the
453 * returned OnClickListener represents the first {@link RemoteAction} (if one exists).
Jan Althaus20d346e2018-03-23 14:03:52 +0100454 *
455 * @deprecated Use {@link #addAction(RemoteAction)} instead.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000456 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100457 @Deprecated
458 @NonNull
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000459 public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
Jan Althaus20d346e2018-03-23 14:03:52 +0100460 mLegacyOnClickListener = onClickListener;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000461 return this;
462 }
463
464 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100465 * Sets an id for the TextClassification object.
Abodunrinwa Toki54486c12017-04-19 21:02:36 +0100466 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100467 @NonNull
468 public Builder setId(@Nullable String id) {
469 mId = id;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100470 return this;
471 }
472
473 /**
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100474 * Builds and returns a {@link TextClassification} object.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000475 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100476 @NonNull
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100477 public TextClassification build() {
Jan Althaus20d346e2018-03-23 14:03:52 +0100478 return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent,
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100479 mLegacyOnClickListener, mActions, mEntityConfidence, mId);
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000480 }
481 }
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100482
483 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100484 * A request object for generating TextClassification.
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100485 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100486 public static final class Request implements Parcelable {
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100487
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100488 private final CharSequence mText;
489 private final int mStartIndex;
490 private final int mEndIndex;
491 @Nullable private final LocaleList mDefaultLocales;
492 @Nullable private final ZonedDateTime mReferenceTime;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100493
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100494 private Request(
495 CharSequence text,
496 int startIndex,
497 int endIndex,
498 LocaleList defaultLocales,
499 ZonedDateTime referenceTime) {
500 mText = text;
501 mStartIndex = startIndex;
502 mEndIndex = endIndex;
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100503 mDefaultLocales = defaultLocales;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100504 mReferenceTime = referenceTime;
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100505 }
506
507 /**
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100508 * Returns the text providing context for the text to classify (which is specified
509 * by the sub sequence starting at startIndex and ending at endIndex)
Jan Althaus705b9e92018-01-22 18:22:29 +0100510 */
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100511 @NonNull
512 public CharSequence getText() {
513 return mText;
514 }
515
516 /**
517 * Returns start index of the text to classify.
518 */
519 @IntRange(from = 0)
520 public int getStartIndex() {
521 return mStartIndex;
522 }
523
524 /**
525 * Returns end index of the text to classify.
526 */
527 @IntRange(from = 0)
528 public int getEndIndex() {
529 return mEndIndex;
Jan Althaus705b9e92018-01-22 18:22:29 +0100530 }
531
532 /**
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100533 * @return ordered list of locale preferences that can be used to disambiguate
534 * the provided text.
535 */
536 @Nullable
537 public LocaleList getDefaultLocales() {
538 return mDefaultLocales;
539 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100540
Jan Althaus705b9e92018-01-22 18:22:29 +0100541 /**
542 * @return reference time based on which relative dates (e.g. "tomorrow") should be
543 * interpreted.
544 */
545 @Nullable
Jan Althausa1652cf2018-03-29 17:51:57 +0200546 public ZonedDateTime getReferenceTime() {
Jan Althaus705b9e92018-01-22 18:22:29 +0100547 return mReferenceTime;
548 }
549
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100550 /**
551 * A builder for building TextClassification requests.
552 */
553 public static final class Builder {
554
555 private final CharSequence mText;
556 private final int mStartIndex;
557 private final int mEndIndex;
558
559 @Nullable private LocaleList mDefaultLocales;
560 @Nullable private ZonedDateTime mReferenceTime;
561
562 /**
563 * @param text text providing context for the text to classify (which is specified
564 * by the sub sequence starting at startIndex and ending at endIndex)
565 * @param startIndex start index of the text to classify
566 * @param endIndex end index of the text to classify
567 */
568 public Builder(
569 @NonNull CharSequence text,
570 @IntRange(from = 0) int startIndex,
571 @IntRange(from = 0) int endIndex) {
572 Utils.checkArgument(text, startIndex, endIndex);
573 mText = text;
574 mStartIndex = startIndex;
575 mEndIndex = endIndex;
576 }
577
578 /**
579 * @param defaultLocales ordered list of locale preferences that may be used to
580 * disambiguate the provided text. If no locale preferences exist, set this to null
581 * or an empty locale list.
582 *
583 * @return this builder
584 */
585 @NonNull
586 public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) {
587 mDefaultLocales = defaultLocales;
588 return this;
589 }
590
591 /**
592 * @param referenceTime reference time based on which relative dates (e.g. "tomorrow"
593 * should be interpreted. This should usually be the time when the text was
594 * originally composed. If no reference time is set, now is used.
595 *
596 * @return this builder
597 */
598 @NonNull
599 public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) {
600 mReferenceTime = referenceTime;
601 return this;
602 }
603
604 /**
605 * Builds and returns the request object.
606 */
607 @NonNull
608 public Request build() {
609 return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales, mReferenceTime);
610 }
611 }
612
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100613 @Override
614 public int describeContents() {
615 return 0;
616 }
617
618 @Override
619 public void writeToParcel(Parcel dest, int flags) {
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100620 dest.writeString(mText.toString());
621 dest.writeInt(mStartIndex);
622 dest.writeInt(mEndIndex);
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100623 dest.writeInt(mDefaultLocales != null ? 1 : 0);
624 if (mDefaultLocales != null) {
625 mDefaultLocales.writeToParcel(dest, flags);
626 }
Jan Althaus705b9e92018-01-22 18:22:29 +0100627 dest.writeInt(mReferenceTime != null ? 1 : 0);
628 if (mReferenceTime != null) {
Jan Althausa1652cf2018-03-29 17:51:57 +0200629 dest.writeString(mReferenceTime.toString());
Jan Althaus705b9e92018-01-22 18:22:29 +0100630 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100631 }
632
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100633 public static final Parcelable.Creator<Request> CREATOR =
634 new Parcelable.Creator<Request>() {
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100635 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100636 public Request createFromParcel(Parcel in) {
637 return new Request(in);
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100638 }
639
640 @Override
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100641 public Request[] newArray(int size) {
642 return new Request[size];
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100643 }
644 };
645
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100646 private Request(Parcel in) {
647 mText = in.readString();
648 mStartIndex = in.readInt();
649 mEndIndex = in.readInt();
650 mDefaultLocales = in.readInt() == 0 ? null : LocaleList.CREATOR.createFromParcel(in);
651 mReferenceTime = in.readInt() == 0 ? null : ZonedDateTime.parse(in.readString());
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100652 }
653 }
654
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800655 @Override
656 public int describeContents() {
657 return 0;
658 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100659
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800660 @Override
661 public void writeToParcel(Parcel dest, int flags) {
662 dest.writeString(mText);
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100663 // NOTE: legacy fields are not parcelled.
Jan Althaus20d346e2018-03-23 14:03:52 +0100664 dest.writeTypedList(mActions);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800665 mEntityConfidence.writeToParcel(dest, flags);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100666 dest.writeString(mId);
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800667 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100668
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800669 public static final Parcelable.Creator<TextClassification> CREATOR =
670 new Parcelable.Creator<TextClassification>() {
671 @Override
672 public TextClassification createFromParcel(Parcel in) {
673 return new TextClassification(in);
674 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100675
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800676 @Override
677 public TextClassification[] newArray(int size) {
678 return new TextClassification[size];
679 }
680 };
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100681
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800682 private TextClassification(Parcel in) {
683 mText = in.readString();
Jan Althaus20d346e2018-03-23 14:03:52 +0100684 mActions = in.createTypedArrayList(RemoteAction.CREATOR);
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100685 if (!mActions.isEmpty()) {
686 final RemoteAction action = mActions.get(0);
687 mLegacyIcon = maybeLoadDrawable(action.getIcon());
688 mLegacyLabel = action.getTitle().toString();
689 mLegacyOnClickListener = createIntentOnClickListener(mActions.get(0).getActionIntent());
690 } else {
691 mLegacyIcon = null;
692 mLegacyLabel = null;
693 mLegacyOnClickListener = null;
694 }
695 mLegacyIntent = null; // mLegacyIntent is not parcelled.
Abodunrinwa Tokid32906c2018-01-18 04:34:44 -0800696 mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100697 mId = in.readString();
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100698 }
Abodunrinwa Tokiae82e7a2018-04-03 23:49:16 +0100699
Abodunrinwa Tokiba196c52018-04-20 19:52:21 +0100700 // Best effort attempt to try to load a drawable from the provided icon.
701 @Nullable
702 private static Drawable maybeLoadDrawable(Icon icon) {
703 if (icon == null) {
704 return null;
705 }
706 switch (icon.getType()) {
707 case Icon.TYPE_BITMAP:
708 return new BitmapDrawable(Resources.getSystem(), icon.getBitmap());
709 case Icon.TYPE_ADAPTIVE_BITMAP:
710 return new AdaptiveIconDrawable(null,
711 new BitmapDrawable(Resources.getSystem(), icon.getBitmap()));
712 case Icon.TYPE_DATA:
713 return new BitmapDrawable(
714 Resources.getSystem(),
715 BitmapFactory.decodeByteArray(
716 icon.getDataBytes(), icon.getDataOffset(), icon.getDataLength()));
717 }
718 return null;
719 }
720
Abodunrinwa Tokiae82e7a2018-04-03 23:49:16 +0100721 // TODO: Remove once apps can build against the latest sdk.
722 /**
723 * Optional input parameters for generating TextClassification.
724 * @hide
725 */
726 public static final class Options {
727
728 @Nullable private final TextClassificationSessionId mSessionId;
729 @Nullable private final Request mRequest;
730 @Nullable private LocaleList mDefaultLocales;
731 @Nullable private ZonedDateTime mReferenceTime;
732
733 public Options() {
734 this(null, null);
735 }
736
737 private Options(
738 @Nullable TextClassificationSessionId sessionId, @Nullable Request request) {
739 mSessionId = sessionId;
740 mRequest = request;
741 }
742
743 /** Helper to create Options from a Request. */
744 public static Options from(TextClassificationSessionId sessionId, Request request) {
745 final Options options = new Options(sessionId, request);
746 options.setDefaultLocales(request.getDefaultLocales());
747 options.setReferenceTime(request.getReferenceTime());
748 return options;
749 }
750
751 /** @param defaultLocales ordered list of locale preferences. */
752 public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
753 mDefaultLocales = defaultLocales;
754 return this;
755 }
756
757 /** @param referenceTime refrence time used for interpreting relatives dates */
758 public Options setReferenceTime(@Nullable ZonedDateTime referenceTime) {
759 mReferenceTime = referenceTime;
760 return this;
761 }
762
763 @Nullable
764 public LocaleList getDefaultLocales() {
765 return mDefaultLocales;
766 }
767
768 @Nullable
769 public ZonedDateTime getReferenceTime() {
770 return mReferenceTime;
771 }
772
773 @Nullable
774 public Request getRequest() {
775 return mRequest;
776 }
777
778 @Nullable
779 public TextClassificationSessionId getSessionId() {
780 return mSessionId;
781 }
782 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000783}