• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 com.android.car.messenger.common;
18 
19 import static java.util.Arrays.stream;
20 
21 import android.app.PendingIntent;
22 import android.app.RemoteAction;
23 import android.app.RemoteInput;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.os.Parcelable;
27 import android.util.Log;
28 
29 import androidx.annotation.IntDef;
30 import androidx.annotation.NonNull;
31 import androidx.annotation.Nullable;
32 import androidx.core.app.Person;
33 import androidx.core.graphics.drawable.IconCompat;
34 
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Objects;
40 import java.util.stream.Collectors;
41 
42 /**
43  * A generic conversation model that holds messaging conversation metadata.
44  */
45 public final class Conversation {
46     private static final String KEY_ID = "id";
47     private static final String KEY_USER = "user";
48     private static final String KEY_TITLE = "title";
49     private static final String KEY_MESSAGES = "messages";
50     private static final String KEY_PARTICIPANTS = "participants";
51     private static final String KEY_ICON = "icon";
52     private static final String KEY_ACTIONS = "actions";
53     private static final String KEY_UNREAD_COUNT = "unread_count";
54     private static final String KEY_IS_MUTED = "is_muted";
55     private static final String KEY_EXTRAS = "extras";
56 
57     private static final String TAG = "CMC.Conversation";
58 
59     @NonNull
60     private final String mId;
61     @NonNull
62     private final Person mUser;
63     @Nullable
64     private final String mConversationTitle;
65     @Nullable
66     private final IconCompat mConversationIcon;
67     @NonNull
68     private final List<Message> mMessages;
69     @NonNull
70     private final List<Person> mParticipants;
71     @Nullable
72     private final List<ConversationAction> mActions;
73 
74     private final int mUnreadCount;
75     private final boolean mIsMuted;
76     @NonNull
77     private final Bundle mExtras;
78 
Conversation( @onNull String id, @NonNull Person user, @Nullable String conversationTitle, @Nullable IconCompat conversationIcon, @NonNull List<Message> messages, @NonNull List<Person> participants, @Nullable List<ConversationAction> actions, int unreadCount, boolean isMuted, @Nullable Bundle extras )79     public Conversation(
80             @NonNull String id,
81             @NonNull Person user,
82             @Nullable String conversationTitle,
83             @Nullable IconCompat conversationIcon,
84             @NonNull List<Message> messages,
85             @NonNull List<Person> participants,
86             @Nullable List<ConversationAction> actions,
87             int unreadCount,
88             boolean isMuted,
89             @Nullable Bundle extras
90     ) {
91         mId = id;
92         mUser = user;
93         mConversationTitle = conversationTitle;
94         mConversationIcon = conversationIcon;
95         mMessages = messages;
96         mParticipants = participants;
97         mActions = actions;
98         mUnreadCount = unreadCount;
99         mIsMuted = isMuted;
100         mExtras = (extras == null) ? new Bundle() : extras;
101     }
102 
103     /**
104      * Useful method for saving as a parcelable and transferring across processes
105      */
106     @NonNull
toBundle()107     public Bundle toBundle() {
108         Bundle bundle = new Bundle();
109         bundle.putString(KEY_ID, mId);
110         bundle.putBundle(KEY_USER, mUser.toBundle());
111         if (mConversationTitle != null) {
112             bundle.putString(KEY_TITLE, mConversationTitle);
113         }
114         if (mConversationIcon != null) {
115             bundle.putBundle(KEY_ICON, mConversationIcon.toBundle());
116         }
117         bundle.putParcelableArray(KEY_MESSAGES, Message.getBundleArrayForMessages(mMessages));
118         Bundle[] participantBundle =
119                 mParticipants.stream().map(Person::toBundle).toArray(Bundle[]::new);
120         bundle.putParcelableArray(KEY_PARTICIPANTS, participantBundle);
121         if (mActions != null) {
122             bundle.putParcelableArray(
123                     KEY_ACTIONS,
124                     ConversationAction.getBundleArrayForAction(mActions));
125         }
126         bundle.putInt(KEY_UNREAD_COUNT, mUnreadCount);
127         bundle.putBoolean(KEY_IS_MUTED, mIsMuted);
128         bundle.putBundle(KEY_EXTRAS, mExtras);
129         return bundle;
130     }
131 
132     /**
133      * Creates a {@link Conversation} from the given Bundle.
134      */
135     @Nullable
fromBundle(@ullable Bundle bundle)136     public static Conversation fromBundle(@Nullable Bundle bundle) {
137         if (bundle == null) {
138             return null;
139         }
140         if (bundle.getString(KEY_ID) == null
141                 || bundle.getBundle(KEY_USER) == null
142                 || bundle.getParcelableArray(KEY_MESSAGES) == null
143                 || bundle.getParcelableArray(KEY_PARTICIPANTS) == null
144         ) {
145             return null;
146         }
147         List<Person> participants =
148                 stream(bundle.getParcelableArray(KEY_PARTICIPANTS))
149                         .map(
150                                 personBundle ->
151                                         personBundle instanceof Bundle
152                                                 ? Person.fromBundle((Bundle) personBundle)
153                                                 : null)
154                         .filter(Objects::nonNull)
155                         .collect(Collectors.toList());
156 
157         Bundle iconBundle = bundle.getBundle(KEY_ICON);
158         IconCompat icon = iconBundle == null
159                 ? null : IconCompat.createFromBundle(iconBundle);
160         return new Conversation(
161                 Objects.requireNonNull(bundle.getString(KEY_ID)),
162                 Person.fromBundle(Objects.requireNonNull(bundle.getBundle(KEY_USER))),
163                 bundle.getString(KEY_TITLE),
164                 icon,
165                 Message.getMessagesFromBundleArray(
166                         Objects.requireNonNull(bundle.getParcelableArray(KEY_MESSAGES))),
167                 participants,
168                 ConversationAction.getActionsFromBundleArray(
169                         bundle.getParcelableArray(KEY_ACTIONS)
170                 ),
171                 bundle.getInt(KEY_UNREAD_COUNT),
172                 bundle.getBoolean(KEY_IS_MUTED),
173                 bundle.getBundle(KEY_EXTRAS)
174         );
175 
176     }
177 
178     /**
179      * Gets the unique identifier for this conversation
180      */
181     @NonNull
getId()182     public String getId() {
183         return mId;
184     }
185 
186     /**
187      * Gets the current user for this conversation.
188      * <p>
189      * The user receives inbound messages and sends outbound messages from the messaging
190      * app/device.
191      */
192     @NonNull
getUser()193     public Person getUser() {
194         return mUser;
195     }
196 
197     /**
198      * Gets conversation title.
199      *
200      * @return null if conversation title is not set, or {@link String} if set
201      */
202     @Nullable
getConversationTitle()203     public String getConversationTitle() {
204         return mConversationTitle;
205     }
206 
207     /**
208      * Gets conversation icon.
209      *
210      * @return null if conversation icon is not set, or {@link IconCompat} if set
211      */
212     @Nullable
getConversationIcon()213     public IconCompat getConversationIcon() {
214         return mConversationIcon;
215     }
216 
217     /**
218      * Gets the list of messages conveyed by this conversation.
219      */
220     @NonNull
getMessages()221     public List<Message> getMessages() {
222         return mMessages;
223     }
224 
225     /**
226      * Gets the participants in the conversation, excluding the user
227      *
228      * @return list of participants or empty if not set
229      */
230     @NonNull
getParticipants()231     public List<Person> getParticipants() {
232         return mParticipants;
233     }
234 
235     /**
236      * Gets the actions in the conversation.
237      */
238     @Nullable
getActions()239     public List<ConversationAction> getActions() {
240         return mActions;
241     }
242 
243     /**
244      * Get the number of unread messages
245      *
246      * @return 0 if no unread messages, or a positive number when there are unread messages.
247      */
getUnreadCount()248     public int getUnreadCount() {
249         return mUnreadCount;
250     }
251 
252     /**
253      * Gets if the conversation should be muted based on the user's preference. This is useful when
254      * posting a notification.
255      */
isMuted()256     public boolean isMuted() {
257         return mIsMuted;
258     }
259 
260     /**
261      * Gets the extras for the conversation. This can be used to hold additional data on the
262      * conversation.
263      */
264     @NonNull
getExtras()265     public Bundle getExtras() {
266         return mExtras;
267     }
268 
269     /**
270      * Creates and returns a new {@link Conversation.Builder} initialized with this Conversation's
271      * data
272      */
273     @NonNull
toBuilder()274     public Conversation.Builder toBuilder() {
275         return new Conversation.Builder(this);
276     }
277 
278     /**
279      * Builder class for {@link Conversation} objects.
280      */
281     public static class Builder {
282         @NonNull
283         private final String mId;
284         @NonNull
285         private final Person mUser;
286         @Nullable
287         private String mConversationTitle;
288         @Nullable
289         private IconCompat mConversationIcon;
290         @NonNull
291         private List<Message> mMessages;
292         @NonNull
293         private List<Person> mParticipants;
294         @Nullable
295         private List<ConversationAction> mActions;
296         private int mUnreadCount;
297         private boolean mIsMuted;
298         @NonNull
299         private Bundle mExtras;
300 
301         /**
302          * Constructs a new builder from a {@link Conversation}?
303          *
304          * @param conversation the conversation containing the data to initialize builder with
305          */
Builder(@onNull Conversation conversation)306         private Builder(@NonNull Conversation conversation) {
307             mUser = conversation.mUser;
308             mId = conversation.mId;
309             mMessages = conversation.mMessages;
310             mConversationTitle = conversation.getConversationTitle();
311             mConversationIcon = conversation.getConversationIcon();
312             mParticipants = conversation.getParticipants();
313             mActions = conversation.getActions();
314             mUnreadCount = conversation.getUnreadCount();
315             mIsMuted = conversation.isMuted();
316             mExtras = conversation.getExtras();
317         }
318 
319         /**
320          * Constructs a new builder for {@link Conversation}.
321          *
322          * @param conversationId the unique identifier of this conversation.
323          * @param user           The name of the other participant in the conversation.
324          */
Builder( @onNull Person user, @NonNull String conversationId )325         public Builder(
326                 @NonNull Person user,
327                 @NonNull String conversationId
328         ) {
329             mUser = user;
330             mId = conversationId;
331             mMessages = new ArrayList<>();
332             mParticipants = new ArrayList<>();
333             mExtras = new Bundle();
334         }
335 
336         /**
337          * Sets list of messages for this conversation.
338          *
339          * @param messages List of messages
340          * @return This object for method chaining.
341          */
342         @NonNull
setMessages(@onNull List<Message> messages)343         public Builder setMessages(@NonNull List<Message> messages) {
344             mMessages = messages;
345             return this;
346         }
347 
348         /**
349          * Sets list of participants for this conversation
350          *
351          * @return This object for method chaining.
352          */
353         @NonNull
setParticipants(@onNull List<Person> participants)354         public Builder setParticipants(@NonNull List<Person> participants) {
355             mParticipants = participants;
356             return this;
357         }
358 
359         /**
360          * Sets list of messages for this conversation.
361          *
362          * @param actions list of actions
363          * @return This object for method chaining.
364          */
365         @NonNull
setActions(@onNull List<ConversationAction> actions)366         public Builder setActions(@NonNull List<ConversationAction> actions) {
367             mActions = actions;
368             return this;
369         }
370 
371         /**
372          * Sets conversation title which would be used when displaying the name of the
373          * conversation.
374          * <p>
375          * This could be the sender name for a 1-1 message, or an appended string of participants
376          * name for a group message, or a nickname for the group.
377          *
378          * @param conversationTitle the title to refer to the conversation
379          * @return This object for method chaining
380          */
381         @NonNull
setConversationTitle(@ullable String conversationTitle)382         public Builder setConversationTitle(@Nullable String conversationTitle) {
383             mConversationTitle = conversationTitle;
384             return this;
385         }
386 
387         /**
388          * Sets conversation icon for display purposes
389          *
390          * @param conversationIcon This could be the sender icon for a 1-1 message or a collage of
391          *                         participants' icons for a group message, or an icon the user
392          *                         uploaded as the conversation icon.
393          * @return This object for method chaining.
394          */
395         @NonNull
setConversationIcon(@ullable IconCompat conversationIcon)396         public Builder setConversationIcon(@Nullable IconCompat conversationIcon) {
397             mConversationIcon = conversationIcon;
398             return this;
399         }
400 
401         /**
402          * Sets the number of unread message, default is 0
403          *
404          * @return This object for method chaining.
405          */
406         @NonNull
setUnreadCount(int unreadCount)407         public Builder setUnreadCount(int unreadCount) {
408             mUnreadCount = unreadCount;
409             return this;
410         }
411 
412         /**
413          * Sets if this conversation should be muted per user's request A muted conversation may not
414          * post notifications for instance.
415          *
416          * @return This object for method chaining.
417          */
418         @NonNull
setMuted(boolean muted)419         public Builder setMuted(boolean muted) {
420             mIsMuted = muted;
421             return this;
422         }
423 
424         /**
425          * Sets bundle extra for the conversation
426          *
427          * @param extras this could contain additional data on the conversation
428          * @return This object for method chaining.
429          */
430         @NonNull
setExtras(@onNull Bundle extras)431         public Builder setExtras(@NonNull Bundle extras) {
432             mExtras = extras;
433             return this;
434         }
435 
436         /**
437          * Builds a new unread conversation object.
438          *
439          * @return The new unread conversation object.
440          */
441         @NonNull
build()442         public Conversation build() {
443             return new Conversation(
444                     mId,
445                     mUser,
446                     mConversationTitle,
447                     mConversationIcon,
448                     mMessages,
449                     mParticipants,
450                     mActions,
451                     mUnreadCount,
452                     mIsMuted,
453                     mExtras
454             );
455         }
456     }
457 
458     /**
459      * A class representing the metadata for a conversation message.
460      */
461     public static final class Message {
462         private static final String KEY_TEXT = "text";
463         private static final String KEY_TIMESTAMP = "time";
464         private static final String KEY_SENDER = "sender";
465         private static final String KEY_DATA_MIME_TYPE = "type";
466         private static final String KEY_DATA_URI = "uri";
467 
468         @NonNull
469         private final String mText;
470         private final long mTimestamp;
471         @Nullable
472         private final Person mPerson;
473         @MessageStatus
474         private int mMessageStatus;
475         @MessageType
476         private int mMessageType;
477 
478         @NonNull
479         private final Bundle mExtras = new Bundle();
480         @Nullable
481         private String mDataMimeType;
482         @Nullable
483         private Uri mDataUri;
484 
485         /**
486          * Creates a new {@link Message} with the given text, timestamp, and sender.
487          *
488          * @param text      A {@link String} to be displayed as the message content
489          * @param timestamp Time at which the message arrived in ms since Unix epoch
490          * @param person    A {@link Person} whose {@link Person#getName()} value is used as the
491          *                  display name for the sender. For messages by the current user, this
492          *                  should be identical to {@link Conversation#getUser()}
493          */
Message(@onNull String text, long timestamp, @Nullable Person person)494         public Message(@NonNull String text, long timestamp, @Nullable Person person) {
495             mText = text;
496             mTimestamp = timestamp;
497             mPerson = person;
498         }
499 
toBundle()500         private Bundle toBundle() {
501             Bundle bundle = new Bundle();
502             bundle.putString(KEY_TEXT, mText);
503             bundle.putLong(KEY_TIMESTAMP, mTimestamp);
504             if (mPerson != null) {
505                 bundle.putBundle(KEY_SENDER, mPerson.toBundle());
506             }
507             if (mDataMimeType != null) {
508                 bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
509             }
510             if (mDataUri != null) {
511                 bundle.putParcelable(KEY_DATA_URI, mDataUri);
512             }
513             return bundle;
514         }
515 
getBundleArrayForMessages(List<Message> messages)516         private static Bundle[] getBundleArrayForMessages(List<Message> messages) {
517             int size = messages.size();
518             Bundle[] bundles = new Bundle[size];
519             for (int i = 0; i < size; i++) {
520                 bundles[i] = messages.get(i).toBundle();
521             }
522             return bundles;
523         }
524 
getMessagesFromBundleArray(Parcelable[] bundles)525         private static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
526             List<Message> messages = new ArrayList<>(bundles.length);
527             for (Parcelable element : bundles) {
528                 if (element instanceof Bundle) {
529                     Message message = getMessageFromBundle((Bundle) element);
530                     if (message != null) {
531                         messages.add(message);
532                     }
533                 }
534             }
535             return messages;
536         }
537 
538         @Nullable
getMessageFromBundle(Bundle bundle)539         private static Message getMessageFromBundle(Bundle bundle) {
540             if (!bundle.containsKey(KEY_TEXT)
541                     || !bundle.containsKey(KEY_TIMESTAMP)
542                     || !bundle.containsKey(KEY_SENDER)
543             ) {
544                 return null;
545             }
546             Message message = new Message(
547                     Objects.requireNonNull(bundle.getString(KEY_TEXT)),
548                     bundle.getLong(KEY_TIMESTAMP),
549                     Person.fromBundle(Objects.requireNonNull(bundle.getBundle(KEY_SENDER))
550                     )
551             );
552             if (bundle.containsKey(KEY_DATA_MIME_TYPE)
553                     && bundle.containsKey(KEY_DATA_URI)) {
554                 try {
555                     message.setData(
556                             Objects.requireNonNull(bundle.getString(KEY_DATA_MIME_TYPE)),
557                             Objects.requireNonNull(bundle.getParcelable(KEY_DATA_URI))
558                     );
559                 } catch (ClassCastException e) {
560                     Log.w(TAG, "Failed to set Data Type/Uri");
561                 }
562             }
563             return message;
564         }
565 
566         /**
567          * Sets a binary blob of data and an associated MIME type for a message. In the case where
568          * the platform doesn't support the MIME type, the original text provided in the constructor
569          * will be used.
570          *
571          * @param dataMimeType The MIME type of the content.
572          * @param dataUri      The uri containing the content whose type is given by the MIME type.
573          * @return this object for method chaining
574          */
575         @NonNull
setData(@onNull String dataMimeType, @NonNull Uri dataUri)576         public Message setData(@NonNull String dataMimeType, @NonNull Uri dataUri) {
577             mDataMimeType = dataMimeType;
578             mDataUri = dataUri;
579             return this;
580         }
581 
582         /**
583          * Sets the message status for a message.
584          */
585         @NonNull
setMessageStatus(@essageStatus int messageStatus)586         public Message setMessageStatus(@MessageStatus int messageStatus) {
587             mMessageStatus = messageStatus;
588             return this;
589         }
590 
591         /**
592          * Sets the message type for a message.
593          *
594          * @return this object for method chaining
595          */
596         @NonNull
setMessageType(@essageType int messageType)597         public Message setMessageType(@MessageType int messageType) {
598             mMessageType = messageType;
599             return this;
600         }
601 
602         /**
603          * Get the text to be used for this message, or the fallback text if a type and content Uri
604          * have been set
605          */
606         @NonNull
getText()607         public String getText() {
608             return mText;
609         }
610 
611         /**
612          * Get the time at which this message arrived in ms since Unix epoch.
613          */
getTimestamp()614         public long getTimestamp() {
615             return mTimestamp;
616         }
617 
618         /**
619          * Gets the message status for this message
620          */
621         @MessageStatus
getMessageStatus()622         public int getMessageStatus() {
623             return mMessageStatus;
624         }
625 
626         /**
627          * Get the message type for this message
628          */
629         @MessageType
getMessageType()630         public int getMessageType() {
631             return mMessageType;
632         }
633 
634         /**
635          * Get the extras Bundle for this message.
636          */
637         @NonNull
getExtras()638         public Bundle getExtras() {
639             return mExtras;
640         }
641 
642         /**
643          * Returns the {@link Person} sender of this message.
644          */
645         @Nullable
getPerson()646         public Person getPerson() {
647             return mPerson;
648         }
649 
650         /**
651          * Get the MIME type of the data pointed to by the URI.
652          */
653         @Nullable
getDataMimeType()654         public String getDataMimeType() {
655             return mDataMimeType;
656         }
657 
658         /**
659          * Get the Uri pointing to the content of the message. Can be null, in which case {@see
660          * #getText()} is used.
661          */
662         @Nullable
getDataUri()663         public Uri getDataUri() {
664             return mDataUri;
665         }
666 
667         /**
668          * Indicates the message status of the message.
669          */
670         @IntDef(value = {
671                 MessageStatus.MESSAGE_STATUS_NONE,
672                 MessageStatus.MESSAGE_STATUS_UNREAD,
673                 MessageStatus.MESSAGE_STATUS_SEEN,
674                 MessageStatus.MESSAGE_STATUS_READ,
675         })
676         @Retention(RetentionPolicy.SOURCE)
677         public @interface MessageStatus {
678             /**
679              * {@code MessageStatus}: No message status defined.
680              */
681             int MESSAGE_STATUS_NONE = 0;
682 
683             /**
684              * {@code MessageStatus}: Message is unread
685              */
686             int MESSAGE_STATUS_UNREAD = 1;
687 
688             /**
689              * {@code MessageStatus}: Message is seen; The "seen" flag determines whether we need to
690              * show a notification.
691              */
692             int MESSAGE_STATUS_SEEN = 2;
693 
694             /**
695              * {@code MessageStatus}: Message is read
696              */
697             int MESSAGE_STATUS_READ = 3;
698 
699         }
700 
701         /**
702          * Indicates the message status of the message.
703          */
704         @IntDef(value = {
705                 MessageType.MESSAGE_TYPE_ALL,
706                 MessageType.MESSAGE_TYPE_INBOX,
707                 MessageType.MESSAGE_TYPE_SENT,
708                 MessageType.MESSAGE_TYPE_DRAFT,
709                 MessageType.MESSAGE_TYPE_FAILED,
710                 MessageType.MESSAGE_TYPE_OUTBOX,
711                 MessageType.MESSAGE_TYPE_QUEUED
712         })
713         @Retention(RetentionPolicy.SOURCE)
714         public @interface MessageType {
715 
716             /**
717              * Message type: all messages.
718              */
719             int MESSAGE_TYPE_ALL = 0;
720 
721             /**
722              * Message type: inbox.
723              */
724             int MESSAGE_TYPE_INBOX = 1;
725 
726             /**
727              * Message type: sent messages.
728              */
729             int MESSAGE_TYPE_SENT = 2;
730 
731             /**
732              * Message type: drafts.
733              */
734             int MESSAGE_TYPE_DRAFT = 3;
735 
736             /**
737              * Message type: outbox.
738              */
739             int MESSAGE_TYPE_OUTBOX = 4;
740 
741             /**
742              * Message type: failed outgoing message.
743              */
744             int MESSAGE_TYPE_FAILED = 5;
745 
746             /**
747              * Message type: queued to send later.
748              */
749             int MESSAGE_TYPE_QUEUED = 6;
750         }
751     }
752 
753     /**
754      * {@link ConversationAction} provides the actions for this conversation. The semantic action
755      * indicates the type of action represented. The remote action holds the pending intent to be
756      * fired The remote input holds a key by which responses can be filled into.
757      */
758     public static class ConversationAction {
759         private static final String KEY_TYPE = "type";
760         private static final String KEY_REMOTE_ACTION = "remote_action";
761         private static final String KEY_REMOTE_INPUT = "remote_input";
762         private static final String KEY_EXTRAS = "extras";
763 
764         @ActionType
765         private final int mActionType;
766         @NonNull
767         private final RemoteAction mRemoteAction;
768         @Nullable
769         private final RemoteInput mRemoteInput;
770         @NonNull
771         private final Bundle mExtras = new Bundle();
772 
ConversationAction( @ctionType int actionType, @NonNull RemoteAction remoteAction, @Nullable RemoteInput remoteInput)773         public ConversationAction(
774                 @ActionType int actionType,
775                 @NonNull RemoteAction remoteAction,
776                 @Nullable RemoteInput remoteInput) {
777             mActionType = actionType;
778             mRemoteAction = remoteAction;
779             mRemoteInput = remoteInput;
780         }
781 
getBundleArrayForAction(List<ConversationAction> actions)782         private static Bundle[] getBundleArrayForAction(List<ConversationAction> actions) {
783             int size = actions.size();
784             Bundle[] bundles = new Bundle[size];
785             for (int i = 0; i < size; i++) {
786                 bundles[i] = actions.get(i).toBundle();
787             }
788             return bundles;
789         }
790 
791         @Nullable
getActionsFromBundleArray( @ullable Parcelable[] bundles )792         private static List<ConversationAction> getActionsFromBundleArray(
793                 @Nullable Parcelable[] bundles
794         ) {
795             if (bundles == null) {
796                 return null;
797             }
798             List<ConversationAction> actions = new ArrayList<>(bundles.length);
799             for (Parcelable element : bundles) {
800                 if (element instanceof Bundle) {
801                     ConversationAction action = fromBundle((Bundle) element);
802                     if (action != null) {
803                         actions.add(action);
804                     }
805                 }
806             }
807             return actions;
808         }
809 
toBundle()810         private Bundle toBundle() {
811             Bundle bundle = new Bundle();
812             bundle.putInt(KEY_TYPE, mActionType);
813             bundle.putParcelable(KEY_REMOTE_ACTION, mRemoteAction);
814             bundle.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
815             bundle.putBundle(KEY_EXTRAS, mExtras);
816             return bundle;
817         }
818 
819         @Nullable
fromBundle(@ullable Bundle bundle)820         private static ConversationAction fromBundle(@Nullable Bundle bundle) {
821             if (bundle == null
822                     || !bundle.containsKey(KEY_REMOTE_ACTION)
823             ) {
824                 return null;
825             }
826             ConversationAction action = new ConversationAction(
827                     bundle.getInt(KEY_TYPE),
828                     bundle.getParcelable(KEY_REMOTE_ACTION),
829                     bundle.getParcelable(KEY_REMOTE_INPUT)
830             );
831             action.getExtras().putAll(bundle.getBundle(KEY_EXTRAS));
832             return action;
833         }
834 
835         @ActionType
getActionType()836         public int getActionType() {
837             return mActionType;
838         }
839 
840         @NonNull
getRemoteAction()841         public RemoteAction getRemoteAction() {
842             return mRemoteAction;
843         }
844 
845         @Nullable
getRemoteInput()846         public RemoteInput getRemoteInput() {
847             return mRemoteInput;
848         }
849 
850         @NonNull
getExtras()851         public Bundle getExtras() {
852             return mExtras;
853         }
854 
855         /**
856          * Provides meaning to an {@link ConversationAction} that hints at what the associated
857          * {@link PendingIntent} will do. For example, an {@link ConversationAction} with a {@link
858          * PendingIntent} that replies to a text message may have the {@link #ACTION_TYPE_REPLY}
859          * {@code ActionType} set within it.
860          */
861         @IntDef(value = {
862                 ActionType.ACTION_TYPE_NONE,
863                 ActionType.ACTION_TYPE_REPLY,
864                 ActionType.ACTION_TYPE_MARK_AS_READ,
865                 ActionType.ACTION_TYPE_MARK_AS_UNREAD,
866                 ActionType.ACTION_TYPE_DELETE,
867                 ActionType.ACTION_TYPE_ARCHIVE,
868                 ActionType.ACTION_TYPE_MUTE,
869                 ActionType.ACTION_TYPE_UNMUTE
870         })
871         @Retention(RetentionPolicy.SOURCE)
872         public @interface ActionType {
873 
874             /**
875              * {@code ActionType}: No semantic action defined.
876              */
877             int ACTION_TYPE_NONE = 0;
878 
879             /**
880              * {@code ActionType}: Reply to a conversation, chat, group, or wherever replies may be
881              * appropriate.
882              */
883             int ACTION_TYPE_REPLY = 1;
884 
885             /**
886              * {@code ActionType}: Mark content as read.
887              */
888             int ACTION_TYPE_MARK_AS_READ = 2;
889 
890             /**
891              * {@code ActionType}: Mark content as unread.
892              */
893             int ACTION_TYPE_MARK_AS_UNREAD = 3;
894 
895             /**
896              * {@code ActionType}: Delete the content associated with the conversation.
897              */
898             int ACTION_TYPE_DELETE = 4;
899 
900             /**
901              * {@code ActionType}: Archive the content associated with the conversation. This could
902              * mean hiding a conversation, or placing it in an archived list.
903              */
904             int ACTION_TYPE_ARCHIVE = 5;
905 
906             /**
907              * {@code ActionType}: Mute the content associated with the conversation. This could
908              * mean silencing a conversation or currently playing media.
909              */
910             int ACTION_TYPE_MUTE = 6;
911 
912             /**
913              * {@code ActionType}: Unmute the content associated with the conversation. This could
914              * mean un-silencing a conversation or currently playing media.
915              */
916             int ACTION_TYPE_UNMUTE = 7;
917         }
918     }
919 }
920