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