mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 17:29:32 +01:00
Add call disposition syncing.
This commit is contained in:
committed by
Greyson Parrelli
parent
d471647e12
commit
06b414f4ef
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)")
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user