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