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