1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view.textclassifier; 18 19 import android.annotation.FloatRange; 20 import android.annotation.IntDef; 21 import android.annotation.IntRange; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.ActivityOptions; 25 import android.app.PendingIntent; 26 import android.app.RemoteAction; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.res.Resources; 30 import android.graphics.BitmapFactory; 31 import android.graphics.drawable.AdaptiveIconDrawable; 32 import android.graphics.drawable.BitmapDrawable; 33 import android.graphics.drawable.Drawable; 34 import android.graphics.drawable.Icon; 35 import android.os.Bundle; 36 import android.os.LocaleList; 37 import android.os.Parcel; 38 import android.os.Parcelable; 39 import android.text.SpannedString; 40 import android.util.ArrayMap; 41 import android.view.View.OnClickListener; 42 import android.view.textclassifier.TextClassifier.EntityType; 43 import android.view.textclassifier.TextClassifier.Utils; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.internal.util.Preconditions; 47 48 import java.lang.annotation.Retention; 49 import java.lang.annotation.RetentionPolicy; 50 import java.time.ZonedDateTime; 51 import java.util.ArrayList; 52 import java.util.Collection; 53 import java.util.Collections; 54 import java.util.List; 55 import java.util.Locale; 56 import java.util.Map; 57 import java.util.Objects; 58 59 /** 60 * Information for generating a widget to handle classified text. 61 * 62 * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may 63 * 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>. 65 * 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. 70 * TextClassification classification = textClassifier.classifyText(allText, 10, 25); 71 * 72 * // Called on the UiThread. 73 * Button button = new Button(context); 74 * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null); 75 * button.setText(classification.getLabel()); 76 * button.setOnClickListener(v -> classification.getActions().get(0).getActionIntent().send()); 77 * }</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. 83 * final TextClassification classification = textClassifier.classifyText(allText, 10, 25); 84 * 85 * // Called on the UiThread. 86 * view.startActionMode(new ActionMode.Callback() { 87 * 88 * public boolean onCreateActionMode(ActionMode mode, Menu menu) { 89 * 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()); 93 * } 94 * return true; 95 * } 96 * 97 * public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 98 * classification.getActions().get(item.getItemId()).getActionIntent().send(); 99 * return true; 100 * } 101 * 102 * ... 103 * }); 104 * }</pre> 105 */ 106 public final class TextClassification implements Parcelable { 107 108 /** 109 * @hide 110 */ 111 public static final TextClassification EMPTY = new TextClassification.Builder().build(); 112 113 private static final String LOG_TAG = "TextClassification"; 114 // TODO(toki): investigate a way to derive this based on device properties. 115 private static final int MAX_LEGACY_ICON_SIZE = 192; 116 117 @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 125 @NonNull private final String mText; 126 @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; 131 @NonNull private final EntityConfidence mEntityConfidence; 132 @Nullable private final String mId; 133 @NonNull private final Bundle mExtras; 134 TextClassification( @ullable String text, @Nullable Drawable legacyIcon, @Nullable String legacyLabel, @Nullable Intent legacyIntent, @Nullable OnClickListener legacyOnClickListener, @NonNull List<RemoteAction> actions, @NonNull EntityConfidence entityConfidence, @Nullable String id, @NonNull Bundle extras)135 private TextClassification( 136 @Nullable String text, 137 @Nullable Drawable legacyIcon, 138 @Nullable String legacyLabel, 139 @Nullable Intent legacyIntent, 140 @Nullable OnClickListener legacyOnClickListener, 141 @NonNull List<RemoteAction> actions, 142 @NonNull EntityConfidence entityConfidence, 143 @Nullable String id, 144 @NonNull Bundle extras) { 145 mText = text; 146 mLegacyIcon = legacyIcon; 147 mLegacyLabel = legacyLabel; 148 mLegacyIntent = legacyIntent; 149 mLegacyOnClickListener = legacyOnClickListener; 150 mActions = Collections.unmodifiableList(actions); 151 mEntityConfidence = Objects.requireNonNull(entityConfidence); 152 mId = id; 153 mExtras = extras; 154 } 155 156 /** 157 * Gets the classified text. 158 */ 159 @Nullable getText()160 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) getEntityCount()168 public int getEntityCount() { 169 return mEntityConfidence.getEntities().size(); 170 } 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 getEntity(int index)180 public @EntityType String getEntity(int index) { 181 return mEntityConfidence.getEntities().get(index); 182 } 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) getConfidenceScore(@ntityType String entity)190 public float getConfidenceScore(@EntityType String entity) { 191 return mEntityConfidence.getConfidenceScore(entity); 192 } 193 194 /** 195 * 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. 197 */ getActions()198 public List<RemoteAction> getActions() { 199 return mActions; 200 } 201 202 /** 203 * Returns an icon that may be rendered on a widget used to act on the classified text. 204 * 205 * <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 * 208 * @deprecated Use {@link #getActions()} instead. 209 */ 210 @Deprecated 211 @Nullable getIcon()212 public Drawable getIcon() { 213 return mLegacyIcon; 214 } 215 216 /** 217 * Returns a label that may be rendered on a widget used to act on the classified text. 218 * 219 * <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 * 222 * @deprecated Use {@link #getActions()} instead. 223 */ 224 @Deprecated 225 @Nullable getLabel()226 public CharSequence getLabel() { 227 return mLegacyLabel; 228 } 229 230 /** 231 * Returns an intent that may be fired to act on the classified text. 232 * 233 * <p><strong>NOTE: </strong>This field is not parcelled and will always return null when this 234 * object is read from a parcel. 235 * 236 * @deprecated Use {@link #getActions()} instead. 237 */ 238 @Deprecated 239 @Nullable getIntent()240 public Intent getIntent() { 241 return mLegacyIntent; 242 } 243 244 /** 245 * 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. 249 * 250 * @deprecated Use {@link #getActions()} instead. 251 */ 252 @Nullable getOnClickListener()253 public OnClickListener getOnClickListener() { 254 return mLegacyOnClickListener; 255 } 256 257 /** 258 * Returns the id, if one exists, for this object. 259 */ 260 @Nullable getId()261 public String getId() { 262 return mId; 263 } 264 265 /** 266 * Returns the extended data. 267 * 268 * <p><b>NOTE: </b>Do not modify this bundle. 269 */ 270 @NonNull getExtras()271 public Bundle getExtras() { 272 return mExtras; 273 } 274 275 /** @hide */ toBuilder()276 public Builder toBuilder() { 277 return new Builder() 278 .setId(mId) 279 .setText(mText) 280 .addActions(mActions) 281 .setEntityConfidence(mEntityConfidence) 282 .setIcon(mLegacyIcon) 283 .setLabel(mLegacyLabel) 284 .setIntent(mLegacyIntent) 285 .setOnClickListener(mLegacyOnClickListener) 286 .setExtras(mExtras); 287 } 288 289 @Override toString()290 public String toString() { 291 return String.format(Locale.US, 292 "TextClassification {text=%s, entities=%s, actions=%s, id=%s, extras=%s}", 293 mText, mEntityConfidence, mActions, mId, mExtras); 294 } 295 296 /** 297 * Creates an OnClickListener that triggers the specified PendingIntent. 298 * 299 * @hide 300 */ createIntentOnClickListener(@onNull final PendingIntent intent)301 public static OnClickListener createIntentOnClickListener(@NonNull final PendingIntent intent) { 302 Objects.requireNonNull(intent); 303 return v -> { 304 try { 305 intent.send(ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode( 306 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle()); 307 } catch (PendingIntent.CanceledException e) { 308 Log.e(LOG_TAG, "Error sending PendingIntent", e); 309 } 310 }; 311 } 312 313 /** 314 * Creates a PendingIntent for the specified intent. 315 * Returns null if the intent is not supported for the specified context. 316 * 317 * @throws IllegalArgumentException if context or intent is null 318 * @hide 319 */ 320 public static PendingIntent createPendingIntent( 321 @NonNull final Context context, @NonNull final Intent intent, int requestCode) { 322 return PendingIntent.getActivity( 323 context, requestCode, intent, 324 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 325 } 326 327 /** 328 * Builder for building {@link TextClassification} objects. 329 * 330 * <p>e.g. 331 * 332 * <pre>{@code 333 * TextClassification classification = new TextClassification.Builder() 334 * .setText(classifiedText) 335 * .setEntityType(TextClassifier.TYPE_EMAIL, 0.9) 336 * .setEntityType(TextClassifier.TYPE_OTHER, 0.1) 337 * .addAction(remoteAction1) 338 * .addAction(remoteAction2) 339 * .build(); 340 * }</pre> 341 */ 342 public static final class Builder { 343 344 @NonNull private final List<RemoteAction> mActions = new ArrayList<>(); 345 @NonNull private final Map<String, Float> mTypeScoreMap = new ArrayMap<>(); 346 @Nullable private String mText; 347 @Nullable private Drawable mLegacyIcon; 348 @Nullable private String mLegacyLabel; 349 @Nullable private Intent mLegacyIntent; 350 @Nullable private OnClickListener mLegacyOnClickListener; 351 @Nullable private String mId; 352 @Nullable private Bundle mExtras; 353 354 /** 355 * Sets the classified text. 356 */ 357 @NonNull 358 public Builder setText(@Nullable String text) { 359 mText = text; 360 return this; 361 } 362 363 /** 364 * Sets an entity type for the classification result and assigns a confidence score. 365 * If a confidence score had already been set for the specified entity type, this will 366 * override that score. 367 * 368 * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence). 369 * 0 implies the entity does not exist for the classified text. 370 * Values greater than 1 are clamped to 1. 371 */ 372 @NonNull 373 public Builder setEntityType( 374 @NonNull @EntityType String type, 375 @FloatRange(from = 0.0, to = 1.0) float confidenceScore) { 376 mTypeScoreMap.put(type, confidenceScore); 377 return this; 378 } 379 380 Builder setEntityConfidence(EntityConfidence scores) { 381 mTypeScoreMap.clear(); 382 mTypeScoreMap.putAll(scores.toMap()); 383 return this; 384 } 385 386 /** @hide */ 387 public Builder clearEntityTypes() { 388 mTypeScoreMap.clear(); 389 return this; 390 } 391 392 /** 393 * Adds an action that may be performed on the classified text. Actions should be added in 394 * order of likelihood that the user will use them, with the most likely action being added 395 * first. 396 */ 397 @NonNull 398 public Builder addAction(@NonNull RemoteAction action) { 399 Preconditions.checkArgument(action != null); 400 mActions.add(action); 401 return this; 402 } 403 404 /** @hide */ 405 public Builder addActions(Collection<RemoteAction> actions) { 406 Objects.requireNonNull(actions); 407 mActions.addAll(actions); 408 return this; 409 } 410 411 /** @hide */ 412 public Builder clearActions() { 413 mActions.clear(); 414 return this; 415 } 416 417 /** 418 * Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act 419 * on the classified text. 420 * 421 * <p><strong>NOTE: </strong>This field is not parcelled. If read from a parcel, the 422 * returned icon represents the icon of the first {@link RemoteAction} (if one exists). 423 * 424 * @deprecated Use {@link #addAction(RemoteAction)} instead. 425 */ 426 @Deprecated 427 @NonNull 428 public Builder setIcon(@Nullable Drawable icon) { 429 mLegacyIcon = icon; 430 return this; 431 } 432 433 /** 434 * Sets the label for the <i>primary</i> action that may be rendered on a widget used to 435 * act on the classified text. 436 * 437 * <p><strong>NOTE: </strong>This field is not parcelled. If read from a parcel, the 438 * returned label represents the label of the first {@link RemoteAction} (if one exists). 439 * 440 * @deprecated Use {@link #addAction(RemoteAction)} instead. 441 */ 442 @Deprecated 443 @NonNull 444 public Builder setLabel(@Nullable String label) { 445 mLegacyLabel = label; 446 return this; 447 } 448 449 /** 450 * Sets the intent for the <i>primary</i> action that may be fired to act on the classified 451 * text. 452 * 453 * <p><strong>NOTE: </strong>This field is not parcelled. 454 * 455 * @deprecated Use {@link #addAction(RemoteAction)} instead. 456 */ 457 @Deprecated 458 @NonNull 459 public Builder setIntent(@Nullable Intent intent) { 460 mLegacyIntent = intent; 461 return this; 462 } 463 464 /** 465 * Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on 466 * the classified text. 467 * 468 * <p><strong>NOTE: </strong>This field is not parcelable. If read from a parcel, the 469 * returned OnClickListener represents the first {@link RemoteAction} (if one exists). 470 * 471 * @deprecated Use {@link #addAction(RemoteAction)} instead. 472 */ 473 @Deprecated 474 @NonNull 475 public Builder setOnClickListener(@Nullable OnClickListener onClickListener) { 476 mLegacyOnClickListener = onClickListener; 477 return this; 478 } 479 480 /** 481 * Sets an id for the TextClassification object. 482 */ 483 @NonNull 484 public Builder setId(@Nullable String id) { 485 mId = id; 486 return this; 487 } 488 489 /** 490 * Sets the extended data. 491 */ 492 @NonNull 493 public Builder setExtras(@Nullable Bundle extras) { 494 mExtras = extras; 495 return this; 496 } 497 498 /** 499 * Builds and returns a {@link TextClassification} object. 500 */ 501 @NonNull 502 public TextClassification build() { 503 EntityConfidence entityConfidence = new EntityConfidence(mTypeScoreMap); 504 return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent, 505 mLegacyOnClickListener, mActions, entityConfidence, mId, 506 mExtras == null ? Bundle.EMPTY : mExtras); 507 } 508 } 509 510 /** 511 * A request object for generating TextClassification. 512 */ 513 public static final class Request implements Parcelable { 514 515 private final CharSequence mText; 516 private final int mStartIndex; 517 private final int mEndIndex; 518 @Nullable private final LocaleList mDefaultLocales; 519 @Nullable private final ZonedDateTime mReferenceTime; 520 @NonNull private final Bundle mExtras; 521 @Nullable private SystemTextClassifierMetadata mSystemTcMetadata; 522 523 private Request( 524 CharSequence text, 525 int startIndex, 526 int endIndex, 527 LocaleList defaultLocales, 528 ZonedDateTime referenceTime, 529 Bundle extras) { 530 mText = text; 531 mStartIndex = startIndex; 532 mEndIndex = endIndex; 533 mDefaultLocales = defaultLocales; 534 mReferenceTime = referenceTime; 535 mExtras = extras; 536 } 537 538 /** 539 * Returns the text providing context for the text to classify (which is specified 540 * by the sub sequence starting at startIndex and ending at endIndex) 541 */ 542 @NonNull 543 public CharSequence getText() { 544 return mText; 545 } 546 547 /** 548 * Returns start index of the text to classify. 549 */ 550 @IntRange(from = 0) 551 public int getStartIndex() { 552 return mStartIndex; 553 } 554 555 /** 556 * Returns end index of the text to classify. 557 */ 558 @IntRange(from = 0) 559 public int getEndIndex() { 560 return mEndIndex; 561 } 562 563 /** 564 * @return ordered list of locale preferences that can be used to disambiguate 565 * the provided text. 566 */ 567 @Nullable 568 public LocaleList getDefaultLocales() { 569 return mDefaultLocales; 570 } 571 572 /** 573 * @return reference time based on which relative dates (e.g. "tomorrow") should be 574 * interpreted. 575 */ 576 @Nullable 577 public ZonedDateTime getReferenceTime() { 578 return mReferenceTime; 579 } 580 581 /** 582 * Returns the name of the package that sent this request. 583 * This returns {@code null} if no calling package name is set. 584 */ 585 @Nullable 586 public String getCallingPackageName() { 587 return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null; 588 } 589 590 /** 591 * Sets the information about the {@link SystemTextClassifier} that sent this request. 592 * 593 * @hide 594 */ 595 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 596 public void setSystemTextClassifierMetadata( 597 @Nullable SystemTextClassifierMetadata systemTcMetadata) { 598 mSystemTcMetadata = systemTcMetadata; 599 } 600 601 /** 602 * Returns the information about the {@link SystemTextClassifier} that sent this request. 603 * 604 * @hide 605 */ 606 @Nullable 607 public SystemTextClassifierMetadata getSystemTextClassifierMetadata() { 608 return mSystemTcMetadata; 609 } 610 611 /** 612 * Returns the extended data. 613 * 614 * <p><b>NOTE: </b>Do not modify this bundle. 615 */ 616 @NonNull 617 public Bundle getExtras() { 618 return mExtras; 619 } 620 621 /** 622 * A builder for building TextClassification requests. 623 */ 624 public static final class Builder { 625 626 private final CharSequence mText; 627 private final int mStartIndex; 628 private final int mEndIndex; 629 private Bundle mExtras; 630 631 @Nullable private LocaleList mDefaultLocales; 632 @Nullable private ZonedDateTime mReferenceTime; 633 634 /** 635 * @param text text providing context for the text to classify (which is specified 636 * by the sub sequence starting at startIndex and ending at endIndex) 637 * @param startIndex start index of the text to classify 638 * @param endIndex end index of the text to classify 639 */ 640 public Builder( 641 @NonNull CharSequence text, 642 @IntRange(from = 0) int startIndex, 643 @IntRange(from = 0) int endIndex) { 644 Utils.checkArgument(text, startIndex, endIndex); 645 mText = text; 646 mStartIndex = startIndex; 647 mEndIndex = endIndex; 648 } 649 650 /** 651 * @param defaultLocales ordered list of locale preferences that may be used to 652 * disambiguate the provided text. If no locale preferences exist, set this to null 653 * or an empty locale list. 654 * 655 * @return this builder 656 */ 657 @NonNull 658 public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) { 659 mDefaultLocales = defaultLocales; 660 return this; 661 } 662 663 /** 664 * @param referenceTime reference time based on which relative dates (e.g. "tomorrow" 665 * should be interpreted. This should usually be the time when the text was 666 * originally composed. If no reference time is set, now is used. 667 * 668 * @return this builder 669 */ 670 @NonNull 671 public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) { 672 mReferenceTime = referenceTime; 673 return this; 674 } 675 676 /** 677 * Sets the extended data. 678 * 679 * @return this builder 680 */ 681 @NonNull 682 public Builder setExtras(@Nullable Bundle extras) { 683 mExtras = extras; 684 return this; 685 } 686 687 /** 688 * Builds and returns the request object. 689 */ 690 @NonNull 691 public Request build() { 692 return new Request(new SpannedString(mText), mStartIndex, mEndIndex, 693 mDefaultLocales, mReferenceTime, 694 mExtras == null ? Bundle.EMPTY : mExtras); 695 } 696 } 697 698 @Override 699 public int describeContents() { 700 return 0; 701 } 702 703 @Override 704 public void writeToParcel(Parcel dest, int flags) { 705 dest.writeCharSequence(mText); 706 dest.writeInt(mStartIndex); 707 dest.writeInt(mEndIndex); 708 dest.writeParcelable(mDefaultLocales, flags); 709 dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString()); 710 dest.writeBundle(mExtras); 711 dest.writeParcelable(mSystemTcMetadata, flags); 712 } 713 714 private static Request readFromParcel(Parcel in) { 715 final CharSequence text = in.readCharSequence(); 716 final int startIndex = in.readInt(); 717 final int endIndex = in.readInt(); 718 final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class); 719 final String referenceTimeString = in.readString(); 720 final ZonedDateTime referenceTime = referenceTimeString == null 721 ? null : ZonedDateTime.parse(referenceTimeString); 722 final Bundle extras = in.readBundle(); 723 final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class); 724 725 final Request request = new Request(text, startIndex, endIndex, 726 defaultLocales, referenceTime, extras); 727 request.setSystemTextClassifierMetadata(systemTcMetadata); 728 return request; 729 } 730 731 public static final @android.annotation.NonNull Parcelable.Creator<Request> CREATOR = 732 new Parcelable.Creator<Request>() { 733 @Override 734 public Request createFromParcel(Parcel in) { 735 return readFromParcel(in); 736 } 737 738 @Override 739 public Request[] newArray(int size) { 740 return new Request[size]; 741 } 742 }; 743 } 744 745 @Override 746 public int describeContents() { 747 return 0; 748 } 749 750 @Override 751 public void writeToParcel(Parcel dest, int flags) { 752 dest.writeString(mText); 753 // NOTE: legacy fields are not parcelled. 754 dest.writeTypedList(mActions); 755 mEntityConfidence.writeToParcel(dest, flags); 756 dest.writeString(mId); 757 dest.writeBundle(mExtras); 758 } 759 760 public static final @android.annotation.NonNull Parcelable.Creator<TextClassification> CREATOR = 761 new Parcelable.Creator<TextClassification>() { 762 @Override 763 public TextClassification createFromParcel(Parcel in) { 764 return new TextClassification(in); 765 } 766 767 @Override 768 public TextClassification[] newArray(int size) { 769 return new TextClassification[size]; 770 } 771 }; 772 773 private TextClassification(Parcel in) { 774 mText = in.readString(); 775 mActions = in.createTypedArrayList(RemoteAction.CREATOR); 776 if (!mActions.isEmpty()) { 777 final RemoteAction action = mActions.get(0); 778 mLegacyIcon = maybeLoadDrawable(action.getIcon()); 779 mLegacyLabel = action.getTitle().toString(); 780 mLegacyOnClickListener = createIntentOnClickListener(mActions.get(0).getActionIntent()); 781 } else { 782 mLegacyIcon = null; 783 mLegacyLabel = null; 784 mLegacyOnClickListener = null; 785 } 786 mLegacyIntent = null; // mLegacyIntent is not parcelled. 787 mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); 788 mId = in.readString(); 789 mExtras = in.readBundle(); 790 } 791 792 // Best effort attempt to try to load a drawable from the provided icon. 793 @Nullable 794 private static Drawable maybeLoadDrawable(Icon icon) { 795 if (icon == null) { 796 return null; 797 } 798 switch (icon.getType()) { 799 case Icon.TYPE_BITMAP: 800 return new BitmapDrawable(Resources.getSystem(), icon.getBitmap()); 801 case Icon.TYPE_ADAPTIVE_BITMAP: 802 return new AdaptiveIconDrawable(null, 803 new BitmapDrawable(Resources.getSystem(), icon.getBitmap())); 804 case Icon.TYPE_DATA: 805 return new BitmapDrawable( 806 Resources.getSystem(), 807 BitmapFactory.decodeByteArray( 808 icon.getDataBytes(), icon.getDataOffset(), icon.getDataLength())); 809 } 810 return null; 811 } 812 } 813