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.IntRange; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.os.Bundle; 24 import android.os.LocaleList; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.text.SpannedString; 28 import android.util.ArrayMap; 29 import android.view.textclassifier.TextClassifier.EntityType; 30 import android.view.textclassifier.TextClassifier.Utils; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.util.Preconditions; 34 35 import java.util.Locale; 36 import java.util.Map; 37 import java.util.Objects; 38 39 /** 40 * Information about where text selection should be. 41 */ 42 public final class TextSelection implements Parcelable { 43 44 private final int mStartIndex; 45 private final int mEndIndex; 46 private final EntityConfidence mEntityConfidence; 47 @Nullable private final String mId; 48 @Nullable 49 private final TextClassification mTextClassification; 50 private final Bundle mExtras; 51 TextSelection( int startIndex, int endIndex, Map<String, Float> entityConfidence, String id, @Nullable TextClassification textClassification, Bundle extras)52 private TextSelection( 53 int startIndex, int endIndex, Map<String, Float> entityConfidence, String id, 54 @Nullable TextClassification textClassification, 55 Bundle extras) { 56 mStartIndex = startIndex; 57 mEndIndex = endIndex; 58 mEntityConfidence = new EntityConfidence(entityConfidence); 59 mId = id; 60 mTextClassification = textClassification; 61 mExtras = extras; 62 } 63 64 /** 65 * Returns the start index of the text selection. 66 */ getSelectionStartIndex()67 public int getSelectionStartIndex() { 68 return mStartIndex; 69 } 70 71 /** 72 * Returns the end index of the text selection. 73 */ getSelectionEndIndex()74 public int getSelectionEndIndex() { 75 return mEndIndex; 76 } 77 78 /** 79 * Returns the number of entities found in the classified text. 80 */ 81 @IntRange(from = 0) getEntityCount()82 public int getEntityCount() { 83 return mEntityConfidence.getEntities().size(); 84 } 85 86 /** 87 * Returns the entity at the specified index. Entities are ordered from high confidence 88 * to low confidence. 89 * 90 * @throws IndexOutOfBoundsException if the specified index is out of range. 91 * @see #getEntityCount() for the number of entities available. 92 */ 93 @NonNull 94 @EntityType getEntity(int index)95 public String getEntity(int index) { 96 return mEntityConfidence.getEntities().get(index); 97 } 98 99 /** 100 * Returns the confidence score for the specified entity. The value ranges from 101 * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the 102 * classified text. 103 */ 104 @FloatRange(from = 0.0, to = 1.0) getConfidenceScore(@ntityType String entity)105 public float getConfidenceScore(@EntityType String entity) { 106 return mEntityConfidence.getConfidenceScore(entity); 107 } 108 109 /** 110 * Returns the id, if one exists, for this object. 111 */ 112 @Nullable getId()113 public String getId() { 114 return mId; 115 } 116 117 /** 118 * Returns the text classification result of the suggested selection span. Enables the text 119 * classification by calling 120 * {@link TextSelection.Request.Builder#setIncludeTextClassification(boolean)}. If the text 121 * classifier does not support it, a {@code null} is returned. 122 * 123 * @see TextSelection.Request.Builder#setIncludeTextClassification(boolean) 124 */ 125 @Nullable getTextClassification()126 public TextClassification getTextClassification() { 127 return mTextClassification; 128 } 129 130 /** 131 * Returns the extended data. 132 * 133 * <p><b>NOTE: </b>Do not modify this bundle. 134 */ 135 @NonNull getExtras()136 public Bundle getExtras() { 137 return mExtras; 138 } 139 140 /** @hide */ toBuilder()141 public TextSelection.Builder toBuilder() { 142 return new TextSelection.Builder(mStartIndex, mEndIndex) 143 .setId(mId) 144 .setEntityConfidence(mEntityConfidence) 145 .setTextClassification(mTextClassification) 146 .setExtras(mExtras); 147 } 148 149 @Override toString()150 public String toString() { 151 return String.format( 152 Locale.US, 153 "TextSelection {id=%s, startIndex=%d, endIndex=%d, entities=%s}", 154 mId, mStartIndex, mEndIndex, mEntityConfidence); 155 } 156 157 /** 158 * Builder used to build {@link TextSelection} objects. 159 */ 160 public static final class Builder { 161 162 private final int mStartIndex; 163 private final int mEndIndex; 164 private final Map<String, Float> mEntityConfidence = new ArrayMap<>(); 165 @Nullable private String mId; 166 @Nullable 167 private TextClassification mTextClassification; 168 @Nullable 169 private Bundle mExtras; 170 171 /** 172 * Creates a builder used to build {@link TextSelection} objects. 173 * 174 * @param startIndex the start index of the text selection. 175 * @param endIndex the end index of the text selection. Must be greater than startIndex 176 */ Builder(@ntRangefrom = 0) int startIndex, @IntRange(from = 0) int endIndex)177 public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) { 178 Preconditions.checkArgument(startIndex >= 0); 179 Preconditions.checkArgument(endIndex > startIndex); 180 mStartIndex = startIndex; 181 mEndIndex = endIndex; 182 } 183 184 /** 185 * Sets an entity type for the classified text and assigns a confidence score. 186 * 187 * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence). 188 * 0 implies the entity does not exist for the classified text. 189 * Values greater than 1 are clamped to 1. 190 */ 191 @NonNull setEntityType( @onNull @ntityType String type, @FloatRange(from = 0.0, to = 1.0) float confidenceScore)192 public Builder setEntityType( 193 @NonNull @EntityType String type, 194 @FloatRange(from = 0.0, to = 1.0) float confidenceScore) { 195 Objects.requireNonNull(type); 196 mEntityConfidence.put(type, confidenceScore); 197 return this; 198 } 199 setEntityConfidence(EntityConfidence scores)200 Builder setEntityConfidence(EntityConfidence scores) { 201 mEntityConfidence.clear(); 202 mEntityConfidence.putAll(scores.toMap()); 203 return this; 204 } 205 206 /** 207 * Sets an id for the TextSelection object. 208 */ 209 @NonNull setId(@ullable String id)210 public Builder setId(@Nullable String id) { 211 mId = id; 212 return this; 213 } 214 215 /** 216 * Sets the text classification result of the suggested selection. If 217 * {@link Request#shouldIncludeTextClassification()} is {@code true}, set this value. 218 * Otherwise this value may be set to null. The intention of this method is to avoid 219 * doing expensive work if the client is not interested in such result. 220 * 221 * @return this builder 222 * @see Request#shouldIncludeTextClassification() 223 */ 224 @NonNull setTextClassification(@ullable TextClassification textClassification)225 public Builder setTextClassification(@Nullable TextClassification textClassification) { 226 mTextClassification = textClassification; 227 return this; 228 } 229 230 /** 231 * Sets the extended data. 232 * 233 * @return this builder 234 */ 235 @NonNull setExtras(@ullable Bundle extras)236 public Builder setExtras(@Nullable Bundle extras) { 237 mExtras = extras; 238 return this; 239 } 240 241 /** 242 * Builds and returns {@link TextSelection} object. 243 */ 244 @NonNull build()245 public TextSelection build() { 246 return new TextSelection( 247 mStartIndex, mEndIndex, mEntityConfidence, mId, 248 mTextClassification, mExtras == null ? Bundle.EMPTY : mExtras); 249 } 250 } 251 252 /** 253 * A request object for generating TextSelection. 254 */ 255 public static final class Request implements Parcelable { 256 257 private final CharSequence mText; 258 private final int mStartIndex; 259 private final int mEndIndex; 260 @Nullable private final LocaleList mDefaultLocales; 261 private final boolean mDarkLaunchAllowed; 262 private final boolean mIncludeTextClassification; 263 private final Bundle mExtras; 264 @Nullable private SystemTextClassifierMetadata mSystemTcMetadata; 265 Request( CharSequence text, int startIndex, int endIndex, LocaleList defaultLocales, boolean darkLaunchAllowed, boolean includeTextClassification, Bundle extras)266 private Request( 267 CharSequence text, 268 int startIndex, 269 int endIndex, 270 LocaleList defaultLocales, 271 boolean darkLaunchAllowed, 272 boolean includeTextClassification, 273 Bundle extras) { 274 mText = text; 275 mStartIndex = startIndex; 276 mEndIndex = endIndex; 277 mDefaultLocales = defaultLocales; 278 mDarkLaunchAllowed = darkLaunchAllowed; 279 mIncludeTextClassification = includeTextClassification; 280 mExtras = extras; 281 } 282 283 /** 284 * Returns the text providing context for the selected text (which is specified by the 285 * sub sequence starting at startIndex and ending at endIndex). 286 */ 287 @NonNull getText()288 public CharSequence getText() { 289 return mText; 290 } 291 292 /** 293 * Returns start index of the selected part of text. 294 */ 295 @IntRange(from = 0) getStartIndex()296 public int getStartIndex() { 297 return mStartIndex; 298 } 299 300 /** 301 * Returns end index of the selected part of text. 302 */ 303 @IntRange(from = 0) getEndIndex()304 public int getEndIndex() { 305 return mEndIndex; 306 } 307 308 /** 309 * Returns true if the TextClassifier should return selection suggestions when "dark 310 * launched". Otherwise, returns false. 311 * 312 * @hide 313 */ isDarkLaunchAllowed()314 public boolean isDarkLaunchAllowed() { 315 return mDarkLaunchAllowed; 316 } 317 318 /** 319 * @return ordered list of locale preferences that can be used to disambiguate the 320 * provided text. 321 */ 322 @Nullable getDefaultLocales()323 public LocaleList getDefaultLocales() { 324 return mDefaultLocales; 325 } 326 327 /** 328 * Returns the name of the package that sent this request. 329 * This returns {@code null} if no calling package name is set. 330 */ 331 @Nullable getCallingPackageName()332 public String getCallingPackageName() { 333 return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null; 334 } 335 336 /** 337 * Sets the information about the {@link SystemTextClassifier} that sent this request. 338 * 339 * @hide 340 */ 341 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) setSystemTextClassifierMetadata( @ullable SystemTextClassifierMetadata systemTcMetadata)342 public void setSystemTextClassifierMetadata( 343 @Nullable SystemTextClassifierMetadata systemTcMetadata) { 344 mSystemTcMetadata = systemTcMetadata; 345 } 346 347 /** 348 * Returns the information about the {@link SystemTextClassifier} that sent this request. 349 * 350 * @hide 351 */ 352 @Nullable getSystemTextClassifierMetadata()353 public SystemTextClassifierMetadata getSystemTextClassifierMetadata() { 354 return mSystemTcMetadata; 355 } 356 357 /** 358 * Returns true if the client wants the text classifier to classify the text as well and 359 * include a {@link TextClassification} object in the result. 360 */ shouldIncludeTextClassification()361 public boolean shouldIncludeTextClassification() { 362 return mIncludeTextClassification; 363 } 364 365 /** 366 * Returns the extended data. 367 * 368 * <p><b>NOTE: </b>Do not modify this bundle. 369 */ 370 @NonNull getExtras()371 public Bundle getExtras() { 372 return mExtras; 373 } 374 375 /** 376 * A builder for building TextSelection requests. 377 */ 378 public static final class Builder { 379 380 private final CharSequence mText; 381 private final int mStartIndex; 382 private final int mEndIndex; 383 @Nullable private LocaleList mDefaultLocales; 384 private boolean mDarkLaunchAllowed; 385 private boolean mIncludeTextClassification; 386 private Bundle mExtras; 387 388 /** 389 * @param text text providing context for the selected text (which is specified by the 390 * sub sequence starting at selectionStartIndex and ending at selectionEndIndex) 391 * @param startIndex start index of the selected part of text 392 * @param endIndex end index of the selected part of text 393 */ Builder( @onNull CharSequence text, @IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex)394 public Builder( 395 @NonNull CharSequence text, 396 @IntRange(from = 0) int startIndex, 397 @IntRange(from = 0) int endIndex) { 398 Utils.checkArgument(text, startIndex, endIndex); 399 mText = text; 400 mStartIndex = startIndex; 401 mEndIndex = endIndex; 402 } 403 404 /** 405 * @param defaultLocales ordered list of locale preferences that may be used to 406 * disambiguate the provided text. If no locale preferences exist, set this to null 407 * or an empty locale list. 408 * 409 * @return this builder. 410 */ 411 @NonNull setDefaultLocales(@ullable LocaleList defaultLocales)412 public Builder setDefaultLocales(@Nullable LocaleList defaultLocales) { 413 mDefaultLocales = defaultLocales; 414 return this; 415 } 416 417 /** 418 * @param allowed whether or not the TextClassifier should return selection suggestions 419 * when "dark launched". When a TextClassifier is dark launched, it can suggest 420 * selection changes that should not be used to actually change the user's 421 * selection. Instead, the suggested selection is logged, compared with the user's 422 * selection interaction, and used to generate quality metrics for the 423 * TextClassifier. Not parceled. 424 * 425 * @return this builder. 426 * @hide 427 */ 428 @NonNull setDarkLaunchAllowed(boolean allowed)429 public Builder setDarkLaunchAllowed(boolean allowed) { 430 mDarkLaunchAllowed = allowed; 431 return this; 432 } 433 434 /** 435 * @param includeTextClassification If true, suggests the TextClassifier to classify the 436 * text in the suggested selection span and include a TextClassification object in 437 * the result. The TextClassifier may not support this and in which case, 438 * {@link TextSelection#getTextClassification()} returns {@code null}. 439 * 440 * @return this builder. 441 * @see TextSelection#getTextClassification() 442 */ 443 @NonNull setIncludeTextClassification(boolean includeTextClassification)444 public Builder setIncludeTextClassification(boolean includeTextClassification) { 445 mIncludeTextClassification = includeTextClassification; 446 return this; 447 } 448 449 /** 450 * Sets the extended data. 451 * 452 * @return this builder 453 */ 454 @NonNull setExtras(@ullable Bundle extras)455 public Builder setExtras(@Nullable Bundle extras) { 456 mExtras = extras; 457 return this; 458 } 459 460 /** 461 * Builds and returns the request object. 462 */ 463 @NonNull build()464 public Request build() { 465 return new Request(new SpannedString(mText), mStartIndex, mEndIndex, 466 mDefaultLocales, mDarkLaunchAllowed, 467 mIncludeTextClassification, 468 mExtras == null ? Bundle.EMPTY : mExtras); 469 } 470 } 471 472 @Override describeContents()473 public int describeContents() { 474 return 0; 475 } 476 477 @Override writeToParcel(Parcel dest, int flags)478 public void writeToParcel(Parcel dest, int flags) { 479 dest.writeCharSequence(mText); 480 dest.writeInt(mStartIndex); 481 dest.writeInt(mEndIndex); 482 dest.writeParcelable(mDefaultLocales, flags); 483 dest.writeBundle(mExtras); 484 dest.writeParcelable(mSystemTcMetadata, flags); 485 dest.writeBoolean(mIncludeTextClassification); 486 } 487 readFromParcel(Parcel in)488 private static Request readFromParcel(Parcel in) { 489 final CharSequence text = in.readCharSequence(); 490 final int startIndex = in.readInt(); 491 final int endIndex = in.readInt(); 492 final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class); 493 final Bundle extras = in.readBundle(); 494 final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class); 495 final boolean includeTextClassification = in.readBoolean(); 496 497 final Request request = new Request(text, startIndex, endIndex, defaultLocales, 498 /* darkLaunchAllowed= */ false, includeTextClassification, extras); 499 request.setSystemTextClassifierMetadata(systemTcMetadata); 500 return request; 501 } 502 503 public static final @android.annotation.NonNull Parcelable.Creator<Request> CREATOR = 504 new Parcelable.Creator<Request>() { 505 @Override 506 public Request createFromParcel(Parcel in) { 507 return readFromParcel(in); 508 } 509 510 @Override 511 public Request[] newArray(int size) { 512 return new Request[size]; 513 } 514 }; 515 } 516 517 @Override describeContents()518 public int describeContents() { 519 return 0; 520 } 521 522 @Override writeToParcel(Parcel dest, int flags)523 public void writeToParcel(Parcel dest, int flags) { 524 dest.writeInt(mStartIndex); 525 dest.writeInt(mEndIndex); 526 mEntityConfidence.writeToParcel(dest, flags); 527 dest.writeString(mId); 528 dest.writeBundle(mExtras); 529 dest.writeParcelable(mTextClassification, flags); 530 } 531 532 public static final @android.annotation.NonNull Parcelable.Creator<TextSelection> CREATOR = 533 new Parcelable.Creator<TextSelection>() { 534 @Override 535 public TextSelection createFromParcel(Parcel in) { 536 return new TextSelection(in); 537 } 538 539 @Override 540 public TextSelection[] newArray(int size) { 541 return new TextSelection[size]; 542 } 543 }; 544 TextSelection(Parcel in)545 private TextSelection(Parcel in) { 546 mStartIndex = in.readInt(); 547 mEndIndex = in.readInt(); 548 mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); 549 mId = in.readString(); 550 mExtras = in.readBundle(); 551 mTextClassification = in.readParcelable(TextClassification.class.getClassLoader(), android.view.textclassifier.TextClassification.class); 552 } 553 } 554