diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveBackupIdReservationJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveBackupIdReservationJob.kt new file mode 100644 index 0000000000..084baede1f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ArchiveBackupIdReservationJob.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.jobs + +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.backup.v2.BackupRepository +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.util.RemoteConfig +import org.thoughtcrime.securesms.util.TextSecurePreferences +import org.whispersystems.signalservice.api.NetworkResult + +/** + * Reserves backupIds for both text+media. The intention is that every registered user should be doing this, so it should happen post-registration + * (as well as in a migration for pre-existing users). + * + * Calling this repeatedly is a no-op from the server's perspective, so no need to be careful around retries or anything. + */ +class ArchiveBackupIdReservationJob private constructor(parameters: Parameters) : Job(parameters) { + + companion object { + private val TAG = Log.tag(ArchiveBackupIdReservationJob::class) + + const val KEY = "ArchiveBackupIdReservationJob" + } + + constructor() : this( + Parameters.Builder() + .setQueue("ArchiveBackupIdReservationJob") + .addConstraint(NetworkConstraint.KEY) + .setLifespan(Parameters.IMMORTAL) + .setMaxAttempts(Parameters.UNLIMITED) + .build() + ) + + override fun serialize(): ByteArray? = null + + override fun getFactoryKey(): String = KEY + + override fun run(): Result { + if (!SignalStore.account.isRegistered) { + Log.w(TAG, "Not registered. Skipping.") + return Result.success() + } + + if (TextSecurePreferences.isUnauthorizedReceived(context)) { + Log.w(TAG, "Not authorized. Skipping.") + return Result.success() + } + + return when (val result = BackupRepository.triggerBackupIdReservation()) { + is NetworkResult.Success -> Result.success() + is NetworkResult.NetworkError -> Result.retry(defaultBackoff()) + is NetworkResult.ApplicationError -> Result.fatalFailure(RuntimeException(result.throwable)) + is NetworkResult.StatusCodeError -> { + when (result.code) { + 429 -> Result.retry(result.retryAfter()?.inWholeMilliseconds ?: defaultBackoff()) + else -> { + Log.w(TAG, "Failed to reserve backupId with status: ${result.code}. This should only happen on a malformed request or server error. Reducing backoff interval to be safe.") + Result.retry(RemoteConfig.serverErrorMaxBackoff) + } + } + } + } + } + + override fun onFailure() = Unit + + class Factory : Job.Factory { + override fun create(parameters: Parameters, data: ByteArray?): ArchiveBackupIdReservationJob { + return ArchiveBackupIdReservationJob(parameters) + } + } +} 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 1f61f58a1a..1b11ac6b1e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -47,6 +47,7 @@ import org.thoughtcrime.securesms.migrations.AccountConsistencyMigrationJob; import org.thoughtcrime.securesms.migrations.AccountRecordMigrationJob; import org.thoughtcrime.securesms.migrations.AepMigrationJob; import org.thoughtcrime.securesms.migrations.ApplyUnknownFieldsToSelfMigrationJob; +import org.thoughtcrime.securesms.migrations.ArchiveBackupIdReservationMigrationJob; import org.thoughtcrime.securesms.migrations.AttachmentCleanupMigrationJob; import org.thoughtcrime.securesms.migrations.AttachmentHashBackfillMigrationJob; import org.thoughtcrime.securesms.migrations.AttributesMigrationJob; @@ -125,6 +126,7 @@ public final class JobManagerFactories { put(ApkUpdateJob.KEY, new ApkUpdateJob.Factory()); put(ArchiveAttachmentBackfillJob.KEY, new ArchiveAttachmentBackfillJob.Factory()); put(ArchiveAttachmentReconciliationJob.KEY, new ArchiveAttachmentReconciliationJob.Factory()); + put(ArchiveBackupIdReservationJob.KEY, new ArchiveBackupIdReservationJob.Factory()); put(ArchiveCommitAttachmentDeletesJob.KEY, new ArchiveCommitAttachmentDeletesJob.Factory()); put(ArchiveThumbnailUploadJob.KEY, new ArchiveThumbnailUploadJob.Factory()); put(AttachmentCompressionJob.KEY, new AttachmentCompressionJob.Factory()); @@ -284,6 +286,7 @@ public final class JobManagerFactories { put(AccountRecordMigrationJob.KEY, new AccountRecordMigrationJob.Factory()); put(AepMigrationJob.KEY, new AepMigrationJob.Factory()); put(ApplyUnknownFieldsToSelfMigrationJob.KEY, new ApplyUnknownFieldsToSelfMigrationJob.Factory()); + put(ArchiveBackupIdReservationMigrationJob.KEY, new ArchiveBackupIdReservationMigrationJob.Factory()); put(AttachmentCleanupMigrationJob.KEY, new AttachmentCleanupMigrationJob.Factory()); put(AttachmentHashBackfillMigrationJob.KEY, new AttachmentHashBackfillMigrationJob.Factory()); put(AttributesMigrationJob.KEY, new AttributesMigrationJob.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index a79a49d06f..fdb0cf31d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -186,9 +186,10 @@ public class ApplicationMigrations { static final int STORAGE_LOCAL_UNKNOWNS_FIX_2 = 142; static final int SVR2_ENCLAVE_UPDATE_4 = 143; static final int RESET_ARCHIVE_TIER = 144; + static final int ARCHIVE_BACKUP_ID = 145; } - public static final int CURRENT_VERSION = 144; + public static final int CURRENT_VERSION = 145; /** * This *must* be called after the {@link JobManager} has been instantiated, but *before* the call @@ -859,6 +860,10 @@ public class ApplicationMigrations { jobs.put(Version.RESET_ARCHIVE_TIER, new ResetArchiveTierMigrationJob()); } + if (lastSeenVersion < Version.ARCHIVE_BACKUP_ID) { + jobs.put(Version.ARCHIVE_BACKUP_ID, new ArchiveBackupIdReservationMigrationJob()); + } + return jobs; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/ArchiveBackupIdReservationMigrationJob.kt b/app/src/main/java/org/thoughtcrime/securesms/migrations/ArchiveBackupIdReservationMigrationJob.kt new file mode 100644 index 0000000000..84e7977a34 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ArchiveBackupIdReservationMigrationJob.kt @@ -0,0 +1,35 @@ +package org.thoughtcrime.securesms.migrations + +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobs.ArchiveBackupIdReservationJob + +/** + * Simple migration job to just enqueue a [ArchiveBackupIdReservationJob] to ensure that all users reserve a backupId. + */ +internal class ArchiveBackupIdReservationMigrationJob( + parameters: Parameters = Parameters.Builder().build() +) : MigrationJob(parameters) { + + companion object { + val TAG = Log.tag(ArchiveBackupIdReservationMigrationJob::class.java) + const val KEY = "ArchiveBackupIdReservationMigrationJob" + } + + override fun getFactoryKey(): String = KEY + + override fun isUiBlocking(): Boolean = false + + override fun performMigration() { + AppDependencies.jobManager.add(ArchiveBackupIdReservationJob()) + } + + override fun shouldRetry(e: Exception): Boolean = false + + class Factory : Job.Factory { + override fun create(parameters: Parameters, serializedData: ByteArray?): ArchiveBackupIdReservationMigrationJob { + return ArchiveBackupIdReservationMigrationJob(parameters) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationUtil.java b/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationUtil.java index fb91b1018f..9e6ac5fc08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationUtil.java @@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.registration.util; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.dependencies.AppDependencies; +import org.thoughtcrime.securesms.jobs.ArchiveBackupIdReservationJob; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; @@ -53,6 +54,8 @@ public final class RegistrationUtil { SignalStore.emoji().clearSearchIndexMetadata(); EmojiSearchIndexDownloadJob.scheduleImmediately(); + AppDependencies.getJobManager().add(new ArchiveBackupIdReservationJob()); + } else if (!SignalStore.registration().isRegistrationComplete()) { Log.i(TAG, "Registration is not yet complete.", new Throwable()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java index be120e23fe..74215c7f67 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -423,15 +423,6 @@ public class TextSecurePreferences { setBooleanPreference(context, TYPING_INDICATORS, enabled); } - /** - * Only kept so that we can avoid showing the megaphone for the new link previews setting - * ({@link SettingsValues#isLinkPreviewsEnabled()}) when users upgrade. This can be removed after - * we stop showing the link previews megaphone. - */ - public static boolean wereLinkPreviewsEnabled(Context context) { - return getBooleanPreference(context, LINK_PREVIEWS, true); - } - public static int getNotificationPriority(Context context) { try { return Integer.parseInt(getStringPreference(context, NOTIFICATION_PRIORITY_PREF, String.valueOf(NotificationCompat.PRIORITY_HIGH)));