diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java index 39e16f0103..de2de92aab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java @@ -22,7 +22,9 @@ import android.graphics.Typeface; import android.graphics.drawable.RippleDrawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import android.text.Spannable; import android.text.SpannableString; +import android.text.TextUtils; import android.text.style.StyleSpan; import android.util.AttributeSet; import android.view.View; @@ -41,6 +43,9 @@ import org.thoughtcrime.securesms.components.DeliveryStatusView; import org.thoughtcrime.securesms.components.FromTextView; import org.thoughtcrime.securesms.components.ThumbnailView; import org.thoughtcrime.securesms.components.TypingIndicatorView; +import org.thoughtcrime.securesms.database.MmsSmsColumns; +import org.thoughtcrime.securesms.database.SmsDatabase; +import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.LiveRecipient; @@ -48,6 +53,8 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.conversationlist.model.MessageResult; import org.thoughtcrime.securesms.util.DateUtils; +import org.thoughtcrime.securesms.util.ExpirationUtil; +import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.SearchUtil; import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.ViewUtil; @@ -94,7 +101,7 @@ public class ConversationListItem extends RelativeLayout private final RecipientForeverObserver groupAddedByObserver = adder -> { if (isAttachedToWindow() && subjectView != null && thread != null) { - subjectView.setText(thread.getDisplayBody(getContext())); + subjectView.setText(getThreadDisplayBody(getContext(), thread)); } }; @@ -176,7 +183,7 @@ public class ConversationListItem extends RelativeLayout this.typingView.stopAnimation(); this.subjectView.setVisibility(VISIBLE); - this.subjectView.setText(getTrimmedSnippet(thread.getDisplayBody(getContext()))); + this.subjectView.setText(getTrimmedSnippet(getThreadDisplayBody(getContext(), thread))); if (thread.getGroupAddedBy() != null) { groupAddedBy = Recipient.live(thread.getGroupAddedBy()); @@ -377,6 +384,99 @@ public class ConversationListItem extends RelativeLayout setRippleColor(recipient); } + + private static SpannableString getThreadDisplayBody(@NonNull Context context, @NonNull ThreadRecord thread) { + if (thread.getGroupAddedBy() != null) { + return emphasisAdded(context.getString(thread.isGv2Invite() ? R.string.ThreadRecord_s_invited_you_to_the_group + : R.string.ThreadRecord_s_added_you_to_the_group, + Recipient.live(thread.getGroupAddedBy()).get().getDisplayName(context))); + } else if (!thread.isMessageRequestAccepted()) { + return emphasisAdded(context.getString(R.string.ThreadRecord_message_request)); + } else if (SmsDatabase.Types.isGroupUpdate(thread.getType())) { + return emphasisAdded(context.getString(R.string.ThreadRecord_group_updated)); + } else if (SmsDatabase.Types.isGroupQuit(thread.getType())) { + return emphasisAdded(context.getString(R.string.ThreadRecord_left_the_group)); + } else if (SmsDatabase.Types.isKeyExchangeType(thread.getType())) { + return emphasisAdded(context.getString(R.string.ConversationListItem_key_exchange_message)); + } else if (SmsDatabase.Types.isFailedDecryptType(thread.getType())) { + return emphasisAdded(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message)); + } else if (SmsDatabase.Types.isNoRemoteSessionType(thread.getType())) { + return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session)); + } else if (SmsDatabase.Types.isEndSessionType(thread.getType())) { + return emphasisAdded(context.getString(R.string.ThreadRecord_secure_session_reset)); + } else if (MmsSmsColumns.Types.isLegacyType(thread.getType())) { + return emphasisAdded(context.getString(R.string.MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported)); + } else if (MmsSmsColumns.Types.isDraftMessageType(thread.getType())) { + String draftText = context.getString(R.string.ThreadRecord_draft); + return emphasisAdded(draftText + " " + thread.getBody(), 0, draftText.length()); + } else if (SmsDatabase.Types.isOutgoingCall(thread.getType())) { + return emphasisAdded(context.getString(org.thoughtcrime.securesms.R.string.ThreadRecord_called)); + } else if (SmsDatabase.Types.isIncomingCall(thread.getType())) { + return emphasisAdded(context.getString(org.thoughtcrime.securesms.R.string.ThreadRecord_called_you)); + } else if (SmsDatabase.Types.isMissedCall(thread.getType())) { + return emphasisAdded(context.getString(org.thoughtcrime.securesms.R.string.ThreadRecord_missed_call)); + } else if (SmsDatabase.Types.isJoinedType(thread.getType())) { + return emphasisAdded(context.getString(R.string.ThreadRecord_s_is_on_signal, thread.getRecipient().toShortString(context))); + } else if (SmsDatabase.Types.isExpirationTimerUpdate(thread.getType())) { + int seconds = (int)(thread.getExpiresIn() / 1000); + if (seconds <= 0) { + return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_messages_disabled)); + } + String time = ExpirationUtil.getExpirationDisplayValue(context, seconds); + return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_message_time_updated_to_s, time)); + } else if (SmsDatabase.Types.isIdentityUpdate(thread.getType())) { + if (thread.getRecipient().isGroup()) { + return emphasisAdded(context.getString(R.string.ThreadRecord_safety_number_changed)); + } else { + return emphasisAdded(context.getString(R.string.ThreadRecord_your_safety_number_with_s_has_changed, thread.getRecipient().toShortString(context))); + } + } else if (SmsDatabase.Types.isIdentityVerified(thread.getType())) { + return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_verified)); + } else if (SmsDatabase.Types.isIdentityDefault(thread.getType())) { + return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_unverified)); + } else if (SmsDatabase.Types.isUnsupportedMessageType(thread.getType())) { + return emphasisAdded(context.getString(R.string.ThreadRecord_message_could_not_be_processed)); + } else { + if (TextUtils.isEmpty(thread.getBody())) { + ThreadDatabase.Extra extra = thread.getExtra(); + if (extra != null && extra.isSticker()) { + return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_sticker))); + } else if (extra != null && extra.isViewOnce()) { + return new SpannableString(emphasisAdded(getViewOnceDescription(context, thread.getContentType()))); + } else if (extra != null && extra.isRemoteDelete()) { + return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_this_message_was_deleted))); + } else { + return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_media_message))); + } + } else { + return new SpannableString(thread.getBody()); + } + } + } + + private static @NonNull SpannableString emphasisAdded(String sequence) { + return emphasisAdded(sequence, 0, sequence.length()); + } + + private static @NonNull SpannableString emphasisAdded(String sequence, int start, int end) { + SpannableString spannable = new SpannableString(sequence); + spannable.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), + start, + end, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return spannable; + } + + private static String getViewOnceDescription(@NonNull Context context, @Nullable String contentType) { + if (MediaUtil.isViewOnceType(contentType)) { + return context.getString(R.string.ThreadRecord_view_once_media); + } else if (MediaUtil.isVideoType(contentType)) { + return context.getString(R.string.ThreadRecord_view_once_video); + } else { + return context.getString(R.string.ThreadRecord_view_once_photo); + } + } + private static class ThumbnailPositioner implements Runnable { private final View thumbnailView; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index eb3f3a40c4..b2c3785494 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -848,31 +848,15 @@ public class ThreadDatabase extends Database { } public ThreadRecord getCurrent() { - long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID)); - int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.TYPE)); - RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_ID))); + RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_ID))); + Recipient recipient = Recipient.live(recipientId).get(); - Recipient recipient = Recipient.live(recipientId).get(); - String body = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET)); - long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE)); - long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)); - int unreadCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_COUNT)); - long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE)); - boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0; - int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS)); - int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DELIVERY_RECEIPT_COUNT)); - int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.READ_RECEIPT_COUNT)); - long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN)); - long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN)); - Uri snippetUri = getSnippetUri(cursor); - String contentType = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_CONTENT_TYPE)); - String extraString = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_EXTRAS)); - if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { - readReceiptCount = 0; - } + int readReceiptCount = TextSecurePreferences.isReadReceiptsEnabled(context) ? cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.READ_RECEIPT_COUNT)) + : 0; - Extra extra = null; + String extraString = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_EXTRAS)); + Extra extra = null; if (extraString != null) { try { @@ -882,9 +866,24 @@ public class ThreadDatabase extends Database { } } - return new ThreadRecord(body, snippetUri, contentType, extra, recipient, date, count, - unreadCount, threadId, deliveryReceiptCount, status, type, - distributionType, archived, expiresIn, lastSeen, readReceiptCount); + return new ThreadRecord.Builder(cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID))) + .setRecipient(recipient) + .setType(cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE))) + .setDistributionType(cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.TYPE))) + .setBody(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET))) + .setDate(cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE))) + .setArchived(cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0) + .setDeliveryStatus(cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS))) + .setDeliveryReceiptCount(cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DELIVERY_RECEIPT_COUNT))) + .setReadReceiptCount(readReceiptCount) + .setExpiresIn(cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN))) + .setLastSeen(cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN))) + .setSnippetUri(getSnippetUri(cursor)) + .setContentType(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_CONTENT_TYPE))) + .setCount(cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT))) + .setUnreadCount(cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_COUNT))) + .setExtra(extra) + .build(); } private @Nullable Uri getSnippetUri(Cursor cursor) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/StatusUtil.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/StatusUtil.java new file mode 100644 index 0000000000..f3e0a5176b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/StatusUtil.java @@ -0,0 +1,29 @@ +package org.thoughtcrime.securesms.database.model; + +import org.thoughtcrime.securesms.database.MmsSmsColumns; +import org.thoughtcrime.securesms.database.SmsDatabase; + +final class StatusUtil { + private StatusUtil() {} + + static boolean isDelivered(long deliveryStatus, int deliveryReceiptCount) { + return (deliveryStatus >= SmsDatabase.Status.STATUS_COMPLETE && + deliveryStatus < SmsDatabase.Status.STATUS_PENDING) || deliveryReceiptCount > 0; + } + + static boolean isPending(long type) { + return MmsSmsColumns.Types.isPendingMessageType(type) && + !MmsSmsColumns.Types.isIdentityVerified(type) && + !MmsSmsColumns.Types.isIdentityDefault(type); + } + + static boolean isFailed(long type, long deliveryStatus) { + return MmsSmsColumns.Types.isFailedMessageType(type) || + MmsSmsColumns.Types.isPendingSecureSmsFallbackType(type) || + deliveryStatus >= SmsDatabase.Status.STATUS_FAILED; + } + + static boolean isVerificationStatusChange(long type) { + return SmsDatabase.Types.isIdentityDefault(type) || SmsDatabase.Types.isIdentityVerified(type); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index dcb1785a6f..4dda834a22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -17,151 +17,83 @@ */ package org.thoughtcrime.securesms.database.model; -import android.content.Context; import android.net.Uri; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.style.StyleSpan; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase.Extra; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.ExpirationUtil; -import org.thoughtcrime.securesms.util.MediaUtil; +import org.whispersystems.libsignal.util.guava.Preconditions; /** - * The message record model which represents thread heading messages. - * - * @author Moxie Marlinspike - * + * Represents an entry in the {@link org.thoughtcrime.securesms.database.ThreadDatabase}. */ -public class ThreadRecord extends DisplayRecord { +public final class ThreadRecord { - private @Nullable final Uri snippetUri; - private @Nullable final String contentType; - private @Nullable final Extra extra; - private final long count; - private final int unreadCount; - private final int distributionType; - private final boolean archived; - private final long expiresIn; - private final long lastSeen; + private final long threadId; + private final String body; + private final Recipient recipient; + private final long type; + private final long date; + private final long deliveryStatus; + private final int deliveryReceiptCount; + private final int readReceiptCount; + private final Uri snippetUri; + private final String contentType; + private final Extra extra; + private final long count; + private final int unreadCount; + private final int distributionType; + private final boolean archived; + private final long expiresIn; + private final long lastSeen; - public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri, - @Nullable String contentType, @Nullable Extra extra, - @NonNull Recipient recipient, long date, long count, int unreadCount, - long threadId, int deliveryReceiptCount, int status, long snippetType, - int distributionType, boolean archived, long expiresIn, long lastSeen, - int readReceiptCount) - { - super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount); - this.snippetUri = snippetUri; - this.contentType = contentType; - this.extra = extra; - this.count = count; - this.unreadCount = unreadCount; - this.distributionType = distributionType; - this.archived = archived; - this.expiresIn = expiresIn; - this.lastSeen = lastSeen; + private ThreadRecord(@NonNull Builder builder) { + this.threadId = builder.threadId; + this.body = builder.body; + this.recipient = builder.recipient; + this.date = builder.date; + this.type = builder.type; + this.deliveryStatus = builder.deliveryStatus; + this.deliveryReceiptCount = builder.deliveryReceiptCount; + this.readReceiptCount = builder.readReceiptCount; + this.snippetUri = builder.snippetUri; + this.contentType = builder.contentType; + this.extra = builder.extra; + this.count = builder.count; + this.unreadCount = builder.unreadCount; + this.distributionType = builder.distributionType; + this.archived = builder.archived; + this.expiresIn = builder.expiresIn; + this.lastSeen = builder.lastSeen; + } + + public long getThreadId() { + return threadId; + } + + public @NonNull Recipient getRecipient() { + return recipient; } public @Nullable Uri getSnippetUri() { return snippetUri; } - @Override - public SpannableString getDisplayBody(@NonNull Context context) { - if (getGroupAddedBy() != null) { - return emphasisAdded(context.getString(isGv2Invite() ? R.string.ThreadRecord_s_invited_you_to_the_group - : R.string.ThreadRecord_s_added_you_to_the_group, - Recipient.live(getGroupAddedBy()).get().getDisplayName(context))); - } else if (!isMessageRequestAccepted()) { - return emphasisAdded(context.getString(R.string.ThreadRecord_message_request)); - } else if (isGroupUpdate()) { - return emphasisAdded(context.getString(R.string.ThreadRecord_group_updated)); - } else if (isGroupQuit()) { - return emphasisAdded(context.getString(R.string.ThreadRecord_left_the_group)); - } else if (isKeyExchange()) { - return emphasisAdded(context.getString(R.string.ConversationListItem_key_exchange_message)); - } else if (SmsDatabase.Types.isFailedDecryptType(type)) { - return emphasisAdded(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message)); - } else if (SmsDatabase.Types.isNoRemoteSessionType(type)) { - return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session)); - } else if (SmsDatabase.Types.isEndSessionType(type)) { - return emphasisAdded(context.getString(R.string.ThreadRecord_secure_session_reset)); - } else if (MmsSmsColumns.Types.isLegacyType(type)) { - return emphasisAdded(context.getString(R.string.MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported)); - } else if (MmsSmsColumns.Types.isDraftMessageType(type)) { - String draftText = context.getString(R.string.ThreadRecord_draft); - return emphasisAdded(draftText + " " + getBody(), 0, draftText.length()); - } else if (SmsDatabase.Types.isOutgoingCall(type)) { - return emphasisAdded(context.getString(org.thoughtcrime.securesms.R.string.ThreadRecord_called)); - } else if (SmsDatabase.Types.isIncomingCall(type)) { - return emphasisAdded(context.getString(org.thoughtcrime.securesms.R.string.ThreadRecord_called_you)); - } else if (SmsDatabase.Types.isMissedCall(type)) { - return emphasisAdded(context.getString(org.thoughtcrime.securesms.R.string.ThreadRecord_missed_call)); - } else if (SmsDatabase.Types.isJoinedType(type)) { - return emphasisAdded(context.getString(R.string.ThreadRecord_s_is_on_signal, getRecipient().toShortString(context))); - } else if (SmsDatabase.Types.isExpirationTimerUpdate(type)) { - int seconds = (int)(getExpiresIn() / 1000); - if (seconds <= 0) { - return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_messages_disabled)); - } - String time = ExpirationUtil.getExpirationDisplayValue(context, seconds); - return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_message_time_updated_to_s, time)); - } else if (SmsDatabase.Types.isIdentityUpdate(type)) { - if (getRecipient().isGroup()) return emphasisAdded(context.getString(R.string.ThreadRecord_safety_number_changed)); - else return emphasisAdded(context.getString(R.string.ThreadRecord_your_safety_number_with_s_has_changed, getRecipient().toShortString(context))); - } else if (SmsDatabase.Types.isIdentityVerified(type)) { - return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_verified)); - } else if (SmsDatabase.Types.isIdentityDefault(type)) { - return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_unverified)); - } else if (SmsDatabase.Types.isUnsupportedMessageType(type)) { - return emphasisAdded(context.getString(R.string.ThreadRecord_message_could_not_be_processed)); - } else { - if (TextUtils.isEmpty(getBody())) { - if (extra != null && extra.isSticker()) { - return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_sticker))); - } else if (extra != null && extra.isViewOnce()) { - return new SpannableString(emphasisAdded(getViewOnceDescription(context, contentType))); - } else if (extra != null && extra.isRemoteDelete()) { - return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_this_message_was_deleted))); - } else { - return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_media_message))); - } - } else { - return new SpannableString(getBody()); - } - } + public @NonNull String getBody() { + return body; } - private SpannableString emphasisAdded(String sequence) { - return emphasisAdded(sequence, 0, sequence.length()); + public @Nullable Extra getExtra() { + return extra; } - private SpannableString emphasisAdded(String sequence, int start, int end) { - SpannableString spannable = new SpannableString(sequence); - spannable.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), - start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - return spannable; - } - - private String getViewOnceDescription(@NonNull Context context, @Nullable String contentType) { - if (MediaUtil.isViewOnceType(contentType)) { - return context.getString(R.string.ThreadRecord_view_once_media); - } else if (MediaUtil.isVideoType(contentType)) { - return context.getString(R.string.ThreadRecord_view_once_video); - } else { - return context.getString(R.string.ThreadRecord_view_once_photo); - } + public @Nullable String getContentType() { + return contentType; } public long getCount() { @@ -173,13 +105,17 @@ public class ThreadRecord extends DisplayRecord { } public long getDate() { - return getDateReceived(); + return date; } public boolean isArchived() { return archived; } + public long getType() { + return type; + } + public int getDistributionType() { return distributionType; } @@ -192,6 +128,38 @@ public class ThreadRecord extends DisplayRecord { return lastSeen; } + public boolean isOutgoing() { + return MmsSmsColumns.Types.isOutgoingMessageType(type); + } + + public boolean isOutgoingCall() { + return SmsDatabase.Types.isOutgoingCall(type); + } + + public boolean isVerificationStatusChange() { + return StatusUtil.isVerificationStatusChange(type); + } + + public boolean isPending() { + return StatusUtil.isPending(type); + } + + public boolean isFailed() { + return StatusUtil.isFailed(type, deliveryStatus); + } + + public boolean isRemoteRead() { + return readReceiptCount > 0; + } + + public boolean isPendingInsecureSmsFallback() { + return SmsDatabase.Types.isPendingInsecureSmsFallbackType(type); + } + + public boolean isDelivered() { + return StatusUtil.isDelivered(deliveryStatus, deliveryReceiptCount); + } + public @Nullable RecipientId getGroupAddedBy() { if (extra != null && extra.getGroupAddedBy() != null) return RecipientId.from(extra.getGroupAddedBy()); else return null; @@ -205,4 +173,121 @@ public class ThreadRecord extends DisplayRecord { if (extra != null) return extra.isMessageRequestAccepted(); else return true; } + + public static class Builder { + private long threadId; + private String body; + private Recipient recipient; + private long type; + private long date; + private long deliveryStatus; + private int deliveryReceiptCount; + private int readReceiptCount; + private Uri snippetUri; + private String contentType; + private Extra extra; + private long count; + private int unreadCount; + private int distributionType; + private boolean archived; + private long expiresIn; + private long lastSeen; + + public Builder(long threadId) { + this.threadId = threadId; + } + + public Builder setBody(@NonNull String body) { + this.body = body; + return this; + } + + public Builder setRecipient(@NonNull Recipient recipient) { + this.recipient = recipient; + return this; + } + + public Builder setType(long type) { + this.type = type; + return this; + } + + public Builder setThreadId(long threadId) { + this.threadId = threadId; + return this; + } + + public Builder setDate(long date) { + this.date = date; + return this; + } + + public Builder setDeliveryStatus(long deliveryStatus) { + this.deliveryStatus = deliveryStatus; + return this; + } + + public Builder setDeliveryReceiptCount(int deliveryReceiptCount) { + this.deliveryReceiptCount = deliveryReceiptCount; + return this; + } + + public Builder setReadReceiptCount(int readReceiptCount) { + this.readReceiptCount = readReceiptCount; + return this; + } + + public Builder setSnippetUri(@Nullable Uri snippetUri) { + this.snippetUri = snippetUri; + return this; + } + + public Builder setContentType(@Nullable String contentType) { + this.contentType = contentType; + return this; + } + + public Builder setExtra(@Nullable Extra extra) { + this.extra = extra; + return this; + } + + public Builder setCount(long count) { + this.count = count; + return this; + } + + public Builder setUnreadCount(int unreadCount) { + this.unreadCount = unreadCount; + return this; + } + + public Builder setDistributionType(int distributionType) { + this.distributionType = distributionType; + return this; + } + + public Builder setArchived(boolean archived) { + this.archived = archived; + return this; + } + + public Builder setExpiresIn(long expiresIn) { + this.expiresIn = expiresIn; + return this; + } + + public Builder setLastSeen(long lastSeen) { + this.lastSeen = lastSeen; + return this; + } + + public ThreadRecord build() { + Preconditions.checkArgument(threadId > 0); + Preconditions.checkArgument(date > 0); + Preconditions.checkNotNull(body); + Preconditions.checkNotNull(recipient); + return new ThreadRecord(this); + } + } }