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 4bc7758f2b..0b6eebfc9c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java @@ -47,14 +47,7 @@ public class LocalBackupListener extends PersistentAlarmManagerListener { LocalDateTime now = LocalDateTime.now(); int hour = SignalStore.settings().getBackupHour(); int minute = SignalStore.settings().getBackupMinute(); - LocalDateTime next = now.withHour(hour).withMinute(minute).withSecond(0); - - if (now.isAfter(next)) { - next = next.plusDays(1); - } - - int jitter = (new Random().nextInt(BACKUP_JITTER_WINDOW_SECONDS)) - (BACKUP_JITTER_WINDOW_SECONDS / 2); - next = next.plusSeconds(jitter); + LocalDateTime next = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, hour, minute, BACKUP_JITTER_WINDOW_SECONDS); long nextTime = JavaTimeExtensionsKt.toMillis(next); 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 e5471aeeb4..53cddea22a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/MessageBackupListener.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/MessageBackupListener.kt @@ -41,21 +41,28 @@ class MessageBackupListener : PersistentAlarmManagerListener() { } } + @JvmStatic + fun getNextDailyBackupTimeFromNowWithJitter(now: LocalDateTime, hour: Int, minute: Int, maxJitterSeconds: Int): LocalDateTime { + var next = now.withHour(hour).withMinute(minute).withSecond(0) + + if (!now.plusSeconds(maxJitterSeconds.toLong() / 2).isBefore(next)) { + next = next.plusDays(1) + } + + val jitter = Random().nextInt(BACKUP_JITTER_WINDOW_SECONDS) - BACKUP_JITTER_WINDOW_SECONDS / 2 + return next.plusSeconds(jitter.toLong()) + } + 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()) + var next = getNextDailyBackupTimeFromNowWithJitter(now, hour, minute, BACKUP_JITTER_WINDOW_SECONDS) next = when (SignalStore.backup().backupFrequency) { - BackupFrequency.DAILY -> next.plusDays(1) - BackupFrequency.MANUAL -> next.plusDays(365) - BackupFrequency.MONTHLY -> next.plusDays(30) - BackupFrequency.WEEKLY -> next.plusDays(7) - } - if (now.isAfter(next)) { - next = next.plusDays(1) + BackupFrequency.MANUAL -> next.plusDays(364) + BackupFrequency.MONTHLY -> next.plusDays(29) + BackupFrequency.WEEKLY -> next.plusDays(6) + else -> next } val nextTime = next.toMillis() SignalStore.backup().nextBackupTime = nextTime diff --git a/app/src/test/java/org/thoughtcrime/securesms/service/BackListenerTest.kt b/app/src/test/java/org/thoughtcrime/securesms/service/BackListenerTest.kt new file mode 100644 index 0000000000..b4e0cf8399 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/service/BackListenerTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.service + +import org.junit.Assert +import org.junit.Test +import org.thoughtcrime.securesms.BaseUnitTest +import java.time.LocalDateTime +import java.util.concurrent.TimeUnit + +class BackListenerTest : BaseUnitTest() { + + @Test + fun testBackupJitterExactlyWithinJitterWindow() { + val jitterWindowSeconds = Math.toIntExact(TimeUnit.MINUTES.toSeconds(10)) + val now = LocalDateTime.of(2024, 6, 7, 2, 55) + val next = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, 3, 0, jitterWindowSeconds) + Assert.assertEquals(8, next.dayOfMonth) + } + + @Test + fun testBackupJitterWithinJitterWindow() { + val jitterWindowSeconds = Math.toIntExact(TimeUnit.MINUTES.toSeconds(10)) + val now = LocalDateTime.of(2024, 6, 7, 2, 58) + val next = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, 3, 0, jitterWindowSeconds) + Assert.assertEquals(8, next.dayOfMonth) + } + + @Test + fun testBackupJitterJustOutsideOfWindow() { + val jitterWindowSeconds = Math.toIntExact(TimeUnit.MINUTES.toSeconds(10)) + val now = LocalDateTime.of(2024, 6, 7, 2, 54, 59) + val next = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, 3, 0, jitterWindowSeconds) + Assert.assertEquals(7, next.dayOfMonth) + } + + @Test + fun testBackupJitter() { + val jitterWindowSeconds = Math.toIntExact(TimeUnit.MINUTES.toSeconds(10)) + val now = LocalDateTime.of(2024, 6, 7, 3, 15, 0) + val next = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, 3, 0, jitterWindowSeconds) + Assert.assertEquals(8, next.dayOfMonth) + } +}