diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index a5dcf5df9e..c3bcddebda 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -88,6 +88,7 @@ import org.thoughtcrime.securesms.service.AnalyzeDatabaseAlarmListener; import org.thoughtcrime.securesms.service.DirectoryRefreshListener; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.LocalBackupListener; +import org.thoughtcrime.securesms.service.MessageBackupListener; import org.thoughtcrime.securesms.service.RotateSenderCertificateListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.service.webrtc.ActiveCallManager; @@ -420,6 +421,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr RotateSignedPreKeyListener.schedule(this); DirectoryRefreshListener.schedule(this); LocalBackupListener.schedule(this); + MessageBackupListener.schedule(this); RotateSenderCertificateListener.schedule(this); RoutineMessageFetchReceiver.startOrUpdateAlarm(this); AnalyzeDatabaseAlarmListener.schedule(this); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/data/ConversationDataSource.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/data/ConversationDataSource.kt index 5b7e2d3dbc..c2411d314a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/data/ConversationDataSource.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/data/ConversationDataSource.kt @@ -10,7 +10,6 @@ import org.signal.core.util.Stopwatch import org.signal.core.util.logging.Log import org.signal.core.util.toInt import org.signal.paging.PagedDataSource -import org.thoughtcrime.securesms.BuildConfig import org.thoughtcrime.securesms.backup.v2.BackupRestoreManager import org.thoughtcrime.securesms.conversation.ConversationData import org.thoughtcrime.securesms.conversation.ConversationMessage @@ -25,6 +24,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel private typealias ConversationElement = MappingModel<*> @@ -125,7 +125,7 @@ class ConversationDataSource( records = MessageDataFetcher.updateModelsWithData(records, extraData).toMutableList() stopwatch.split("models") - if (BuildConfig.MESSAGE_BACKUP_RESTORE_ENABLED && SignalStore.backup().restoreState.inProgress) { + if (FeatureFlags.messageBackups() && SignalStore.backup().restoreState.inProgress) { BackupRestoreManager.prioritizeAttachmentsIfNeeded(records) stopwatch.split("restore") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt index c3bd4080c1..7ffbfe67a8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupMessagesJob.kt @@ -30,13 +30,21 @@ class BackupMessagesJob private constructor(parameters: Parameters) : BaseJob(pa private val TAG = Log.tag(BackupMessagesJob::class.java) const val KEY = "BackupMessagesJob" + + const val QUEUE = "BackupMessagesQueue" + + fun enqueue() { + val jobManager = ApplicationDependencies.getJobManager() + jobManager.add(BackupMessagesJob()) + } } constructor() : this( Parameters.Builder() .addConstraint(NetworkConstraint.KEY) - .setMaxAttempts(Parameters.UNLIMITED) - .setMaxInstancesForFactory(2) + .setMaxAttempts(3) + .setMaxInstancesForFactory(1) + .setQueue(QUEUE) .build() ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt index 57d0e91bc4..f32f06a27d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/BackupValues.kt @@ -20,6 +20,8 @@ internal class BackupValues(store: KeyValueStore) : SignalStoreValues(store) { private const val KEY_CDN_READ_CREDENTIALS_TIMESTAMP = "backup.cdn.readCredentials.timestamp" private const val KEY_RESTORE_STATE = "backup.restoreState" + private const val KEY_NEXT_BACKUP_TIME = "backup.nextBackupTime" + private const val KEY_CDN_BACKUP_DIRECTORY = "backup.cdn.directory" private const val KEY_CDN_BACKUP_MEDIA_DIRECTORY = "backup.cdn.mediaDirectory" @@ -45,6 +47,8 @@ internal class BackupValues(store: KeyValueStore) : SignalStoreValues(store) { var restoreState: RestoreState by enumValue(KEY_RESTORE_STATE, RestoreState.NONE, RestoreState.serializer) var optimizeStorage: Boolean by booleanValue(KEY_OPTIMIZE_STORAGE, false) + var nextBackupTime: Long by longValue(KEY_NEXT_BACKUP_TIME, -1) + var areBackupsEnabled: Boolean by booleanValue(KEY_BACKUPS_ENABLED, false) /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java index 5877dd24f0..d6fa92b6a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java @@ -6,7 +6,6 @@ import android.content.Context; import androidx.annotation.NonNull; import org.thoughtcrime.securesms.jobs.LocalBackupJob; -import org.thoughtcrime.securesms.keyvalue.SettingsValues; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.util.JavaTimeExtensionsKt; import org.thoughtcrime.securesms.util.TextSecurePreferences; diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/MessageBackupListener.kt b/app/src/main/java/org/thoughtcrime/securesms/service/MessageBackupListener.kt new file mode 100644 index 0000000000..ddd95042e8 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/service/MessageBackupListener.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.service + +import android.content.Context +import org.thoughtcrime.securesms.jobs.BackupMessagesJob +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.util.FeatureFlags +import org.thoughtcrime.securesms.util.toMillis +import java.time.LocalDateTime +import java.util.Random +import java.util.concurrent.TimeUnit + +class MessageBackupListener : PersistentAlarmManagerListener() { + override fun shouldScheduleExact(): Boolean { + return true + } + + override fun getNextScheduledExecutionTime(context: Context): Long { + return SignalStore.backup().nextBackupTime + } + + override fun onAlarm(context: Context, scheduledTime: Long): Long { + if (SignalStore.backup().areBackupsEnabled) { + BackupMessagesJob.enqueue() + } + return setNextBackupTimeToIntervalFromNow() + } + + companion object { + private val BACKUP_JITTER_WINDOW_SECONDS = Math.toIntExact(TimeUnit.MINUTES.toSeconds(10)) + + @JvmStatic + fun schedule(context: Context?) { + if (FeatureFlags.messageBackups() && SignalStore.backup().areBackupsEnabled) { + MessageBackupListener().onReceive(context, getScheduleIntent()) + } + } + + fun setNextBackupTimeToIntervalFromNow(): Long { + val now = LocalDateTime.now() + val hour = SignalStore.settings().backupHour + val minute = SignalStore.settings().backupMinute + var next = now.withHour(hour).withMinute(minute).withSecond(0) + val jitter = Random().nextInt(BACKUP_JITTER_WINDOW_SECONDS) - BACKUP_JITTER_WINDOW_SECONDS / 2 + next.plusSeconds(jitter.toLong()) + if (now.isAfter(next)) { + next = next.plusDays(1) + } + val nextTime = next.toMillis() + SignalStore.backup().nextBackupTime = nextTime + return nextTime + } + } +} 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 e99380aa9c..e83ee017fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -741,7 +741,7 @@ public final class FeatureFlags { * Note: This feature is in active development and is not intended to currently function. */ public static boolean messageBackups() { - return getBoolean(MESSAGE_BACKUPS, false); + return BuildConfig.MESSAGE_BACKUP_RESTORE_ENABLED || getBoolean(MESSAGE_BACKUPS, false); } /** Whether or not to use the custom CameraX controller class */