diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java index 5d0b4081e..ac6b0c3c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -36,6 +36,9 @@ import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; import org.thoughtcrime.securesms.util.SqlUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import java.io.Closeable; +import java.io.IOException; + public class DatabaseFactory { private static final Object lock = new Object(); @@ -311,4 +314,17 @@ public class DatabaseFactory { public boolean hasTable(String table) { return SqlUtil.tableExists(databaseHelper.getRawReadableDatabase(), table); } + + public @NonNull Transaction transaction() { + getRawDatabase().beginTransaction(); + return () -> { + getRawDatabase().setTransactionSuccessful(); + getRawDatabase().endTransaction(); + }; + } + + public interface Transaction extends Closeable { + @Override + void close(); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java index b747fdbb2..05a551818 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java @@ -102,7 +102,6 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns public abstract void markExpireStarted(Collection messageId, long startTime); public abstract void markAsEndSession(long id); - public abstract void markAsPreKeyBundle(long id); public abstract void markAsInvalidVersionKeyExchange(long id); public abstract void markAsSecure(long id); public abstract void markAsInsecure(long id); @@ -111,7 +110,6 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns public abstract void markAsRateLimited(long id); public abstract void clearRateLimitStatus(Collection ids); public abstract void markAsDecryptFailed(long id); - public abstract void markAsDecryptDuplicate(long id); public abstract void markAsNoSession(long id); public abstract void markAsUnsupportedProtocolVersion(long id); public abstract void markAsInvalidMessage(long id); @@ -119,8 +117,8 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns public abstract void markAsOutbox(long id); public abstract void markAsPendingInsecureSmsFallback(long id); public abstract void markAsSent(long messageId, boolean secure); - public abstract void markAsSentFailed(long id); public abstract void markUnidentified(long messageId, boolean unidentified); + public abstract void markAsSentFailed(long id); public abstract void markAsSending(long messageId); public abstract void markAsRemoteDelete(long messageId); public abstract void markAsMissedCall(long id, boolean isVideoOffer); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 3313d4766..754280110 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -56,6 +56,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; +import org.thoughtcrime.securesms.jobs.ThreadUpdateJob; import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; @@ -296,11 +297,6 @@ public class MmsDatabase extends MessageDatabase { throw new UnsupportedOperationException(); } - @Override - public void markAsPreKeyBundle(long id) { - throw new UnsupportedOperationException(); - } - @Override public void markAsInvalidVersionKeyExchange(long id) { throw new UnsupportedOperationException(); @@ -321,11 +317,6 @@ public class MmsDatabase extends MessageDatabase { throw new UnsupportedOperationException(); } - @Override - public void markAsDecryptDuplicate(long id) { - throw new UnsupportedOperationException(); - } - @Override public void markAsNoSession(long id) { throw new UnsupportedOperationException(); @@ -759,7 +750,7 @@ public class MmsDatabase extends MessageDatabase { " WHERE " + ID + " = ?", new String[] {id + ""}); if (threadId.isPresent()) { - DatabaseFactory.getThreadDatabase(context).update(threadId.get(), false); + DatabaseFactory.getThreadDatabase(context).updateSnippetTypeSilently(threadId.get()); } } @@ -1352,7 +1343,7 @@ public class MmsDatabase extends MessageDatabase { if (!Types.isExpirationTimerUpdate(mailbox)) { DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1); - DatabaseFactory.getThreadDatabase(context).update(threadId, true); + ThreadUpdateJob.enqueue(threadId); } notifyConversationListeners(threadId); @@ -1445,12 +1436,13 @@ public class MmsDatabase extends MessageDatabase { @Override public void markIncomingNotificationReceived(long threadId) { notifyConversationListeners(threadId); - DatabaseFactory.getThreadDatabase(context).update(threadId, true); if (org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context)) { DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1); } + ThreadUpdateJob.enqueue(threadId); + TrimThreadJob.enqueueAsync(threadId); } @@ -1658,9 +1650,11 @@ public class MmsDatabase extends MessageDatabase { insertListener.onComplete(); } - notifyConversationListeners(contentValues.getAsLong(THREAD_ID)); - DatabaseFactory.getThreadDatabase(context).setLastScrolled(contentValues.getAsLong(THREAD_ID), 0); - DatabaseFactory.getThreadDatabase(context).update(contentValues.getAsLong(THREAD_ID), true); + long contentValuesThreadId = contentValues.getAsLong(THREAD_ID); + + notifyConversationListeners(contentValuesThreadId); + DatabaseFactory.getThreadDatabase(context).setLastScrolled(contentValuesThreadId, 0); + ThreadUpdateJob.enqueue(contentValuesThreadId); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 1cfe36c73..63c7ffbb1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateD import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; +import org.thoughtcrime.securesms.jobs.ThreadUpdateJob; import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException; @@ -58,6 +59,7 @@ import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.CursorUtil; import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.SqlUtil; +import org.thoughtcrime.securesms.util.Stopwatch; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.Pair; @@ -177,14 +179,24 @@ public class SmsDatabase extends MessageDatabase { private void updateTypeBitmask(long id, long maskOff, long maskOn) { SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); - db.execSQL("UPDATE " + TABLE_NAME + - " SET " + TYPE + " = (" + TYPE + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + " )" + - " WHERE " + ID + " = ?", SqlUtil.buildArgs(id)); - long threadId = getThreadIdForMessage(id); - DatabaseFactory.getThreadDatabase(context).updateSnippetTypeSilently(threadId); + long threadId; + + db.beginTransaction(); + try { + db.execSQL("UPDATE " + TABLE_NAME + + " SET " + TYPE + " = (" + TYPE + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + " )" + + " WHERE " + ID + " = ?", SqlUtil.buildArgs(id)); + + threadId = getThreadIdForMessage(id); + + DatabaseFactory.getThreadDatabase(context).updateSnippetTypeSilently(threadId); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } - notifyConversationListListeners(); notifyConversationListeners(threadId); } @@ -267,11 +279,6 @@ public class SmsDatabase extends MessageDatabase { updateTypeBitmask(id, Types.KEY_EXCHANGE_MASK, Types.END_SESSION_BIT); } - @Override - public void markAsPreKeyBundle(long id) { - updateTypeBitmask(id, Types.KEY_EXCHANGE_MASK, Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT); - } - @Override public void markAsInvalidVersionKeyExchange(long id) { updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_INVALID_VERSION_BIT); @@ -323,11 +330,6 @@ public class SmsDatabase extends MessageDatabase { updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_FAILED_BIT); } - @Override - public void markAsDecryptDuplicate(long id) { - updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_DUPLICATE_BIT); - } - @Override public void markAsNoSession(long id) { updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_NO_SESSION_BIT); @@ -656,9 +658,8 @@ public class SmsDatabase extends MessageDatabase { long threadId = getThreadIdForMessage(messageId); - DatabaseFactory.getThreadDatabase(context).update(threadId, true); + ThreadUpdateJob.enqueue(threadId); notifyConversationListeners(threadId); - notifyConversationListListeners(); return new InsertResult(messageId, threadId); } @@ -741,7 +742,7 @@ public class SmsDatabase extends MessageDatabase { DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1); } - DatabaseFactory.getThreadDatabase(context).update(threadId, true); + ThreadUpdateJob.enqueue(threadId); db.setTransactionSuccessful(); } finally { @@ -818,7 +819,7 @@ public class SmsDatabase extends MessageDatabase { DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1); } - DatabaseFactory.getThreadDatabase(context).update(threadId, true); + ThreadUpdateJob.enqueue(threadId); db.setTransactionSuccessful(); } finally { @@ -883,14 +884,15 @@ public class SmsDatabase extends MessageDatabase { values.put(TYPE, type); values.put(THREAD_ID, threadId); - SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); - long messageId = db.insert(TABLE_NAME, null, values); + SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); + long messageId = db.insert(TABLE_NAME, null, values); - DatabaseFactory.getThreadDatabase(context).update(threadId, true); if (unread) { DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1); } + ThreadUpdateJob.enqueue(threadId); + notifyConversationListeners(threadId); TrimThreadJob.enqueueAsync(threadId); @@ -1109,7 +1111,7 @@ public class SmsDatabase extends MessageDatabase { } if (!silent) { - DatabaseFactory.getThreadDatabase(context).update(threadId, true); + ThreadUpdateJob.enqueue(threadId); } if (message.getSubscriptionId() != -1) { @@ -1152,7 +1154,7 @@ public class SmsDatabase extends MessageDatabase { long messageId = db.insert(TABLE_NAME, null, values); DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1); - DatabaseFactory.getThreadDatabase(context).update(threadId, true); + ThreadUpdateJob.enqueue(threadId); notifyConversationListeners(threadId); @@ -1176,7 +1178,7 @@ public class SmsDatabase extends MessageDatabase { databaseHelper.getSignalWritableDatabase().insert(TABLE_NAME, null, values); DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1); - DatabaseFactory.getThreadDatabase(context).update(threadId, true); + ThreadUpdateJob.enqueue(threadId); notifyConversationListeners(threadId); @@ -1226,7 +1228,6 @@ public class SmsDatabase extends MessageDatabase { if (!message.isIdentityVerified() && !message.isIdentityDefault()) { DatabaseFactory.getThreadDatabase(context).setLastScrolled(threadId, 0); - DatabaseFactory.getThreadDatabase(context).updateSilently(threadId, true); DatabaseFactory.getThreadDatabase(context).setLastSeenSilently(threadId); } @@ -1237,7 +1238,6 @@ public class SmsDatabase extends MessageDatabase { } notifyConversationListeners(threadId); - notifyConversationListListeners(); if (!message.isIdentityVerified() && !message.isIdentityDefault()) { TrimThreadJob.enqueueAsync(threadId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index d52ef9724..f2ae5bcac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -224,8 +224,6 @@ public class ThreadDatabase extends Database { StorageSyncHelper.scheduleSyncForDataChange(); } } - - notifyConversationListListeners(); } public void updateSnippet(long threadId, String snippet, @Nullable Uri attachment, long date, long type, boolean unarchive) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobSchedulerScheduler.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobSchedulerScheduler.java index 6080fc32c..d3d4a978f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobSchedulerScheduler.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobSchedulerScheduler.java @@ -13,6 +13,7 @@ import androidx.annotation.RequiresApi; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; +import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -33,32 +34,34 @@ public final class JobSchedulerScheduler implements Scheduler { @RequiresApi(26) @Override public void schedule(long delay, @NonNull List constraints) { - JobScheduler jobScheduler = application.getSystemService(JobScheduler.class); + SignalExecutors.BOUNDED.execute(() -> { + JobScheduler jobScheduler = application.getSystemService(JobScheduler.class); - String constraintNames = constraints.isEmpty() ? "" - : Stream.of(constraints) - .map(Constraint::getJobSchedulerKeyPart) - .withoutNulls() - .sorted() - .collect(Collectors.joining("-")); + String constraintNames = constraints.isEmpty() ? "" + : Stream.of(constraints) + .map(Constraint::getJobSchedulerKeyPart) + .withoutNulls() + .sorted() + .collect(Collectors.joining("-")); - int jobId = constraintNames.hashCode(); + int jobId = constraintNames.hashCode(); - if (jobScheduler.getPendingJob(jobId) != null) { - return; - } + if (jobScheduler.getPendingJob(jobId) != null) { + return; + } - Log.i(TAG, String.format(Locale.US, "JobScheduler enqueue of %s (%d)", constraintNames, jobId)); + Log.i(TAG, String.format(Locale.US, "JobScheduler enqueue of %s (%d)", constraintNames, jobId)); - JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(jobId, new ComponentName(application, SystemService.class)) - .setMinimumLatency(delay) - .setPersisted(true); + JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(jobId, new ComponentName(application, SystemService.class)) + .setMinimumLatency(delay) + .setPersisted(true); - for (Constraint constraint : constraints) { - constraint.applyToJobInfo(jobInfoBuilder); - } + for (Constraint constraint : constraints) { + constraint.applyToJobInfo(jobInfoBuilder); + } - jobScheduler.schedule(jobInfoBuilder.build()); + jobScheduler.schedule(jobInfoBuilder.build()); + }); } @RequiresApi(api = 26) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index b10a92e8c..0a6cdcb7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -162,6 +162,7 @@ public final class JobManagerFactories { put(StorageForcePushJob.KEY, new StorageForcePushJob.Factory()); put(StorageSyncJob.KEY, new StorageSyncJob.Factory()); put(SubmitRateLimitPushChallengeJob.KEY, new SubmitRateLimitPushChallengeJob.Factory()); + put(ThreadUpdateJob.KEY, new ThreadUpdateJob.Factory()); put(TrimThreadJob.KEY, new TrimThreadJob.Factory()); put(TypingSendJob.KEY, new TypingSendJob.Factory()); put(UpdateApkJob.KEY, new UpdateApkJob.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PaymentNotificationSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PaymentNotificationSendJob.java index 0d159d8c1..89ddc3d32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PaymentNotificationSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PaymentNotificationSendJob.java @@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.SignalServiceMessageSender.IndividualSendEvents; import org.whispersystems.signalservice.api.crypto.ContentHint; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; import org.whispersystems.signalservice.api.messages.SendMessageResult; @@ -109,7 +110,7 @@ public final class PaymentNotificationSendJob extends BaseJob { .withPayment(new SignalServiceDataMessage.Payment(new SignalServiceDataMessage.PaymentNotification(payment.getReceipt(), payment.getNote()))) .build(); - SendMessageResult sendMessageResult = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.DEFAULT, dataMessage); + SendMessageResult sendMessageResult = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.DEFAULT, dataMessage, IndividualSendEvents.EMPTY); if (sendMessageResult.getIdentityFailure() != null) { Log.w(TAG, "Identity failure for " + recipient.getId()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java index 58d9a2ec6..c5c0cf0af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.SignalServiceMessageSender.IndividualSendEvents; import org.whispersystems.signalservice.api.crypto.ContentHint; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; @@ -137,7 +138,8 @@ public class PushGroupUpdateJob extends BaseJob { messageSender.sendDataMessage(RecipientUtil.toSignalServiceAddress(context, sourceRecipient), UnidentifiedAccessUtil.getAccessFor(context, sourceRecipient), ContentHint.DEFAULT, - message); + message, + IndividualSendEvents.EMPTY); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 98bcff806..135459f45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.SignalServiceMessageSender.IndividualSendEvents; import org.whispersystems.signalservice.api.crypto.ContentHint; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; @@ -233,7 +234,7 @@ public class PushMediaSendJob extends PushSendJob { DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId, true)); return syncAccess.isPresent(); } else { - SendMessageResult result = messageSender.sendDataMessage(address, UnidentifiedAccessUtil.getAccessFor(context, messageRecipient), ContentHint.RESENDABLE, mediaMessage); + SendMessageResult result = messageSender.sendDataMessage(address, UnidentifiedAccessUtil.getAccessFor(context, messageRecipient), ContentHint.RESENDABLE, mediaMessage, IndividualSendEvents.EMPTY); DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId, true)); return result.getSuccess().isUnidentified(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 534c605ae..6f2655969 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -47,7 +47,7 @@ public class PushTextSendJob extends PushSendJob { private static final String KEY_MESSAGE_ID = "message_id"; - private long messageId; + private final long messageId; public PushTextSendJob(long messageId, @NonNull Recipient recipient) { this(constructParameters(recipient, false), messageId); @@ -91,35 +91,37 @@ public class PushTextSendJob extends PushSendJob { RecipientUtil.shareProfileIfFirstSecureMessage(context, record.getRecipient()); - Recipient recipient = record.getRecipient().fresh(); + Recipient recipient = record.getRecipient().resolve(); byte[] profileKey = recipient.getProfileKey(); UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode(); boolean unidentified = deliver(record); - database.markAsSent(messageId, true); - database.markUnidentified(messageId, unidentified); + try (DatabaseFactory.Transaction unused = DatabaseFactory.getInstance(context).transaction()) { + database.markAsSent(messageId, true); + database.markUnidentified(messageId, unidentified); - if (recipient.isSelf()) { - SyncMessageId id = new SyncMessageId(recipient.getId(), record.getDateSent()); - DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis()); - DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis()); - } + if (recipient.isSelf()) { + SyncMessageId id = new SyncMessageId(recipient.getId(), record.getDateSent()); + DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis()); + DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis()); + } - if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) { - log(TAG, String.valueOf(record.getDateSent()), "Marking recipient as UD-unrestricted following a UD send."); - DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.UNRESTRICTED); - } else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) { - log(TAG, String.valueOf(record.getDateSent()), "Marking recipient as UD-enabled following a UD send."); - DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.ENABLED); - } else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) { - log(TAG, String.valueOf(record.getDateSent()), "Marking recipient as UD-disabled following a non-UD send."); - DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.DISABLED); - } + if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) { + log(TAG, String.valueOf(record.getDateSent()), "Marking recipient as UD-unrestricted following a UD send."); + DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.UNRESTRICTED); + } else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) { + log(TAG, String.valueOf(record.getDateSent()), "Marking recipient as UD-enabled following a UD send."); + DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.ENABLED); + } else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) { + log(TAG, String.valueOf(record.getDateSent()), "Marking recipient as UD-disabled following a non-UD send."); + DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.DISABLED); + } - if (record.getExpiresIn() > 0) { - database.markExpireStarted(messageId); - expirationManager.scheduleDeletion(record.getId(), record.isMms(), record.getExpiresIn()); + if (record.getExpiresIn() > 0) { + database.markExpireStarted(messageId); + expirationManager.scheduleDeletion(record.getId(), record.isMms(), record.getExpiresIn()); + } } log(TAG, String.valueOf(record.getDateSent()), "Sent message: " + messageId); @@ -167,7 +169,7 @@ public class PushTextSendJob extends PushSendJob { try { rotateSenderCertificateIfNecessary(); - Recipient messageRecipient = message.getIndividualRecipient().fresh(); + Recipient messageRecipient = message.getIndividualRecipient().resolve(); if (messageRecipient.isUnregistered()) { throw new UndeliverableMessageException(messageRecipient.getId() + " not registered!"); @@ -192,16 +194,14 @@ public class PushTextSendJob extends PushSendJob { Optional syncAccess = UnidentifiedAccessUtil.getAccessForSync(context); SignalServiceSyncMessage syncMessage = buildSelfSendSyncMessage(context, textSecureMessage, syncAccess); - SignalLocalMetrics.IndividualMessageSend.onNetworkStarted(messageId); + SignalLocalMetrics.IndividualMessageSend.onDeliveryStarted(messageId); SendMessageResult result = messageSender.sendSyncMessage(syncMessage, syncAccess); - SignalLocalMetrics.IndividualMessageSend.onNetworkFinished(messageId); DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(messageRecipient.getId(), message.getDateSent(), result, ContentHint.RESENDABLE, new MessageId(messageId, false)); return syncAccess.isPresent(); } else { - SignalLocalMetrics.IndividualMessageSend.onNetworkStarted(messageId); - SendMessageResult result = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.RESENDABLE, textSecureMessage); - SignalLocalMetrics.IndividualMessageSend.onNetworkFinished(messageId); + SignalLocalMetrics.IndividualMessageSend.onDeliveryStarted(messageId); + SendMessageResult result = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.RESENDABLE, textSecureMessage, new MetricEventListener(messageId)); DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(messageRecipient.getId(), message.getDateSent(), result, ContentHint.RESENDABLE, new MessageId(messageId, false)); return result.getSuccess().isUnidentified(); @@ -218,6 +218,29 @@ public class PushTextSendJob extends PushSendJob { return data.getLong(KEY_MESSAGE_ID); } + private static class MetricEventListener implements SignalServiceMessageSender.IndividualSendEvents { + private final long messageId; + + private MetricEventListener(long messageId) { + this.messageId = messageId; + } + + @Override + public void onMessageEncrypted() { + SignalLocalMetrics.IndividualMessageSend.onMessageEncrypted(messageId); + } + + @Override + public void onMessageSent() { + SignalLocalMetrics.IndividualMessageSend.onMessageSent(messageId); + } + + @Override + public void onSyncMessageSent() { + SignalLocalMetrics.IndividualMessageSend.onSyncMessageSent(messageId); + } + } + public static class Factory implements Job.Factory { @Override public @NonNull PushTextSendJob create(@NonNull Parameters parameters, @NonNull Data data) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java index 1aaf4781f..7caf83cdd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java @@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.SignalServiceMessageSender.IndividualSendEvents; import org.whispersystems.signalservice.api.crypto.ContentHint; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; @@ -94,7 +95,8 @@ public class RequestGroupInfoJob extends BaseJob { messageSender.sendDataMessage(RecipientUtil.toSignalServiceAddress(context, recipient), UnidentifiedAccessUtil.getAccessFor(context, recipient), ContentHint.IMPLICIT, - message); + message, + IndividualSendEvents.EMPTY); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ThreadUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ThreadUpdateJob.java new file mode 100644 index 000000000..ff22e598a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ThreadUpdateJob.java @@ -0,0 +1,67 @@ +package org.thoughtcrime.securesms.jobs; + +import androidx.annotation.NonNull; + +import org.signal.core.util.ThreadUtil; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; + +public final class ThreadUpdateJob extends BaseJob { + + public static final String KEY = "ThreadUpdateJob"; + + private static final String KEY_THREAD_ID = "thread_id"; + + private final long threadId; + + private ThreadUpdateJob(long threadId) { + this(new Parameters.Builder() + .setQueue("ThreadUpdateJob_" + threadId) + .setMaxInstancesForQueue(2) + .build(), + threadId); + } + + private ThreadUpdateJob(@NonNull Parameters parameters, long threadId) { + super(parameters); + this.threadId = threadId; + } + + public static void enqueue(long threadId) { + ApplicationDependencies.getJobManager().add(new ThreadUpdateJob(threadId)); + } + + @Override + public @NonNull Data serialize() { + return new Data.Builder().putLong(KEY_THREAD_ID, threadId).build(); + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + protected void onRun() throws Exception { + DatabaseFactory.getThreadDatabase(context).update(threadId, true); + ThreadUtil.sleep(1000); + } + + @Override + protected boolean onShouldRetry(@NonNull Exception e) { + return false; + } + + @Override + public void onFailure() { + } + + public static final class Factory implements Job.Factory { + @Override + public @NonNull ThreadUpdateJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new ThreadUpdateJob(parameters, data.getLong(KEY_THREAD_ID)); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index 2dd43c5fb..eb5d084c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -82,6 +82,7 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob; import org.thoughtcrime.securesms.jobs.SenderKeyDistributionSendJob; import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob; +import org.thoughtcrime.securesms.jobs.ThreadUpdateJob; import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.linkpreview.LinkPreview; @@ -721,10 +722,13 @@ public final class MessageContentProcessor { SecurityEvent.broadcastSecurityUpdateEvent(context); - long messageId = database.insertMessageOutbox(threadId, outgoingEndSessionMessage, - false, message.getTimestamp(), - null); + long messageId = database.insertMessageOutbox(threadId, + outgoingEndSessionMessage, + false, + message.getTimestamp(), + null); database.markAsSent(messageId, true); + ThreadUpdateJob.enqueue(threadId); } return threadId; @@ -1555,6 +1559,7 @@ public final class MessageContentProcessor { messageId = DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoingTextMessage, false, message.getTimestamp(), null); database = DatabaseFactory.getSmsDatabase(context); database.markUnidentified(messageId, isUnidentified(message, recipient)); + ThreadUpdateJob.enqueue(threadId); } database.markAsSent(messageId, true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java index 03f1938d9..cc448bd26 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java @@ -245,6 +245,10 @@ public class RecipientUtil { @WorkerThread public static void shareProfileIfFirstSecureMessage(@NonNull Context context, @NonNull Recipient recipient) { + if (recipient.isProfileSharing()) { + return; + } + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient.getId()); if (isPreMessageRequestThread(context, threadId)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java b/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java index 174c9c3d6..a8166bbe9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java @@ -16,6 +16,7 @@ */ package org.thoughtcrime.securesms.sms; +import android.app.Application; import android.content.Context; import android.os.Parcel; import android.os.Parcelable; @@ -61,6 +62,7 @@ import org.thoughtcrime.securesms.jobs.ReactionSendJob; import org.thoughtcrime.securesms.jobs.RemoteDeleteSendJob; import org.thoughtcrime.securesms.jobs.ResumableUploadSpecJob; import org.thoughtcrime.securesms.jobs.SmsSendJob; +import org.thoughtcrime.securesms.jobs.ThreadUpdateJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.mms.MmsException; @@ -120,6 +122,7 @@ public class MessageSender { sendTextMessage(context, recipient, forceSms, keyExchange, messageId); onMessageSent(); + ThreadUpdateJob.enqueue(threadId); return allocatedThreadId; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java index d3f881e38..8b30ae341 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.MessageDatabase; import org.thoughtcrime.securesms.database.MessageDatabase.InsertResult; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobs.ThreadUpdateJob; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.IncomingIdentityDefaultMessage; @@ -90,6 +91,7 @@ public final class IdentityUtil { else outgoing = new OutgoingIdentityDefaultMessage(recipient); DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoing, false, time, null); + ThreadUpdateJob.enqueue(threadId); } } } @@ -112,6 +114,7 @@ public final class IdentityUtil { Log.i(TAG, "Inserting verified outbox..."); DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoing, false, time, null); + ThreadUpdateJob.enqueue(threadId); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SignalLocalMetrics.java b/app/src/main/java/org/thoughtcrime/securesms/util/SignalLocalMetrics.java index fc7135ba1..89c6bf808 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SignalLocalMetrics.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SignalLocalMetrics.java @@ -99,7 +99,9 @@ public final class SignalLocalMetrics { private static final String SPLIT_DB_INSERT = "db-insert"; private static final String SPLIT_JOB_ENQUEUE = "job-enqueue"; private static final String SPLIT_JOB_PRE_NETWORK = "job-pre-network"; - private static final String SPLIT_NETWORK = "network"; + private static final String SPLIT_ENCRYPT = "encrypt"; + private static final String SPLIT_NETWORK_MAIN = "network-main"; + private static final String SPLIT_NETWORK_SYNC = "network-sync"; private static final String SPLIT_JOB_POST_NETWORK = "job-post-network"; private static final String SPLIT_UI_UPDATE = "ui-update"; @@ -123,14 +125,24 @@ public final class SignalLocalMetrics { LocalMetrics.getInstance().split(requireId(messageId), SPLIT_JOB_ENQUEUE); } - public static void onNetworkStarted(long messageId) { + public static void onDeliveryStarted(long messageId) { if (!ID_MAP.containsKey(messageId)) return; LocalMetrics.getInstance().split(requireId(messageId), SPLIT_JOB_PRE_NETWORK); } - public static void onNetworkFinished(long messageId) { + public static void onMessageEncrypted(long messageId) { if (!ID_MAP.containsKey(messageId)) return; - LocalMetrics.getInstance().split(requireId(messageId), SPLIT_NETWORK); + LocalMetrics.getInstance().split(requireId(messageId), SPLIT_ENCRYPT); + } + + public static void onMessageSent(long messageId) { + if (!ID_MAP.containsKey(messageId)) return; + LocalMetrics.getInstance().split(requireId(messageId), SPLIT_NETWORK_MAIN); + } + + public static void onSyncMessageSent(long messageId) { + if (!ID_MAP.containsKey(messageId)) return; + LocalMetrics.getInstance().split(requireId(messageId), SPLIT_NETWORK_SYNC); } public static void onJobFinished(long messageId) { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index f00427620..9b46511df 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -319,15 +319,21 @@ public class SignalServiceMessageSender { public SendMessageResult sendDataMessage(SignalServiceAddress recipient, Optional unidentifiedAccess, ContentHint contentHint, - SignalServiceDataMessage message) + SignalServiceDataMessage message, + IndividualSendEvents sendEvents) throws UntrustedIdentityException, IOException { Log.d(TAG, "[" + message.getTimestamp() + "] Sending a data message."); Content content = createMessageContent(message); EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, contentHint, message.getGroupId()); - long timestamp = message.getTimestamp(); - SendMessageResult result = sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null); + + sendEvents.onMessageEncrypted(); + + long timestamp = message.getTimestamp(); + SendMessageResult result = sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null); + + sendEvents.onMessageSent(); if (result.getSuccess() != null && result.getSuccess().isNeedsSync()) { Content syncMessage = createMultiDeviceSentTranscriptContent(content, Optional.of(recipient), timestamp, Collections.singletonList(result), false); @@ -336,6 +342,8 @@ public class SignalServiceMessageSender { sendMessage(localAddress, Optional.absent(), timestamp, syncMessageContent, false, null); } + sendEvents.onSyncMessageSent(); + // TODO [greyson][session] Delete this when we delete the button if (message.isEndSession()) { store.deleteAllSessions(recipient.getUuid().toString()); @@ -2077,4 +2085,21 @@ public class SignalServiceMessageSender { public interface EventListener { void onSecurityEvent(SignalServiceAddress address); } + + public interface IndividualSendEvents { + IndividualSendEvents EMPTY = new IndividualSendEvents() { + @Override + public void onMessageEncrypted() { } + + @Override + public void onMessageSent() { } + + @Override + public void onSyncMessageSent() { } + }; + + void onMessageEncrypted(); + void onMessageSent(); + void onSyncMessageSent(); + } }