Add call disposition syncing.

This commit is contained in:
Cody Henthorne
2022-12-21 23:56:28 -05:00
committed by Greyson Parrelli
parent d471647e12
commit 06b414f4ef
23 changed files with 788 additions and 76 deletions

View File

@@ -0,0 +1,266 @@
package org.thoughtcrime.securesms.database
import android.content.Context
import android.database.Cursor
import androidx.core.content.contentValuesOf
import org.signal.core.util.IntSerializer
import org.signal.core.util.Serializer
import org.signal.core.util.SqlUtil
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireLong
import org.signal.core.util.requireObject
import org.signal.core.util.select
import org.signal.core.util.update
import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.CallEvent
/**
* Contains details for each 1:1 call.
*/
class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
private val TAG = Log.tag(CallTable::class.java)
private const val TABLE_NAME = "call"
private const val ID = "_id"
private const val CALL_ID = "call_id"
private const val MESSAGE_ID = "message_id"
private const val PEER = "peer"
private const val TYPE = "type"
private const val DIRECTION = "direction"
private const val EVENT = "event"
val CREATE_TABLE = """
CREATE TABLE $TABLE_NAME (
$ID INTEGER PRIMARY KEY,
$CALL_ID INTEGER NOT NULL UNIQUE,
$MESSAGE_ID INTEGER NOT NULL REFERENCES ${MessageTable.TABLE_NAME} (${MessageTable.ID}) ON DELETE CASCADE,
$PEER INTEGER NOT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
$TYPE INTEGER NOT NULL,
$DIRECTION INTEGER NOT NULL,
$EVENT INTEGER NOT NULL
)
""".trimIndent()
val CREATE_INDEXES = arrayOf(
"CREATE INDEX call_call_id_index ON $TABLE_NAME ($CALL_ID)",
"CREATE INDEX call_message_id_index ON $TABLE_NAME ($MESSAGE_ID)"
)
}
fun insertCall(callId: Long, timestamp: Long, peer: RecipientId, type: Type, direction: Direction, event: Event) {
val messageType: Long = Call.getMessageType(type, direction, event)
writableDatabase.withinTransaction {
val result = SignalDatabase.messages.insertCallLog(peer, messageType, timestamp)
val values = contentValuesOf(
CALL_ID to callId,
MESSAGE_ID to result.messageId,
PEER to peer.serialize(),
TYPE to Type.serialize(type),
DIRECTION to Direction.serialize(direction),
EVENT to Event.serialize(event)
)
writableDatabase.insert(TABLE_NAME, null, values)
}
ApplicationDependencies.getMessageNotifier().updateNotification(context)
Log.i(TAG, "Inserted call: $callId type: $type direction: $direction event:$event")
}
fun updateCall(callId: Long, event: Event): Call? {
return writableDatabase.withinTransaction {
writableDatabase
.update(TABLE_NAME)
.values(EVENT to Event.serialize(event))
.where("$CALL_ID = ?", callId)
.run()
val call = readableDatabase
.select()
.from(TABLE_NAME)
.where("$CALL_ID = ?", callId)
.run()
.readToSingleObject(Call.Deserializer)
if (call != null) {
Log.i(TAG, "Updated call: $callId event: $event")
SignalDatabase.messages.updateCallLog(call.messageId, call.messageType)
ApplicationDependencies.getMessageNotifier().updateNotification(context)
}
call
}
}
fun getCallById(callId: Long): Call? {
return readableDatabase
.select()
.from(TABLE_NAME)
.where("$CALL_ID = ?", callId)
.run()
.readToSingleObject(Call.Deserializer)
}
fun getCallByMessageId(messageId: Long): Call? {
return readableDatabase
.select()
.from(TABLE_NAME)
.where("$MESSAGE_ID = ?", messageId)
.run()
.readToSingleObject(Call.Deserializer)
}
fun getCalls(messageIds: Collection<Long>): Map<Long, Call> {
val calls = mutableMapOf<Long, Call>()
val queries = SqlUtil.buildCollectionQuery(MESSAGE_ID, messageIds)
queries.forEach { query ->
val cursor = readableDatabase
.select()
.from(TABLE_NAME)
.where(query.where, query.whereArgs)
.run()
calls.putAll(cursor.readToList { c -> c.requireLong(MESSAGE_ID) to Call.deserialize(c) })
}
return calls
}
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
writableDatabase
.update(TABLE_NAME)
.values(PEER to toId.serialize())
.where("$PEER = ?", fromId)
.run()
}
data class Call(
val callId: Long,
val peer: RecipientId,
val type: Type,
val direction: Direction,
val event: Event,
val messageId: Long
) {
val messageType: Long = getMessageType(type, direction, event)
companion object Deserializer : Serializer<Call, Cursor> {
fun getMessageType(type: Type, direction: Direction, event: Event): Long {
return if (direction == Direction.INCOMING && event == Event.MISSED) {
if (type == Type.VIDEO_CALL) MmsSmsColumns.Types.MISSED_VIDEO_CALL_TYPE else MmsSmsColumns.Types.MISSED_AUDIO_CALL_TYPE
} else if (direction == Direction.INCOMING) {
if (type == Type.VIDEO_CALL) MmsSmsColumns.Types.INCOMING_VIDEO_CALL_TYPE else MmsSmsColumns.Types.INCOMING_AUDIO_CALL_TYPE
} else {
if (type == Type.VIDEO_CALL) MmsSmsColumns.Types.OUTGOING_VIDEO_CALL_TYPE else MmsSmsColumns.Types.OUTGOING_AUDIO_CALL_TYPE
}
}
override fun serialize(data: Call): Cursor {
throw UnsupportedOperationException()
}
override fun deserialize(data: Cursor): Call {
return Call(
callId = data.requireLong(CALL_ID),
peer = RecipientId.from(data.requireLong(PEER)),
type = data.requireObject(TYPE, Type.Serializer),
direction = data.requireObject(DIRECTION, Direction.Serializer),
event = data.requireObject(EVENT, Event.Serializer),
messageId = data.requireLong(MESSAGE_ID)
)
}
}
}
enum class Type(private val code: Int) {
AUDIO_CALL(0),
VIDEO_CALL(1);
companion object Serializer : IntSerializer<Type> {
override fun serialize(data: Type): Int = data.code
override fun deserialize(data: Int): Type {
return when (data) {
AUDIO_CALL.code -> AUDIO_CALL
VIDEO_CALL.code -> VIDEO_CALL
else -> throw IllegalArgumentException("Unknown type $data")
}
}
@JvmStatic
fun from(type: CallEvent.Type): Type? {
return when (type) {
CallEvent.Type.UNKNOWN_TYPE -> null
CallEvent.Type.AUDIO_CALL -> AUDIO_CALL
CallEvent.Type.VIDEO_CALL -> VIDEO_CALL
}
}
}
}
enum class Direction(private val code: Int) {
INCOMING(0),
OUTGOING(1);
companion object Serializer : IntSerializer<Direction> {
override fun serialize(data: Direction): Int = data.code
override fun deserialize(data: Int): Direction {
return when (data) {
INCOMING.code -> INCOMING
OUTGOING.code -> OUTGOING
else -> throw IllegalArgumentException("Unknown type $data")
}
}
@JvmStatic
fun from(direction: CallEvent.Direction): Direction? {
return when (direction) {
CallEvent.Direction.UNKNOWN_DIRECTION -> null
CallEvent.Direction.INCOMING -> INCOMING
CallEvent.Direction.OUTGOING -> OUTGOING
}
}
}
}
enum class Event(private val code: Int) {
ONGOING(0),
ACCEPTED(1),
NOT_ACCEPTED(2),
MISSED(3);
companion object Serializer : IntSerializer<Event> {
override fun serialize(data: Event): Int = data.code
override fun deserialize(data: Int): Event {
return when (data) {
ONGOING.code -> ONGOING
ACCEPTED.code -> ACCEPTED
NOT_ACCEPTED.code -> NOT_ACCEPTED
MISSED.code -> MISSED
else -> throw IllegalArgumentException("Unknown type $data")
}
}
@JvmStatic
fun from(event: CallEvent.Event): Event? {
return when (event) {
CallEvent.Event.UNKNOWN_ACTION -> null
CallEvent.Event.ACCEPTED -> ACCEPTED
CallEvent.Event.NOT_ACCEPTED -> NOT_ACCEPTED
}
}
}
}
}

View File

@@ -559,23 +559,12 @@ public class MessageTable extends DatabaseTable implements MmsSmsColumns, Recipi
return results;
}
public @NonNull Pair<Long, Long> insertReceivedCall(@NonNull RecipientId address, boolean isVideoOffer) {
return insertCallLog(address, isVideoOffer ? Types.INCOMING_VIDEO_CALL_TYPE : Types.INCOMING_AUDIO_CALL_TYPE, false, System.currentTimeMillis());
}
public @NonNull Pair<Long, Long> insertOutgoingCall(@NonNull RecipientId address, boolean isVideoOffer) {
return insertCallLog(address, isVideoOffer ? Types.OUTGOING_VIDEO_CALL_TYPE : Types.OUTGOING_AUDIO_CALL_TYPE, false, System.currentTimeMillis());
}
public @NonNull Pair<Long, Long> insertMissedCall(@NonNull RecipientId address, long timestamp, boolean isVideoOffer) {
return insertCallLog(address, isVideoOffer ? Types.MISSED_VIDEO_CALL_TYPE : Types.MISSED_AUDIO_CALL_TYPE, true, timestamp);
}
private @NonNull Pair<Long, Long> insertCallLog(@NonNull RecipientId recipientId, long type, boolean unread, long timestamp) {
public @NonNull InsertResult insertCallLog(@NonNull RecipientId recipientId, long type, long timestamp) {
boolean unread = Types.isMissedAudioCall(type) || Types.isMissedVideoCall(type);
Recipient recipient = Recipient.resolved(recipientId);
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
ContentValues values = new ContentValues(6);
ContentValues values = new ContentValues(7);
values.put(RECIPIENT_ID, recipientId.serialize());
values.put(RECIPIENT_DEVICE_ID, 1);
values.put(DATE_RECEIVED, System.currentTimeMillis());
@@ -584,19 +573,40 @@ public class MessageTable extends DatabaseTable implements MmsSmsColumns, Recipi
values.put(TYPE, type);
values.put(THREAD_ID, threadId);
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
long messageId = db.insert(TABLE_NAME, null, values);
long messageId = getWritableDatabase().insert(TABLE_NAME, null, values);
boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && Recipient.resolved(recipientId).isMuted();
if (unread) {
SignalDatabase.threads().incrementUnread(threadId, 1, 0);
}
boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && Recipient.resolved(recipientId).isMuted();
SignalDatabase.threads().update(threadId, !keepThreadArchived);
notifyConversationListeners(threadId);
TrimThreadJob.enqueueAsync(threadId);
return new Pair<>(messageId, threadId);
return new InsertResult(messageId, threadId);
}
public void updateCallLog(long messageId, long type) {
boolean unread = Types.isMissedAudioCall(type) || Types.isMissedVideoCall(type);
ContentValues values = new ContentValues(2);
values.put(TYPE, type);
values.put(READ, unread ? 0 : 1);
getWritableDatabase().update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(messageId));
long threadId = getThreadIdForMessage(messageId);
Recipient recipient = SignalDatabase.threads().getRecipientForThreadId(threadId);
boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && recipient != null && recipient.isMuted();
if (unread) {
SignalDatabase.threads().incrementUnread(threadId, 1, 0);
}
SignalDatabase.threads().update(threadId, !keepThreadArchived);
notifyConversationListeners(threadId);
ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(new MessageId(messageId));
}
public void insertOrUpdateGroupCall(@NonNull RecipientId groupRecipientId,
@@ -4179,6 +4189,7 @@ public class MessageTable extends DatabaseTable implements MmsSmsColumns, Recipi
message.getStoryType(),
message.getParentStoryId(),
message.getGiftBadge(),
null,
null);
}
}
@@ -4367,7 +4378,7 @@ public class MessageTable extends DatabaseTable implements MmsSmsColumns, Recipi
networkFailures, subscriptionId, expiresIn, expireStarted,
isViewOnce, readReceiptCount, quote, contacts, previews, unidentified, Collections.emptyList(),
remoteDelete, mentionsSelf, notifiedTimestamp, viewedReceiptCount, receiptTimestamp, messageRanges,
storyType, parentStoryId, giftBadge, null);
storyType, parentStoryId, giftBadge, null, null);
}
private Set<IdentityKeyMismatch> getMismatchedIdentities(String document) {

View File

@@ -74,6 +74,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
val cdsTable: CdsTable = CdsTable(context, this)
val remoteMegaphoneTable: RemoteMegaphoneTable = RemoteMegaphoneTable(context, this)
val pendingPniSignatureMessageTable: PendingPniSignatureMessageTable = PendingPniSignatureMessageTable(context, this)
val callTable: CallTable = CallTable(context, this)
override fun onOpen(db: net.zetetic.database.sqlcipher.SQLiteDatabase) {
db.setForeignKeyConstraintsEnabled(true)
@@ -109,6 +110,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
db.execSQL(CdsTable.CREATE_TABLE)
db.execSQL(RemoteMegaphoneTable.CREATE_TABLE)
db.execSQL(PendingPniSignatureMessageTable.CREATE_TABLE)
db.execSQL(CallTable.CREATE_TABLE)
executeStatements(db, SearchTable.CREATE_TABLE)
executeStatements(db, RemappedRecordTables.CREATE_TABLE)
executeStatements(db, MessageSendLogTables.CREATE_TABLE)
@@ -133,6 +135,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
executeStatements(db, StorySendTable.CREATE_INDEXS)
executeStatements(db, DistributionListTables.CREATE_INDEXES)
executeStatements(db, PendingPniSignatureMessageTable.CREATE_INDEXES)
executeStatements(db, CallTable.CREATE_INDEXES)
executeStatements(db, SearchTable.CREATE_TRIGGERS)
executeStatements(db, MessageSendLogTables.CREATE_TRIGGERS)
@@ -523,5 +526,10 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
@get:JvmName("remoteMegaphones")
val remoteMegaphones: RemoteMegaphoneTable
get() = instance!!.remoteMegaphoneTable
@get:JvmStatic
@get:JvmName("calls")
val calls: CallTable
get() = instance!!.callTable
}
}

View File

@@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V166_ThreadAndMessa
import org.thoughtcrime.securesms.database.helpers.migration.V167_RecreateReactionTriggers
import org.thoughtcrime.securesms.database.helpers.migration.V168_SingleMessageTableMigration
import org.thoughtcrime.securesms.database.helpers.migration.V169_EmojiSearchIndexRank
import org.thoughtcrime.securesms.database.helpers.migration.V170_CallTableMigration
/**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@@ -33,7 +34,7 @@ object SignalDatabaseMigrations {
val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass)
const val DATABASE_VERSION = 169
const val DATABASE_VERSION = 170
@JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
@@ -120,6 +121,10 @@ object SignalDatabaseMigrations {
if (oldVersion < 169) {
V169_EmojiSearchIndexRank.migrate(context, db, oldVersion, newVersion)
}
if (oldVersion < 170) {
V170_CallTableMigration.migrate(context, db, oldVersion, newVersion)
}
}
@JvmStatic

View File

@@ -0,0 +1,26 @@
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import net.zetetic.database.sqlcipher.SQLiteDatabase
@Suppress("ClassName")
object V170_CallTableMigration : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL(
"""
CREATE TABLE call (
_id INTEGER PRIMARY KEY,
call_id INTEGER NOT NULL UNIQUE,
message_id INTEGER NOT NULL REFERENCES mms (_id) ON DELETE CASCADE,
peer INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
type INTEGER NOT NULL,
direction INTEGER NOT NULL,
event INTEGER NOT NULL
)
""".trimIndent()
)
db.execSQL("CREATE INDEX call_call_id_index ON call (call_id)")
db.execSQL("CREATE INDEX call_message_id_index ON call (message_id)")
}
}

View File

@@ -22,6 +22,7 @@ import android.text.SpannableString;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.content.ContextCompat;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
@@ -29,6 +30,7 @@ import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.MmsSmsColumns.Status;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
@@ -39,6 +41,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.payments.Payment;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.payments.FormatterOptions;
import java.util.HashMap;
@@ -47,6 +50,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -60,9 +64,10 @@ import java.util.stream.Collectors;
public class MediaMmsMessageRecord extends MmsMessageRecord {
private final static String TAG = Log.tag(MediaMmsMessageRecord.class);
private final boolean mentionsSelf;
private final BodyRangeList messageRanges;
private final Payment payment;
private final boolean mentionsSelf;
private final BodyRangeList messageRanges;
private final Payment payment;
private final CallTable.Call call;
public MediaMmsMessageRecord(long id,
Recipient conversationRecipient,
@@ -97,7 +102,8 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
@NonNull StoryType storyType,
@Nullable ParentStoryId parentStoryId,
@Nullable GiftBadge giftBadge,
@Nullable Payment payment)
@Nullable Payment payment,
@Nullable CallTable.Call call)
{
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent,
dateReceived, dateServer, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
@@ -107,6 +113,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
this.mentionsSelf = mentionsSelf;
this.messageRanges = messageRanges;
this.payment = payment;
this.call = call;
}
@Override
@@ -137,6 +144,40 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return super.getDisplayBody(context);
}
@Override
public @Nullable UpdateDescription getUpdateDisplayBody(@NonNull Context context, @Nullable Consumer<RecipientId> recipientClickHandler) {
if (isCallLog() && call != null) {
boolean accepted = call.getEvent() == CallTable.Event.ACCEPTED;
String callDateString = getCallDateString(context);
if (call.getDirection() == CallTable.Direction.OUTGOING) {
if (call.getType() == CallTable.Type.AUDIO_CALL) {
int updateString = accepted ? R.string.MessageRecord_you_called_date : R.string.MessageRecord_unanswered_audio_call_date;
return staticUpdateDescription(context.getString(updateString, callDateString), R.drawable.ic_update_audio_call_outgoing_16);
} else {
int updateString = accepted ? R.string.MessageRecord_you_called_date : R.string.MessageRecord_unanswered_video_call_date;
return staticUpdateDescription(context.getString(updateString, callDateString), R.drawable.ic_update_video_call_outgoing_16);
}
} else {
boolean isVideoCall = call.getType() == CallTable.Type.VIDEO_CALL;
boolean isMissed = call.getEvent() == CallTable.Event.MISSED;
if (accepted) {
int icon = isVideoCall ? R.drawable.ic_update_video_call_incoming_16 : R.drawable.ic_update_audio_call_incoming_16;
return fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_s_called_you_date, r.getDisplayName(context), callDateString), icon);
} else if (isMissed) {
return isVideoCall ? staticUpdateDescription(context.getString(R.string.MessageRecord_missed_video_call_date, callDateString), R.drawable.ic_update_video_call_missed_16, ContextCompat.getColor(context, R.color.core_red_shade), ContextCompat.getColor(context, R.color.core_red))
: staticUpdateDescription(context.getString(R.string.MessageRecord_missed_audio_call_date, callDateString), R.drawable.ic_update_audio_call_missed_16, ContextCompat.getColor(context, R.color.core_red_shade), ContextCompat.getColor(context, R.color.core_red));
} else {
return isVideoCall ? fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_declined_video_call_date, callDateString), R.drawable.ic_update_video_call_incoming_16)
: fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_declined_audio_call_date, callDateString), R.drawable.ic_update_audio_call_incoming_16);
}
}
}
return super.getUpdateDisplayBody(context, recipientClickHandler);
}
public @Nullable BodyRangeList getMessageRanges() {
return messageRanges;
}
@@ -155,18 +196,22 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return payment;
}
public @Nullable CallTable.Call getCall() {
return call;
}
public @NonNull MediaMmsMessageRecord withReactions(@NonNull List<ReactionRecord> reactions) {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall());
}
public @NonNull MediaMmsMessageRecord withoutQuote() {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), null, getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall());
}
public @NonNull MediaMmsMessageRecord withAttachments(@NonNull Context context, @NonNull List<DatabaseAttachment> attachments) {
@@ -187,14 +232,22 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), slideDeck,
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall());
}
public @NonNull MediaMmsMessageRecord withPayment(@NonNull Payment payment) {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment);
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall());
}
public @NonNull MediaMmsMessageRecord withCall(@Nullable CallTable.Call call) {
return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call);
}
private static @NonNull List<Contact> updateContacts(@NonNull List<Contact> contacts, @NonNull Map<AttachmentId, DatabaseAttachment> attachmentIdMap) {

View File

@@ -337,29 +337,29 @@ public abstract class MessageRecord extends DisplayRecord {
return null;
}
private @NonNull String getCallDateString(@NonNull Context context) {
protected @NonNull String getCallDateString(@NonNull Context context) {
return DateUtils.getSimpleRelativeTimeSpanString(context, Locale.getDefault(), getDateSent());
}
private static @NonNull UpdateDescription fromRecipient(@NonNull Recipient recipient,
@NonNull Function<Recipient, String> stringGenerator,
@DrawableRes int iconResource)
protected static @NonNull UpdateDescription fromRecipient(@NonNull Recipient recipient,
@NonNull Function<Recipient, String> stringGenerator,
@DrawableRes int iconResource)
{
return UpdateDescription.mentioning(Collections.singletonList(recipient.getServiceId().orElse(ServiceId.UNKNOWN)),
() -> new SpannableString(stringGenerator.apply(recipient.resolve())),
iconResource);
}
private static @NonNull UpdateDescription staticUpdateDescription(@NonNull String string,
@DrawableRes int iconResource)
protected static @NonNull UpdateDescription staticUpdateDescription(@NonNull String string,
@DrawableRes int iconResource)
{
return UpdateDescription.staticDescription(string, iconResource);
}
private static @NonNull UpdateDescription staticUpdateDescription(@NonNull String string,
@DrawableRes int iconResource,
@ColorInt int lightTint,
@ColorInt int darkTint)
protected static @NonNull UpdateDescription staticUpdateDescription(@NonNull String string,
@DrawableRes int iconResource,
@ColorInt int lightTint,
@ColorInt int darkTint)
{
return UpdateDescription.staticDescription(string, iconResource, lightTint, darkTint);
}