1 /* 2 * Copyright (C) 2018 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 package android.view.textclassifier; 17 18 import static java.lang.annotation.RetentionPolicy.SOURCE; 19 20 import android.annotation.IntRange; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.StringDef; 24 import android.app.Person; 25 import android.os.Bundle; 26 import android.os.Parcel; 27 import android.os.Parcelable; 28 import android.text.SpannedString; 29 30 import java.lang.annotation.Retention; 31 import java.time.ZonedDateTime; 32 import java.time.format.DateTimeFormatter; 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.List; 36 import java.util.Objects; 37 38 /** 39 * Represents a list of actions suggested by a {@link TextClassifier} on a given conversation. 40 * 41 * @see TextClassifier#suggestConversationActions(Request) 42 */ 43 public final class ConversationActions implements Parcelable { 44 45 public static final @android.annotation.NonNull Creator<ConversationActions> CREATOR = 46 new Creator<ConversationActions>() { 47 @Override 48 public ConversationActions createFromParcel(Parcel in) { 49 return new ConversationActions(in); 50 } 51 52 @Override 53 public ConversationActions[] newArray(int size) { 54 return new ConversationActions[size]; 55 } 56 }; 57 58 private final List<ConversationAction> mConversationActions; 59 private final String mId; 60 61 /** Constructs a {@link ConversationActions} object. */ ConversationActions( @onNull List<ConversationAction> conversationActions, @Nullable String id)62 public ConversationActions( 63 @NonNull List<ConversationAction> conversationActions, @Nullable String id) { 64 mConversationActions = 65 Collections.unmodifiableList(Objects.requireNonNull(conversationActions)); 66 mId = id; 67 } 68 ConversationActions(Parcel in)69 private ConversationActions(Parcel in) { 70 mConversationActions = 71 Collections.unmodifiableList(in.createTypedArrayList(ConversationAction.CREATOR)); 72 mId = in.readString(); 73 } 74 75 @Override describeContents()76 public int describeContents() { 77 return 0; 78 } 79 80 @Override writeToParcel(Parcel parcel, int flags)81 public void writeToParcel(Parcel parcel, int flags) { 82 parcel.writeTypedList(mConversationActions); 83 parcel.writeString(mId); 84 } 85 86 /** 87 * Returns an immutable list of {@link ConversationAction} objects, which are ordered from high 88 * confidence to low confidence. 89 */ 90 @NonNull getConversationActions()91 public List<ConversationAction> getConversationActions() { 92 return mConversationActions; 93 } 94 95 /** 96 * Returns the id, if one exists, for this object. 97 */ 98 @Nullable getId()99 public String getId() { 100 return mId; 101 } 102 103 /** Represents a message in the conversation. */ 104 public static final class Message implements Parcelable { 105 /** 106 * Represents the local user. 107 * 108 * @see Builder#Builder(Person) 109 */ 110 @NonNull 111 public static final Person PERSON_USER_SELF = 112 new Person.Builder() 113 .setKey("text-classifier-conversation-actions-user-self") 114 .build(); 115 116 /** 117 * Represents the remote user. 118 * <p> 119 * If possible, you are suggested to create a {@link Person} object that can identify 120 * the remote user better, so that the underlying model could differentiate between 121 * different remote users. 122 * 123 * @see Builder#Builder(Person) 124 */ 125 @NonNull 126 public static final Person PERSON_USER_OTHERS = 127 new Person.Builder() 128 .setKey("text-classifier-conversation-actions-user-others") 129 .build(); 130 131 @Nullable 132 private final Person mAuthor; 133 @Nullable 134 private final ZonedDateTime mReferenceTime; 135 @Nullable 136 private final CharSequence mText; 137 @NonNull 138 private final Bundle mExtras; 139 Message( @ullable Person author, @Nullable ZonedDateTime referenceTime, @Nullable CharSequence text, @NonNull Bundle bundle)140 private Message( 141 @Nullable Person author, 142 @Nullable ZonedDateTime referenceTime, 143 @Nullable CharSequence text, 144 @NonNull Bundle bundle) { 145 mAuthor = author; 146 mReferenceTime = referenceTime; 147 mText = text; 148 mExtras = Objects.requireNonNull(bundle); 149 } 150 Message(Parcel in)151 private Message(Parcel in) { 152 mAuthor = in.readParcelable(null, android.app.Person.class); 153 mReferenceTime = 154 in.readInt() == 0 155 ? null 156 : ZonedDateTime.parse( 157 in.readString(), DateTimeFormatter.ISO_ZONED_DATE_TIME); 158 mText = in.readCharSequence(); 159 mExtras = in.readBundle(); 160 } 161 162 @Override writeToParcel(Parcel parcel, int flags)163 public void writeToParcel(Parcel parcel, int flags) { 164 parcel.writeParcelable(mAuthor, flags); 165 parcel.writeInt(mReferenceTime != null ? 1 : 0); 166 if (mReferenceTime != null) { 167 parcel.writeString(mReferenceTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); 168 } 169 parcel.writeCharSequence(mText); 170 parcel.writeBundle(mExtras); 171 } 172 173 @Override describeContents()174 public int describeContents() { 175 return 0; 176 } 177 178 public static final @android.annotation.NonNull Creator<Message> CREATOR = 179 new Creator<Message>() { 180 @Override 181 public Message createFromParcel(Parcel in) { 182 return new Message(in); 183 } 184 185 @Override 186 public Message[] newArray(int size) { 187 return new Message[size]; 188 } 189 }; 190 191 /** Returns the person that composed the message. */ 192 @NonNull getAuthor()193 public Person getAuthor() { 194 return mAuthor; 195 } 196 197 /** 198 * Returns the reference time of the message, for example it could be the compose or send 199 * time of this message. 200 */ 201 @Nullable getReferenceTime()202 public ZonedDateTime getReferenceTime() { 203 return mReferenceTime; 204 } 205 206 /** Returns the text of the message. */ 207 @Nullable getText()208 public CharSequence getText() { 209 return mText; 210 } 211 212 /** 213 * Returns the extended data related to this conversation action. 214 * 215 * <p><b>NOTE: </b>Do not modify this bundle. 216 */ 217 @NonNull getExtras()218 public Bundle getExtras() { 219 return mExtras; 220 } 221 222 /** Builder class to construct a {@link Message} */ 223 public static final class Builder { 224 @Nullable 225 private Person mAuthor; 226 @Nullable 227 private ZonedDateTime mReferenceTime; 228 @Nullable 229 private CharSequence mText; 230 @Nullable 231 private Bundle mExtras; 232 233 /** 234 * Constructs a builder. 235 * 236 * @param author the person that composed the message, use {@link #PERSON_USER_SELF} 237 * to represent the local user. If it is not possible to identify the 238 * remote user that the local user is conversing with, use 239 * {@link #PERSON_USER_OTHERS} to represent a remote user. 240 */ Builder(@onNull Person author)241 public Builder(@NonNull Person author) { 242 mAuthor = Objects.requireNonNull(author); 243 } 244 245 /** Sets the text of this message. */ 246 @NonNull setText(@ullable CharSequence text)247 public Builder setText(@Nullable CharSequence text) { 248 mText = text; 249 return this; 250 } 251 252 /** 253 * Sets the reference time of this message, for example it could be the compose or send 254 * time of this message. 255 */ 256 @NonNull setReferenceTime(@ullable ZonedDateTime referenceTime)257 public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) { 258 mReferenceTime = referenceTime; 259 return this; 260 } 261 262 /** Sets a set of extended data to the message. */ 263 @NonNull setExtras(@ullable Bundle bundle)264 public Builder setExtras(@Nullable Bundle bundle) { 265 this.mExtras = bundle; 266 return this; 267 } 268 269 /** Builds the {@link Message} object. */ 270 @NonNull build()271 public Message build() { 272 return new Message( 273 mAuthor, 274 mReferenceTime, 275 mText == null ? null : new SpannedString(mText), 276 mExtras == null ? Bundle.EMPTY : mExtras); 277 } 278 } 279 } 280 281 /** 282 * A request object for generating conversation action suggestions. 283 * 284 * @see TextClassifier#suggestConversationActions(Request) 285 */ 286 public static final class Request implements Parcelable { 287 288 /** @hide */ 289 @Retention(SOURCE) 290 @StringDef( 291 value = { 292 HINT_FOR_NOTIFICATION, 293 HINT_FOR_IN_APP, 294 }, 295 prefix = "HINT_") 296 public @interface Hint {} 297 298 /** 299 * To indicate the generated actions will be used within the app. 300 */ 301 public static final String HINT_FOR_IN_APP = "in_app"; 302 /** 303 * To indicate the generated actions will be used for notification. 304 */ 305 public static final String HINT_FOR_NOTIFICATION = "notification"; 306 307 @NonNull 308 private final List<Message> mConversation; 309 @NonNull 310 private final TextClassifier.EntityConfig mTypeConfig; 311 private final int mMaxSuggestions; 312 @NonNull 313 @Hint 314 private final List<String> mHints; 315 @NonNull 316 private Bundle mExtras; 317 @Nullable private SystemTextClassifierMetadata mSystemTcMetadata; 318 Request( @onNull List<Message> conversation, @NonNull TextClassifier.EntityConfig typeConfig, int maxSuggestions, @Nullable @Hint List<String> hints, @NonNull Bundle extras)319 private Request( 320 @NonNull List<Message> conversation, 321 @NonNull TextClassifier.EntityConfig typeConfig, 322 int maxSuggestions, 323 @Nullable @Hint List<String> hints, 324 @NonNull Bundle extras) { 325 mConversation = Objects.requireNonNull(conversation); 326 mTypeConfig = Objects.requireNonNull(typeConfig); 327 mMaxSuggestions = maxSuggestions; 328 mHints = hints; 329 mExtras = extras; 330 } 331 readFromParcel(Parcel in)332 private static Request readFromParcel(Parcel in) { 333 List<Message> conversation = new ArrayList<>(); 334 in.readParcelableList(conversation, null, android.view.textclassifier.ConversationActions.Message.class); 335 TextClassifier.EntityConfig typeConfig = in.readParcelable(null, android.view.textclassifier.TextClassifier.EntityConfig.class); 336 int maxSuggestions = in.readInt(); 337 List<String> hints = new ArrayList<>(); 338 in.readStringList(hints); 339 Bundle extras = in.readBundle(); 340 SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class); 341 342 Request request = new Request( 343 conversation, 344 typeConfig, 345 maxSuggestions, 346 hints, 347 extras); 348 request.setSystemTextClassifierMetadata(systemTcMetadata); 349 return request; 350 } 351 352 @Override writeToParcel(Parcel parcel, int flags)353 public void writeToParcel(Parcel parcel, int flags) { 354 parcel.writeParcelableList(mConversation, flags); 355 parcel.writeParcelable(mTypeConfig, flags); 356 parcel.writeInt(mMaxSuggestions); 357 parcel.writeStringList(mHints); 358 parcel.writeBundle(mExtras); 359 parcel.writeParcelable(mSystemTcMetadata, flags); 360 } 361 362 @Override describeContents()363 public int describeContents() { 364 return 0; 365 } 366 367 public static final @android.annotation.NonNull Creator<Request> CREATOR = 368 new Creator<Request>() { 369 @Override 370 public Request createFromParcel(Parcel in) { 371 return readFromParcel(in); 372 } 373 374 @Override 375 public Request[] newArray(int size) { 376 return new Request[size]; 377 } 378 }; 379 380 /** Returns the type config. */ 381 @NonNull getTypeConfig()382 public TextClassifier.EntityConfig getTypeConfig() { 383 return mTypeConfig; 384 } 385 386 /** Returns an immutable list of messages that make up the conversation. */ 387 @NonNull getConversation()388 public List<Message> getConversation() { 389 return mConversation; 390 } 391 392 /** 393 * Return the maximal number of suggestions the caller wants, value -1 means no restriction 394 * and this is the default. 395 */ 396 @IntRange(from = -1) getMaxSuggestions()397 public int getMaxSuggestions() { 398 return mMaxSuggestions; 399 } 400 401 /** Returns an immutable list of hints */ 402 @NonNull 403 @Hint getHints()404 public List<String> getHints() { 405 return mHints; 406 } 407 408 /** 409 * Returns the name of the package that sent this request. 410 * This returns {@code null} if no calling package name is set. 411 */ 412 @Nullable getCallingPackageName()413 public String getCallingPackageName() { 414 return mSystemTcMetadata != null ? mSystemTcMetadata.getCallingPackageName() : null; 415 } 416 417 /** 418 * Sets the information about the {@link SystemTextClassifier} that sent this request. 419 * 420 * @hide 421 */ setSystemTextClassifierMetadata(@ullable SystemTextClassifierMetadata systemTcData)422 void setSystemTextClassifierMetadata(@Nullable SystemTextClassifierMetadata systemTcData) { 423 mSystemTcMetadata = systemTcData; 424 } 425 426 /** 427 * Returns the information about the {@link SystemTextClassifier} that sent this request. 428 * 429 * @hide 430 */ 431 @Nullable getSystemTextClassifierMetadata()432 public SystemTextClassifierMetadata getSystemTextClassifierMetadata() { 433 return mSystemTcMetadata; 434 } 435 436 /** 437 * Returns the extended data related to this request. 438 * 439 * <p><b>NOTE: </b>Do not modify this bundle. 440 */ 441 @NonNull getExtras()442 public Bundle getExtras() { 443 return mExtras; 444 } 445 446 /** Builder object to construct the {@link Request} object. */ 447 public static final class Builder { 448 @NonNull 449 private List<Message> mConversation; 450 @Nullable 451 private TextClassifier.EntityConfig mTypeConfig; 452 private int mMaxSuggestions = -1; 453 @Nullable 454 @Hint 455 private List<String> mHints; 456 @Nullable 457 private Bundle mExtras; 458 459 /** 460 * Constructs a builder. 461 * 462 * @param conversation the conversation that the text classifier is going to generate 463 * actions for. 464 */ Builder(@onNull List<Message> conversation)465 public Builder(@NonNull List<Message> conversation) { 466 mConversation = Objects.requireNonNull(conversation); 467 } 468 469 /** 470 * Sets the hints to help text classifier to generate actions. It could be used to help 471 * text classifier to infer what types of actions the caller may be interested in. 472 */ 473 @NonNull setHints(@ullable @int List<String> hints)474 public Builder setHints(@Nullable @Hint List<String> hints) { 475 mHints = hints; 476 return this; 477 } 478 479 /** Sets the type config. */ 480 @NonNull setTypeConfig(@ullable TextClassifier.EntityConfig typeConfig)481 public Builder setTypeConfig(@Nullable TextClassifier.EntityConfig typeConfig) { 482 mTypeConfig = typeConfig; 483 return this; 484 } 485 486 /** 487 * Sets the maximum number of suggestions you want. Value -1 means no restriction and 488 * this is the default. 489 */ 490 @NonNull setMaxSuggestions(@ntRangefrom = -1) int maxSuggestions)491 public Builder setMaxSuggestions(@IntRange(from = -1) int maxSuggestions) { 492 if (maxSuggestions < -1) { 493 throw new IllegalArgumentException("maxSuggestions has to be greater than or " 494 + "equal to -1."); 495 } 496 mMaxSuggestions = maxSuggestions; 497 return this; 498 } 499 500 /** Sets a set of extended data to the request. */ 501 @NonNull setExtras(@ullable Bundle bundle)502 public Builder setExtras(@Nullable Bundle bundle) { 503 mExtras = bundle; 504 return this; 505 } 506 507 /** Builds the {@link Request} object. */ 508 @NonNull build()509 public Request build() { 510 return new Request( 511 Collections.unmodifiableList(mConversation), 512 mTypeConfig == null 513 ? new TextClassifier.EntityConfig.Builder().build() 514 : mTypeConfig, 515 mMaxSuggestions, 516 mHints == null 517 ? Collections.emptyList() 518 : Collections.unmodifiableList(mHints), 519 mExtras == null ? Bundle.EMPTY : mExtras); 520 } 521 } 522 } 523 } 524