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