diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRepository.kt index 827123baf7..1ee831ddd0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogRepository.kt @@ -51,10 +51,11 @@ class CallLogRepository : CallLogPagedDataSource.CallRepository { } fun deleteAllCallLogsExcept( - selectedCallRowIds: Set + selectedCallRowIds: Set, + missedOnly: Boolean ): Completable { return Completable.fromAction { - SignalDatabase.calls.deleteAllCallEventsExcept(selectedCallRowIds) + SignalDatabase.calls.deleteAllCallEventsExcept(selectedCallRowIds, missedOnly) }.observeOn(Schedulers.io()) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogStagedDeletion.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogStagedDeletion.kt index 1b0db1ebe1..53397f49f2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogStagedDeletion.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogStagedDeletion.kt @@ -6,6 +6,7 @@ import androidx.annotation.MainThread * Encapsulates a single deletion action */ class CallLogStagedDeletion( + private val filter: CallLogFilter, private val stateSnapshot: CallLogSelectionState, private val repository: CallLogRepository ) { @@ -35,7 +36,7 @@ class CallLogStagedDeletion( .toSet() if (stateSnapshot.isExclusionary()) { - repository.deleteAllCallLogsExcept(callRowIds).subscribe() + repository.deleteAllCallLogsExcept(callRowIds, filter == CallLogFilter.MISSED).subscribe() } else { repository.deleteSelectedCallLogs(callRowIds).subscribe() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogViewModel.kt index 24c045a991..e9012abd05 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogViewModel.kt @@ -101,6 +101,7 @@ class CallLogViewModel( callLogStore.update { it.copy( stagedDeletion = CallLogStagedDeletion( + it.filter, CallLogSelectionState.empty().toggle(call.id), callLogRepository ) @@ -114,6 +115,7 @@ class CallLogViewModel( callLogStore.update { it.copy( stagedDeletion = CallLogStagedDeletion( + it.filter, it.selectionState, callLogRepository ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt index 128d9f7903..c23bd5be56 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/CallTable.kt @@ -20,6 +20,7 @@ import org.signal.core.util.requireNonNullString import org.signal.core.util.requireObject import org.signal.core.util.requireString import org.signal.core.util.select +import org.signal.core.util.toSingleLine import org.signal.core.util.update import org.signal.core.util.withinTransaction import org.signal.ringrtc.CallId @@ -544,6 +545,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl else -> Log.w(TAG, "Received a REQUESTED ring event while in ${call.event}. Ignoring.") } } + RingUpdate.EXPIRED_REQUEST, RingUpdate.CANCELLED_BY_RINGER -> { when (call.event) { Event.GENERIC_GROUP_CALL, Event.RINGING -> updateEventFromRingState(ringId, Event.MISSED, ringerRecipient) @@ -551,6 +553,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl else -> Unit } } + RingUpdate.BUSY_LOCALLY, RingUpdate.BUSY_ON_ANOTHER_DEVICE -> { when (call.event) { Event.JOINED -> updateEventFromRingState(ringId, Event.ACCEPTED) @@ -558,9 +561,11 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl else -> Log.w(TAG, "Received a busy event we can't process. Ignoring.") } } + RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE -> { updateEventFromRingState(ringId, Event.ACCEPTED) } + RingUpdate.DECLINED_ON_ANOTHER_DEVICE -> { when (call.event) { Event.RINGING, Event.MISSED -> updateEventFromRingState(ringId, Event.DECLINED) @@ -577,14 +582,17 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl Log.w(TAG, "Missed original ring request for $ringId") Event.ACCEPTED } + RingUpdate.DECLINED_ON_ANOTHER_DEVICE -> { Log.w(TAG, "Missed original ring request for $ringId") Event.DECLINED } + RingUpdate.BUSY_LOCALLY, RingUpdate.BUSY_ON_ANOTHER_DEVICE -> { Log.w(TAG, "Missed original ring request for $ringId") Event.MISSED } + RingUpdate.CANCELLED_BY_RINGER -> { Log.w(TAG, "Missed original ring request for $ringId") Event.MISSED @@ -702,10 +710,72 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl updateCallEventDeletionTimestamps() } - fun deleteAllCallEventsExcept(callRowIds: Set) { - val messageIds = getMessageIds(callRowIds) - SignalDatabase.messages.deleteAllCallUpdatesExcept(messageIds) - updateCallEventDeletionTimestamps() + fun deleteAllCallEventsExcept(callRowIds: Set, missedOnly: Boolean) { + val callFilter = if (missedOnly) { + "$EVENT = ${Event.serialize(Event.MISSED)} AND $DELETION_TIMESTAMP = 0" + } else { + "$DELETION_TIMESTAMP = 0" + } + + if (callRowIds.isEmpty()) { + val threadIds = writableDatabase.withinTransaction { db -> + val ids = db.select(MessageTable.THREAD_ID) + .from(MessageTable.TABLE_NAME) + .where( + """ + ${MessageTable.ID} IN ( + SELECT $MESSAGE_ID FROM $TABLE_NAME + WHERE $callFilter + ) + """.toSingleLine() + ) + .run() + .readToList { it.requireLong(MessageTable.THREAD_ID) } + + db.delete(MessageTable.TABLE_NAME) + .where( + """ + ${MessageTable.ID} IN ( + SELECT $MESSAGE_ID FROM $TABLE_NAME + WHERE $callFilter + ) + """.toSingleLine() + ) + .run() + + ids.toSet() + } + + threadIds.forEach { + SignalDatabase.threads.update( + threadId = it, + unarchive = false, + allowDeletion = true + ) + } + + notifyConversationListeners(threadIds) + notifyConversationListListeners() + updateCallEventDeletionTimestamps() + } else { + writableDatabase.withinTransaction { db -> + SqlUtil.buildCollectionQuery( + column = ID, + values = callRowIds, + prefix = "$callFilter AND", + collectionOperator = SqlUtil.CollectionOperator.NOT_IN + ).forEach { query -> + val messageIds = db.select(MESSAGE_ID) + .from(TABLE_NAME) + .where(query.where, query.whereArgs) + .run() + .readToList { it.requireLong(MESSAGE_ID) } + .toSet() + SignalDatabase.messages.deleteCallUpdates(messageIds) + updateCallEventDeletionTimestamps() + } + } + } } @Discouraged("Using this method is generally considered an error. Utilize other deletion methods instead of this.") @@ -876,8 +946,6 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl val date = cursor.requireLong(MessageTable.DATE_RECEIVED) val groupCallDetails = GroupCallUpdateDetailsUtil.parse(cursor.requireString(MessageTable.BODY)) - Log.d(TAG, "${cursor.requireNonNullString("in_period")}") - val children = cursor.requireNonNullString("children") .split(',') .map { it.toLong() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt index f0b274a3aa..9850070200 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -3193,14 +3193,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat return deleteCallUpdatesInternal(messageIds, SqlUtil.CollectionOperator.IN) } - /** - * Deletes all call updates except for those specified in the parameter. - */ - fun deleteAllCallUpdatesExcept(excludedMessageIds: Set): Int { - return deleteCallUpdatesInternal(excludedMessageIds, SqlUtil.CollectionOperator.NOT_IN) - } - - private fun deleteCallUpdatesInternal(messageIds: Set, collectionOperator: SqlUtil.CollectionOperator): Int { + private fun deleteCallUpdatesInternal( + messageIds: Set, + collectionOperator: SqlUtil.CollectionOperator + ): Int { var rowsDeleted = 0 val threadIds: Set = writableDatabase.withinTransaction { SqlUtil.buildCollectionQuery( diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/CallSyncEventJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/CallSyncEventJob.kt index 18b3f37cb4..eaf88c3345 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/CallSyncEventJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/CallSyncEventJob.kt @@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.ringrtc.RemotePeer import org.thoughtcrime.securesms.service.webrtc.CallEventSyncMessageUtil +import org.thoughtcrime.securesms.util.FeatureFlags import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage import java.util.Optional import java.util.concurrent.TimeUnit @@ -46,8 +47,7 @@ class CallSyncEventJob private constructor( ) } - @JvmStatic - fun createForDelete(conversationRecipientId: RecipientId, callId: Long, isIncoming: Boolean): CallSyncEventJob { + private fun createForDelete(conversationRecipientId: RecipientId, callId: Long, isIncoming: Boolean): CallSyncEventJob { return CallSyncEventJob( getParameters(conversationRecipientId), conversationRecipientId, @@ -57,16 +57,17 @@ class CallSyncEventJob private constructor( ) } - @JvmStatic fun enqueueDeleteSyncEvents(deletedCalls: Set) { - for (call in deletedCalls) { - ApplicationDependencies.getJobManager().add( - createForDelete( - call.peer, - call.callId, - call.direction == CallTable.Direction.INCOMING + if (FeatureFlags.callDeleteSync()) { + for (call in deletedCalls) { + ApplicationDependencies.getJobManager().add( + createForDelete( + call.peer, + call.callId, + call.direction == CallTable.Direction.INCOMING + ) ) - ) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index cb1f300520..26c903d920 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -111,6 +111,7 @@ public final class FeatureFlags { private static final String AD_HOC_CALLING = "android.calling.ad.hoc"; private static final String EDIT_MESSAGE_RECEIVE = "android.editMessage.receive"; private static final String EDIT_MESSAGE_SEND = "android.editMessage.send"; + private static final String CALL_DELETE_SYNC = "android.calling.deleteSync"; /** * We will only store remote values for flags in this set. If you want a flag to be controllable @@ -177,7 +178,8 @@ public final class FeatureFlags { @VisibleForTesting static final Set NOT_REMOTE_CAPABLE = SetUtil.newHashSet( PHONE_NUMBER_PRIVACY, - AD_HOC_CALLING + AD_HOC_CALLING, + CALL_DELETE_SYNC ); /** @@ -626,6 +628,13 @@ public final class FeatureFlags { return getBoolean(AD_HOC_CALLING, false); } + /** + * Whether sending deletion sync events is supported + */ + public static boolean callDeleteSync() { + return getBoolean(CALL_DELETE_SYNC, false); + } + /** Only for rendering debug info. */ public static synchronized @NonNull Map getMemoryValues() { return new TreeMap<>(REMOTE_VALUES);