From e4a1e69f61538fe0e21bc418488b31e0e96b9d6f Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Wed, 10 May 2023 01:05:30 -0700 Subject: [PATCH 01/24] Add backup frequency as a setting that can be set --- .../securesms/keyvalue/SettingsValues.java | 15 +++++++++++---- .../migrations/BackupJitterMigrationJob.kt | 3 ++- .../preferences/BackupsPreferenceFragment.java | 3 ++- 3 files changed, 15 insertions(+), 6 deletions(-) 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 13c9e7d06f6..c14c3990ca8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java @@ -50,6 +50,7 @@ public final class SettingsValues extends SignalStoreValues { public static final String PREFER_SYSTEM_EMOJI = "settings.use.system.emoji"; public static final String ENTER_KEY_SENDS = "settings.enter.key.sends"; public static final String BACKUPS_ENABLED = "settings.backups.enabled"; + public static final String BACKUPS_SCHEDULE_FREQUENCY = "settings.backups.schedule.frequency"; // days 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 SMS_DELIVERY_REPORTS_ENABLED = "settings.sms.delivery.reports.enabled"; @@ -73,8 +74,9 @@ public final class SettingsValues extends SignalStoreValues { private static final String KEEP_MUTED_CHATS_ARCHIVED = "settings.keepMutedChatsArchived"; private static final String USE_COMPACT_NAVIGATION_BAR = "settings.useCompactNavigationBar"; - public static final int BACKUP_DEFAULT_HOUR = 2; - public static final int BACKUP_DEFAULT_MINUTE = 0; + public static final int BACKUP_DEFAULT_FREQUENCY = 30; // days + public static final int BACKUP_DEFAULT_HOUR = 2; + public static final int BACKUP_DEFAULT_MINUTE = 0; private final SingleLiveEvent onConfigurationSettingChanged = new SingleLiveEvent<>(); @@ -92,7 +94,7 @@ void onFirstEverAppLaunch() { } if (!store.containsKey(BACKUPS_SCHEDULE_HOUR)) { // Initialize backup time to a 5min interval between 1-5am - setBackupSchedule(new Random().nextInt(5) + 1, new Random().nextInt(12) * 5); + setBackupSchedule(BACKUP_DEFAULT_FREQUENCY, new Random().nextInt(5) + 1, new Random().nextInt(12) * 5); } } @@ -275,6 +277,10 @@ public void setBackupEnabled(boolean backupEnabled) { putBoolean(BACKUPS_ENABLED, backupEnabled); } + public int getBackupFrequency() { + return getInteger(BACKUPS_SCHEDULE_FREQUENCY, BACKUP_DEFAULT_FREQUENCY); + } + public int getBackupHour() { return getInteger(BACKUPS_SCHEDULE_HOUR, BACKUP_DEFAULT_HOUR); } @@ -283,7 +289,8 @@ public int getBackupMinute() { return getInteger(BACKUPS_SCHEDULE_MINUTE, BACKUP_DEFAULT_MINUTE); } - public void setBackupSchedule(int hour, int minute) { + public void setBackupSchedule(int days, int hour, int minute) { + putInteger(BACKUPS_SCHEDULE_FREQUENCY, days); putInteger(BACKUPS_SCHEDULE_HOUR, hour); putInteger(BACKUPS_SCHEDULE_MINUTE, minute); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/BackupJitterMigrationJob.kt b/app/src/main/java/org/thoughtcrime/securesms/migrations/BackupJitterMigrationJob.kt index fba71ce2e86..d662d83bea6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/BackupJitterMigrationJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/BackupJitterMigrationJob.kt @@ -22,13 +22,14 @@ internal class BackupJitterMigrationJob(parameters: Parameters = Parameters.Buil override fun isUiBlocking(): Boolean = false override fun performMigration() { + val frequency = SignalStore.settings().backupFrequency val hour = SignalStore.settings().backupHour val minute = SignalStore.settings().backupMinute if (hour == SettingsValues.BACKUP_DEFAULT_HOUR && minute == SettingsValues.BACKUP_DEFAULT_MINUTE) { val rand = Random() val newHour = rand.nextInt(3) + 1 // between 1AM - 3AM val newMinute = rand.nextInt(12) * 5 // 5 minute intervals up to +55 minutes - SignalStore.settings().setBackupSchedule(newHour, newMinute) + SignalStore.settings().setBackupSchedule(frequency, newHour, newMinute) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 590193f078f..af44f9d0b91 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.database.NoExternalStorageException; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.LocalBackupJob; +import org.thoughtcrime.securesms.keyvalue.SettingsValues; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.service.LocalBackupListener; @@ -271,7 +272,7 @@ private void pickTime() { timePickerFragment.addOnPositiveButtonClickListener(v -> { int hour = timePickerFragment.getHour(); int minute = timePickerFragment.getMinute(); - SignalStore.settings().setBackupSchedule(hour, minute); + SignalStore.settings().setBackupSchedule(SettingsValues.BACKUP_DEFAULT_FREQUENCY, hour, minute); updateTimeLabel(); TextSecurePreferences.setNextBackupTime(requireContext(), 0); LocalBackupListener.schedule(requireContext()); From e85ebb4503d0d886e221ceb1db5a30b34c84b59c Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Wed, 10 May 2023 01:05:02 -0700 Subject: [PATCH 02/24] Show dialog to select backup frequency --- .../BackupsPreferenceFragment.java | 38 +++++++++++-------- .../DayPickerShitDialogFragment.kt | 33 ++++++++++++++++ 2 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/preferences/DayPickerShitDialogFragment.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index af44f9d0b91..8bbdb62b9a5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -2,6 +2,8 @@ import android.Manifest; import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; @@ -20,6 +22,7 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.text.HtmlCompat; +import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import com.google.android.material.timepicker.MaterialTimePicker; @@ -36,7 +39,6 @@ import org.thoughtcrime.securesms.database.NoExternalStorageException; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.LocalBackupJob; -import org.thoughtcrime.securesms.keyvalue.SettingsValues; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.service.LocalBackupListener; @@ -262,22 +264,26 @@ private void onCreateClickedApi29() { } private void pickTime() { - int timeFormat = DateFormat.is24HourFormat(requireContext()) ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H; - final MaterialTimePicker timePickerFragment = new MaterialTimePicker.Builder() - .setTimeFormat(timeFormat) - .setHour(SignalStore.settings().getBackupHour()) - .setMinute(SignalStore.settings().getBackupMinute()) - .setTitleText("Set Backup Time") - .build(); - timePickerFragment.addOnPositiveButtonClickListener(v -> { - int hour = timePickerFragment.getHour(); - int minute = timePickerFragment.getMinute(); - SignalStore.settings().setBackupSchedule(SettingsValues.BACKUP_DEFAULT_FREQUENCY, hour, minute); - updateTimeLabel(); - TextSecurePreferences.setNextBackupTime(requireContext(), 0); - LocalBackupListener.schedule(requireContext()); + final DayPickerShitDialogFragment shit = new DayPickerShitDialogFragment(); + shit.show(getChildFragmentManager(), "SHITFUCK"); + shit.setOnPositiveButtonClickListener((unused1, unused2) -> { + int timeFormat = DateFormat.is24HourFormat(requireContext()) ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H; + final MaterialTimePicker timePickerFragment = new MaterialTimePicker.Builder() + .setTimeFormat(timeFormat) + .setHour(SignalStore.settings().getBackupHour()) + .setMinute(SignalStore.settings().getBackupMinute()) + .setTitleText("Set Backup Time") + .build(); + timePickerFragment.addOnPositiveButtonClickListener(v -> { + int hour = timePickerFragment.getHour(); + int minute = timePickerFragment.getMinute(); + SignalStore.settings().setBackupSchedule(shit.getValue(), hour, minute); + updateTimeLabel(); + TextSecurePreferences.setNextBackupTime(requireContext(), 0); + LocalBackupListener.schedule(requireContext()); + }); + timePickerFragment.show(getChildFragmentManager(), "TIME_PICKER"); }); - timePickerFragment.show(getChildFragmentManager(), "TIME_PICKER"); } private void onCreateClickedLegacy() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/DayPickerShitDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/DayPickerShitDialogFragment.kt new file mode 100644 index 00000000000..bd421c8b86f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/DayPickerShitDialogFragment.kt @@ -0,0 +1,33 @@ +package org.thoughtcrime.securesms.preferences + +import android.app.AlertDialog +import android.app.Dialog +import android.content.DialogInterface +import android.content.DialogInterface.OnClickListener +import android.os.Bundle +import android.widget.Toast +import androidx.fragment.app.DialogFragment + +class DayPickerShitDialogFragment : DialogFragment() { + private var dayOptions = arrayOf("1", "7", "30", "90", "180", "365") + private var index: Int = 0 + private var callback: OnClickListener? = null + + override fun onCreateDialog(savedInstance: Bundle?): Dialog { + return AlertDialog.Builder(requireContext()) + .setSingleChoiceItems(this.dayOptions, 0) { _, i -> this.index = i } + .setTitle("Every N days") + .setPositiveButton("okeydokey") { dialog, i -> + val backupFrequencyDays = this.dayOptions[this.index].toInt() + Toast.makeText(requireContext(), "Backup every $backupFrequencyDays days", Toast.LENGTH_LONG).show() + callback?.onClick(dialog, i) + } + .create() + } + + fun getValue(): Int = this.dayOptions[this.index].toInt() + + fun setOnPositiveButtonClickListener(cb: OnClickListener) { + this.callback = cb + } +} \ No newline at end of file From 79c0f26c3dc9a4c5075f38dbf8a8f25b1ddd25af Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Wed, 10 May 2023 01:25:39 -0700 Subject: [PATCH 03/24] todo commit --- .../org/thoughtcrime/securesms/service/LocalBackupListener.java | 1 + 1 file changed, 1 insertion(+) 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 26b2ffadbac..f7e5b96a702 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java @@ -55,6 +55,7 @@ public static long setNextBackupTimeToIntervalFromNow(@NonNull Context context) next.plusSeconds(jitter); if (now.isAfter(next)) { + // TODO(farewelltospring) figure out how to incorporate frequency into this next = next.plusDays(1); } From dd995987b79549022a143658e287a747e7128c20 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Thu, 11 May 2023 21:43:03 -0700 Subject: [PATCH 04/24] Use frequency setting to schedule future backups --- .../thoughtcrime/securesms/service/LocalBackupListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 f7e5b96a702..80d6f037b84 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java @@ -46,16 +46,16 @@ public static void schedule(Context context) { public static long setNextBackupTimeToIntervalFromNow(@NonNull Context context) { LocalDateTime now = LocalDateTime.now(); + int freq = SignalStore.settings().getBackupFrequency(); int hour = SignalStore.settings().getBackupHour(); int minute = SignalStore.settings().getBackupMinute(); - LocalDateTime next = now.withHour(hour).withMinute(minute).withSecond(0); + LocalDateTime next = now.withHour(hour).withMinute(minute).withSecond(0).plusDays(freq); int jitter = (new Random().nextInt(BACKUP_JITTER_WINDOW_SECONDS)) - (BACKUP_JITTER_WINDOW_SECONDS / 2); next.plusSeconds(jitter); if (now.isAfter(next)) { - // TODO(farewelltospring) figure out how to incorporate frequency into this next = next.plusDays(1); } From 9aa3d0558aa0e2669171e83f45df7867111cf3b2 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Fri, 12 May 2023 00:04:15 -0700 Subject: [PATCH 05/24] Pre-select backup frequency choice with user's current setting Also minor Kotlin cleanups --- .../securesms/preferences/BackupsPreferenceFragment.java | 2 +- .../securesms/preferences/DayPickerShitDialogFragment.kt | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 8bbdb62b9a5..8610921bc64 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -264,7 +264,7 @@ private void onCreateClickedApi29() { } private void pickTime() { - final DayPickerShitDialogFragment shit = new DayPickerShitDialogFragment(); + final DayPickerShitDialogFragment shit = new DayPickerShitDialogFragment(SignalStore.settings().getBackupFrequency()); shit.show(getChildFragmentManager(), "SHITFUCK"); shit.setOnPositiveButtonClickListener((unused1, unused2) -> { int timeFormat = DateFormat.is24HourFormat(requireContext()) ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H; diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/DayPickerShitDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/DayPickerShitDialogFragment.kt index bd421c8b86f..61ba80c19ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/DayPickerShitDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/DayPickerShitDialogFragment.kt @@ -8,14 +8,15 @@ import android.os.Bundle import android.widget.Toast import androidx.fragment.app.DialogFragment -class DayPickerShitDialogFragment : DialogFragment() { - private var dayOptions = arrayOf("1", "7", "30", "90", "180", "365") +class DayPickerShitDialogFragment(private val defaultFrequency: Int) : DialogFragment() { + private val dayOptions = arrayOf("1", "7", "30", "90", "180", "365") private var index: Int = 0 private var callback: OnClickListener? = null override fun onCreateDialog(savedInstance: Bundle?): Dialog { + val defaultIndex = this.dayOptions.indexOf(this.defaultFrequency.toString()) // preselect the backup frequency choice if it's valid return AlertDialog.Builder(requireContext()) - .setSingleChoiceItems(this.dayOptions, 0) { _, i -> this.index = i } + .setSingleChoiceItems(this.dayOptions, defaultIndex) { _, i -> this.index = i } .setTitle("Every N days") .setPositiveButton("okeydokey") { dialog, i -> val backupFrequencyDays = this.dayOptions[this.index].toInt() From dbb9390afa313c14b673787a8b5e00c2c60edab0 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Sun, 30 Jul 2023 00:49:09 -0700 Subject: [PATCH 06/24] Rename backup frequency picker dialog class for accuracy --- ...nt.kt => BackupFrequencyPickerDialogFragment.kt} | 3 +-- .../preferences/BackupsPreferenceFragment.java | 13 ++++--------- 2 files changed, 5 insertions(+), 11 deletions(-) rename app/src/main/java/org/thoughtcrime/securesms/preferences/{DayPickerShitDialogFragment.kt => BackupFrequencyPickerDialogFragment.kt} (90%) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/DayPickerShitDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt similarity index 90% rename from app/src/main/java/org/thoughtcrime/securesms/preferences/DayPickerShitDialogFragment.kt rename to app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt index 61ba80c19ba..55928ac16f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/DayPickerShitDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt @@ -2,13 +2,12 @@ package org.thoughtcrime.securesms.preferences import android.app.AlertDialog import android.app.Dialog -import android.content.DialogInterface import android.content.DialogInterface.OnClickListener import android.os.Bundle import android.widget.Toast import androidx.fragment.app.DialogFragment -class DayPickerShitDialogFragment(private val defaultFrequency: Int) : DialogFragment() { +class BackupFrequencyPickerDialogFragment(private val defaultFrequency: Int) : DialogFragment() { private val dayOptions = arrayOf("1", "7", "30", "90", "180", "365") private var index: Int = 0 private var callback: OnClickListener? = null diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 8610921bc64..b8e726943fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -2,16 +2,12 @@ import android.Manifest; import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.text.format.DateFormat; import android.text.method.LinkMovementMethod; -import android.util.TimeUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -22,7 +18,6 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.text.HtmlCompat; -import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import com.google.android.material.timepicker.MaterialTimePicker; @@ -264,9 +259,9 @@ private void onCreateClickedApi29() { } private void pickTime() { - final DayPickerShitDialogFragment shit = new DayPickerShitDialogFragment(SignalStore.settings().getBackupFrequency()); - shit.show(getChildFragmentManager(), "SHITFUCK"); - shit.setOnPositiveButtonClickListener((unused1, unused2) -> { + final BackupFrequencyPickerDialogFragment frequencyPickerDialogFragment = new BackupFrequencyPickerDialogFragment(SignalStore.settings().getBackupFrequency()); + frequencyPickerDialogFragment.show(getChildFragmentManager(), "FREQUENCY_PICKER"); + frequencyPickerDialogFragment.setOnPositiveButtonClickListener((unused1, unused2) -> { int timeFormat = DateFormat.is24HourFormat(requireContext()) ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H; final MaterialTimePicker timePickerFragment = new MaterialTimePicker.Builder() .setTimeFormat(timeFormat) @@ -277,7 +272,7 @@ private void pickTime() { timePickerFragment.addOnPositiveButtonClickListener(v -> { int hour = timePickerFragment.getHour(); int minute = timePickerFragment.getMinute(); - SignalStore.settings().setBackupSchedule(shit.getValue(), hour, minute); + SignalStore.settings().setBackupSchedule(frequencyPickerDialogFragment.getValue(), hour, minute); updateTimeLabel(); TextSecurePreferences.setNextBackupTime(requireContext(), 0); LocalBackupListener.schedule(requireContext()); From 9c4d5d3dc22cddae2f7545d9a1b75a9db59ad2f1 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Sun, 30 Jul 2023 03:39:03 -0700 Subject: [PATCH 07/24] Show backup frequency in app settings Yes I know this is bad practice but it's just for testing purposes --- .../securesms/preferences/BackupsPreferenceFragment.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index b8e726943fc..0f55be49092 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -294,10 +294,11 @@ private void onCreateClickedLegacy() { } private void updateTimeLabel() { - final int backupHour = SignalStore.settings().getBackupHour(); - final int backupMinute = SignalStore.settings().getBackupMinute(); - LocalTime time = LocalTime.of(backupHour, backupMinute); - timeLabel.setText(JavaTimeExtensionsKt.formatHours(time, requireContext())); + final int backupFrequency = SignalStore.settings().getBackupFrequency(); + final int backupHour = SignalStore.settings().getBackupHour(); + final int backupMinute = SignalStore.settings().getBackupMinute(); + LocalTime time = LocalTime.of(backupHour, backupMinute); + timeLabel.setText(JavaTimeExtensionsKt.formatHours(time, requireContext()) + " every " + backupFrequency + " days"); } private void setBackupsEnabled() { From 4b19f99afa0414eb3a1814307e3c4137c7d7d7ca Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Sun, 30 Jul 2023 03:44:45 -0700 Subject: [PATCH 08/24] Improve backup frequency picker buttons Rename OK button and add cancel button --- .../preferences/BackupFrequencyPickerDialogFragment.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt index 55928ac16f8..a83d61aad61 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt @@ -17,11 +17,12 @@ class BackupFrequencyPickerDialogFragment(private val defaultFrequency: Int) : D return AlertDialog.Builder(requireContext()) .setSingleChoiceItems(this.dayOptions, defaultIndex) { _, i -> this.index = i } .setTitle("Every N days") - .setPositiveButton("okeydokey") { dialog, i -> + .setPositiveButton("OK") { dialog, i -> val backupFrequencyDays = this.dayOptions[this.index].toInt() Toast.makeText(requireContext(), "Backup every $backupFrequencyDays days", Toast.LENGTH_LONG).show() callback?.onClick(dialog, i) } + .setNegativeButton("Cancel", null) .create() } From 3031c6c84d3912910e5ebe057f538af275f79ec0 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Sun, 30 Jul 2023 03:58:16 -0700 Subject: [PATCH 09/24] Minor cleanups to backup frequency stuff --- .../preferences/BackupFrequencyPickerDialogFragment.kt | 3 ++- .../securesms/preferences/BackupsPreferenceFragment.java | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt index a83d61aad61..e89d0386855 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt @@ -14,13 +14,14 @@ class BackupFrequencyPickerDialogFragment(private val defaultFrequency: Int) : D override fun onCreateDialog(savedInstance: Bundle?): Dialog { val defaultIndex = this.dayOptions.indexOf(this.defaultFrequency.toString()) // preselect the backup frequency choice if it's valid + this.index = defaultIndex return AlertDialog.Builder(requireContext()) .setSingleChoiceItems(this.dayOptions, defaultIndex) { _, i -> this.index = i } .setTitle("Every N days") .setPositiveButton("OK") { dialog, i -> val backupFrequencyDays = this.dayOptions[this.index].toInt() Toast.makeText(requireContext(), "Backup every $backupFrequencyDays days", Toast.LENGTH_LONG).show() - callback?.onClick(dialog, i) + this.callback?.onClick(dialog, i) } .setNegativeButton("Cancel", null) .create() diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 0f55be49092..a6b888e7ddb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -259,8 +259,8 @@ private void onCreateClickedApi29() { } private void pickTime() { + // User should select the backup frequency first, and then the time of day to do the backups. final BackupFrequencyPickerDialogFragment frequencyPickerDialogFragment = new BackupFrequencyPickerDialogFragment(SignalStore.settings().getBackupFrequency()); - frequencyPickerDialogFragment.show(getChildFragmentManager(), "FREQUENCY_PICKER"); frequencyPickerDialogFragment.setOnPositiveButtonClickListener((unused1, unused2) -> { int timeFormat = DateFormat.is24HourFormat(requireContext()) ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H; final MaterialTimePicker timePickerFragment = new MaterialTimePicker.Builder() @@ -277,8 +277,11 @@ private void pickTime() { TextSecurePreferences.setNextBackupTime(requireContext(), 0); LocalBackupListener.schedule(requireContext()); }); + timePickerFragment.show(getChildFragmentManager(), "TIME_PICKER"); }); + + frequencyPickerDialogFragment.show(getChildFragmentManager(), "FREQUENCY_PICKER"); } private void onCreateClickedLegacy() { From 174502738b48bd29d391baee5a4d0b9884c4e8a9 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Mon, 2 Oct 2023 03:10:15 -0700 Subject: [PATCH 10/24] Add docstring for the unit of next backup time Apparently it's a Unix timestamp in milliseconds and this took me way too long to figure out myself --- .../org/thoughtcrime/securesms/util/TextSecurePreferences.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ecab8b5e4b9..d21fdec8642 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -129,7 +129,7 @@ public class TextSecurePreferences { public static final String BACKUP_ENABLED = "pref_backup_enabled"; private static final String BACKUP_PASSPHRASE = "pref_backup_passphrase"; private static final String ENCRYPTED_BACKUP_PASSPHRASE = "pref_encrypted_backup_passphrase"; - private static final String BACKUP_TIME = "pref_backup_next_time"; + private static final String BACKUP_TIME = "pref_backup_next_time"; // milliseconds since 1970 public static final String TRANSFER = "pref_transfer"; From 5bea670394148b2fe7b4fc2d42b1a03201ad6fa7 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Mon, 2 Oct 2023 03:27:10 -0700 Subject: [PATCH 11/24] Schedule next backup relative to last backup when changing frequency When changing backup frequency settings, schedule the next backup using the new setting, but relative to the timestamp of the latest backup, instead of immediately. This ensures that backups are not created too frequently. A backup will only be immediately created if the latest backup was too long ago, relative to the new frequency setting, or if there is no latest backup. --- .../preferences/BackupsPreferenceFragment.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index a6b888e7ddb..059c4658e20 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -46,6 +46,7 @@ import java.time.LocalTime; import java.util.Locale; import java.util.Objects; +import java.util.Optional; public class BackupsPreferenceFragment extends Fragment { @@ -270,11 +271,21 @@ private void pickTime() { .setTitleText("Set Backup Time") .build(); timePickerFragment.addOnPositiveButtonClickListener(v -> { + int days = frequencyPickerDialogFragment.getValue(); int hour = timePickerFragment.getHour(); int minute = timePickerFragment.getMinute(); - SignalStore.settings().setBackupSchedule(frequencyPickerDialogFragment.getValue(), hour, minute); + SignalStore.settings().setBackupSchedule(days, hour, minute); updateTimeLabel(); - TextSecurePreferences.setNextBackupTime(requireContext(), 0); + // Schedule the next backup using the newly set frequency, but relative to the time of the + // last backup. This should only kick off a new backup to be created immediately if the + // last backup was long enough ago (or doesn't exist at all). + long lastBackupTime = 0; + try { + lastBackupTime = Optional.ofNullable(BackupUtil.getLatestBackup()) + .map(BackupUtil.BackupInfo::getTimestamp) + .orElse(0L); + } catch (NoExternalStorageException ignored) {} + TextSecurePreferences.setNextBackupTime(requireContext(), lastBackupTime + days * 24 * 60 * 60 * 1000L); LocalBackupListener.schedule(requireContext()); }); From 509c14ec77ec7aee9843eb67ec92ad7d4670ad4b Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Sun, 3 Dec 2023 02:26:23 -0800 Subject: [PATCH 12/24] Convert raw strings to resources in backup settings - Replace debug toast with debug log statement - Use MaterialAlertDialog instead of boring AlertDialog --- .../BackupFrequencyPickerDialogFragment.kt | 17 +++++++---------- .../preferences/BackupsPreferenceFragment.java | 7 ++++++- app/src/main/res/values/strings.xml | 7 +++++++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt index e89d0386855..1e6396e9990 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt @@ -1,11 +1,12 @@ package org.thoughtcrime.securesms.preferences -import android.app.AlertDialog import android.app.Dialog import android.content.DialogInterface.OnClickListener import android.os.Bundle -import android.widget.Toast import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +import org.thoughtcrime.securesms.R class BackupFrequencyPickerDialogFragment(private val defaultFrequency: Int) : DialogFragment() { private val dayOptions = arrayOf("1", "7", "30", "90", "180", "365") @@ -15,15 +16,11 @@ class BackupFrequencyPickerDialogFragment(private val defaultFrequency: Int) : D override fun onCreateDialog(savedInstance: Bundle?): Dialog { val defaultIndex = this.dayOptions.indexOf(this.defaultFrequency.toString()) // preselect the backup frequency choice if it's valid this.index = defaultIndex - return AlertDialog.Builder(requireContext()) + return MaterialAlertDialogBuilder(requireContext()) .setSingleChoiceItems(this.dayOptions, defaultIndex) { _, i -> this.index = i } - .setTitle("Every N days") - .setPositiveButton("OK") { dialog, i -> - val backupFrequencyDays = this.dayOptions[this.index].toInt() - Toast.makeText(requireContext(), "Backup every $backupFrequencyDays days", Toast.LENGTH_LONG).show() - this.callback?.onClick(dialog, i) - } - .setNegativeButton("Cancel", null) + .setTitle(R.string.BackupFrequencyPickerDialogFragment__enter_frequency) + .setPositiveButton(R.string.BackupFrequencyPickerDialogFragment__ok, this.callback) + .setNegativeButton(R.string.BackupFrequencyPickerDialogFragment__cancel, null) .create() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 5f469d73bec..00baa9cd728 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -276,6 +276,7 @@ private void pickTime() { int days = frequencyPickerDialogFragment.getValue(); int hour = timePickerFragment.getHour(); int minute = timePickerFragment.getMinute(); + Log.i(TAG, "Setting backup schedule: every " + days + " days at" + hour + "h" + minute + "m"); SignalStore.settings().setBackupSchedule(days, hour, minute); updateTimeLabel(); // Schedule the next backup using the newly set frequency, but relative to the time of the @@ -314,7 +315,11 @@ private void updateTimeLabel() { final int backupHour = SignalStore.settings().getBackupHour(); final int backupMinute = SignalStore.settings().getBackupMinute(); LocalTime time = LocalTime.of(backupHour, backupMinute); - timeLabel.setText(JavaTimeExtensionsKt.formatHours(time, requireContext()) + " every " + backupFrequency + " days"); + + String backupTimeString = JavaTimeExtensionsKt.formatHours(time, requireContext()); + timeLabel.setText(backupFrequency == 1 ? getString(R.string.BackupsPreferenceFragment__time_label_daily, backupTimeString) + : getString(R.string.BackupsPreferenceFragment__time_label_n_days, backupTimeString, backupFrequency) + ); } private void setBackupsEnabled() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8e51c33230c..f3b4044b459 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -647,8 +647,15 @@ Signal requires external storage permission in order to create backups, but it has been permanently denied. Please continue to app settings, select \"Permissions\" and enable \"Storage\". Set backup time + %1$s every day + %1$s every %2$d days + + Enter frequency (days) + OK + Cancel + Using custom: %s Using default: %s From 05e82fc846795c79c53938645c0309b83c1f0a01 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Sun, 3 Dec 2023 03:03:53 -0800 Subject: [PATCH 13/24] Add newline to end of file --- .../preferences/BackupFrequencyPickerDialogFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt index 1e6396e9990..2cebd0afb9a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt @@ -29,4 +29,4 @@ class BackupFrequencyPickerDialogFragment(private val defaultFrequency: Int) : D fun setOnPositiveButtonClickListener(cb: OnClickListener) { this.callback = cb } -} \ No newline at end of file +} From d57b50a398afdcc5ca6e2cc076b355682d4a9f2c Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Mon, 4 Dec 2023 01:22:53 -0800 Subject: [PATCH 14/24] Convert string to plurals for non-English language Just because the plural has a single manifestation in English doesn't mean we don't need to have plurals for it in other languages. --- .../securesms/preferences/BackupsPreferenceFragment.java | 4 +++- app/src/main/res/values/strings.xml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 00baa9cd728..8e774fab082 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -318,7 +318,9 @@ private void updateTimeLabel() { String backupTimeString = JavaTimeExtensionsKt.formatHours(time, requireContext()); timeLabel.setText(backupFrequency == 1 ? getString(R.string.BackupsPreferenceFragment__time_label_daily, backupTimeString) - : getString(R.string.BackupsPreferenceFragment__time_label_n_days, backupTimeString, backupFrequency) + : getResources().getQuantityString(R.plurals.BackupsPreferenceFragment__time_label_n_days, + backupFrequency, + backupTimeString, backupFrequency) ); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f3b4044b459..525cbf6f1ef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -648,7 +648,9 @@ Set backup time %1$s every day - %1$s every %2$d days + + %1$s every %2$d days + From 4032a70b782f4ec9a6d98edfaf6df37b0757a2b1 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Tue, 25 Jun 2024 04:30:02 -0700 Subject: [PATCH 15/24] Fix slow backups (farewelltospring's version) There's some kind of divergence in the parallel implementation that the people at Signal are doing instead of just merging my code (sadface), which caused some lossy merge conflicts. This fixes it and restores my version of slow backups to full functionality. --- .../org/thoughtcrime/securesms/service/LocalBackupListener.java | 1 + 1 file changed, 1 insertion(+) 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 b183034d7d0..a92d2fde837 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java @@ -51,6 +51,7 @@ public static long setNextBackupTimeToIntervalFromNow(@NonNull Context context) int minute = SignalStore.settings().getBackupMinute(); LocalDateTime next = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, hour, minute, BACKUP_JITTER_WINDOW_SECONDS); + next = next.plusDays(freq); long nextTime = JavaTimeExtensionsKt.toMillis(next); TextSecurePreferences.setNextBackupTime(context, nextTime); From 293ceff9837c162a61f5a13b82ecc836ab90fed4 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Mon, 17 Feb 2025 11:52:31 -0800 Subject: [PATCH 16/24] Add a 'never' option for backup frequency, new UI - Split frequency setting into separate setting entry in the UI - Add 'never' option for backup frequency - Do not schedule a backup when the user has selected 'never' for backup frequency - Cleanups --- .../securesms/keyvalue/SettingsValues.java | 15 +-- .../BackupFrequencyPickerDialogFragment.kt | 22 ++-- .../preferences/BackupFrequencyV1.kt | 28 +++++ .../BackupsPreferenceFragment.java | 103 ++++++++++-------- .../service/LocalBackupListener.java | 23 ++-- app/src/main/res/layout/fragment_backups.xml | 30 +++++ app/src/main/res/values/strings.xml | 14 ++- 7 files changed, 162 insertions(+), 73 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt 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 1a6bb5752f5..0dab197fb59 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.mms.SentMediaQuality; +import org.thoughtcrime.securesms.preferences.BackupFrequencyV1; import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference; import org.thoughtcrime.securesms.util.SingleLiveEvent; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -45,7 +46,7 @@ public final class SettingsValues extends SignalStoreValues { public static final String PREFER_SYSTEM_EMOJI = "settings.use.system.emoji"; public static final String ENTER_KEY_SENDS = "settings.enter.key.sends"; public static final String BACKUPS_ENABLED = "settings.backups.enabled"; - public static final String BACKUPS_SCHEDULE_FREQUENCY = "settings.backups.schedule.frequency"; // days + public static final String BACKUPS_SCHEDULE_FREQUENCY = "settings.backups.schedule.frequency"; 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 SMS_DELIVERY_REPORTS_ENABLED = "settings.sms.delivery.reports.enabled"; @@ -74,8 +75,8 @@ public final class SettingsValues extends SignalStoreValues { private static final String SCREEN_LOCK_ENABLED = "settings.screen.lock.enabled"; private static final String SCREEN_LOCK_TIMEOUT = "settings.screen.lock.timeout"; - public static final int BACKUP_DEFAULT_FREQUENCY = 30; // days - public static final int BACKUP_DEFAULT_HOUR = 2; + public static final BackupFrequencyV1 BACKUP_DEFAULT_FREQUENCY = BackupFrequencyV1.MONTHLY; + public static final int BACKUP_DEFAULT_HOUR = 2; public static final int BACKUP_DEFAULT_MINUTE = 0; private final SingleLiveEvent onConfigurationSettingChanged = new SingleLiveEvent<>(); @@ -309,8 +310,8 @@ public void setBackupEnabled(boolean backupEnabled) { putBoolean(BACKUPS_ENABLED, backupEnabled); } - public int getBackupFrequency() { - return getInteger(BACKUPS_SCHEDULE_FREQUENCY, BACKUP_DEFAULT_FREQUENCY); + public BackupFrequencyV1 getBackupFrequency() { + return BackupFrequencyV1.valueOf(getString(BACKUPS_SCHEDULE_FREQUENCY, BACKUP_DEFAULT_FREQUENCY.name())); } public int getBackupHour() { @@ -321,8 +322,8 @@ public int getBackupMinute() { return getInteger(BACKUPS_SCHEDULE_MINUTE, BACKUP_DEFAULT_MINUTE); } - public void setBackupSchedule(int days, int hour, int minute) { - putInteger(BACKUPS_SCHEDULE_FREQUENCY, days); + public void setBackupSchedule(BackupFrequencyV1 frequency, int hour, int minute) { + putString(BACKUPS_SCHEDULE_FREQUENCY, frequency.name()); putInteger(BACKUPS_SCHEDULE_HOUR, hour); putInteger(BACKUPS_SCHEDULE_MINUTE, minute); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt index 2cebd0afb9a..ba3e87bac77 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt @@ -7,24 +7,28 @@ import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.thoughtcrime.securesms.R +class BackupFrequencyPickerDialogFragment(private val defaultFrequency: BackupFrequencyV1) : DialogFragment() { -class BackupFrequencyPickerDialogFragment(private val defaultFrequency: Int) : DialogFragment() { - private val dayOptions = arrayOf("1", "7", "30", "90", "180", "365") - private var index: Int = 0 + private val frequencyOptions = BackupFrequencyV1.entries + private var selectedFrequency: BackupFrequencyV1 = defaultFrequency private var callback: OnClickListener? = null override fun onCreateDialog(savedInstance: Bundle?): Dialog { - val defaultIndex = this.dayOptions.indexOf(this.defaultFrequency.toString()) // preselect the backup frequency choice if it's valid - this.index = defaultIndex - return MaterialAlertDialogBuilder(requireContext()) - .setSingleChoiceItems(this.dayOptions, defaultIndex) { _, i -> this.index = i } + val context = requireContext() + val localizedFrequencyOptions = frequencyOptions.map { context.getString(it.getResourceId()) }.toTypedArray() + val defaultIndex = frequencyOptions.indexOf(defaultFrequency) + + return MaterialAlertDialogBuilder(context) + .setSingleChoiceItems(localizedFrequencyOptions, defaultIndex) { _, selectedIndex -> + selectedFrequency = frequencyOptions[selectedIndex] + } .setTitle(R.string.BackupFrequencyPickerDialogFragment__enter_frequency) - .setPositiveButton(R.string.BackupFrequencyPickerDialogFragment__ok, this.callback) + .setPositiveButton(R.string.BackupFrequencyPickerDialogFragment__ok, callback) .setNegativeButton(R.string.BackupFrequencyPickerDialogFragment__cancel, null) .create() } - fun getValue(): Int = this.dayOptions[this.index].toInt() + fun getValue(): BackupFrequencyV1 = selectedFrequency fun setOnPositiveButtonClickListener(cb: OnClickListener) { this.callback = cb diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt new file mode 100644 index 00000000000..8407f16f10f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.preferences + +import androidx.annotation.StringRes +import org.thoughtcrime.securesms.R + +enum class BackupFrequencyV1(val days: Int?) { + DAILY(1), + WEEKLY(7), + MONTHLY(30), + QUARTERLY(90), + NEVER(999); + + @StringRes + fun getResourceId(): Int { + return when (this) { + DAILY -> R.string.BackupsPreferenceFragment__frequency_label_daily + WEEKLY -> R.string.BackupsPreferenceFragment__frequency_label_weekly + MONTHLY -> R.string.BackupsPreferenceFragment__frequency_label_monthly + QUARTERLY -> R.string.BackupsPreferenceFragment__frequency_label_quarterly + NEVER -> R.string.BackupsPreferenceFragment__frequency_label_never + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 1e92e71c886..35e3017b96b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -2,14 +2,12 @@ import android.Manifest; import android.app.Activity; -import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.text.format.DateFormat; import android.text.method.LinkMovementMethod; -import android.util.TimeUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -59,8 +57,10 @@ public class BackupsPreferenceFragment extends Fragment { private View create; private View folder; private View verify; - private View timer; - private TextView timeLabel; + private View timer; + private View frequencyView; + private TextView timeLabel; + private TextView frequencyLabel; private TextView toggle; private TextView info; private TextView summary; @@ -82,6 +82,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat verify = view.findViewById(R.id.fragment_backup_verify); timer = view.findViewById(R.id.fragment_backup_time); timeLabel = view.findViewById(R.id.fragment_backup_time_value); + frequencyView = view.findViewById(R.id.fragment_backup_frequency); + frequencyLabel = view.findViewById(R.id.fragment_backup_frequency_value); toggle = view.findViewById(R.id.fragment_backup_toggle); info = view.findViewById(R.id.fragment_backup_info); summary = view.findViewById(R.id.fragment_backup_create_summary); @@ -93,6 +95,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat create.setOnClickListener(unused -> onCreateClicked()); verify.setOnClickListener(unused -> BackupDialog.showVerifyBackupPassphraseDialog(requireContext())); timer.setOnClickListener(unused -> pickTime()); + frequencyView.setOnClickListener(unused -> pickFrequency()); formatter.setMinimumFractionDigits(1); formatter.setMaximumFractionDigits(1); @@ -262,42 +265,57 @@ private void onCreateClickedApi29() { } private void pickTime() { + int timeFormat = DateFormat.is24HourFormat(requireContext()) ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H; + final MaterialTimePicker timePickerFragment = new MaterialTimePicker.Builder() + .setTimeFormat(timeFormat) + .setHour(SignalStore.settings().getBackupHour()) + .setMinute(SignalStore.settings().getBackupMinute()) + .setTitleText(R.string.BackupsPreferenceFragment__set_backup_time) + .build(); + timePickerFragment.addOnPositiveButtonClickListener(v -> { + BackupFrequencyV1 frequency = SignalStore.settings().getBackupFrequency(); + int hour = timePickerFragment.getHour(); + int minute = timePickerFragment.getMinute(); + handleNewBackupScheduleSetting(frequency, hour, minute); + updateTimeLabel(); + }); + timePickerFragment.show(getChildFragmentManager(), "TIME_PICKER"); + } + + private void pickFrequency() { // User should select the backup frequency first, and then the time of day to do the backups. final BackupFrequencyPickerDialogFragment frequencyPickerDialogFragment = new BackupFrequencyPickerDialogFragment(SignalStore.settings().getBackupFrequency()); frequencyPickerDialogFragment.setOnPositiveButtonClickListener((unused1, unused2) -> { - int timeFormat = DateFormat.is24HourFormat(requireContext()) ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H; - final MaterialTimePicker timePickerFragment = new MaterialTimePicker.Builder() - .setTimeFormat(timeFormat) - .setHour(SignalStore.settings().getBackupHour()) - .setMinute(SignalStore.settings().getBackupMinute()) - .setTitleText(R.string.BackupsPreferenceFragment__set_backup_time) - .build(); - timePickerFragment.addOnPositiveButtonClickListener(v -> { - int days = frequencyPickerDialogFragment.getValue(); - int hour = timePickerFragment.getHour(); - int minute = timePickerFragment.getMinute(); - Log.i(TAG, "Setting backup schedule: every " + days + " days at" + hour + "h" + minute + "m"); - SignalStore.settings().setBackupSchedule(days, hour, minute); - updateTimeLabel(); - // Schedule the next backup using the newly set frequency, but relative to the time of the - // last backup. This should only kick off a new backup to be created immediately if the - // last backup was long enough ago (or doesn't exist at all). - long lastBackupTime = 0; - try { - lastBackupTime = Optional.ofNullable(BackupUtil.getLatestBackup()) - .map(BackupUtil.BackupInfo::getTimestamp) - .orElse(0L); - } catch (NoExternalStorageException ignored) {} - TextSecurePreferences.setNextBackupTime(requireContext(), lastBackupTime + days * 24 * 60 * 60 * 1000L); - LocalBackupListener.schedule(requireContext()); - }); - - timePickerFragment.show(getChildFragmentManager(), "TIME_PICKER"); + BackupFrequencyV1 frequency = frequencyPickerDialogFragment.getValue(); + int hour = SignalStore.settings().getBackupHour(); + int minute = SignalStore.settings().getBackupMinute(); + handleNewBackupScheduleSetting(frequency, hour, minute); + updateDateLabel(); }); - frequencyPickerDialogFragment.show(getChildFragmentManager(), "FREQUENCY_PICKER"); } + /** Update the settings on disk and then schedule a backup. + * + *

This method should be called when the user presses the buttons to set a new backup schedule with the given parameters. */ + private void handleNewBackupScheduleSetting(BackupFrequencyV1 frequency, int hour, int minute) { + Log.i(TAG, "Setting backup schedule: " + frequency.name() + " at" + hour + "h" + minute + "m"); + SignalStore.settings().setBackupSchedule(frequency, hour, minute); + if (frequency != BackupFrequencyV1.NEVER) { + // Schedule the next backup using the newly set frequency, but relative to the time of the + // last backup. This should only kick off a new backup to be created immediately if the + // last backup was long enough ago (or doesn't exist at all). + long lastBackupTime = 0; + try { + lastBackupTime = Optional.ofNullable(BackupUtil.getLatestBackup()) + .map(BackupUtil.BackupInfo::getTimestamp) + .orElse(0L); + } catch (NoExternalStorageException ignored) {} + TextSecurePreferences.setNextBackupTime(requireContext(), lastBackupTime + frequency.getDays() * 24 * 60 * 60 * 1000L); + LocalBackupListener.schedule(requireContext()); + } + } + private void onCreateClickedLegacy() { Permissions.with(this) .request(Manifest.permission.WRITE_EXTERNAL_STORAGE) @@ -311,17 +329,14 @@ private void onCreateClickedLegacy() { } private void updateTimeLabel() { - final int backupFrequency = SignalStore.settings().getBackupFrequency(); - final int backupHour = SignalStore.settings().getBackupHour(); - final int backupMinute = SignalStore.settings().getBackupMinute(); - LocalTime time = LocalTime.of(backupHour, backupMinute); - - String backupTimeString = JavaTimeExtensionsKt.formatHours(time, requireContext()); - timeLabel.setText(backupFrequency == 1 ? getString(R.string.BackupsPreferenceFragment__time_label_daily, backupTimeString) - : getResources().getQuantityString(R.plurals.BackupsPreferenceFragment__time_label_n_days, - backupFrequency, - backupTimeString, backupFrequency) - ); + final int backupHour = SignalStore.settings().getBackupHour(); + final int backupMinute = SignalStore.settings().getBackupMinute(); + LocalTime time = LocalTime.of(backupHour, backupMinute); + timeLabel.setText(JavaTimeExtensionsKt.formatHours(time, requireContext())); + } + + private void updateDateLabel() { + frequencyLabel.setText(getResources().getString(SignalStore.settings().getBackupFrequency().getResourceId())); } private void setBackupsEnabled() { 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 d6a016bcf02..1e147bf84df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.jobs.LocalBackupJob; import org.thoughtcrime.securesms.keyvalue.SettingsValues; import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.preferences.BackupFrequencyV1; import org.thoughtcrime.securesms.util.JavaTimeExtensionsKt; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -31,7 +32,7 @@ protected long getNextScheduledExecutionTime(Context context) { @Override protected long onAlarm(Context context, long scheduledTime) { - if (SignalStore.settings().isBackupEnabled()) { + if (SignalStore.settings().isBackupEnabled() && SignalStore.settings().getBackupFrequency() != BackupFrequencyV1.NEVER) { LocalBackupJob.enqueue(false); } @@ -39,19 +40,25 @@ protected long onAlarm(Context context, long scheduledTime) { } public static void schedule(Context context) { - if (SignalStore.settings().isBackupEnabled()) { + if (SignalStore.settings().isBackupEnabled() && SignalStore.settings().getBackupFrequency() != BackupFrequencyV1.NEVER) { new LocalBackupListener().onReceive(context, getScheduleIntent()); } } public static long setNextBackupTimeToIntervalFromNow(@NonNull Context context) { - LocalDateTime now = LocalDateTime.now(); - int freq = SignalStore.settings().getBackupFrequency(); - int hour = SignalStore.settings().getBackupHour(); - int minute = SignalStore.settings().getBackupMinute(); - LocalDateTime next = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, hour, minute, BACKUP_JITTER_WINDOW_SECONDS, new Random()); + BackupFrequencyV1 freq = SignalStore.settings().getBackupFrequency(); - next = next.plusDays(freq); + if (freq == BackupFrequencyV1.NEVER) { + TextSecurePreferences.setNextBackupTime(context, -1); + return -1; + } + + LocalDateTime now = LocalDateTime.now(); + int hour = SignalStore.settings().getBackupHour(); + int minute = SignalStore.settings().getBackupMinute(); + LocalDateTime next = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, hour, minute, BACKUP_JITTER_WINDOW_SECONDS, new Random()); + + next = next.plusDays(freq.getDays()); long nextTime = JavaTimeExtensionsKt.toMillis(next); TextSecurePreferences.setNextBackupTime(context, nextTime); diff --git a/app/src/main/res/layout/fragment_backups.xml b/app/src/main/res/layout/fragment_backups.xml index d3a076875b5..8e8046f5eff 100644 --- a/app/src/main/res/layout/fragment_backups.xml +++ b/app/src/main/res/layout/fragment_backups.xml @@ -148,6 +148,36 @@ tools:text="3:00" /> + + + + + + + Backup folder Backup time + + Backup frequency Verify backup passphrase Test your backup passphrase and verify that it matches Turn on @@ -908,14 +910,16 @@ Signal requires external storage permission in order to create backups, but it has been permanently denied. Please continue to app settings, select \"Permissions\" and enable \"Storage\". Set backup time - %1$s every day - - %1$s every %2$d days - + + Daily + Weekly + Monthly + Quarterly + Never - Enter frequency (days) + Enter frequency OK Cancel From 382496115838dd857384b70f024b995b8faf2f81 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Mon, 17 Feb 2025 12:14:47 -0800 Subject: [PATCH 17/24] Do not show frequency setting when backups are disabled Also formatting --- .../preferences/BackupFrequencyPickerDialogFragment.kt | 5 ++++- .../securesms/preferences/BackupFrequencyV1.kt | 4 ++-- .../preferences/BackupsPreferenceFragment.java | 10 ++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt index ba3e87bac77..b30fdfb187d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt @@ -15,7 +15,10 @@ class BackupFrequencyPickerDialogFragment(private val defaultFrequency: BackupFr override fun onCreateDialog(savedInstance: Bundle?): Dialog { val context = requireContext() - val localizedFrequencyOptions = frequencyOptions.map { context.getString(it.getResourceId()) }.toTypedArray() + val localizedFrequencyOptions = frequencyOptions + .map { it.getResourceId() } + .map { context.getString(it) } + .toTypedArray() val defaultIndex = frequencyOptions.indexOf(defaultFrequency) return MaterialAlertDialogBuilder(context) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt index 8407f16f10f..6ad2c1fecd9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt @@ -8,7 +8,7 @@ package org.thoughtcrime.securesms.preferences import androidx.annotation.StringRes import org.thoughtcrime.securesms.R -enum class BackupFrequencyV1(val days: Int?) { +enum class BackupFrequencyV1(val days: Int) { DAILY(1), WEEKLY(7), MONTHLY(30), @@ -25,4 +25,4 @@ enum class BackupFrequencyV1(val days: Int?) { NEVER -> R.string.BackupsPreferenceFragment__frequency_label_never } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 35e3017b96b..45212e42635 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -57,10 +57,10 @@ public class BackupsPreferenceFragment extends Fragment { private View create; private View folder; private View verify; - private View timer; - private View frequencyView; - private TextView timeLabel; - private TextView frequencyLabel; + private View timer; + private View frequencyView; + private TextView timeLabel; + private TextView frequencyLabel; private TextView toggle; private TextView info; private TextView summary; @@ -344,6 +344,7 @@ private void setBackupsEnabled() { create.setVisibility(View.VISIBLE); verify.setVisibility(View.VISIBLE); timer.setVisibility(View.VISIBLE); + frequencyView.setVisibility(View.VISIBLE); updateTimeLabel(); setBackupFolderName(); } @@ -354,6 +355,7 @@ private void setBackupsDisabled() { folder.setVisibility(View.GONE); verify.setVisibility(View.GONE); timer.setVisibility(View.GONE); + frequencyView.setVisibility(View.GONE); AppDependencies.getJobManager().cancelAllInQueue(LocalBackupJob.QUEUE); } } From ee4c713bf6f8fad483c09f6d8dc8658a53119c6f Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Mon, 17 Feb 2025 12:17:24 -0800 Subject: [PATCH 18/24] More formatting :( --- .../preferences/BackupFrequencyPickerDialogFragment.kt | 1 - .../org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt index b30fdfb187d..b63fbbb5a25 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt @@ -5,7 +5,6 @@ import android.content.DialogInterface.OnClickListener import android.os.Bundle import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder - import org.thoughtcrime.securesms.R class BackupFrequencyPickerDialogFragment(private val defaultFrequency: BackupFrequencyV1) : DialogFragment() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt index 6ad2c1fecd9..d369f531096 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyV1.kt @@ -16,7 +16,7 @@ enum class BackupFrequencyV1(val days: Int) { NEVER(999); @StringRes - fun getResourceId(): Int { + fun getResourceId(): Int { return when (this) { DAILY -> R.string.BackupsPreferenceFragment__frequency_label_daily WEEKLY -> R.string.BackupsPreferenceFragment__frequency_label_weekly From 54ed0b2e96cef8c4dba4c6ac84ff504b666a364d Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Tue, 22 Apr 2025 02:31:12 -0700 Subject: [PATCH 19/24] Add date label when backups are known to be enabled --- .../securesms/preferences/BackupsPreferenceFragment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 45212e42635..a66aac44076 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -346,6 +346,7 @@ private void setBackupsEnabled() { timer.setVisibility(View.VISIBLE); frequencyView.setVisibility(View.VISIBLE); updateTimeLabel(); + updateDateLabel(); setBackupFolderName(); } From 76a86f880e3d007353a5fd4f224625b3ed787ac5 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Mon, 28 Apr 2025 02:23:11 -0700 Subject: [PATCH 20/24] Rename backup frequency picker for consistency with backup time picker --- .../preferences/BackupFrequencyPickerDialogFragment.kt | 2 +- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt index b63fbbb5a25..c142f1744f2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupFrequencyPickerDialogFragment.kt @@ -24,7 +24,7 @@ class BackupFrequencyPickerDialogFragment(private val defaultFrequency: BackupFr .setSingleChoiceItems(localizedFrequencyOptions, defaultIndex) { _, selectedIndex -> selectedFrequency = frequencyOptions[selectedIndex] } - .setTitle(R.string.BackupFrequencyPickerDialogFragment__enter_frequency) + .setTitle(R.string.BackupFrequencyPickerDialogFragment__set_backup_frequency) .setPositiveButton(R.string.BackupFrequencyPickerDialogFragment__ok, callback) .setNegativeButton(R.string.BackupFrequencyPickerDialogFragment__cancel, null) .create() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 420583f2c1b..9bc972840c6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -942,7 +942,7 @@ - Enter frequency + Set backup frequency OK Cancel From e077943b258009257fed082db1c8ddfb26a5ac08 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Thu, 8 May 2025 04:45:49 -0700 Subject: [PATCH 21/24] Cancel system alarm for backups when disabling backup frequency This saves battery life by not requiring Android to wake up just to go back to sleep after realising that backup frequency is disabled. --- .../preferences/BackupsPreferenceFragment.java | 4 +++- .../securesms/service/LocalBackupListener.java | 5 +++++ .../service/PersistentAlarmManagerListener.java | 10 ++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index a66aac44076..6a2d0335820 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -301,7 +301,9 @@ private void pickFrequency() { private void handleNewBackupScheduleSetting(BackupFrequencyV1 frequency, int hour, int minute) { Log.i(TAG, "Setting backup schedule: " + frequency.name() + " at" + hour + "h" + minute + "m"); SignalStore.settings().setBackupSchedule(frequency, hour, minute); - if (frequency != BackupFrequencyV1.NEVER) { + if (frequency == BackupFrequencyV1.NEVER) { + LocalBackupListener.cancelScheduled(requireContext()); + } else { // Schedule the next backup using the newly set frequency, but relative to the time of the // last backup. This should only kick off a new backup to be created immediately if the // last backup was long enough ago (or doesn't exist at all). 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 1e147bf84df..2c8542e38b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java @@ -45,6 +45,11 @@ public static void schedule(Context context) { } } + /** If there is a backup scheduled in the future via the AlarmManager, cancel it. */ + public static void cancelScheduled(Context context) { + new LocalBackupListener().cancel(context); + } + public static long setNextBackupTimeToIntervalFromNow(@NonNull Context context) { BackupFrequencyV1 freq = SignalStore.settings().getBackupFrequency(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java index c430089a382..09993cbbd02 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java @@ -28,6 +28,15 @@ public abstract class PersistentAlarmManagerListener extends BroadcastReceiver { protected abstract long onAlarm(Context context, long scheduledTime); + public void cancel(Context context) { + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent alarmIntent = new Intent(context, getClass()); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntentFlags.immutable()); + + info("Cancelling alarm"); + alarmManager.cancel(pendingIntent); + } + @Override public void onReceive(Context context, Intent intent) { info(String.format("onReceive(%s)", intent.getAction())); @@ -47,6 +56,7 @@ public void onReceive(Context context, Intent intent) { return; } + // If we've already scheduled this alarm, cancel it so we can schedule it again with the new time. alarmManager.cancel(pendingIntent); if (shouldScheduleExact()) { From ba49dc4ff9fe47cd409b909251a5c4eca58a2e75 Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Thu, 8 May 2025 05:05:32 -0700 Subject: [PATCH 22/24] Cancel ongoing backup jobs when the backup frequency is set to never --- .../org/thoughtcrime/securesms/jobs/LocalBackupJob.java | 6 ++++++ .../securesms/preferences/BackupsPreferenceFragment.java | 2 +- .../thoughtcrime/securesms/service/LocalBackupListener.java | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java index 6e64a2a7260..967a8b0cbde 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java @@ -75,6 +75,12 @@ public static void enqueueArchive() { jobManager.add(new LocalArchiveJob(parameters.build())); } + /** Sends a cancellation signal to all ongoing backup jobs. */ + public static void cancelRunningJobs() { + JobManager jobManager = AppDependencies.getJobManager(); + jobManager.cancelAllInQueue(QUEUE); + } + private LocalBackupJob(@NonNull Job.Parameters parameters) { super(parameters); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 6a2d0335820..05af63d8f10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -302,7 +302,7 @@ private void handleNewBackupScheduleSetting(BackupFrequencyV1 frequency, int hou Log.i(TAG, "Setting backup schedule: " + frequency.name() + " at" + hour + "h" + minute + "m"); SignalStore.settings().setBackupSchedule(frequency, hour, minute); if (frequency == BackupFrequencyV1.NEVER) { - LocalBackupListener.cancelScheduled(requireContext()); + LocalBackupListener.unschedule(requireContext()); } else { // Schedule the next backup using the newly set frequency, but relative to the time of the // last backup. This should only kick off a new backup to be created immediately if the 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 2c8542e38b1..cbec6839ae6 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 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.preferences.BackupFrequencyV1; import org.thoughtcrime.securesms.util.JavaTimeExtensionsKt; @@ -45,9 +44,10 @@ public static void schedule(Context context) { } } - /** If there is a backup scheduled in the future via the AlarmManager, cancel it. */ - public static void cancelScheduled(Context context) { + /** Cancels any future backup scheduled with AlarmManager and attempts to cancel any ongoing backup job. */ + public static void unschedule(Context context) { new LocalBackupListener().cancel(context); + LocalBackupJob.cancelRunningJobs(); } public static long setNextBackupTimeToIntervalFromNow(@NonNull Context context) { From 2990158404155550f4aac74c7c6be1ce377b14aa Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Thu, 8 May 2025 13:39:41 -0700 Subject: [PATCH 23/24] Remove inaccurate/obsolete comment --- .../securesms/preferences/BackupsPreferenceFragment.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 05af63d8f10..0c1fcc7223e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -283,7 +283,6 @@ private void pickTime() { } private void pickFrequency() { - // User should select the backup frequency first, and then the time of day to do the backups. final BackupFrequencyPickerDialogFragment frequencyPickerDialogFragment = new BackupFrequencyPickerDialogFragment(SignalStore.settings().getBackupFrequency()); frequencyPickerDialogFragment.setOnPositiveButtonClickListener((unused1, unused2) -> { BackupFrequencyV1 frequency = frequencyPickerDialogFragment.getValue(); From 357ac9cfcf8a6e94ffe15612d30adcfb5c29d12b Mon Sep 17 00:00:00 2001 From: farewelltospring Date: Thu, 8 May 2025 13:40:45 -0700 Subject: [PATCH 24/24] Rename function to apply new backup schedule setting for accuracy --- .../securesms/preferences/BackupsPreferenceFragment.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 0c1fcc7223e..52bb98003b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -276,7 +276,7 @@ private void pickTime() { BackupFrequencyV1 frequency = SignalStore.settings().getBackupFrequency(); int hour = timePickerFragment.getHour(); int minute = timePickerFragment.getMinute(); - handleNewBackupScheduleSetting(frequency, hour, minute); + applyNewBackupScheduleSetting(frequency, hour, minute); updateTimeLabel(); }); timePickerFragment.show(getChildFragmentManager(), "TIME_PICKER"); @@ -288,7 +288,7 @@ private void pickFrequency() { BackupFrequencyV1 frequency = frequencyPickerDialogFragment.getValue(); int hour = SignalStore.settings().getBackupHour(); int minute = SignalStore.settings().getBackupMinute(); - handleNewBackupScheduleSetting(frequency, hour, minute); + applyNewBackupScheduleSetting(frequency, hour, minute); updateDateLabel(); }); frequencyPickerDialogFragment.show(getChildFragmentManager(), "FREQUENCY_PICKER"); @@ -297,7 +297,7 @@ private void pickFrequency() { /** Update the settings on disk and then schedule a backup. * *

This method should be called when the user presses the buttons to set a new backup schedule with the given parameters. */ - private void handleNewBackupScheduleSetting(BackupFrequencyV1 frequency, int hour, int minute) { + private void applyNewBackupScheduleSetting(BackupFrequencyV1 frequency, int hour, int minute) { Log.i(TAG, "Setting backup schedule: " + frequency.name() + " at" + hour + "h" + minute + "m"); SignalStore.settings().setBackupSchedule(frequency, hour, minute); if (frequency == BackupFrequencyV1.NEVER) {