blob: 7089677d744bc4e03094dbae4d9fe62d07c770d0 [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;
20import android.annotation.IntRange;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.content.Context;
24import android.content.Intent;
Jan Althaus0d9fbb92017-11-28 12:19:33 +010025import android.graphics.Bitmap;
26import android.graphics.Canvas;
27import android.graphics.drawable.BitmapDrawable;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000028import android.graphics.drawable.Drawable;
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +010029import android.os.LocaleList;
Jan Althaus0d9fbb92017-11-28 12:19:33 +010030import android.os.Parcel;
31import android.os.Parcelable;
Jan Althausbbe43df2017-11-30 15:01:40 +010032import android.util.ArrayMap;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000033import android.view.View.OnClickListener;
34import android.view.textclassifier.TextClassifier.EntityType;
35
36import com.android.internal.util.Preconditions;
37
Jan Althaus92d76832017-09-27 18:14:35 +020038import java.util.ArrayList;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000039import java.util.List;
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000040import java.util.Locale;
Jan Althausbbe43df2017-11-30 15:01:40 +010041import java.util.Map;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000042
43/**
44 * Information for generating a widget to handle classified text.
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010045 *
46 * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may
Abodunrinwa Tokiba385622017-11-29 19:30:32 +000047 * be used to build a widget that can be used to act on classified text. There is the concept of a
48 * <i>primary action</i> and other <i>secondary actions</i>.
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010049 *
50 * <p>e.g. building a view that, when clicked, shares the classified text with the preferred app:
51 *
52 * <pre>{@code
53 * // Called preferably outside the UiThread.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000054 * TextClassification classification = textClassifier.classifyText(allText, 10, 25);
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010055 *
56 * // Called on the UiThread.
57 * Button button = new Button(context);
58 * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null);
59 * button.setText(classification.getLabel());
Jan Althaus0d9fbb92017-11-28 12:19:33 +010060 * button.setOnClickListener(v -> context.startActivity(classification.getIntent()));
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010061 * }</pre>
62 *
63 * <p>e.g. starting an action mode with menu items that can handle the classified text:
64 *
65 * <pre>{@code
66 * // Called preferably outside the UiThread.
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000067 * final TextClassification classification = textClassifier.classifyText(allText, 10, 25);
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010068 *
69 * // Called on the UiThread.
70 * view.startActionMode(new ActionMode.Callback() {
71 *
72 * public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Abodunrinwa Tokiba385622017-11-29 19:30:32 +000073 * // Add the "primary" action.
74 * if (thisAppHasPermissionToInvokeIntent(classification.getIntent())) {
75 * menu.add(Menu.NONE, 0, 20, classification.getLabel())
76 * .setIcon(classification.getIcon())
77 * .setIntent(classification.getIntent());
78 * }
79 * // Add the "secondary" actions.
80 * for (int i = 0; i < classification.getSecondaryActionsCount(); i++) {
81 * if (thisAppHasPermissionToInvokeIntent(classification.getSecondaryIntent(i))) {
82 * menu.add(Menu.NONE, i + 1, 20, classification.getSecondaryLabel(i))
83 * .setIcon(classification.getSecondaryIcon(i))
84 * .setIntent(classification.getSecondaryIntent(i));
Abodunrinwa Toki33ff2002017-10-24 00:49:27 +010085 * }
86 * }
87 * return true;
88 * }
89 *
90 * public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
91 * context.startActivity(item.getIntent());
92 * return true;
93 * }
94 *
95 * ...
96 * });
97 * }</pre>
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +000098 */
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +010099public final class TextClassification {
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000100
101 /**
102 * @hide
103 */
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100104 static final TextClassification EMPTY = new TextClassification.Builder().build();
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000105
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100106 // TODO(toki): investigate a way to derive this based on device properties.
107 private static final int MAX_PRIMARY_ICON_SIZE = 192;
108 private static final int MAX_SECONDARY_ICON_SIZE = 144;
109
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000110 @NonNull private final String mText;
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000111 @Nullable private final Drawable mPrimaryIcon;
112 @Nullable private final String mPrimaryLabel;
113 @Nullable private final Intent mPrimaryIntent;
114 @Nullable private final OnClickListener mPrimaryOnClickListener;
115 @NonNull private final List<Drawable> mSecondaryIcons;
116 @NonNull private final List<String> mSecondaryLabels;
117 @NonNull private final List<Intent> mSecondaryIntents;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100118 @NonNull private final EntityConfidence mEntityConfidence;
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000119 @NonNull private final String mSignature;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000120
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100121 private TextClassification(
Abodunrinwa Tokid44286f2017-07-12 19:38:54 +0100122 @Nullable String text,
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000123 @Nullable Drawable primaryIcon,
124 @Nullable String primaryLabel,
125 @Nullable Intent primaryIntent,
126 @Nullable OnClickListener primaryOnClickListener,
127 @NonNull List<Drawable> secondaryIcons,
128 @NonNull List<String> secondaryLabels,
129 @NonNull List<Intent> secondaryIntents,
Jan Althausbbe43df2017-11-30 15:01:40 +0100130 @NonNull Map<String, Float> entityConfidence,
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000131 @NonNull String signature) {
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000132 Preconditions.checkArgument(secondaryLabels.size() == secondaryIntents.size());
133 Preconditions.checkArgument(secondaryIcons.size() == secondaryIntents.size());
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000134 mText = text;
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000135 mPrimaryIcon = primaryIcon;
136 mPrimaryLabel = primaryLabel;
137 mPrimaryIntent = primaryIntent;
138 mPrimaryOnClickListener = primaryOnClickListener;
139 mSecondaryIcons = secondaryIcons;
140 mSecondaryLabels = secondaryLabels;
141 mSecondaryIntents = secondaryIntents;
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100142 mEntityConfidence = new EntityConfidence(entityConfidence);
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000143 mSignature = signature;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000144 }
145
146 /**
147 * Gets the classified text.
148 */
Abodunrinwa Tokid44286f2017-07-12 19:38:54 +0100149 @Nullable
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000150 public String getText() {
151 return mText;
152 }
153
154 /**
155 * Returns the number of entities found in the classified text.
156 */
157 @IntRange(from = 0)
158 public int getEntityCount() {
Jan Althausbbe43df2017-11-30 15:01:40 +0100159 return mEntityConfidence.getEntities().size();
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000160 }
161
162 /**
163 * Returns the entity at the specified index. Entities are ordered from high confidence
164 * to low confidence.
165 *
166 * @throws IndexOutOfBoundsException if the specified index is out of range.
167 * @see #getEntityCount() for the number of entities available.
168 */
169 @NonNull
170 public @EntityType String getEntity(int index) {
Jan Althausbbe43df2017-11-30 15:01:40 +0100171 return mEntityConfidence.getEntities().get(index);
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000172 }
173
174 /**
175 * Returns the confidence score for the specified entity. The value ranges from
176 * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
177 * classified text.
178 */
179 @FloatRange(from = 0.0, to = 1.0)
180 public float getConfidenceScore(@EntityType String entity) {
181 return mEntityConfidence.getConfidenceScore(entity);
182 }
183
184 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000185 * Returns the number of <i>secondary</i> actions that are available to act on the classified
186 * text.
187 *
188 * <p><strong>Note: </strong> that there may or may not be a <i>primary</i> action.
189 *
190 * @see #getSecondaryIntent(int)
191 * @see #getSecondaryLabel(int)
192 * @see #getSecondaryIcon(int)
Jan Althaus92d76832017-09-27 18:14:35 +0200193 */
194 @IntRange(from = 0)
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000195 public int getSecondaryActionsCount() {
196 return mSecondaryIntents.size();
Jan Althaus92d76832017-09-27 18:14:35 +0200197 }
198
199 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000200 * Returns one of the <i>secondary</i> icons that maybe rendered on a widget used to act on the
201 * classified text.
202 *
Jan Althaus92d76832017-09-27 18:14:35 +0200203 * @param index Index of the action to get the icon for.
204 * @throws IndexOutOfBoundsException if the specified index is out of range.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000205 * @see #getSecondaryActionsCount() for the number of actions available.
206 * @see #getSecondaryIntent(int)
207 * @see #getSecondaryLabel(int)
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000208 * @see #getIcon()
Jan Althaus92d76832017-09-27 18:14:35 +0200209 */
210 @Nullable
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000211 public Drawable getSecondaryIcon(int index) {
212 return mSecondaryIcons.get(index);
Jan Althaus92d76832017-09-27 18:14:35 +0200213 }
214
215 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000216 * Returns an icon for the <i>primary</i> intent that may be rendered on a widget used to act
217 * on the classified text.
218 *
219 * @see #getSecondaryIcon(int)
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000220 */
221 @Nullable
222 public Drawable getIcon() {
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000223 return mPrimaryIcon;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000224 }
225
226 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000227 * Returns one of the <i>secondary</i> labels that may be rendered on a widget used to act on
228 * the classified text.
229 *
Jan Althaus92d76832017-09-27 18:14:35 +0200230 * @param index Index of the action to get the label for.
231 * @throws IndexOutOfBoundsException if the specified index is out of range.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000232 * @see #getSecondaryActionsCount()
233 * @see #getSecondaryIntent(int)
234 * @see #getSecondaryIcon(int)
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000235 * @see #getLabel()
Jan Althaus92d76832017-09-27 18:14:35 +0200236 */
237 @Nullable
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000238 public CharSequence getSecondaryLabel(int index) {
239 return mSecondaryLabels.get(index);
Jan Althaus92d76832017-09-27 18:14:35 +0200240 }
241
242 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000243 * Returns a label for the <i>primary</i> intent that may be rendered on a widget used to act
244 * on the classified text.
245 *
246 * @see #getSecondaryLabel(int)
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000247 */
248 @Nullable
249 public CharSequence getLabel() {
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000250 return mPrimaryLabel;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000251 }
252
253 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000254 * Returns one of the <i>secondary</i> intents that may be fired to act on the classified text.
255 *
Jan Althaus92d76832017-09-27 18:14:35 +0200256 * @param index Index of the action to get the intent for.
257 * @throws IndexOutOfBoundsException if the specified index is out of range.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000258 * @see #getSecondaryActionsCount()
259 * @see #getSecondaryLabel(int)
260 * @see #getSecondaryIcon(int)
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000261 * @see #getIntent()
Jan Althaus92d76832017-09-27 18:14:35 +0200262 */
263 @Nullable
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000264 public Intent getSecondaryIntent(int index) {
265 return mSecondaryIntents.get(index);
Jan Althaus92d76832017-09-27 18:14:35 +0200266 }
267
268 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000269 * Returns the <i>primary</i> intent that may be fired to act on the classified text.
270 *
271 * @see #getSecondaryIntent(int)
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000272 */
273 @Nullable
274 public Intent getIntent() {
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000275 return mPrimaryIntent;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000276 }
277
278 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000279 * Returns the <i>primary</i> OnClickListener that may be triggered to act on the classified
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100280 * text. This field is not parcelable and will be null for all objects read from a parcel.
281 * Instead, call Context#startActivity(Intent) with the result of #getSecondaryIntent(int).
282 * Note that this may fail if the activity doesn't have permission to send the intent.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000283 */
284 @Nullable
285 public OnClickListener getOnClickListener() {
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000286 return mPrimaryOnClickListener;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000287 }
288
Abodunrinwa Toki54486c12017-04-19 21:02:36 +0100289 /**
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000290 * Returns the signature for this object.
291 * The TextClassifier that generates this object may use it as a way to internally identify
292 * this object.
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100293 */
294 @NonNull
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000295 public String getSignature() {
296 return mSignature;
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100297 }
298
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000299 @Override
300 public String toString() {
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000301 return String.format(Locale.US, "TextClassification {"
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000302 + "text=%s, entities=%s, "
303 + "primaryLabel=%s, secondaryLabels=%s, "
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000304 + "primaryIntent=%s, secondaryIntents=%s, "
305 + "signature=%s}",
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000306 mText, mEntityConfidence,
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000307 mPrimaryLabel, mSecondaryLabels,
308 mPrimaryIntent, mSecondaryIntents,
309 mSignature);
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000310 }
311
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100312 /** Helper for parceling via #ParcelableWrapper. */
313 private void writeToParcel(Parcel dest, int flags) {
314 dest.writeString(mText);
315 final Bitmap primaryIconBitmap = drawableToBitmap(mPrimaryIcon, MAX_PRIMARY_ICON_SIZE);
316 dest.writeInt(primaryIconBitmap != null ? 1 : 0);
317 if (primaryIconBitmap != null) {
318 primaryIconBitmap.writeToParcel(dest, flags);
319 }
320 dest.writeString(mPrimaryLabel);
321 dest.writeInt(mPrimaryIntent != null ? 1 : 0);
322 if (mPrimaryIntent != null) {
323 mPrimaryIntent.writeToParcel(dest, flags);
324 }
325 // mPrimaryOnClickListener is not parcelable.
326 dest.writeTypedList(drawablesToBitmaps(mSecondaryIcons, MAX_SECONDARY_ICON_SIZE));
327 dest.writeStringList(mSecondaryLabels);
328 dest.writeTypedList(mSecondaryIntents);
329 mEntityConfidence.writeToParcel(dest, flags);
330 dest.writeString(mSignature);
331 }
332
333 /** Helper for unparceling via #ParcelableWrapper. */
334 private TextClassification(Parcel in) {
335 mText = in.readString();
336 mPrimaryIcon = in.readInt() == 0
337 ? null : new BitmapDrawable(null, Bitmap.CREATOR.createFromParcel(in));
338 mPrimaryLabel = in.readString();
339 mPrimaryIntent = in.readInt() == 0 ? null : Intent.CREATOR.createFromParcel(in);
340 mPrimaryOnClickListener = null; // not parcelable
341 mSecondaryIcons = bitmapsToDrawables(in.createTypedArrayList(Bitmap.CREATOR));
342 mSecondaryLabels = in.createStringArrayList();
343 mSecondaryIntents = in.createTypedArrayList(Intent.CREATOR);
344 mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
345 mSignature = in.readString();
346 }
347
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000348 /**
349 * Creates an OnClickListener that starts an activity with the specified intent.
350 *
351 * @throws IllegalArgumentException if context or intent is null
352 * @hide
353 */
354 @NonNull
Abodunrinwa Toki9b4c82a2017-02-06 20:29:36 +0000355 public static OnClickListener createStartActivityOnClickListener(
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000356 @NonNull final Context context, @NonNull final Intent intent) {
357 Preconditions.checkArgument(context != null);
358 Preconditions.checkArgument(intent != null);
359 return v -> context.startActivity(intent);
360 }
361
362 /**
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100363 * Returns a Bitmap representation of the Drawable
364 *
365 * @param drawable The drawable to convert.
366 * @param maxDims The maximum edge length of the resulting bitmap (in pixels).
367 */
368 @Nullable
369 private static Bitmap drawableToBitmap(@Nullable Drawable drawable, int maxDims) {
370 if (drawable == null) {
371 return null;
372 }
373 final int actualWidth = Math.max(1, drawable.getIntrinsicWidth());
374 final int actualHeight = Math.max(1, drawable.getIntrinsicHeight());
375 final double scaleWidth = ((double) maxDims) / actualWidth;
376 final double scaleHeight = ((double) maxDims) / actualHeight;
377 final double scale = Math.min(1.0, Math.min(scaleWidth, scaleHeight));
378 final int width = (int) (actualWidth * scale);
379 final int height = (int) (actualHeight * scale);
380 if (drawable instanceof BitmapDrawable) {
381 final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
382 if (actualWidth != width || actualHeight != height) {
383 return Bitmap.createScaledBitmap(
384 bitmapDrawable.getBitmap(), width, height, /*filter=*/false);
385 } else {
386 return bitmapDrawable.getBitmap();
387 }
388 } else {
389 final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
390 final Canvas canvas = new Canvas(bitmap);
391 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
392 drawable.draw(canvas);
393 return bitmap;
394 }
395 }
396
397 /**
398 * Returns a list of drawables converted to Bitmaps
399 *
400 * @param drawables The drawables to convert.
401 * @param maxDims The maximum edge length of the resulting bitmaps (in pixels).
402 */
403 private static List<Bitmap> drawablesToBitmaps(List<Drawable> drawables, int maxDims) {
404 final List<Bitmap> bitmaps = new ArrayList<>(drawables.size());
405 for (Drawable drawable : drawables) {
406 bitmaps.add(drawableToBitmap(drawable, maxDims));
407 }
408 return bitmaps;
409 }
410
411 /** Returns a list of drawable wrappers for a list of bitmaps. */
412 private static List<Drawable> bitmapsToDrawables(List<Bitmap> bitmaps) {
413 final List<Drawable> drawables = new ArrayList<>(bitmaps.size());
414 for (Bitmap bitmap : bitmaps) {
415 if (bitmap != null) {
416 drawables.add(new BitmapDrawable(null, bitmap));
417 } else {
418 drawables.add(null);
419 }
420 }
421 return drawables;
422 }
423
424 /**
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100425 * Builder for building {@link TextClassification} objects.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000426 *
427 * <p>e.g.
428 *
429 * <pre>{@code
430 * TextClassification classification = new TextClassification.Builder()
431 * .setText(classifiedText)
432 * .setEntityType(TextClassifier.TYPE_EMAIL, 0.9)
433 * .setEntityType(TextClassifier.TYPE_OTHER, 0.1)
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100434 * .setPrimaryAction(intent, label, icon)
435 * .addSecondaryAction(intent1, label1, icon1)
436 * .addSecondaryAction(intent2, label2, icon2)
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000437 * .build();
438 * }</pre>
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000439 */
440 public static final class Builder {
441
442 @NonNull private String mText;
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000443 @NonNull private final List<Drawable> mSecondaryIcons = new ArrayList<>();
444 @NonNull private final List<String> mSecondaryLabels = new ArrayList<>();
445 @NonNull private final List<Intent> mSecondaryIntents = new ArrayList<>();
Jan Althausbbe43df2017-11-30 15:01:40 +0100446 @NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000447 @Nullable Drawable mPrimaryIcon;
448 @Nullable String mPrimaryLabel;
449 @Nullable Intent mPrimaryIntent;
450 @Nullable OnClickListener mPrimaryOnClickListener;
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000451 @NonNull private String mSignature = "";
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000452
453 /**
454 * Sets the classified text.
455 */
Abodunrinwa Tokid44286f2017-07-12 19:38:54 +0100456 public Builder setText(@Nullable String text) {
457 mText = text;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000458 return this;
459 }
460
461 /**
462 * Sets an entity type for the classification result and assigns a confidence score.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000463 * If a confidence score had already been set for the specified entity type, this will
464 * override that score.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000465 *
466 * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
467 * 0 implies the entity does not exist for the classified text.
468 * Values greater than 1 are clamped to 1.
469 */
470 public Builder setEntityType(
471 @NonNull @EntityType String type,
Abodunrinwa Tokid44286f2017-07-12 19:38:54 +0100472 @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
Jan Althausbbe43df2017-11-30 15:01:40 +0100473 mEntityConfidence.put(type, confidenceScore);
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000474 return this;
475 }
476
477 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000478 * Adds an <i>secondary</i> action that may be performed on the classified text.
479 * Secondary actions are in addition to the <i>primary</i> action which may or may not
480 * exist.
481 *
482 * <p>The label and icon are used for rendering of widgets that offer the intent.
483 * Actions should be added in order of priority.
484 *
485 * <p><stong>Note: </stong> If all input parameters are set to null, this method will be a
486 * no-op.
487 *
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100488 * @see #setPrimaryAction(Intent, String, Drawable)
Jan Althaus92d76832017-09-27 18:14:35 +0200489 */
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000490 public Builder addSecondaryAction(
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100491 @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) {
492 if (intent != null || label != null || icon != null) {
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000493 mSecondaryIntents.add(intent);
494 mSecondaryLabels.add(label);
495 mSecondaryIcons.add(icon);
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000496 }
Jan Althaus92d76832017-09-27 18:14:35 +0200497 return this;
498 }
499
500 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000501 * Removes all the <i>secondary</i> actions.
Jan Althaus92d76832017-09-27 18:14:35 +0200502 */
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000503 public Builder clearSecondaryActions() {
504 mSecondaryIntents.clear();
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000505 mSecondaryLabels.clear();
506 mSecondaryIcons.clear();
Jan Althaus92d76832017-09-27 18:14:35 +0200507 return this;
508 }
509
510 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000511 * Sets the <i>primary</i> action that may be performed on the classified text. This is
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100512 * equivalent to calling {@code setIntent(intent).setLabel(label).setIcon(icon)}.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000513 *
514 * <p><strong>Note: </strong>If all input parameters are null, there will be no
515 * <i>primary</i> action but there may still be <i>secondary</i> actions.
516 *
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100517 * @see #addSecondaryAction(Intent, String, Drawable)
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000518 */
519 public Builder setPrimaryAction(
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100520 @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) {
521 return setIntent(intent).setLabel(label).setIcon(icon);
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000522 }
523
524 /**
525 * Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act
526 * on the classified text.
527 *
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100528 * @see #setPrimaryAction(Intent, String, Drawable)
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000529 */
530 public Builder setIcon(@Nullable Drawable icon) {
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000531 mPrimaryIcon = icon;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000532 return this;
533 }
534
535 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000536 * Sets the label for the <i>primary</i> action that may be rendered on a widget used to
537 * act on the classified text.
538 *
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100539 * @see #setPrimaryAction(Intent, String, Drawable)
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000540 */
541 public Builder setLabel(@Nullable String label) {
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000542 mPrimaryLabel = label;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000543 return this;
544 }
545
546 /**
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000547 * Sets the intent for the <i>primary</i> action that may be fired to act on the classified
548 * text.
549 *
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100550 * @see #setPrimaryAction(Intent, String, Drawable)
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000551 */
552 public Builder setIntent(@Nullable Intent intent) {
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000553 mPrimaryIntent = intent;
554 return this;
555 }
556
557 /**
558 * Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100559 * the classified text. This field is not parcelable and will always be null when the
560 * object is read from a parcel.
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000561 */
562 public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
563 mPrimaryOnClickListener = onClickListener;
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000564 return this;
565 }
566
567 /**
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000568 * Sets a signature for the TextClassification object.
569 * The TextClassifier that generates the TextClassification object may use it as a way to
570 * internally identify the TextClassification object.
Abodunrinwa Toki54486c12017-04-19 21:02:36 +0100571 */
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000572 public Builder setSignature(@NonNull String signature) {
573 mSignature = Preconditions.checkNotNull(signature);
Abodunrinwa Toki692b1962017-08-15 15:05:11 +0100574 return this;
575 }
576
577 /**
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100578 * Builds and returns a {@link TextClassification} object.
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000579 */
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100580 public TextClassification build() {
581 return new TextClassification(
Abodunrinwa Tokiba385622017-11-29 19:30:32 +0000582 mText,
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100583 mPrimaryIcon, mPrimaryLabel, mPrimaryIntent, mPrimaryOnClickListener,
584 mSecondaryIcons, mSecondaryLabels, mSecondaryIntents,
Abodunrinwa Toki008f3872017-11-27 19:32:35 +0000585 mEntityConfidence, mSignature);
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000586 }
587 }
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100588
589 /**
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000590 * Optional input parameters for generating TextClassification.
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100591 */
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100592 public static final class Options implements Parcelable {
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100593
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100594 private @Nullable LocaleList mDefaultLocales;
595
596 public Options() {}
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100597
598 /**
599 * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
600 * the provided text. If no locale preferences exist, set this to null or an empty
601 * locale list.
602 */
603 public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
604 mDefaultLocales = defaultLocales;
605 return this;
606 }
607
608 /**
609 * @return ordered list of locale preferences that can be used to disambiguate
610 * the provided text.
611 */
612 @Nullable
613 public LocaleList getDefaultLocales() {
614 return mDefaultLocales;
615 }
Jan Althaus0d9fbb92017-11-28 12:19:33 +0100616
617 @Override
618 public int describeContents() {
619 return 0;
620 }
621
622 @Override
623 public void writeToParcel(Parcel dest, int flags) {
624 dest.writeInt(mDefaultLocales != null ? 1 : 0);
625 if (mDefaultLocales != null) {
626 mDefaultLocales.writeToParcel(dest, flags);
627 }
628 }
629
630 public static final Parcelable.Creator<Options> CREATOR =
631 new Parcelable.Creator<Options>() {
632 @Override
633 public Options createFromParcel(Parcel in) {
634 return new Options(in);
635 }
636
637 @Override
638 public Options[] newArray(int size) {
639 return new Options[size];
640 }
641 };
642
643 private Options(Parcel in) {
644 if (in.readInt() > 0) {
645 mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
646 }
647 }
648 }
649
650 /**
651 * Parcelable wrapper for TextClassification objects.
652 * @hide
653 */
654 public static final class ParcelableWrapper implements Parcelable {
655
656 @NonNull private TextClassification mTextClassification;
657
658 public ParcelableWrapper(@NonNull TextClassification textClassification) {
659 Preconditions.checkNotNull(textClassification);
660 mTextClassification = textClassification;
661 }
662
663 @NonNull
664 public TextClassification getTextClassification() {
665 return mTextClassification;
666 }
667
668 @Override
669 public int describeContents() {
670 return 0;
671 }
672
673 @Override
674 public void writeToParcel(Parcel dest, int flags) {
675 mTextClassification.writeToParcel(dest, flags);
676 }
677
678 public static final Parcelable.Creator<ParcelableWrapper> CREATOR =
679 new Parcelable.Creator<ParcelableWrapper>() {
680 @Override
681 public ParcelableWrapper createFromParcel(Parcel in) {
682 return new ParcelableWrapper(new TextClassification(in));
683 }
684
685 @Override
686 public ParcelableWrapper[] newArray(int size) {
687 return new ParcelableWrapper[size];
688 }
689 };
690
Abodunrinwa Toki2b6020f2017-10-28 02:28:45 +0100691 }
Abodunrinwa Tokif001fef2017-01-04 23:51:42 +0000692}