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, 322 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 323 } 324 325 /** 326 * Builder for building {@link TextClassification} objects. 327 * 328 * <p>e.g. 329 * 330 * <pre>{@code 331 * TextClassification classification = new TextClassification.Builder() 332 * .setText(classifiedText) 333 * .setEntityType(TextClassifier.TYPE_EMAIL, 0.9) 334 * .setEntityType(TextClassifier.TYPE_OTHER, 0.1) 335 * .addAction(remoteAction1) 336 * .addAction(remoteAction2) 337 * .build(); 338 * }</pre> 339 */ 340 public static final class Builder { 341 342 @NonNull private final List<RemoteAction> mActions = new ArrayList<>(); 343 @NonNull private final Map<String, Float> mTypeScoreMap = new ArrayMap<>(); 344 @Nullable private String mText; 345 @Nullable private Drawable mLegacyIcon; 346 @Nullable private String mLegacyLabel; 347 @Nullable private Intent mLegacyIntent; 348 @Nullable private OnClickListener mLegacyOnClickListener; 349 @Nullable private String mId; 350 @Nullable private Bundle mExtras; 351 352 /** 353 * Sets the classified text. 354 */ 355 @NonNull 356 public Builder setText(@Nullable String text) { 357 mText = text; 358 return this; 359 } 360 361 /** 362 * Sets an entity type for the classification result and assigns a confidence score. 363 * If a confidence score had already been set for the specified entity type, this will 364 * override that score. 365 * 366 * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence). 367 * 0 implies the entity does not exist for the classified text. 368 * Values greater than 1 are clamped to 1. 369 */ 370 @NonNull 371 public Builder setEntityType( 372 @NonNull @EntityType String type, 373 @FloatRange(from = 0.0, to = 1.0) float confidenceScore) { 374 mTypeScoreMap.put(type, confidenceScore); 375 return this; 376 } 377 378 Builder setEntityConfidence(EntityConfidence scores) { 379 mTypeScoreMap.clear(); 380 mTypeScoreMap.putAll(scores.toMap()); 381 return this; 382 } 383 384 /** @hide */ 385 public Builder clearEntityTypes() { 386 mTypeScoreMap.clear(); 387 return this; 388 } 389 390 /** 391 * Adds an action that may be performed on the classified text. Actions should be added in 392 * order of likelihood that the user will use them, with the most likely action being added 393 * first. 394 */ 395 @NonNull 396 public Builder addAction(@NonNull RemoteAction action) { 397 Preconditions.checkArgument(action != null); 398 mActions.add(action); 399 return this; 400 } 401 402 /** @hide */ 403 public Builder addActions(Collection<RemoteAction> actions) { 404 Objects.requireNonNull(actions); 405 mActions.addAll(actions); 406 return this; 407 } 408 409 /** @hide */ 410 public Builder clearActions() { 411 mActions.clear(); 412 return this; 413 } 414 415 /** 416 * Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act 417 * on the classified text. 418 * 419 * <p><strong>NOTE: </strong>This field is not parcelled. If read from a parcel, the 420 * returned icon represents the icon of the first {@link RemoteAction} (if one exists). 421 * 422 * @deprecated Use {@link #addAction(RemoteAction)} instead. 423 */ 424 @Deprecated 425 @NonNull 426 public Builder setIcon(@Nullable Drawable icon) { 427 mLegacyIcon = icon; 428 return this; 429 } 430 431 /** 432 * Sets the label for the <i>primary</i> action that may be rendered on a widget used to 433 * act on the classified text. 434 * 435 * <p><strong>NOTE: </strong>This field is not parcelled. If read from a parcel, the 436 * returned label represents the label of the first {@link RemoteAction} (if one exists). 437 * 438 * @deprecated Use {@link #addAction(RemoteAction)} instead. 439 */ 440 @Deprecated 441 @NonNull 442 public Builder setLabel(@Nullable String label) { 443 mLegacyLabel = label; 444 return this; 445 } 446 447 /** 448 * Sets the intent for the <i>primary</i> action that may be fired to act on the classified 449 * text. 450 * 451 * <p><strong>NOTE: </strong>This field is not parcelled. 452 * 453 * @deprecated Use {@link #addAction(RemoteAction)} instead. 454 */ 455 @Deprecated 456 @NonNull 457 public Builder setIntent(@Nullable Intent intent) { 458 mLegacyIntent = intent; 459 return this; 460 } 461 462 /** 463 * Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on 464 * the classified text. 465 * 466 * <p><strong>NOTE: </strong>This field is not parcelable. If read from a parcel, the 467 * returned OnClickListener represents the first {@link RemoteAction} (if one exists). 468 * 469 * @deprecated Use {@link #addAction(RemoteAction)} instead. 470 */ 471 @Deprecated 472 @NonNull 473 public Builder setOnClickListener(@Nullable OnClickListener onClickListener) { 474 mLegacyOnClickListener = onClickListener; 475 return this; 476 } 477 478 /** 479 * Sets an id for the TextClassification object. 480 */ 481 @NonNull 482 public Builder setId(@Nullable String id) { 483 mId = id; 484 return this; 485 } 486 487 /** 488 * Sets the extended data. 489 */ 490 @NonNull 491 public Builder setExtras(@Nullable Bundle extras) { 492 mExtras = extras; 493 return this; 494 } 495 496 /** 497 * Builds and returns a {@link TextClassification} object. 498 */ 499 @NonNull 500 public TextClassification build() { 501 EntityConfidence entityConfidence = new EntityConfidence(mTypeScoreMap); 502 return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent, 503 mLegacyOnClickListener, mActions, entityConfidence, mId, 504 mExtras == null ? Bundle.EMPTY : mExtras); 505 } 506 } 507 508 /** 509 * A request object for generating TextClassification. 510 */ 511 public static final class Request implements Parcelable { 512 513 private final CharSequence mText; 514 private final int mStartIndex; 515 private final int mEndIndex; 516 @Nullable private final LocaleList mDefaultLocales; 517 @Nullable private final ZonedDateTime mReferenceTime; 518 @NonNull private final Bundle mExtras; 519 @Nullable private SystemTextClassifierMetadata mSystemTcMetadata; 520 521 private Request( 522 CharSequence text, 523 int startIndex, 524 int endIndex, 525 LocaleList defaultLocales, 526 ZonedDateTime referenceTime, 527 Bundle extras) { 528 mText = text; 529 mStartIndex = startIndex; 530 mEndIndex = endIndex; 531 mDefaultLocales = defaultLocales; 532 mReferenceTime = referenceTime; 533 mExtras = extras; 534 } 535 536 /** 537 * Returns the text providing context for the text to classify (which is specified 538 * by the sub sequence starting at startIndex and ending at endIndex) 539 */ 540 @NonNull 541 public CharSequence getText() { 542 return mText; 543 } 544 545 /** 546 * Returns start index of the text to classify. 547 */ 548 @IntRange(from = 0) 549 public int getStartIndex() { 550 return mStartIndex; 551 } 552 553 /** 554 * Returns end index of the text to classify. 555 */ 556 @IntRange(from = 0) 557 public int getEndIndex() { 558 return mEndIndex; 559 } 560 561 /** 562 * @return ordered list of locale preferences that can be used to disambiguate 563 * the provided text. 564 */ 565 @Nullable 566 public LocaleList getDefaultLocales() { 567 return mDefaultLocales; 568 } 569 570 /** 571 * @return reference time based on which relative dates (e.g. "tomorrow") should be 572 * interpreted. 573 */ 574 @Nullable 575 public ZonedDateTime getReferenceTime() { 576 return mReferenceTime; 577 } 578 579 /** 580 * Returns the name of the package that sent this request. 581 * This returns {@code null} if no calling package name is set. 582 */ 583 @Nullable 584 public String getCallingPackageName() { 585 return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null; 586 } 587 588 /** 589 * Sets the information about the {@link SystemTextClassifier} that sent this request. 590 * 591 * @hide 592 */ 593 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 594 public void setSystemTextClassifierMetadata( 595 @Nullable SystemTextClassifierMetadata systemTcMetadata) { 596 mSystemTcMetadata = systemTcMetadata; 597 } 598 599 /** 600 * Returns the information about the {@link SystemTextClassifier} that sent this request. 601 * 602 * @hide 603 */ 604 @Nullable 605 public SystemTextClassifierMetadata getSystemTextClassifierMetadata() { 606 return mSystemTcMetadata; 607 } 608 609 /** 610 * Returns the extended data. 611 * 612 * <p><b>NOTE: </b>Do not modify this bundle. 613 */ 614 @NonNull 615 public Bundle getExtras() { 616 return mExtras; 617 } 618 619 /** 620 * A builder for building TextClassification requests. 621 */ 622 public static final class Builder { 623 624 private final CharSequence mText; 625 private final int mStartIndex; 626 private final int mEndIndex; 627 private Bundle mExtras; 628 629 @Nullable private LocaleList mDefaultLocales; 630 @Nullable private ZonedDateTime mReferenceTime; 631 632 /** 633 * @param text text providing context for the text to classify (which is specified 634 * by the sub sequence starting at startIndex and ending at endIndex) 635 * @param startIndex start index of the text to classify 636 * @param endIndex end index of the text to classify 637 */ 638 public Builder( 639 @NonNull CharSequence text, 640 @IntRange(from = 0) int startIndex, 641 @IntRange(from = 0) int endIndex) { 642 Utils.checkArgument(text, startIndex, endIndex); 643 mText = text; 644 mStartIndex = startIndex; 645 mEndIndex = endIndex; 646 } 647 648 /** 649 * @param defaultLocales ordered list of locale preferences that may be used to 650 * disambiguate the provided text. If no locale preferences exist, set this to null 651 * or an empty locale list. 652 * 653 * @return this builder 654 */ 655 @NonNull 656 public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) { 657 mDefaultLocales = defaultLocales; 658 return this; 659 } 660 661 /** 662 * @param referenceTime reference time based on which relative dates (e.g. "tomorrow" 663 * should be interpreted. This should usually be the time when the text was 664 * originally composed. If no reference time is set, now is used. 665 * 666 * @return this builder 667 */ 668 @NonNull 669 public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) { 670 mReferenceTime = referenceTime; 671 return this; 672 } 673 674 /** 675 * Sets the extended data. 676 * 677 * @return this builder 678 */ 679 @NonNull 680 public Builder setExtras(@Nullable Bundle extras) { 681 mExtras = extras; 682 return this; 683 } 684 685 /** 686 * Builds and returns the request object. 687 */ 688 @NonNull 689 public Request build() { 690 return new Request(new SpannedString(mText), mStartIndex, mEndIndex, 691 mDefaultLocales, mReferenceTime, 692 mExtras == null ? Bundle.EMPTY : mExtras); 693 } 694 } 695 696 @Override 697 public int describeContents() { 698 return 0; 699 } 700 701 @Override 702 public void writeToParcel(Parcel dest, int flags) { 703 dest.writeCharSequence(mText); 704 dest.writeInt(mStartIndex); 705 dest.writeInt(mEndIndex); 706 dest.writeParcelable(mDefaultLocales, flags); 707 dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString()); 708 dest.writeBundle(mExtras); 709 dest.writeParcelable(mSystemTcMetadata, flags); 710 } 711 712 private static Request readFromParcel(Parcel in) { 713 final CharSequence text = in.readCharSequence(); 714 final int startIndex = in.readInt(); 715 final int endIndex = in.readInt(); 716 final LocaleList defaultLocales = in.readParcelable(null); 717 final String referenceTimeString = in.readString(); 718 final ZonedDateTime referenceTime = referenceTimeString == null 719 ? null : ZonedDateTime.parse(referenceTimeString); 720 final Bundle extras = in.readBundle(); 721 final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null); 722 723 final Request request = new Request(text, startIndex, endIndex, 724 defaultLocales, referenceTime, extras); 725 request.setSystemTextClassifierMetadata(systemTcMetadata); 726 return request; 727 } 728 729 public static final @android.annotation.NonNull Parcelable.Creator<Request> CREATOR = 730 new Parcelable.Creator<Request>() { 731 @Override 732 public Request createFromParcel(Parcel in) { 733 return readFromParcel(in); 734 } 735 736 @Override 737 public Request[] newArray(int size) { 738 return new Request[size]; 739 } 740 }; 741 } 742 743 @Override 744 public int describeContents() { 745 return 0; 746 } 747 748 @Override 749 public void writeToParcel(Parcel dest, int flags) { 750 dest.writeString(mText); 751 // NOTE: legacy fields are not parcelled. 752 dest.writeTypedList(mActions); 753 mEntityConfidence.writeToParcel(dest, flags); 754 dest.writeString(mId); 755 dest.writeBundle(mExtras); 756 } 757 758 public static final @android.annotation.NonNull Parcelable.Creator<TextClassification> CREATOR = 759 new Parcelable.Creator<TextClassification>() { 760 @Override 761 public TextClassification createFromParcel(Parcel in) { 762 return new TextClassification(in); 763 } 764 765 @Override 766 public TextClassification[] newArray(int size) { 767 return new TextClassification[size]; 768 } 769 }; 770 771 private TextClassification(Parcel in) { 772 mText = in.readString(); 773 mActions = in.createTypedArrayList(RemoteAction.CREATOR); 774 if (!mActions.isEmpty()) { 775 final RemoteAction action = mActions.get(0); 776 mLegacyIcon = maybeLoadDrawable(action.getIcon()); 777 mLegacyLabel = action.getTitle().toString(); 778 mLegacyOnClickListener = createIntentOnClickListener(mActions.get(0).getActionIntent()); 779 } else { 780 mLegacyIcon = null; 781 mLegacyLabel = null; 782 mLegacyOnClickListener = null; 783 } 784 mLegacyIntent = null; // mLegacyIntent is not parcelled. 785 mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); 786 mId = in.readString(); 787 mExtras = in.readBundle(); 788 } 789 790 // Best effort attempt to try to load a drawable from the provided icon. 791 @Nullable 792 private static Drawable maybeLoadDrawable(Icon icon) { 793 if (icon == null) { 794 return null; 795 } 796 switch (icon.getType()) { 797 case Icon.TYPE_BITMAP: 798 return new BitmapDrawable(Resources.getSystem(), icon.getBitmap()); 799 case Icon.TYPE_ADAPTIVE_BITMAP: 800 return new AdaptiveIconDrawable(null, 801 new BitmapDrawable(Resources.getSystem(), icon.getBitmap())); 802 case Icon.TYPE_DATA: 803 return new BitmapDrawable( 804 Resources.getSystem(), 805 BitmapFactory.decodeByteArray( 806 icon.getDataBytes(), icon.getDataOffset(), icon.getDataLength())); 807 } 808 return null; 809 } 810 } 811