diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java index e89e6dc2b1..c41d9fece0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java @@ -47,6 +47,8 @@ public final class SettingsValues extends SignalStoreValues { public static final String BACKUPS_ENABLED = "settings.backups.enabled"; public static final String BACKUPS_SCHEDULE_HOUR = "settings.backups.schedule.hour"; public static final String BACKUPS_SCHEDULE_MINUTE = "settings.backups.schedule.minute"; + public static final String SIGNAL_BACKUPS_SCHEDULE_HOUR = "settings.signal.backups.schedule.hour"; + public static final String SIGNAL_BACKUPS_SCHEDULE_MINUTE = "settings.signal.backups.schedule.minute"; public static final String SMS_DELIVERY_REPORTS_ENABLED = "settings.sms.delivery.reports.enabled"; public static final String WIFI_CALLING_COMPATIBILITY_MODE_ENABLED = "settings.wifi.calling.compatibility.mode.enabled"; public static final String MESSAGE_NOTIFICATIONS_ENABLED = "settings.message.notifications.enabled"; @@ -108,6 +110,9 @@ public final class SettingsValues extends SignalStoreValues { // Initialize backup time to a 5min interval between 1-5am setBackupSchedule(new Random().nextInt(5) + 1, new Random().nextInt(12) * 5); } + if (!store.containsKey(SIGNAL_BACKUPS_SCHEDULE_HOUR)) { + initSignalBackupsSchedule(); + } } @Override @@ -315,11 +320,40 @@ public final class SettingsValues extends SignalStoreValues { return getInteger(BACKUPS_SCHEDULE_MINUTE, BACKUP_DEFAULT_MINUTE); } + public int getSignalBackupHour() { + int hour = getInteger(SIGNAL_BACKUPS_SCHEDULE_HOUR, -1); + if (hour < 0) { + initSignalBackupsSchedule(); + return getInteger(SIGNAL_BACKUPS_SCHEDULE_HOUR, BACKUP_DEFAULT_HOUR); + } else { + return hour; + } + } + + public int getSignalBackupMinute() { + int minute = getInteger(SIGNAL_BACKUPS_SCHEDULE_MINUTE, -1); + if (minute < 0) { + initSignalBackupsSchedule(); + return getInteger(SIGNAL_BACKUPS_SCHEDULE_MINUTE, BACKUP_DEFAULT_MINUTE); + } else { + return minute; + } + } + public void setBackupSchedule(int hour, int minute) { putInteger(BACKUPS_SCHEDULE_HOUR, hour); putInteger(BACKUPS_SCHEDULE_MINUTE, minute); } + private void initSignalBackupsSchedule() { + setSignalBackupSchedule(new Random().nextInt(5) + 1, new Random().nextInt(12) * 5); + } + + public void setSignalBackupSchedule(int hour, int minute) { + putInteger(SIGNAL_BACKUPS_SCHEDULE_HOUR, hour); + putInteger(SIGNAL_BACKUPS_SCHEDULE_MINUTE, minute); + } + public boolean isSmsDeliveryReportsEnabled() { return getBoolean(SMS_DELIVERY_REPORTS_ENABLED, TextSecurePreferences.isSmsDeliveryReportsEnabled(AppDependencies.getApplication())); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/MessageBackupListener.kt b/app/src/main/java/org/thoughtcrime/securesms/service/MessageBackupListener.kt index 402625624b..b9ae7f31d0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/MessageBackupListener.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/MessageBackupListener.kt @@ -24,7 +24,7 @@ class MessageBackupListener : PersistentAlarmManagerListener() { @VisibleForTesting public override fun getNextScheduledExecutionTime(context: Context): Long { val nextTime = SignalStore.backup.nextBackupTime - return if (nextTime > (System.currentTimeMillis() + 2.days.inWholeMilliseconds)) { + return if (nextTime < 0 || nextTime > (System.currentTimeMillis() + 2.days.inWholeMilliseconds)) { setNextBackupTimeToIntervalFromNow() } else { nextTime @@ -62,11 +62,11 @@ class MessageBackupListener : PersistentAlarmManagerListener() { return next.plusSeconds(jitter.toLong()) } - fun setNextBackupTimeToIntervalFromNow(maxJitterSeconds: Int = BACKUP_JITTER_WINDOW_SECONDS): Long { - val now = LocalDateTime.now() - val hour = SignalStore.settings.backupHour - val minute = SignalStore.settings.backupMinute - val next = getNextDailyBackupTimeFromNowWithJitter(now, hour, minute, maxJitterSeconds).plusDays(1) + @VisibleForTesting + fun setNextBackupTimeToIntervalFromNow(now: LocalDateTime = LocalDateTime.now(), maxJitterSeconds: Int = BACKUP_JITTER_WINDOW_SECONDS, randomSource: Random = Random()): Long { + val hour = SignalStore.settings.signalBackupHour + val minute = SignalStore.settings.signalBackupMinute + val next = getNextDailyBackupTimeFromNowWithJitter(now, hour, minute, maxJitterSeconds, randomSource) val nextTime = next.toMillis() SignalStore.backup.nextBackupTime = nextTime return nextTime diff --git a/app/src/test/java/org/thoughtcrime/securesms/service/MessageBackupListenerTest.kt b/app/src/test/java/org/thoughtcrime/securesms/service/MessageBackupListenerTest.kt index 384d725682..273d57c09c 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/service/MessageBackupListenerTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/service/MessageBackupListenerTest.kt @@ -24,6 +24,7 @@ import org.robolectric.annotation.Config import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.testutil.MockRandom import org.thoughtcrime.securesms.testutil.MockSignalStoreRule +import org.thoughtcrime.securesms.util.toLocalDateTime import java.time.Duration import java.time.LocalDateTime import java.util.concurrent.TimeUnit @@ -56,14 +57,35 @@ class MessageBackupListenerTest { nextTime = System.currentTimeMillis() + 7.days.inWholeMilliseconds every { SignalStore.backup.nextBackupTime } returns nextTime - every { SignalStore.settings.backupHour } returns 2 - every { SignalStore.settings.backupMinute } returns 0 + every { SignalStore.settings.signalBackupHour } returns 2 + every { SignalStore.settings.signalBackupMinute } returns 0 every { SignalStore.backup.nextBackupTime = any() } just runs val adjustedTime = listener.getNextScheduledExecutionTime(ApplicationProvider.getApplicationContext()) assertThat(adjustedTime).isGreaterThan(System.currentTimeMillis()) assertThat(adjustedTime).isLessThan(System.currentTimeMillis() + 2.days.inWholeMilliseconds) } + @Test + fun testSetNextBackupTimeToIntervalFromNow() { + val mockRandom = MockRandom(listOf(1.minutes.inWholeSeconds.toInt())) + val now = LocalDateTime.of(2025, 6, 27, 2, 0, 0) + val jitterWindow = 10.minutes + + every { SignalStore.settings.signalBackupHour } returns 2 + every { SignalStore.settings.signalBackupMinute } returns 1 + every { SignalStore.backup.nextBackupTime = any() } just runs + + val nextDateTime = MessageBackupListener.setNextBackupTimeToIntervalFromNow( + now = now, + maxJitterSeconds = jitterWindow.inWholeSeconds.toInt(), + randomSource = mockRandom + ).toLocalDateTime() + + assertThat(nextDateTime.dayOfMonth).isEqualTo(28) + assertThat(nextDateTime.hour).isEqualTo(1) + assertThat(nextDateTime.minute).isEqualTo(57) + } + @Test fun testBackupJitterExactlyWithinJitterWindow() { val jitterWindowSeconds = Math.toIntExact(TimeUnit.MINUTES.toSeconds(10)) diff --git a/app/src/test/java/org/thoughtcrime/securesms/testutil/MockRandom.kt b/app/src/test/java/org/thoughtcrime/securesms/testutil/MockRandom.kt index 798fea3d40..5fb5f8b2c4 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/testutil/MockRandom.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/testutil/MockRandom.kt @@ -15,4 +15,8 @@ class MockRandom(initialInts: List) : Random() { override fun nextInt(): Int { return nextInts.remove() } + + override fun nextInt(bound: Int): Int { + return nextInts.remove() % bound + } }