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