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