mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-02 08:23:00 +01:00
Add pending and failed states for admin delete.
This commit is contained in:
committed by
Greyson Parrelli
parent
3af8b6050c
commit
74d9e3248b
@@ -291,7 +291,11 @@ public class ConversationItemFooter extends ConstraintLayout {
|
||||
dateView.setText(null);
|
||||
} else if (messageRecord.isFailed()) {
|
||||
int errorMsg;
|
||||
if (messageRecord.hasFailedWithNetworkFailures()) {
|
||||
if (messageRecord.isFailedAdminDelete() && messageRecord.isIdentityMismatchFailure()) {
|
||||
errorMsg = R.string.ConversationItem_error_partially_not_deleted;
|
||||
} else if (messageRecord.isFailedAdminDelete()) {
|
||||
errorMsg = R.string.ConversationItem_error_delete_failed;
|
||||
} else if (messageRecord.hasFailedWithNetworkFailures()) {
|
||||
errorMsg = R.string.ConversationItem_error_network_not_delivered;
|
||||
} else if (messageRecord.getToRecipient().isPushGroup() && messageRecord.isIdentityMismatchFailure()) {
|
||||
errorMsg = R.string.ConversationItem_error_partially_not_delivered;
|
||||
@@ -397,7 +401,7 @@ public class ConversationItemFooter extends ConstraintLayout {
|
||||
}
|
||||
|
||||
if (onlyShowSendingStatus) {
|
||||
if (messageRecord.isOutgoing() && messageRecord.isPending()) {
|
||||
if (messageRecord.isPending()) {
|
||||
deliveryStatusView.setPending();
|
||||
} else {
|
||||
deliveryStatusView.setNone();
|
||||
|
||||
@@ -183,7 +183,9 @@ public final class SafetyNumberChangeRepository {
|
||||
}
|
||||
}
|
||||
|
||||
if (messageRecord.isOutgoing()) {
|
||||
if (messageRecord.isFailedAdminDelete()) {
|
||||
processAdminDeletedMessageRecord(changedRecipients, messageRecord);
|
||||
} else if (messageRecord.isOutgoing()) {
|
||||
processOutgoingMessageRecord(changedRecipients, messageRecord);
|
||||
}
|
||||
|
||||
@@ -223,6 +225,24 @@ public final class SafetyNumberChangeRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void processAdminDeletedMessageRecord(@NonNull List<ChangedRecipient> changedRecipients, @NonNull MessageRecord messageRecord) {
|
||||
Log.d(TAG, "processAdminDeletedMessageRecord");
|
||||
Set<RecipientId> resendIds = new HashSet<>();
|
||||
|
||||
for (ChangedRecipient changedRecipient : changedRecipients) {
|
||||
RecipientId id = changedRecipient.getRecipient().getId();
|
||||
IdentityKey identityKey = changedRecipient.getIdentityRecord().getIdentityKey();
|
||||
|
||||
SignalDatabase.messages().removeMismatchedIdentity(messageRecord.getId(), id, identityKey);
|
||||
resendIds.add(id);
|
||||
}
|
||||
|
||||
if (Util.hasItems(resendIds) ) {
|
||||
MessageSender.resendAdminDelete(messageRecord, resendIds.stream().collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
static final class SafetyNumberChangeState {
|
||||
|
||||
private final List<ChangedRecipient> changedRecipients;
|
||||
|
||||
@@ -94,6 +94,25 @@ object ConversationDialogs {
|
||||
.show()
|
||||
}
|
||||
|
||||
fun displayDeletionFailedDialog(context: Context, messageRecord: MessageRecord, canRetry: Boolean) {
|
||||
if (canRetry) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.conversation_activity__message_failed_to_delete_retry)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.conversation_activity__send) { _, _ ->
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
MessageSender.resendAdminDelete(messageRecord, messageRecord.networkFailures.map { it.recipientId })
|
||||
}
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.conversation_activity__message_failed_to_delete)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun displayDeleteDialog(context: Context, recipient: Recipient, onDelete: () -> Unit) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
|
||||
@@ -3344,9 +3344,18 @@ class ConversationFragment :
|
||||
|
||||
override fun onMessageWithErrorClicked(messageRecord: MessageRecord) {
|
||||
val recipientId = viewModel.recipientSnapshot?.id ?: return
|
||||
if (messageRecord.isIdentityMismatchFailure) {
|
||||
if (messageRecord.isFailedAdminDelete) {
|
||||
val canRetry = MessageConstraintsUtil.isValidAdminDeleteSend(message = messageRecord, currentTime = System.currentTimeMillis(), isAdmin = conversationGroupViewModel.isAdmin(), isResend = true)
|
||||
if (messageRecord.isIdentityMismatchFailure && canRetry) {
|
||||
SafetyNumberBottomSheet
|
||||
.forIncomingMessageRecord(messageRecord, viewModel.recipientSnapshot!!)
|
||||
.show(childFragmentManager)
|
||||
} else {
|
||||
ConversationDialogs.displayDeletionFailedDialog(requireContext(), messageRecord, canRetry)
|
||||
}
|
||||
} else if (messageRecord.isIdentityMismatchFailure) {
|
||||
SafetyNumberBottomSheet
|
||||
.forMessageRecord(requireContext(), messageRecord)
|
||||
.forOutgoingMessageRecord(requireContext(), messageRecord)
|
||||
.show(childFragmentManager)
|
||||
} else if (messageRecord.hasFailedWithNetworkFailures()) {
|
||||
ConversationDialogs.displayMessageCouldNotBeSentDialog(requireContext(), messageRecord)
|
||||
|
||||
@@ -106,6 +106,7 @@ import org.thoughtcrime.securesms.database.model.StoryResult
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.database.model.StoryType.Companion.fromCode
|
||||
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.AdminDeleteStatus
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
|
||||
@@ -3788,6 +3789,45 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets admin delete status to pending
|
||||
*/
|
||||
fun markAsPendingAdminDelete(messageId: Long) {
|
||||
val messageExtras = MessageExtras(adminDeleteStatus = AdminDeleteStatus(AdminDeleteStatus.Status.PENDING))
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(MESSAGE_EXTRAS to messageExtras.encode())
|
||||
.where("$ID = ?", messageId)
|
||||
.run()
|
||||
AppDependencies.databaseObserver.notifyMessageUpdateObservers(MessageId(messageId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets admin delete status to failed
|
||||
*/
|
||||
fun markAsFailedAdminDelete(messageId: Long) {
|
||||
val messageExtras = MessageExtras(adminDeleteStatus = AdminDeleteStatus(AdminDeleteStatus.Status.FAILED))
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(MESSAGE_EXTRAS to messageExtras.encode())
|
||||
.where("$ID = ?", messageId)
|
||||
.run()
|
||||
AppDependencies.databaseObserver.notifyMessageUpdateObservers(MessageId(messageId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets admin delete status to complete.
|
||||
*/
|
||||
fun markAsSentAdminDelete(messageId: Long) {
|
||||
val messageExtras = MessageExtras(adminDeleteStatus = AdminDeleteStatus(AdminDeleteStatus.Status.DONE))
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(MESSAGE_EXTRAS to messageExtras.encode())
|
||||
.where("$ID = ?", messageId)
|
||||
.run()
|
||||
AppDependencies.databaseObserver.notifyMessageUpdateObservers(MessageId(messageId))
|
||||
}
|
||||
|
||||
/**
|
||||
* When a message gets deleted, clear the pinned record and remove any references
|
||||
*/
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.components.transfercontrols.TransferControlVie
|
||||
import org.thoughtcrime.securesms.database.MessageTypes;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.AdminDeleteStatus;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails;
|
||||
@@ -173,6 +174,15 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return MessageTypes.isLegacyType(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFailed() {
|
||||
return super.isFailed() || isFailedAdminDelete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPending() {
|
||||
return super.isPending() || isPendingAdminDelete();
|
||||
}
|
||||
|
||||
@Override
|
||||
@WorkerThread
|
||||
@@ -789,6 +799,18 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return deletedBy;
|
||||
}
|
||||
|
||||
public boolean isPendingAdminDelete() {
|
||||
return messageExtras != null &&
|
||||
messageExtras.adminDeleteStatus != null &&
|
||||
messageExtras.adminDeleteStatus.status == AdminDeleteStatus.Status.PENDING;
|
||||
}
|
||||
|
||||
public boolean isFailedAdminDelete() {
|
||||
return messageExtras != null &&
|
||||
messageExtras.adminDeleteStatus != null &&
|
||||
messageExtras.adminDeleteStatus.status == AdminDeleteStatus.Status.FAILED;
|
||||
}
|
||||
|
||||
public boolean isInMemoryMessageRecord() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.signal.core.models.ServiceId
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.logging.Log.tag
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure
|
||||
import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
@@ -37,15 +38,19 @@ class AdminDeleteSendJob private constructor(
|
||||
private val TAG = tag(AdminDeleteSendJob::class.java)
|
||||
|
||||
@JvmStatic
|
||||
fun create(messageId: Long): AdminDeleteSendJob? {
|
||||
val message = SignalDatabase.messages.getMessageRecord(messageId)
|
||||
fun create(messageId: Long, filterRecipients: List<RecipientId>): AdminDeleteSendJob? {
|
||||
val message = SignalDatabase.messages.getMessageRecordOrNull(messageId)
|
||||
if (message == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val conversationRecipient = SignalDatabase.threads.getRecipientForThreadId(message.threadId)
|
||||
|
||||
if (conversationRecipient == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val recipientIds = conversationRecipient.participantIds.map { it.toLong() }.toMutableList()
|
||||
val recipientIds = filterRecipients.ifEmpty { conversationRecipient.participantIds }.map { it.toLong() }.toMutableList()
|
||||
|
||||
return AdminDeleteSendJob(
|
||||
messageId = messageId,
|
||||
@@ -81,7 +86,11 @@ class AdminDeleteSendJob private constructor(
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
val recipients = recipientIds.map { Recipient.resolved(RecipientId.from(it)) }.toMutableList()
|
||||
val existingNetworkFailures = message.networkFailures.toMutableSet()
|
||||
val existingIdentityMismatches = message.identityKeyMismatches.toMutableSet()
|
||||
val targets = (recipientIds + existingIdentityMismatches.map { it.recipientId.toLong() } + existingNetworkFailures.map { it.recipientId.toLong() }).toSet()
|
||||
|
||||
val recipients = targets.map { Recipient.resolved(RecipientId.from(it)) }.toMutableList()
|
||||
val targetSentTimestamp = message.dateSent
|
||||
val targetAuthor = message.fromRecipient.requireServiceId()
|
||||
|
||||
@@ -103,9 +112,22 @@ class AdminDeleteSendJob private constructor(
|
||||
}
|
||||
|
||||
val eligible = RecipientUtil.getEligibleForSending(recipients.filter { it.hasServiceId })
|
||||
val skippedRecipients = recipients - eligible
|
||||
val ineligibleRecipients = recipients - eligible
|
||||
val sendResult = deliver(conversationRecipient, eligible, targetAuthor, targetSentTimestamp)
|
||||
|
||||
val completedIds = sendResult.completed.map { it.id }.toSet()
|
||||
existingNetworkFailures.removeAll { completedIds.contains(it.recipientId) }
|
||||
existingIdentityMismatches.removeAll { completedIds.contains(it.recipientId) }
|
||||
|
||||
val ineligibleIds = (ineligibleRecipients.map { it.id } + sendResult.unregistered).toSet()
|
||||
existingNetworkFailures.removeAll { ineligibleIds.contains(it.recipientId) }
|
||||
existingIdentityMismatches.removeAll { ineligibleIds.contains(it.recipientId) }
|
||||
|
||||
existingIdentityMismatches.addAll(sendResult.identityMismatch)
|
||||
|
||||
SignalDatabase.messages.setNetworkFailures(messageId, existingNetworkFailures)
|
||||
SignalDatabase.messages.setMismatchedIdentities(messageId, existingIdentityMismatches)
|
||||
|
||||
for (completion in sendResult.completed) {
|
||||
recipientIds.remove(completion.id.toLong())
|
||||
}
|
||||
@@ -114,22 +136,35 @@ class AdminDeleteSendJob private constructor(
|
||||
SignalDatabase.recipients.markUnregistered(unregistered)
|
||||
}
|
||||
|
||||
for (recipient in skippedRecipients) {
|
||||
for (recipient in ineligibleRecipients) {
|
||||
recipientIds.remove(recipient.id.toLong())
|
||||
}
|
||||
|
||||
Log.i(TAG, "Completed now: ${sendResult.completed.size} Skipped: ${skippedRecipients.size + sendResult.skipped.size} Remaining: ${recipientIds.size}")
|
||||
Log.i(TAG, "Completed now: ${sendResult.completed.size} Skipped: ${ineligibleRecipients.size + sendResult.skipped.size} Remaining: ${recipientIds.size}")
|
||||
|
||||
if (recipientIds.isEmpty()) {
|
||||
if (existingNetworkFailures.isEmpty() && existingIdentityMismatches.isEmpty() && recipientIds.isEmpty()) {
|
||||
SignalDatabase.messages.markAsSentAdminDelete(messageId)
|
||||
return Result.success()
|
||||
} else if (existingIdentityMismatches.isNotEmpty()) {
|
||||
Log.w(TAG, "Failing because there were ${existingIdentityMismatches.size} identity mismatches.")
|
||||
return Result.failure()
|
||||
} else {
|
||||
Log.w(TAG, "Still need to send to ${recipients.size} recipients. Retrying.")
|
||||
Log.w(TAG, "Still need to send to ${recipientIds.size} recipients. Retrying.")
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure() {
|
||||
Log.w(TAG, "Failed to send admin delete to all recipients! ${initialRecipientCount - recipientIds.size} / $initialRecipientCount")
|
||||
Log.w(TAG, "Failed to send admin delete to all recipients! ${initialRecipientCount - recipientIds.size} / $initialRecipientCount. Marking remaining non-identity mismatched failures as network failure.")
|
||||
val message = SignalDatabase.messages.getMessageRecordOrNull(messageId)
|
||||
if (message == null) {
|
||||
Log.w(TAG, "Message no longer exists, ignoring.")
|
||||
} else {
|
||||
val existingIdentityMismatches = message.identityKeyMismatches.map { it.recipientId.toLong() }
|
||||
recipientIds.removeAll { existingIdentityMismatches.contains(it) }
|
||||
SignalDatabase.messages.setNetworkFailures(messageId, recipientIds.map { NetworkFailure(RecipientId.from(it)) }.toSet())
|
||||
SignalDatabase.messages.markAsFailedAdminDelete(messageId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deliver(
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.RecipientAccessList;
|
||||
@@ -20,16 +21,18 @@ public final class GroupSendJobHelper {
|
||||
}
|
||||
|
||||
public static @NonNull SendResult getCompletedSends(@NonNull List<Recipient> possibleRecipients, @NonNull Collection<SendMessageResult> results) {
|
||||
RecipientAccessList accessList = new RecipientAccessList(possibleRecipients);
|
||||
List<Recipient> completions = new ArrayList<>(results.size());
|
||||
List<RecipientId> skipped = new ArrayList<>();
|
||||
List<RecipientId> unregistered = new ArrayList<>();
|
||||
RecipientAccessList accessList = new RecipientAccessList(possibleRecipients);
|
||||
List<Recipient> completions = new ArrayList<>(results.size());
|
||||
List<RecipientId> skipped = new ArrayList<>();
|
||||
List<RecipientId> unregistered = new ArrayList<>();
|
||||
List<IdentityKeyMismatch> identityMismatch = new ArrayList<>();
|
||||
|
||||
for (SendMessageResult sendMessageResult : results) {
|
||||
Recipient recipient = accessList.requireByAddress(sendMessageResult.getAddress());
|
||||
|
||||
if (sendMessageResult.getIdentityFailure() != null) {
|
||||
Log.w(TAG, "Identity failure for " + recipient.getId());
|
||||
identityMismatch.add(new IdentityKeyMismatch(recipient.getId(), sendMessageResult.getIdentityFailure().getIdentityKey()));
|
||||
}
|
||||
|
||||
if (sendMessageResult.isUnregisteredFailure()) {
|
||||
@@ -63,7 +66,7 @@ public final class GroupSendJobHelper {
|
||||
}
|
||||
}
|
||||
|
||||
return new SendResult(completions, skipped, unregistered);
|
||||
return new SendResult(completions, skipped, unregistered, identityMismatch);
|
||||
}
|
||||
|
||||
public static class SendResult {
|
||||
@@ -76,10 +79,14 @@ public final class GroupSendJobHelper {
|
||||
/** Recipients that were discovered to be unregistered. Important: items in this list can overlap with other lists in the result. */
|
||||
public final List<RecipientId> unregistered;
|
||||
|
||||
public SendResult(@NonNull List<Recipient> completed, @NonNull List<RecipientId> skipped, @NonNull List<RecipientId> unregistered) {
|
||||
this.completed = completed;
|
||||
this.skipped = skipped;
|
||||
this.unregistered = unregistered;
|
||||
/** Recipients that were not sent to due to an identity failure. Important: items in this list overlap with other lists in the result. */
|
||||
public final List<IdentityKeyMismatch> identityMismatch;
|
||||
|
||||
public SendResult(@NonNull List<Recipient> completed, @NonNull List<RecipientId> skipped, @NonNull List<RecipientId> unregistered, List<IdentityKeyMismatch> identityMismatch) {
|
||||
this.completed = completed;
|
||||
this.skipped = skipped;
|
||||
this.unregistered = unregistered;
|
||||
this.identityMismatch = identityMismatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ import org.thoughtcrime.securesms.polls.PollOption
|
||||
import org.thoughtcrime.securesms.polls.PollRecord
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet.forMessageRecord
|
||||
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet.forOutgoingMessageRecord
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
||||
import org.thoughtcrime.securesms.util.fragments.requireListener
|
||||
@@ -153,7 +153,7 @@ class MessageDetailsFragment : Fragment(), MessageDetailsAdapter.Callbacks {
|
||||
}
|
||||
|
||||
override fun onErrorClicked(messageRecord: MessageRecord) {
|
||||
forMessageRecord(requireContext(), messageRecord)
|
||||
forOutgoingMessageRecord(requireContext(), messageRecord)
|
||||
.show(childFragmentManager)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.util.Preconditions
|
||||
|
||||
@@ -73,14 +74,14 @@ object SafetyNumberBottomSheet {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a factory to generate a sheet for the given message record. This will try
|
||||
* Create a factory to generate a sheet for an outgoing message record. This will try
|
||||
* to resend the message automatically when the user confirms.
|
||||
*
|
||||
* @param context Not held on to, so any context is fine.
|
||||
* @param messageRecord The message record containing failed identities.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun forMessageRecord(context: Context, messageRecord: MessageRecord): Factory {
|
||||
fun forOutgoingMessageRecord(context: Context, messageRecord: MessageRecord): Factory {
|
||||
val args = SafetyNumberBottomSheetArgs(
|
||||
untrustedRecipients = messageRecord.identityKeyMismatches.map { it.recipientId },
|
||||
destinations = getDestinationFromRecord(messageRecord),
|
||||
@@ -90,6 +91,21 @@ object SafetyNumberBottomSheet {
|
||||
return SheetFactory(args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a factory to generate a sheet for an incoming message record. This will try
|
||||
* to resend the message automatically when the user confirms.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun forIncomingMessageRecord(messageRecord: MessageRecord, conversationRecipient: Recipient): Factory {
|
||||
val args = SafetyNumberBottomSheetArgs(
|
||||
untrustedRecipients = messageRecord.identityKeyMismatches.map { it.recipientId },
|
||||
destinations = listOf(ContactSearchKey.RecipientSearchKey(conversationRecipient.id, false)),
|
||||
messageId = MessageId(messageRecord.id)
|
||||
)
|
||||
|
||||
return SheetFactory(args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a factory to generate a sheet for the given identity records and destinations.
|
||||
*
|
||||
|
||||
@@ -519,9 +519,9 @@ public class MessageSender {
|
||||
}
|
||||
|
||||
public static void sendAdminDelete(long messageId) {
|
||||
// TODO(michelle): Update with failure states
|
||||
SignalDatabase.messages().markAsDeleteBySelf(messageId);
|
||||
AdminDeleteSendJob job = AdminDeleteSendJob.create(messageId);
|
||||
SignalDatabase.messages().markAsPendingAdminDelete(messageId);
|
||||
AdminDeleteSendJob job = AdminDeleteSendJob.create(messageId, Collections.emptyList());
|
||||
if (job != null) {
|
||||
AppDependencies.getJobManager().add(job);
|
||||
} else {
|
||||
@@ -529,6 +529,16 @@ public class MessageSender {
|
||||
}
|
||||
}
|
||||
|
||||
public static void resendAdminDelete(MessageRecord message, List<RecipientId> filteredRecipients) {
|
||||
SignalDatabase.messages().markAsPendingAdminDelete(message.getId());
|
||||
AdminDeleteSendJob job = AdminDeleteSendJob.create(message.getId(), filteredRecipients);
|
||||
if (job != null) {
|
||||
AppDependencies.getJobManager().add(job);
|
||||
} else {
|
||||
Log.w(TAG, "[resendAdminDelete] Could not resend the admin delete job.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void resendGroupMessage(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull Set<RecipientId> filterRecipientIds) {
|
||||
if (!messageRecord.isMms()) throw new AssertionError("Not Group");
|
||||
sendGroupPush(context, messageRecord.getToRecipient(), messageRecord.getId(), filterRecipientIds, Collections.emptyList());
|
||||
|
||||
@@ -288,7 +288,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
|
||||
} else if (model.data.primaryStory.messageRecord.isOutgoing && model.data.primaryStory.messageRecord.isFailed) {
|
||||
if (model.data.primaryStory.messageRecord.isIdentityMismatchFailure) {
|
||||
SafetyNumberBottomSheet
|
||||
.forMessageRecord(requireContext(), model.data.primaryStory.messageRecord)
|
||||
.forOutgoingMessageRecord(requireContext(), model.data.primaryStory.messageRecord)
|
||||
.show(childFragmentManager)
|
||||
} else {
|
||||
StoryDialogs.resendStory(requireContext()) {
|
||||
|
||||
@@ -118,7 +118,7 @@ class MyStoriesFragment : DSLSettingsFragment(
|
||||
if (it.distributionStory.messageRecord.isOutgoing && it.distributionStory.messageRecord.isFailed) {
|
||||
if (it.distributionStory.messageRecord.isIdentityMismatchFailure) {
|
||||
SafetyNumberBottomSheet
|
||||
.forMessageRecord(requireContext(), it.distributionStory.messageRecord)
|
||||
.forOutgoingMessageRecord(requireContext(), it.distributionStory.messageRecord)
|
||||
.show(childFragmentManager)
|
||||
} else {
|
||||
StoryDialogs.resendStory(requireContext()) {
|
||||
|
||||
@@ -833,7 +833,7 @@ class StoryViewerPageFragment :
|
||||
viewModel.setIsDisplayingPartialSendDialog(true)
|
||||
if (storyPost.conversationMessage.messageRecord.isIdentityMismatchFailure) {
|
||||
SafetyNumberBottomSheet
|
||||
.forMessageRecord(requireContext(), storyPost.conversationMessage.messageRecord)
|
||||
.forOutgoingMessageRecord(requireContext(), storyPost.conversationMessage.messageRecord)
|
||||
.show(childFragmentManager)
|
||||
} else {
|
||||
StoryDialogs.resendStory(requireContext(), { viewModel.setIsDisplayingPartialSendDialog(false) }) {
|
||||
|
||||
@@ -315,7 +315,7 @@ class StoryGroupReplyFragment :
|
||||
|
||||
if (messageRecord.isIdentityMismatchFailure) {
|
||||
SafetyNumberBottomSheet
|
||||
.forMessageRecord(requireContext(), messageRecord)
|
||||
.forOutgoingMessageRecord(requireContext(), messageRecord)
|
||||
.show(childFragmentManager)
|
||||
} else if (messageRecord.hasFailedWithNetworkFailures()) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
|
||||
@@ -55,7 +55,7 @@ object MessageConstraintsUtil {
|
||||
|
||||
@JvmStatic
|
||||
fun isValidAdminDeleteSend(targetMessages: Collection<MessageRecord>, currentTime: Long, isAdmin: Boolean): Boolean {
|
||||
return targetMessages.all { isValidAdminDeleteSend(it, currentTime, isAdmin) }
|
||||
return targetMessages.all { isValidAdminDeleteSend(message = it, currentTime = currentTime, isAdmin = isAdmin, isResend = false) }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -110,13 +110,13 @@ object MessageConstraintsUtil {
|
||||
(currentTime - message.dateSent < SEND_THRESHOLD || message.toRecipient.isSelf)
|
||||
}
|
||||
|
||||
private fun isValidAdminDeleteSend(message: MessageRecord, currentTime: Long, isAdmin: Boolean): Boolean {
|
||||
fun isValidAdminDeleteSend(message: MessageRecord, currentTime: Long, isAdmin: Boolean, isResend: Boolean): Boolean {
|
||||
return RemoteConfig.sendAdminDelete &&
|
||||
isAdmin &&
|
||||
!message.isUpdate &&
|
||||
message.isPush &&
|
||||
(!message.toRecipient.isGroup || message.toRecipient.isActiveGroup) &&
|
||||
!message.isRemoteDelete &&
|
||||
(!message.isRemoteDelete || isResend) &&
|
||||
!message.hasGiftBadge() &&
|
||||
!message.isPaymentNotification &&
|
||||
!message.isPaymentTombstone &&
|
||||
|
||||
@@ -536,6 +536,7 @@ message MessageExtras {
|
||||
PaymentTombstone paymentTombstone = 4;
|
||||
PollTerminate pollTerminate = 5;
|
||||
PinnedMessage pinnedMessage = 6;
|
||||
AdminDeleteStatus adminDeleteStatus = 7;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,6 +551,15 @@ message PaymentTombstone {
|
||||
CryptoValue fee = 3;
|
||||
}
|
||||
|
||||
message AdminDeleteStatus {
|
||||
enum Status {
|
||||
PENDING = 0;
|
||||
DONE = 1;
|
||||
FAILED = 2;
|
||||
}
|
||||
Status status = 1;
|
||||
}
|
||||
|
||||
message PollTerminate {
|
||||
string question = 1;
|
||||
uint64 messageId = 2;
|
||||
|
||||
@@ -285,21 +285,20 @@
|
||||
|
||||
</org.thoughtcrime.securesms.conversation.ConversationItemBodyBubble>
|
||||
|
||||
<org.thoughtcrime.securesms.components.AlertView
|
||||
android:id="@+id/indicators_parent"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_toStartOf="@id/quoted_indicator"
|
||||
android:gravity="center_vertical"
|
||||
android:visibility="gone" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@id/body_bubble"
|
||||
android:layout_alignEnd="@id/body_bubble"
|
||||
android:layout_alignBottom="@id/body_bubble"
|
||||
android:layout_marginEnd="-42dp">
|
||||
android:layout_marginEnd="-30dp" >
|
||||
|
||||
<org.thoughtcrime.securesms.components.AlertView
|
||||
android:id="@+id/indicators_parent"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/quoted_indicator"
|
||||
@@ -309,6 +308,7 @@
|
||||
android:background="@drawable/circle_tintable"
|
||||
android:backgroundTint="@color/signal_colorSurfaceVariant"
|
||||
android:padding="6dp"
|
||||
android:layout_marginEnd="-12dp"
|
||||
android:tint="@color/signal_colorOnSurfaceVariant"
|
||||
app:srcCompat="@drawable/ic_replies_outline_20" />
|
||||
|
||||
@@ -320,6 +320,7 @@
|
||||
android:background="@drawable/circle_tintable"
|
||||
android:backgroundTint="@color/signal_colorSurfaceVariant"
|
||||
android:padding="6dp"
|
||||
android:layout_marginEnd="-12dp"
|
||||
android:tint="@color/signal_colorOnSurfaceVariant"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/symbol_chat_arrow_24" />
|
||||
|
||||
@@ -435,7 +435,11 @@
|
||||
<!-- ConversationItem -->
|
||||
<string name="ConversationItem_error_not_sent_tap_for_details">Not sent, tap for details</string>
|
||||
<string name="ConversationItem_error_partially_not_delivered">Partially sent, tap for details</string>
|
||||
<!-- Warning footer when an admin delete has not been sent to everyone -->
|
||||
<string name="ConversationItem_error_partially_not_deleted">Partially deleted, tap for details</string>
|
||||
<string name="ConversationItem_error_network_not_delivered">Send failed</string>
|
||||
<!-- Warning footer when an admin delete has failed to send -->
|
||||
<string name="ConversationItem_error_delete_failed">Delete failed, tap for details</string>
|
||||
<string name="ConversationItem_group_action_left">%1$s has left the group.</string>
|
||||
<string name="ConversationItem_send_paused">Send paused</string>
|
||||
<string name="ConversationItem_click_to_approve_unencrypted_sms_dialog_title">Fallback to unencrypted SMS?</string>
|
||||
@@ -3592,6 +3596,10 @@
|
||||
<!-- Accessibility content description describing how a user can lock voice note recording. -->
|
||||
<string name="conversation_activity__quick_attachment_drawer_lock_record_description">Lock recording of audio attachment</string>
|
||||
<string name="conversation_activity__message_could_not_be_sent">Message could not be sent. Check your connection and try again.</string>
|
||||
<!-- Dialog body when a message failed to delete and retry is possible. -->
|
||||
<string name="conversation_activity__message_failed_to_delete_retry">Message failed to delete. Check your connection and try again.</string>
|
||||
<!-- Dialog body when a message failed to delete. -->
|
||||
<string name="conversation_activity__message_failed_to_delete">Message failed to delete.</string>
|
||||
|
||||
<!-- conversation_input_panel -->
|
||||
<string name="conversation_input_panel__slide_to_cancel">Slide to cancel</string>
|
||||
|
||||
Reference in New Issue
Block a user