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 5d03b776c2..402625624b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/MessageBackupListener.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/MessageBackupListener.kt @@ -6,12 +6,14 @@ package org.thoughtcrime.securesms.service import android.content.Context +import androidx.annotation.VisibleForTesting import org.thoughtcrime.securesms.jobs.BackupMessagesJob import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.util.RemoteConfig import org.thoughtcrime.securesms.util.toMillis import java.time.LocalDateTime import java.util.Random +import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.minutes class MessageBackupListener : PersistentAlarmManagerListener() { @@ -19,8 +21,14 @@ class MessageBackupListener : PersistentAlarmManagerListener() { return true } - override fun getNextScheduledExecutionTime(context: Context): Long { - return SignalStore.backup.nextBackupTime + @VisibleForTesting + public override fun getNextScheduledExecutionTime(context: Context): Long { + val nextTime = SignalStore.backup.nextBackupTime + return if (nextTime > (System.currentTimeMillis() + 2.days.inWholeMilliseconds)) { + setNextBackupTimeToIntervalFromNow() + } else { + nextTime + } } override fun onAlarm(context: Context, scheduledTime: Long): Long { @@ -40,6 +48,7 @@ class MessageBackupListener : PersistentAlarmManagerListener() { } } + @VisibleForTesting @JvmStatic fun getNextDailyBackupTimeFromNowWithJitter(now: LocalDateTime, hour: Int, minute: Int, maxJitterSeconds: Int, randomSource: Random = Random()): LocalDateTime { var next = now.withHour(hour).withMinute(minute).withSecond(0) diff --git a/app/src/test/java/org/thoughtcrime/securesms/service/BackPendingParticipantsListenerTest.kt b/app/src/test/java/org/thoughtcrime/securesms/service/MessageBackupListenerTest.kt similarity index 51% rename from app/src/test/java/org/thoughtcrime/securesms/service/BackPendingParticipantsListenerTest.kt rename to app/src/test/java/org/thoughtcrime/securesms/service/MessageBackupListenerTest.kt index 0f7413617b..384d725682 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/service/BackPendingParticipantsListenerTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/service/MessageBackupListenerTest.kt @@ -5,17 +5,64 @@ package org.thoughtcrime.securesms.service +import android.app.Application +import androidx.test.core.app.ApplicationProvider +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isGreaterThan +import assertk.assertions.isLessThan +import io.mockk.every +import io.mockk.just +import io.mockk.runs import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue +import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.testutil.MockRandom +import org.thoughtcrime.securesms.testutil.MockSignalStoreRule import java.time.Duration import java.time.LocalDateTime import java.util.concurrent.TimeUnit import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.minutes -class BackPendingParticipantsListenerTest { +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE, application = Application::class) +class MessageBackupListenerTest { + + @get:Rule + val rule = MockSignalStoreRule() + + @Test + fun testGetNextScheduledExecutionTime() { + val listener = MessageBackupListener() + + var nextTime = System.currentTimeMillis() + 1.days.inWholeMilliseconds + every { SignalStore.backup.nextBackupTime } returns nextTime + assertThat(listener.getNextScheduledExecutionTime(ApplicationProvider.getApplicationContext())).isEqualTo(nextTime) + + nextTime = System.currentTimeMillis() + 2.days.inWholeMilliseconds + every { SignalStore.backup.nextBackupTime } returns nextTime + assertThat(listener.getNextScheduledExecutionTime(ApplicationProvider.getApplicationContext())).isEqualTo(nextTime) + + nextTime = System.currentTimeMillis() + 8.hours.inWholeMilliseconds + every { SignalStore.backup.nextBackupTime } returns nextTime + assertThat(listener.getNextScheduledExecutionTime(ApplicationProvider.getApplicationContext())).isEqualTo(nextTime) + + 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.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 testBackupJitterExactlyWithinJitterWindow() { diff --git a/app/src/test/java/org/thoughtcrime/securesms/testutil/MockSignalStoreRule.kt b/app/src/test/java/org/thoughtcrime/securesms/testutil/MockSignalStoreRule.kt index f3736f367e..91f9af0554 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/testutil/MockSignalStoreRule.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/testutil/MockSignalStoreRule.kt @@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.keyvalue.EmojiValues import org.thoughtcrime.securesms.keyvalue.InAppPaymentValues import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues import org.thoughtcrime.securesms.keyvalue.RegistrationValues +import org.thoughtcrime.securesms.keyvalue.SettingsValues import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SvrValues import kotlin.reflect.KClass @@ -53,6 +54,9 @@ class MockSignalStoreRule(private val relaxed: Set> = emptySet()) : Ex lateinit var backup: BackupValues private set + lateinit var settings: SettingsValues + private set + override fun before() { account = mockk(relaxed = relaxed.contains(AccountValues::class), relaxUnitFun = true) phoneNumberPrivacy = mockk(relaxed = relaxed.contains(PhoneNumberPrivacyValues::class), relaxUnitFun = true) @@ -61,6 +65,7 @@ class MockSignalStoreRule(private val relaxed: Set> = emptySet()) : Ex emoji = mockk(relaxed = relaxed.contains(EmojiValues::class), relaxUnitFun = true) inAppPayments = mockk(relaxed = relaxed.contains(InAppPaymentValues::class), relaxUnitFun = true) backup = mockk(relaxed = relaxed.contains(BackupValues::class), relaxUnitFun = true) + settings = mockk(relaxed = relaxed.contains(SettingsValues::class), relaxUnitFun = true) mockkObject(SignalStore) every { SignalStore.account } returns account @@ -70,6 +75,7 @@ class MockSignalStoreRule(private val relaxed: Set> = emptySet()) : Ex every { SignalStore.emoji } returns emoji every { SignalStore.inAppPayments } returns inAppPayments every { SignalStore.backup } returns backup + every { SignalStore.settings } returns settings } override fun after() {