Fix crash when scheduling a message in an empty thread.
This commit is contained in:
parent
1f31f4a50a
commit
418b486776
6 changed files with 52 additions and 28 deletions
|
@ -493,7 +493,8 @@ public final class ConversationListItem extends ConstraintLayout implements Bind
|
||||||
} else if (!thread.isOutgoing() ||
|
} else if (!thread.isOutgoing() ||
|
||||||
thread.isOutgoingAudioCall() ||
|
thread.isOutgoingAudioCall() ||
|
||||||
thread.isOutgoingVideoCall() ||
|
thread.isOutgoingVideoCall() ||
|
||||||
thread.isVerificationStatusChange())
|
thread.isVerificationStatusChange() ||
|
||||||
|
thread.isScheduledMessage())
|
||||||
{
|
{
|
||||||
deliveryStatusIndicator.setNone();
|
deliveryStatusIndicator.setNone();
|
||||||
alertView.setNone();
|
alertView.setNone();
|
||||||
|
@ -588,6 +589,8 @@ public final class ConversationListItem extends ConstraintLayout implements Bind
|
||||||
return emphasisAdded(context, context.getString(R.string.ThreadRecord_secure_session_reset), defaultTint);
|
return emphasisAdded(context, context.getString(R.string.ThreadRecord_secure_session_reset), defaultTint);
|
||||||
} else if (MessageTypes.isLegacyType(thread.getType())) {
|
} else if (MessageTypes.isLegacyType(thread.getType())) {
|
||||||
return emphasisAdded(context, context.getString(R.string.MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported), defaultTint);
|
return emphasisAdded(context, context.getString(R.string.MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported), defaultTint);
|
||||||
|
} else if (thread.isScheduledMessage()) {
|
||||||
|
return emphasisAdded(context, context.getString(R.string.ThreadRecord_scheduled_message), R.drawable.symbol_calendar_compact_light_16, defaultTint);
|
||||||
} else if (MessageTypes.isDraftMessageType(thread.getType())) {
|
} else if (MessageTypes.isDraftMessageType(thread.getType())) {
|
||||||
String draftText = context.getString(R.string.ThreadRecord_draft);
|
String draftText = context.getString(R.string.ThreadRecord_draft);
|
||||||
return emphasisAdded(context, draftText + " " + thread.getBody(), defaultTint);
|
return emphasisAdded(context, draftText + " " + thread.getBody(), defaultTint);
|
||||||
|
|
|
@ -1716,8 +1716,8 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull SqlUtil.Query buildMeaningfulMessagesQuery(long threadId) {
|
private @NonNull SqlUtil.Query buildMeaningfulMessagesQuery(long threadId) {
|
||||||
String query = THREAD_ID + " = ? AND " + STORY_TYPE + " = ? AND " + PARENT_STORY_ID + " <= ? AND " + SCHEDULED_DATE + " = ? AND (NOT " + TYPE + " & ? AND " + TYPE + " != ? AND " + TYPE + " != ? AND " + TYPE + " != ? AND " + TYPE + " != ? AND " + TYPE + " & " + MessageTypes.GROUP_V2_LEAVE_BITS + " != " + MessageTypes.GROUP_V2_LEAVE_BITS + ")";
|
String query = THREAD_ID + " = ? AND " + STORY_TYPE + " = ? AND " + PARENT_STORY_ID + " <= ? AND " + "(NOT " + TYPE + " & ? AND " + TYPE + " != ? AND " + TYPE + " != ? AND " + TYPE + " != ? AND " + TYPE + " != ? AND " + TYPE + " & " + MessageTypes.GROUP_V2_LEAVE_BITS + " != " + MessageTypes.GROUP_V2_LEAVE_BITS + ")";
|
||||||
return SqlUtil.buildQuery(query, threadId, 0, 0, -1, MessageTypes.IGNORABLE_TYPESMASK_WHEN_COUNTING, MessageTypes.PROFILE_CHANGE_TYPE, MessageTypes.CHANGE_NUMBER_TYPE, MessageTypes.SMS_EXPORT_TYPE, MessageTypes.BOOST_REQUEST_TYPE);
|
return SqlUtil.buildQuery(query, threadId, 0, 0, MessageTypes.IGNORABLE_TYPESMASK_WHEN_COUNTING, MessageTypes.PROFILE_CHANGE_TYPE, MessageTypes.CHANGE_NUMBER_TYPE, MessageTypes.SMS_EXPORT_TYPE, MessageTypes.BOOST_REQUEST_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addFailures(long messageId, List<NetworkFailure> failure) {
|
public void addFailures(long messageId, List<NetworkFailure> failure) {
|
||||||
|
|
|
@ -62,6 +62,8 @@ public final class ThreadBodyUtil {
|
||||||
return format(EmojiStrings.CARD, getPaymentActivatedSummary(context, record));
|
return format(EmojiStrings.CARD, getPaymentActivatedSummary(context, record));
|
||||||
} else if (record.isCallLog() && !record.isGroupCall()) {
|
} else if (record.isCallLog() && !record.isGroupCall()) {
|
||||||
return new ThreadBody(getCallLogSummary(context, record));
|
return new ThreadBody(getCallLogSummary(context, record));
|
||||||
|
} else if (MessageRecordUtil.isScheduled(record)) {
|
||||||
|
return new ThreadBody(context.getString(R.string.ThreadRecord_scheduled_message));
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasImage = false;
|
boolean hasImage = false;
|
||||||
|
|
|
@ -58,6 +58,7 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||||
import org.thoughtcrime.securesms.util.JsonUtils
|
import org.thoughtcrime.securesms.util.JsonUtils
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
|
import org.thoughtcrime.securesms.util.isScheduled
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
|
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
|
||||||
import org.whispersystems.signalservice.api.storage.SignalAccountRecord.PinnedConversation
|
import org.whispersystems.signalservice.api.storage.SignalAccountRecord.PinnedConversation
|
||||||
|
@ -1344,31 +1345,36 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||||
val record: MessageRecord = try {
|
val record: MessageRecord = try {
|
||||||
messages.getConversationSnippet(threadId)
|
messages.getConversationSnippet(threadId)
|
||||||
} catch (e: NoSuchMessageException) {
|
} catch (e: NoSuchMessageException) {
|
||||||
Log.w(TAG, "Failed to get a conversation snippet for thread $threadId")
|
val scheduledMessage: MessageRecord? = messages.getScheduledMessagesInThread(threadId).lastOrNull()
|
||||||
|
|
||||||
if (shouldDelete) {
|
if (scheduledMessage == null) {
|
||||||
deleteConversation(threadId)
|
Log.w(TAG, "Failed to get a conversation snippet for thread $threadId")
|
||||||
|
if (shouldDelete) {
|
||||||
|
deleteConversation(threadId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPinned) {
|
||||||
|
updateThread(
|
||||||
|
threadId = threadId,
|
||||||
|
meaningfulMessages = meaningfulMessages,
|
||||||
|
body = null,
|
||||||
|
attachment = null,
|
||||||
|
contentType = null,
|
||||||
|
extra = null,
|
||||||
|
date = 0,
|
||||||
|
status = 0,
|
||||||
|
deliveryReceiptCount = 0,
|
||||||
|
type = 0,
|
||||||
|
unarchive = unarchive,
|
||||||
|
expiresIn = 0,
|
||||||
|
readReceiptCount = 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Using scheduled message for conversation snippet")
|
||||||
|
scheduledMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPinned) {
|
|
||||||
updateThread(
|
|
||||||
threadId = threadId,
|
|
||||||
meaningfulMessages = meaningfulMessages,
|
|
||||||
body = null,
|
|
||||||
attachment = null,
|
|
||||||
contentType = null,
|
|
||||||
extra = null,
|
|
||||||
date = 0,
|
|
||||||
status = 0,
|
|
||||||
deliveryReceiptCount = 0,
|
|
||||||
type = 0,
|
|
||||||
unarchive = unarchive,
|
|
||||||
expiresIn = 0,
|
|
||||||
readReceiptCount = 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val drafts: DraftTable.Drafts = SignalDatabase.drafts.getDrafts(threadId)
|
val drafts: DraftTable.Drafts = SignalDatabase.drafts.getDrafts(threadId)
|
||||||
|
@ -1575,7 +1581,9 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val extras: Extra? = if (record.isRemoteDelete) {
|
val extras: Extra? = if (record.isScheduled()) {
|
||||||
|
Extra.forScheduledMessage(individualRecipientId)
|
||||||
|
} else if (record.isRemoteDelete) {
|
||||||
Extra.forRemoteDelete(individualRecipientId)
|
Extra.forRemoteDelete(individualRecipientId)
|
||||||
} else if (record.isViewOnce) {
|
} else if (record.isViewOnce) {
|
||||||
Extra.forViewOnce(individualRecipientId)
|
Extra.forViewOnce(individualRecipientId)
|
||||||
|
@ -1778,7 +1786,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||||
@field:JsonProperty @param:JsonProperty("isGv2Invite") val isGv2Invite: Boolean = false,
|
@field:JsonProperty @param:JsonProperty("isGv2Invite") val isGv2Invite: Boolean = false,
|
||||||
@field:JsonProperty @param:JsonProperty("groupAddedBy") val groupAddedBy: String? = null,
|
@field:JsonProperty @param:JsonProperty("groupAddedBy") val groupAddedBy: String? = null,
|
||||||
@field:JsonProperty @param:JsonProperty("individualRecipientId") private val individualRecipientId: String,
|
@field:JsonProperty @param:JsonProperty("individualRecipientId") private val individualRecipientId: String,
|
||||||
@field:JsonProperty @param:JsonProperty("bodyRanges") val bodyRanges: String? = null
|
@field:JsonProperty @param:JsonProperty("bodyRanges") val bodyRanges: String? = null,
|
||||||
|
@field:JsonProperty @param:JsonProperty("isScheduled") val isScheduled: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun getIndividualRecipientId(): String {
|
fun getIndividualRecipientId(): String {
|
||||||
|
@ -1821,6 +1830,10 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||||
fun forBodyRanges(bodyRanges: BodyRangeList, individualRecipient: RecipientId): Extra {
|
fun forBodyRanges(bodyRanges: BodyRangeList, individualRecipient: RecipientId): Extra {
|
||||||
return Extra(individualRecipientId = individualRecipient.serialize(), bodyRanges = bodyRanges.serialize())
|
return Extra(individualRecipientId = individualRecipient.serialize(), bodyRanges = bodyRanges.serialize())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun forScheduledMessage(individualRecipient: RecipientId): Extra {
|
||||||
|
return Extra(individualRecipientId = individualRecipient.serialize(), isScheduled = true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -185,6 +185,10 @@ public final class ThreadRecord {
|
||||||
return StatusUtil.isDelivered(deliveryStatus, deliveryReceiptCount);
|
return StatusUtil.isDelivered(deliveryStatus, deliveryReceiptCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isScheduledMessage() {
|
||||||
|
return extra != null && extra.isScheduled();
|
||||||
|
}
|
||||||
|
|
||||||
public @Nullable RecipientId getGroupAddedBy() {
|
public @Nullable RecipientId getGroupAddedBy() {
|
||||||
if (extra != null && extra.getGroupAddedBy() != null) return RecipientId.from(extra.getGroupAddedBy());
|
if (extra != null && extra.getGroupAddedBy() != null) return RecipientId.from(extra.getGroupAddedBy());
|
||||||
else return null;
|
else return null;
|
||||||
|
|
|
@ -1960,6 +1960,8 @@
|
||||||
<string name="ThreadRecord__reacted_s_to_their_story">Reacted %1$s to their story</string>
|
<string name="ThreadRecord__reacted_s_to_their_story">Reacted %1$s to their story</string>
|
||||||
<!-- Displayed in the conversation list when your most recent message is a payment to or from the person the conversation is with -->
|
<!-- Displayed in the conversation list when your most recent message is a payment to or from the person the conversation is with -->
|
||||||
<string name="ThreadRecord_payment">Payment</string>
|
<string name="ThreadRecord_payment">Payment</string>
|
||||||
|
<!-- Displayed in the conversation list when your only message in a conversation is a scheduled send. -->
|
||||||
|
<string name="ThreadRecord_scheduled_message">Scheduled message</string>
|
||||||
|
|
||||||
<!-- UpdateApkReadyListener -->
|
<!-- UpdateApkReadyListener -->
|
||||||
<string name="UpdateApkReadyListener_Signal_update">Signal update</string>
|
<string name="UpdateApkReadyListener_Signal_update">Signal update</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue