Fix receiving old text message in HUN

Changes cursor queries to split into SMS and MMS to get around timestamp inconsistency.
PiperOrigin-RevId: 408770794
Change-Id: I9ad9589a0df93d3d27744b7353849a119b5a160c
Bug: 195684900
diff --git a/src/com/android/car/messenger/impl/datamodels/NewMessageLiveData.java b/src/com/android/car/messenger/impl/datamodels/NewMessageLiveData.java
index f725418..a5e0922 100644
--- a/src/com/android/car/messenger/impl/datamodels/NewMessageLiveData.java
+++ b/src/com/android/car/messenger/impl/datamodels/NewMessageLiveData.java
@@ -65,7 +65,7 @@
     private final CarStateListener mCarStateListener = AppFactory.get().getCarStateListener();
 
     NewMessageLiveData() {
-        super(Telephony.Sms.CONTENT_URI, Telephony.Mms.CONTENT_URI, Telephony.MmsSms.CONTENT_URI);
+        super(Telephony.MmsSms.CONTENT_URI);
     }
 
     @Override
diff --git a/src/com/android/car/messenger/impl/datamodels/util/ConversationFetchUtil.java b/src/com/android/car/messenger/impl/datamodels/util/ConversationFetchUtil.java
index 5cea752..b066417 100644
--- a/src/com/android/car/messenger/impl/datamodels/util/ConversationFetchUtil.java
+++ b/src/com/android/car/messenger/impl/datamodels/util/ConversationFetchUtil.java
@@ -55,17 +55,25 @@
     public static Conversation fetchConversation(@NonNull String conversationId) {
         L.d("Fetching latest data for Conversation " + conversationId);
         Conversation.Builder conversationBuilder = initConversationBuilder(conversationId);
-        Cursor messagesCursor =
-                CursorUtils.getMessagesCursor(conversationId, MESSAGE_LIMIT, /* offset= */ 0);
+        Cursor mmsCursor = getMmsCursor(conversationId);
+        Cursor smsCursor = getSmsCursor(conversationId);
+
+        // message list sorted by date desc
+        List<Conversation.Message> messages =
+                MessageUtils.getMessages(MESSAGE_LIMIT, mmsCursor, smsCursor);
+
         // messages to read: first get unread messages
-        List<Conversation.Message> messagesToRead = MessageUtils.getUnreadMessages(messagesCursor);
+        // List should truncate at the latest reply or read message since reading a recent message
+        // does not mark all previous messages read.
+        List<Conversation.Message> messagesToRead = MessageUtils.getUnreadMessages(messages);
+
         int unreadCount = messagesToRead.size();
         Conversation.Message lastReply = null;
 
         // if no unread messages, get read messages
         if (messagesToRead.isEmpty()) {
             Pair<List<Conversation.Message>, Conversation.Message> readMessagesAndReplyTimestamp =
-                    MessageUtils.getReadMessagesAndReplyTimestamp(messagesCursor);
+                    MessageUtils.getReadMessagesAndReplyTimestamp(messages);
             messagesToRead = readMessagesAndReplyTimestamp.first;
             lastReply = readMessagesAndReplyTimestamp.second;
         }
@@ -152,4 +160,14 @@
         return sharedPreferences.getStringSet(
                 MessageConstants.KEY_MUTED_CONVERSATIONS, new HashSet<>());
     }
+
+    private static Cursor getMmsCursor(@NonNull String conversationId) {
+        return CursorUtils.getMessagesCursor(
+                conversationId, MESSAGE_LIMIT, /* offset= */ 0, CursorUtils.ContentType.MMS);
+    }
+
+    private static Cursor getSmsCursor(@NonNull String conversationId) {
+        return CursorUtils.getMessagesCursor(
+                conversationId, MESSAGE_LIMIT, /* offset= */ 0, CursorUtils.ContentType.SMS);
+    }
 }
diff --git a/src/com/android/car/messenger/impl/datamodels/util/CursorUtils.java b/src/com/android/car/messenger/impl/datamodels/util/CursorUtils.java
index 5ce2302..93b6a85 100644
--- a/src/com/android/car/messenger/impl/datamodels/util/CursorUtils.java
+++ b/src/com/android/car/messenger/impl/datamodels/util/CursorUtils.java
@@ -28,6 +28,8 @@
 import static android.provider.Telephony.ThreadsColumns.READ;
 import static android.provider.Telephony.ThreadsColumns.RECIPIENT_IDS;
 
+import static com.android.car.messenger.impl.datamodels.util.MmsUtils.MMS_CONTENT_TYPE;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
@@ -59,6 +61,16 @@
     @NonNull
     public static final String DEFAULT_SORT_ORDER = Telephony.TextBasedSmsColumns.DATE + " DESC";
 
+    private static final String MMS_QUERY =
+            CONTENT_TYPE + " = '" + MMS_CONTENT_TYPE + "' AND " + DATE + " > ";
+    private static final String SMS_QUERY = CONTENT_TYPE + " IS NULL AND " + DATE + " > ";
+
+    /** This enum is used for describing the type of message being fetched by a cursor */
+    public enum ContentType {
+        SMS,
+        MMS
+    }
+
     /**
      * Get simplified thread cursor with metadata information on the thread, such as recipient ids
      */
@@ -82,13 +94,19 @@
      * @param offset The starting point in timestamp in millisecond to fetch for data
      */
     @Nullable
-    public static Cursor getMessagesCursor(@NonNull String conversationId, int limit, long offset) {
+    public static Cursor getMessagesCursor(@NonNull String conversationId, int limit, long offset,
+            @NonNull ContentType contentType) {
         Context context = AppFactory.get().getContext();
         ContentResolver contentResolver = context.getContentResolver();
+
+        String query = contentType == ContentType.MMS
+                ? MMS_QUERY + offset / 1000
+                : SMS_QUERY + offset;
+
         return contentResolver.query(
                 getConversationUri(conversationId),
                 CONTENT_CONVERSATION_PROJECTION,
-                DATE + " > " + offset,
+                query,
                 /* selectionArgs= */ null,
                 DEFAULT_SORT_ORDER + " LIMIT " + limit);
     }
diff --git a/src/com/android/car/messenger/impl/datamodels/util/MessageUtils.java b/src/com/android/car/messenger/impl/datamodels/util/MessageUtils.java
index 555d91c..0bb454b 100644
--- a/src/com/android/car/messenger/impl/datamodels/util/MessageUtils.java
+++ b/src/com/android/car/messenger/impl/datamodels/util/MessageUtils.java
@@ -20,6 +20,7 @@
 import static com.android.car.messenger.common.Conversation.Message.MessageStatus.MESSAGE_STATUS_READ;
 import static com.android.car.messenger.common.Conversation.Message.MessageStatus.MESSAGE_STATUS_UNREAD;
 
+import static java.lang.Math.min;
 import static java.util.Comparator.comparingLong;
 
 import android.content.Context;
@@ -47,61 +48,80 @@
 public final class MessageUtils {
 
     /**
-     * Gets all unread messages in cursor
+     * Returns all messages in the given cursors.
      *
-     * @param messagesCursor The messageCursor in descending order
+     * @param limit The maximum number of messages
+     * @param messageCursors The messageCursors of messages in descending order
      */
     @NonNull
-    public static List<Message> getUnreadMessages(@Nullable Cursor messagesCursor) {
-        List<Message> unreadMessages = new ArrayList<>();
-        MessageUtils.forEachDesc(
-                messagesCursor,
-                message -> {
-                    if (message.getMessageStatus() == MessageStatus.MESSAGE_STATUS_UNREAD) {
-                        unreadMessages.add(message);
+    public static List<Message> getMessages(int limit, @Nullable Cursor... messageCursors) {
+        List<Message> messages = new ArrayList<>();
+        for (Cursor cursor : messageCursors) {
+            MessageUtils.forEachDesc(
+                    cursor,
+                    message -> {
+                        messages.add(message);
                         return true;
-                    }
-                    return false;
-                });
-        unreadMessages.sort(comparingLong(Message::getTimestamp));
+                    });
+        }
+        messages.sort(comparingLong(Message::getTimestamp).reversed());
+        return messages.subList(0, min(limit, messages.size()));
+    }
+
+    /**
+     * Returns unread messages from a conversation, in ascending order.
+     *
+     * @param messages The messages in descending order
+     */
+    @NonNull
+    public static List<Message> getUnreadMessages(@NonNull List<Message> messages) {
+        int i = 0;
+        for (Conversation.Message message : messages) {
+            if (message.getMessageStatus() != MessageStatus.MESSAGE_STATUS_UNREAD) {
+                break;
+            }
+            i++;
+        }
+        List<Message> unreadMessages = messages.subList(0, i);
+        unreadMessages.sort(comparingLong(Conversation.Message::getTimestamp));
         return unreadMessages;
     }
 
+
     /**
      * Gets Read Messages and Last Reply
      *
-     * @param messagesCursor MessageCursor in descending order
+     * @param messages List of messages in descending order
      */
     @NonNull
     public static Pair<List<Message>, Message> getReadMessagesAndReplyTimestamp(
-            @Nullable Cursor messagesCursor) {
+            @Nullable List<Message> messages) {
         List<Message> readMessages = new ArrayList<>();
         AtomicReference<Message> replyMessage = new AtomicReference<>();
         AtomicReference<Long> lastReply = new AtomicReference<>(0L);
-        MessageUtils.forEachDesc(
-                messagesCursor,
-                message -> {
-                    // Desired impact: 4. Reply -> 3. Messages -> 2. Reply -> 1 Messages (stop
-                    // parsing at 2.)
-                    // lastReply references 4., messages references 3.
-                    // Desired impact: 3. Messages -> 2. Reply -> 1. Messages (stop parsing at 2.)
-                    // lastReply references 2., messages references 3.
-                    int messageStatus = message.getMessageStatus();
-                    if (message.getMessageType() == MessageType.MESSAGE_TYPE_SENT) {
-                        if (lastReply.get() < message.getTimestamp()) {
-                            lastReply.set(message.getTimestamp());
-                            replyMessage.set(message);
-                        }
-                        return readMessages.isEmpty();
-                    }
 
-                    if (messageStatus == MessageStatus.MESSAGE_STATUS_READ
-                            || messageStatus == MessageStatus.MESSAGE_STATUS_NONE) {
-                        readMessages.add(message);
-                        return true;
-                    }
-                    return false;
-                });
+        for (Message message : messages) {
+            // Desired impact: 4. Reply -> 3. Messages -> 2. Reply -> 1 Messages (stop
+            // parsing at 2.)
+            // lastReply references 4., messages references 3.
+            // Desired impact: 3. Messages -> 2. Reply -> 1. Messages (stop parsing at 2.)
+            // lastReply references 2., messages references 3.
+            int messageStatus = message.getMessageStatus();
+            if (message.getMessageType() == MessageType.MESSAGE_TYPE_SENT) {
+                if (lastReply.get() < message.getTimestamp()) {
+                    lastReply.set(message.getTimestamp());
+                    replyMessage.set(message);
+                }
+                if (!readMessages.isEmpty()) {
+                    break;
+                }
+            } else if (messageStatus == MessageStatus.MESSAGE_STATUS_READ
+                    || messageStatus == MessageStatus.MESSAGE_STATUS_NONE) {
+                readMessages.add(message);
+            } else {
+                break;
+            }
+        }
         readMessages.sort(comparingLong(Message::getTimestamp));
         return new Pair<>(readMessages, replyMessage.get());
     }
@@ -113,7 +133,7 @@
      * @param processor A consumer that takes in the {@link Message} and returns true for the method
      *     to continue parsing the cursor or false to return.
      */
-    public static void forEachDesc(
+    private static void forEachDesc(
             @Nullable Cursor messageCursor, @NonNull Function<Message, Boolean> processor) {
         if (messageCursor == null || !messageCursor.moveToFirst()) {
             return;