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