mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 16:49:40 +01:00
Add basic pinned message support.
This commit is contained in:
committed by
jeffrey-signal
parent
22701da765
commit
80598d42cc
@@ -111,6 +111,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.PinnedMessage
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.PollTerminate
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
|
||||
@@ -140,6 +141,7 @@ import org.thoughtcrime.securesms.stories.Stories.isFeatureEnabled
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.MessageConstraintsUtil
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.isStory
|
||||
@@ -155,6 +157,7 @@ import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), MessageTypes, RecipientIdDatabaseReference, ThreadIdDatabaseReference {
|
||||
|
||||
@@ -217,6 +220,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
const val MESSAGE_EXTRAS = "message_extras"
|
||||
const val VOTES_UNREAD = "votes_unread"
|
||||
const val VOTES_LAST_SEEN = "votes_last_seen"
|
||||
const val PINNED_UNTIL = "pinned_until"
|
||||
const val PINNING_MESSAGE_ID = "pinning_message_id"
|
||||
const val PINNED_AT = "pinned_at"
|
||||
|
||||
const val QUOTE_NOT_PRESENT_ID = 0L
|
||||
const val QUOTE_TARGET_MISSING_ID = -1L
|
||||
@@ -224,6 +230,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
const val ADDRESSABLE_MESSAGE_LIMIT = 5
|
||||
const val PARENT_STORY_MISSING_ID = -1L
|
||||
|
||||
const val PIN_FOREVER = Long.MAX_VALUE
|
||||
|
||||
const val CREATE_TABLE = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
$ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -281,7 +289,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
$MESSAGE_EXTRAS BLOB DEFAULT NULL,
|
||||
$EXPIRE_TIMER_VERSION INTEGER DEFAULT 1 NOT NULL,
|
||||
$VOTES_UNREAD INTEGER DEFAULT 0,
|
||||
$VOTES_LAST_SEEN INTEGER DEFAULT 0
|
||||
$VOTES_LAST_SEEN INTEGER DEFAULT 0,
|
||||
$PINNED_UNTIL INTEGER DEFAULT 0,
|
||||
$PINNING_MESSAGE_ID INTEGER DEFAULT 0,
|
||||
$PINNED_AT INTEGER DEFAULT 0
|
||||
)
|
||||
"""
|
||||
|
||||
@@ -312,7 +323,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
"CREATE INDEX IF NOT EXISTS $INDEX_THREAD_COUNT ON $TABLE_NAME ($THREAD_ID) WHERE $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL",
|
||||
// This index is created specifically for getting the number of unread messages in a thread and therefore needs to be kept in sync with that query
|
||||
"CREATE INDEX IF NOT EXISTS $INDEX_THREAD_UNREAD_COUNT ON $TABLE_NAME ($THREAD_ID) WHERE $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $ORIGINAL_MESSAGE_ID IS NULL AND $READ = 0",
|
||||
"CREATE INDEX IF NOT EXISTS message_votes_unread_index ON $TABLE_NAME ($VOTES_UNREAD)"
|
||||
"CREATE INDEX IF NOT EXISTS message_votes_unread_index ON $TABLE_NAME ($VOTES_UNREAD)",
|
||||
"CREATE INDEX IF NOT EXISTS message_pinned_until_index ON $TABLE_NAME ($PINNED_UNTIL)",
|
||||
"CREATE INDEX IF NOT EXISTS message_pinned_at_index ON $TABLE_NAME ($PINNED_AT)"
|
||||
)
|
||||
|
||||
private val MMS_PROJECTION_BASE = arrayOf(
|
||||
@@ -367,7 +380,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
REVISION_NUMBER,
|
||||
MESSAGE_EXTRAS,
|
||||
VOTES_UNREAD,
|
||||
VOTES_LAST_SEEN
|
||||
VOTES_LAST_SEEN,
|
||||
PINNED_UNTIL
|
||||
)
|
||||
|
||||
private val MMS_PROJECTION: Array<String> = MMS_PROJECTION_BASE + "NULL AS ${AttachmentTable.ATTACHMENT_JSON_ALIAS}"
|
||||
@@ -2027,7 +2041,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
}
|
||||
|
||||
private fun rawQueryWithAttachments(where: String, arguments: Array<String>?, reverse: Boolean = false, limit: Long = 0): Cursor {
|
||||
/**
|
||||
* Note: [reverse] and [orderBy] are mutually exclusive. If you want the order to be reversed, explicitly use 'ASC' or 'DESC'
|
||||
*/
|
||||
private fun rawQueryWithAttachments(where: String, arguments: Array<String>?, reverse: Boolean = false, limit: Long = 0, orderBy: String = ""): Cursor {
|
||||
val database = databaseHelper.signalReadableDatabase
|
||||
var rawQueryString = """
|
||||
SELECT
|
||||
@@ -2040,7 +2057,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
$TABLE_NAME.$ID
|
||||
""".toSingleLine()
|
||||
|
||||
if (reverse) {
|
||||
if (orderBy.isNotEmpty()) {
|
||||
rawQueryString += " ORDER BY $orderBy"
|
||||
} else if (reverse) {
|
||||
rawQueryString += " ORDER BY $TABLE_NAME.$ID DESC"
|
||||
}
|
||||
|
||||
@@ -2068,6 +2087,27 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
}
|
||||
|
||||
fun getPinnedMessages(threadId: Long, orderByPinned: Boolean): List<MmsMessageRecord> {
|
||||
val cursor = rawQueryWithAttachments(
|
||||
where = "$THREAD_ID = ? AND $PINNED_UNTIL > 0",
|
||||
arguments = buildArgs(threadId),
|
||||
reverse = true,
|
||||
orderBy = if (orderByPinned) "$PINNED_AT ASC" else ""
|
||||
)
|
||||
|
||||
return mmsReaderFor(cursor).use { reader ->
|
||||
reader.mapNotNull {
|
||||
if (!it.isMms) {
|
||||
null
|
||||
} else if (it.isPaymentNotification) {
|
||||
SignalDatabase.payments.updateMessageWithPayment(it) as MmsMessageRecord
|
||||
} else {
|
||||
it as MmsMessageRecord
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getRecentPendingMessages(): MmsReader {
|
||||
val now = System.currentTimeMillis()
|
||||
val oneDayAgo = now.milliseconds - 1.days
|
||||
@@ -2695,6 +2735,13 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
expiresIn = expiresIn,
|
||||
messageExtras = messageExtras
|
||||
)
|
||||
} else if (MessageTypes.isPinnedMessageUpdate(outboxType) && messageExtras != null) {
|
||||
OutgoingMessage.pinMessage(
|
||||
threadRecipient = threadRecipient,
|
||||
sentTimeMillis = timestamp,
|
||||
expiresIn = expiresIn,
|
||||
messageExtras = messageExtras
|
||||
)
|
||||
} else {
|
||||
val giftBadge: GiftBadge? = if (body != null && MessageTypes.isGiftBadge(outboxType)) {
|
||||
GiftBadge.ADAPTER.decode(Base64.decode(body))
|
||||
@@ -2850,7 +2897,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
updateThread = updateThread,
|
||||
unarchive = true,
|
||||
poll = retrieved.poll,
|
||||
pollTerminate = retrieved.messageExtras?.pollTerminate
|
||||
pollTerminate = retrieved.messageExtras?.pollTerminate,
|
||||
pinnedMessage = retrieved.messageExtras?.pinnedMessage
|
||||
)
|
||||
|
||||
if (messageId < 0) {
|
||||
@@ -3181,6 +3229,14 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
hasSpecialType = true
|
||||
}
|
||||
|
||||
if (message.messageExtras?.pinnedMessage != null) {
|
||||
if (hasSpecialType) {
|
||||
throw MmsException("Cannot insert message with multiple special types.")
|
||||
}
|
||||
type = type or MessageTypes.SPECIAL_TYPE_PINNED_MESSAGE
|
||||
hasSpecialType = true
|
||||
}
|
||||
|
||||
val earlyDeliveryReceipts: Map<RecipientId, Receipt> = earlyDeliveryReceiptCache.remove(message.sentTimeMillis)
|
||||
|
||||
if (earlyDeliveryReceipts.isNotEmpty()) {
|
||||
@@ -3296,7 +3352,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
updateThread = false,
|
||||
unarchive = false,
|
||||
poll = message.poll,
|
||||
pollTerminate = message.messageExtras?.pollTerminate
|
||||
pollTerminate = message.messageExtras?.pollTerminate,
|
||||
pinnedMessage = message.messageExtras?.pinnedMessage
|
||||
)
|
||||
|
||||
if (messageId < 0) {
|
||||
@@ -3405,7 +3462,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
updateThread: Boolean,
|
||||
unarchive: Boolean,
|
||||
poll: Poll? = null,
|
||||
pollTerminate: PollTerminate? = null
|
||||
pollTerminate: PollTerminate? = null,
|
||||
pinnedMessage: PinnedMessage?
|
||||
): kotlin.Pair<Long, Map<Attachment, AttachmentId>?> {
|
||||
val mentionsSelf = mentions.any { Recipient.resolved(it.recipientId).isSelf }
|
||||
val allAttachments: MutableList<Attachment> = mutableListOf()
|
||||
@@ -3471,6 +3529,31 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
}
|
||||
|
||||
if (pinnedMessage != null) {
|
||||
val pinnedUntil = if (pinnedMessage.pinDurationInSeconds == PIN_FOREVER) {
|
||||
PIN_FOREVER
|
||||
} else {
|
||||
System.currentTimeMillis() + pinnedMessage.pinDurationInSeconds.seconds.inWholeMilliseconds
|
||||
}
|
||||
val rows = db
|
||||
.update(TABLE_NAME)
|
||||
.values(
|
||||
PINNED_UNTIL to pinnedUntil,
|
||||
PINNING_MESSAGE_ID to messageId,
|
||||
PINNED_AT to System.currentTimeMillis()
|
||||
)
|
||||
.where("$ID = ?", pinnedMessage.pinnedMessageId)
|
||||
.run()
|
||||
|
||||
if (rows <= 0) {
|
||||
Log.w(TAG, "Failed to pin a message.")
|
||||
} else {
|
||||
enforcePinSizeLimit(threadId, RemoteConfig.pinLimit)
|
||||
}
|
||||
|
||||
AppDependencies.databaseObserver.notifyConversationListeners(threadId)
|
||||
}
|
||||
|
||||
messageId to insertedAttachments
|
||||
}
|
||||
|
||||
@@ -3487,6 +3570,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
threads.update(threadId, unarchive)
|
||||
}
|
||||
|
||||
if (pinnedMessage != null && pinnedMessage.pinDurationInSeconds != PIN_FOREVER) {
|
||||
AppDependencies.pinnedMessageManager.scheduleIfNecessary()
|
||||
}
|
||||
|
||||
return messageId to insertedAttachments
|
||||
}
|
||||
|
||||
@@ -3544,6 +3631,31 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return rowsDeleted
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpins the oldest pins if the thread exceeds the [limit]
|
||||
*/
|
||||
private fun enforcePinSizeLimit(threadId: Long, limit: Int) {
|
||||
val pinnedList = readableDatabase
|
||||
.select(PINNED_AT)
|
||||
.from(TABLE_NAME)
|
||||
.where("$THREAD_ID = ? AND $PINNED_UNTIL > 0", threadId)
|
||||
.orderBy("$PINNED_AT DESC")
|
||||
.run()
|
||||
.readToList { cursor -> cursor.requireLong(PINNED_AT) }
|
||||
|
||||
if (pinnedList.size > limit) {
|
||||
val oldestPin = pinnedList[limit]
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(
|
||||
PINNED_UNTIL to 0,
|
||||
PINNED_AT to 0
|
||||
)
|
||||
.where("$PINNED_AT > 0 AND $PINNED_AT <= ?", oldestPin)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteMessage(messageId: Long): Boolean {
|
||||
val threadId = getThreadIdForMessage(messageId)
|
||||
return deleteMessage(messageId, threadId)
|
||||
@@ -5084,6 +5196,33 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
}
|
||||
|
||||
fun getOldestExpiringPinnedMessageTimestamp(): MessageRecord? {
|
||||
val cursor = readableDatabase
|
||||
.select(*MMS_PROJECTION)
|
||||
.from(TABLE_NAME)
|
||||
.where("$PINNED_UNTIL > 0 AND $PINNED_UNTIL != ?", PIN_FOREVER)
|
||||
.orderBy("$PINNED_UNTIL ASC, $ID ASC")
|
||||
.limit(1)
|
||||
.run()
|
||||
|
||||
return mmsReaderFor(cursor).use { reader ->
|
||||
reader.firstOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
fun getPinnedMessagesBefore(time: Long): List<MessageRecord> {
|
||||
val cursor = readableDatabase
|
||||
.select(*MMS_PROJECTION)
|
||||
.from(TABLE_NAME)
|
||||
.where("$PINNED_UNTIL > 0 AND $PINNED_UNTIL <= ?", time)
|
||||
.orderBy("$PINNED_UNTIL ASC, $ID ASC")
|
||||
.run()
|
||||
|
||||
return mmsReaderFor(cursor).use { reader ->
|
||||
reader.filterNotNull()
|
||||
}
|
||||
}
|
||||
|
||||
fun getMessagesForNotificationState(stickyThreads: Collection<StickyThread>): Cursor {
|
||||
val stickyQuery = StringBuilder()
|
||||
|
||||
@@ -5306,6 +5445,15 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
}
|
||||
|
||||
fun unpinMessage(messageId: Long, threadId: Long) {
|
||||
writableDatabase.update(TABLE_NAME)
|
||||
.values(PINNED_UNTIL to 0)
|
||||
.where("$ID = ?", messageId)
|
||||
.run()
|
||||
|
||||
notifyConversationListeners(threadId)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
protected fun <D : Document<I>?, I> removeFromDocument(messageId: Long, column: String, item: I, clazz: Class<D>) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
@@ -5463,6 +5611,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
MessageType.IDENTITY_DEFAULT -> MessageTypes.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT or MessageTypes.BASE_INBOX_TYPE
|
||||
MessageType.END_SESSION -> MessageTypes.END_SESSION_BIT or MessageTypes.BASE_INBOX_TYPE
|
||||
MessageType.POLL_TERMINATE -> MessageTypes.SPECIAL_TYPE_POLL_TERMINATE or MessageTypes.BASE_INBOX_TYPE
|
||||
MessageType.PINNED_MESSAGE -> MessageTypes.SPECIAL_TYPE_PINNED_MESSAGE or MessageTypes.BASE_INBOX_TYPE
|
||||
MessageType.GROUP_UPDATE -> {
|
||||
val isOnlyGroupLeave = this.groupContext?.let { GroupV2UpdateMessageUtil.isJustAGroupLeave(it) } ?: false
|
||||
|
||||
@@ -6005,6 +6154,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val originalMessageId: MessageId? = cursor.requireLong(ORIGINAL_MESSAGE_ID).let { if (it == 0L) null else MessageId(it) }
|
||||
val editCount = cursor.requireInt(REVISION_NUMBER)
|
||||
val isRead = cursor.requireBoolean(READ)
|
||||
val pinnedUntil = cursor.requireLong(PINNED_UNTIL)
|
||||
val messageExtraBytes = cursor.requireBlob(MESSAGE_EXTRAS)
|
||||
val messageExtras = messageExtraBytes?.let { MessageExtras.ADAPTER.decode(it) }
|
||||
|
||||
@@ -6099,6 +6249,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
originalMessageId,
|
||||
editCount,
|
||||
isRead,
|
||||
pinnedUntil,
|
||||
messageExtras
|
||||
)
|
||||
}
|
||||
|
||||
@@ -48,5 +48,8 @@ enum class MessageType {
|
||||
END_SESSION,
|
||||
|
||||
/** A poll has ended **/
|
||||
POLL_TERMINATE
|
||||
POLL_TERMINATE,
|
||||
|
||||
/** A message has been pinned **/
|
||||
PINNED_MESSAGE
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ public interface MessageTypes {
|
||||
long SPECIAL_TYPE_BLOCKED = 0xA00000000L;
|
||||
long SPECIAL_TYPE_UNBLOCKED = 0xB00000000L;
|
||||
long SPECIAL_TYPE_POLL_TERMINATE = 0xC00000000L;
|
||||
long SPECIAL_TYPE_PINNED_MESSAGE = 0xD00000000L;
|
||||
|
||||
long IGNORABLE_TYPESMASK_WHEN_COUNTING = END_SESSION_BIT | KEY_EXCHANGE_IDENTITY_UPDATE_BIT | KEY_EXCHANGE_IDENTITY_VERIFIED_BIT;
|
||||
|
||||
@@ -170,6 +171,10 @@ public interface MessageTypes {
|
||||
return (type & SPECIAL_TYPES_MASK) == SPECIAL_TYPE_POLL_TERMINATE;
|
||||
}
|
||||
|
||||
static boolean isPinnedMessageUpdate(long type) {
|
||||
return (type & SPECIAL_TYPES_MASK) == SPECIAL_TYPE_PINNED_MESSAGE;
|
||||
}
|
||||
|
||||
static boolean isDraftMessageType(long type) {
|
||||
return (type & BASE_TYPE_MASK) == BASE_DRAFT_TYPE;
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@ public final class ThreadBodyUtil {
|
||||
} else if (MessageRecordUtil.hasPollTerminate(record)) {
|
||||
return record.getFromRecipient().isSelf() ? new ThreadBody(context.getString(R.string.Poll__you_poll_end, record.getMessageExtras().pollTerminate.question))
|
||||
: new ThreadBody(context.getString(R.string.Poll__poll_end, record.getFromRecipient().getDisplayName(context), record.getMessageExtras().pollTerminate.question));
|
||||
} else if (MessageRecordUtil.hasPinnedMessageUpdate(record)) {
|
||||
return record.getFromRecipient().isSelf() ? new ThreadBody(context.getString(R.string.PinnedMessage__you_pinned_a_message))
|
||||
: new ThreadBody(context.getString(R.string.PinnedMessage__s_pinned_a_message, record.getFromRecipient().getDisplayName(context)));
|
||||
}
|
||||
|
||||
boolean hasImage = false;
|
||||
|
||||
@@ -150,6 +150,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V292_AddPollTables
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V294_RemoveLastResortKeyTupleColumnConstraintMigration
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V295_AddLastRestoreKeyTypeTableIfMissingMigration
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V296_RemovePollVoteConstraint
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V297_AddPinnedMessageColumns
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase
|
||||
|
||||
/**
|
||||
@@ -306,10 +307,11 @@ object SignalDatabaseMigrations {
|
||||
// 293 to V293_LastResortKeyTupleTableMigration, - removed due to crashing on some devices.
|
||||
294 to V294_RemoveLastResortKeyTupleColumnConstraintMigration,
|
||||
295 to V295_AddLastRestoreKeyTypeTableIfMissingMigration,
|
||||
296 to V296_RemovePollVoteConstraint
|
||||
296 to V296_RemovePollVoteConstraint,
|
||||
297 to V297_AddPinnedMessageColumns
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 296
|
||||
const val DATABASE_VERSION = 297
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* Adds the columns and indexes necessary for pinned messages
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V297_AddPinnedMessageColumns : SignalDatabaseMigration {
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("ALTER TABLE message ADD COLUMN pinned_until INTEGER DEFAULT 0")
|
||||
db.execSQL("ALTER TABLE message ADD COLUMN pinning_message_id INTEGER DEFAULT 0")
|
||||
db.execSQL("ALTER TABLE message ADD COLUMN pinned_at INTEGER DEFAULT 0")
|
||||
|
||||
db.execSQL("CREATE INDEX message_pinned_until_index ON message (pinned_until)")
|
||||
db.execSQL("CREATE INDEX message_pinned_at_index ON message (pinned_at)")
|
||||
}
|
||||
}
|
||||
@@ -264,4 +264,8 @@ public abstract class DisplayRecord {
|
||||
public boolean isPollTerminate() {
|
||||
return MessageTypes.isPollTerminate(type);
|
||||
}
|
||||
|
||||
public boolean isPinnedMessageUpdate() {
|
||||
return MessageTypes.isPinnedMessageUpdate(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ public class InMemoryMessageRecord extends MessageRecord {
|
||||
-1,
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
private final long receiptTimestamp;
|
||||
private final MessageId originalMessageId;
|
||||
private final int revisionNumber;
|
||||
private final long pinnedUntil;
|
||||
private final MessageExtras messageExtras;
|
||||
|
||||
protected Boolean isJumboji = null;
|
||||
@@ -135,6 +136,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
long receiptTimestamp,
|
||||
@Nullable MessageId originalMessageId,
|
||||
int revisionNumber,
|
||||
long pinnedUntil,
|
||||
@Nullable MessageExtras messageExtras)
|
||||
{
|
||||
super(body, fromRecipient, toRecipient, dateSent, dateReceived,
|
||||
@@ -156,6 +158,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
this.receiptTimestamp = receiptTimestamp;
|
||||
this.originalMessageId = originalMessageId;
|
||||
this.revisionNumber = revisionNumber;
|
||||
this.pinnedUntil = pinnedUntil;
|
||||
this.messageExtras = messageExtras;
|
||||
}
|
||||
|
||||
@@ -297,6 +300,9 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
} else if (MessageRecordUtil.hasPollTerminate(this)) {
|
||||
return getFromRecipient().isSelf() ? staticUpdateDescriptionWithExpiration(context.getString(R.string.MessageRecord_you_ended_the_poll, messageExtras.pollTerminate.question), Glyph.POLL)
|
||||
: staticUpdateDescriptionWithExpiration(context.getString(R.string.MessageRecord_ended_the_poll, getFromRecipient().getDisplayName(context), messageExtras.pollTerminate.question), Glyph.POLL);
|
||||
} else if (MessageRecordUtil.hasPinnedMessageUpdate(this)) {
|
||||
return getFromRecipient().isSelf() ? staticUpdateDescriptionWithExpiration(context.getString(R.string.PinnedMessage__you_pinned_a_message), Glyph.PIN)
|
||||
: staticUpdateDescriptionWithExpiration(context.getString(R.string.PinnedMessage__s_pinned_a_message, getFromRecipient().getDisplayName(context)), Glyph.PIN);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -740,7 +746,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
isProfileChange() || isGroupV1MigrationEvent() || isChatSessionRefresh() || isBadDecryptType() ||
|
||||
isChangeNumber() || isReleaseChannelDonationRequest() || isThreadMergeEventType() || isSmsExportType() || isSessionSwitchoverEventType() ||
|
||||
isPaymentsRequestToActivate() || isPaymentsActivated() || isReportedSpam() || isMessageRequestAccepted() ||
|
||||
isBlocked() || isUnblocked() || isUnsupported() || isPollTerminate();
|
||||
isBlocked() || isUnblocked() || isUnsupported() || isPollTerminate() || isPinnedMessageUpdate();
|
||||
}
|
||||
|
||||
public boolean isMediaPending() {
|
||||
@@ -775,6 +781,10 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return MessageTypes.isChatSessionRefresh(type);
|
||||
}
|
||||
|
||||
public long getPinnedUntil() {
|
||||
return pinnedUntil;
|
||||
}
|
||||
|
||||
public boolean isInMemoryMessageRecord() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -120,12 +120,13 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
@Nullable MessageId originalMessageId,
|
||||
int revisionNumber,
|
||||
boolean isRead,
|
||||
long pinnedUntil,
|
||||
@Nullable MessageExtras messageExtras)
|
||||
{
|
||||
super(id, body, fromRecipient, fromDeviceId, toRecipient,
|
||||
dateSent, dateReceived, dateServer, threadId, Status.STATUS_NONE, hasDeliveryReceipt,
|
||||
mailbox, mismatches, failures, subscriptionId, expiresIn, expireStarted, expireTimerVersion, hasReadReceipt,
|
||||
unidentified, reactions, remoteDelete, notifiedTimestamp, viewed, receiptTimestamp, originalMessageId, revisionNumber, messageExtras);
|
||||
unidentified, reactions, remoteDelete, notifiedTimestamp, viewed, receiptTimestamp, originalMessageId, revisionNumber, pinnedUntil, messageExtras);
|
||||
|
||||
this.slideDeck = slideDeck;
|
||||
this.quote = quote;
|
||||
@@ -338,7 +339,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getMessageExtras());
|
||||
}
|
||||
|
||||
public @NonNull MmsMessageRecord withoutQuote() {
|
||||
@@ -346,7 +347,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), null, getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getMessageExtras());
|
||||
}
|
||||
|
||||
public @NonNull MmsMessageRecord withAttachments(@NonNull List<DatabaseAttachment> attachments) {
|
||||
@@ -368,7 +369,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getMessageExtras());
|
||||
}
|
||||
|
||||
public @NonNull MmsMessageRecord withPayment(@NonNull Payment payment) {
|
||||
@@ -376,7 +377,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getMessageExtras());
|
||||
}
|
||||
|
||||
|
||||
@@ -385,7 +386,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call, getPoll(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getMessageExtras());
|
||||
}
|
||||
|
||||
public @NonNull MmsMessageRecord withPoll(@Nullable PollRecord poll) {
|
||||
@@ -393,7 +394,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), poll, getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getMessageExtras());
|
||||
}
|
||||
|
||||
private static @NonNull List<Contact> updateContacts(@NonNull List<Contact> contacts, @NonNull Map<AttachmentId, DatabaseAttachment> attachmentIdMap) {
|
||||
|
||||
Reference in New Issue
Block a user