Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setting for users to determine frequency of backup creation #13199

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e4a1e69
Add backup frequency as a setting that can be set
May 10, 2023
e85ebb4
Show dialog to select backup frequency
May 10, 2023
79c0f26
todo commit
May 10, 2023
dd99598
Use frequency setting to schedule future backups
May 12, 2023
bb1be2c
Merge branch 'main' into slowbackups
May 12, 2023
9aa3d05
Pre-select backup frequency choice with user's current setting
May 12, 2023
dbb9390
Rename backup frequency picker dialog class for accuracy
Jul 30, 2023
8a01eb1
Merge branch 'main' into slowbackups
Jul 30, 2023
9c4d5d3
Show backup frequency in app settings
Jul 30, 2023
4b19f99
Improve backup frequency picker buttons
Jul 30, 2023
3031c6c
Minor cleanups to backup frequency stuff
Jul 30, 2023
92a448f
Merge branch 'main' into slowbackups
Oct 1, 2023
1745027
Add docstring for the unit of next backup time
Oct 2, 2023
5bea670
Schedule next backup relative to last backup when changing frequency
Oct 2, 2023
515a5b4
Merge branch 'main' into slowbackups
Dec 3, 2023
509c14e
Convert raw strings to resources in backup settings
Dec 3, 2023
05e82fc
Add newline to end of file
Dec 3, 2023
d57b50a
Convert string to plurals for non-English language
Dec 4, 2023
9309712
Merge branch 'main' into slowbackups
Jan 3, 2024
28f72a4
Merge branch 'main' into slowbackups
Jan 11, 2024
194df22
Merge branch 'main' into slowbackups
Mar 12, 2024
18a2bc1
Merge branch 'main' into slowbackups
Apr 15, 2024
8eba4bd
Merge branch 'main' into slowbackups
Jun 25, 2024
4032a70
Fix slow backups (farewelltospring's version)
Jun 25, 2024
175ad4d
Merge branch 'main' into slowbackups
Aug 27, 2024
db926b0
Merge branch 'main' into slowbackups
Oct 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,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";
Expand Down Expand Up @@ -73,8 +74,9 @@ 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_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<String> onConfigurationSettingChanged = new SingleLiveEvent<>();

Expand Down Expand Up @@ -106,7 +108,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);
}
}

Expand Down Expand Up @@ -307,6 +309,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);
}
Expand All @@ -315,7 +321,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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.thoughtcrime.securesms.preferences

import android.app.Dialog
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: 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
this.index = defaultIndex
return MaterialAlertDialogBuilder(requireContext())
.setSingleChoiceItems(this.dayOptions, defaultIndex) { _, i -> this.index = i }
.setTitle(R.string.BackupFrequencyPickerDialogFragment__enter_frequency)
.setPositiveButton(R.string.BackupFrequencyPickerDialogFragment__ok, this.callback)
.setNegativeButton(R.string.BackupFrequencyPickerDialogFragment__cancel, null)
.create()
}

fun getValue(): Int = this.dayOptions[this.index].toInt()

fun setOnPositiveButtonClickListener(cb: OnClickListener) {
this.callback = cb
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

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;
Expand Down Expand Up @@ -46,6 +48,7 @@
import java.time.LocalTime;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;

public class BackupsPreferenceFragment extends Fragment {

Expand Down Expand Up @@ -259,22 +262,40 @@ 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 -> {
int hour = timePickerFragment.getHour();
int minute = timePickerFragment.getMinute();
SignalStore.settings().setBackupSchedule(hour, minute);
updateTimeLabel();
TextSecurePreferences.setNextBackupTime(requireContext(), 0);
LocalBackupListener.schedule(requireContext());
// 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");
});
timePickerFragment.show(getChildFragmentManager(), "TIME_PICKER");

frequencyPickerDialogFragment.show(getChildFragmentManager(), "FREQUENCY_PICKER");
}

private void onCreateClickedLegacy() {
Expand All @@ -290,10 +311,17 @@ 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);

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)
);
}

private void setBackupsEnabled() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import androidx.annotation.NonNull;

import org.thoughtcrime.securesms.jobs.LocalBackupJob;
import org.thoughtcrime.securesms.keyvalue.SettingsValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.JavaTimeExtensionsKt;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
Expand Down Expand Up @@ -45,10 +46,12 @@ 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 = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, hour, minute, BACKUP_JITTER_WINDOW_SECONDS, new Random());

next = next.plusDays(freq);
long nextTime = JavaTimeExtensionsKt.toMillis(next);

TextSecurePreferences.setNextBackupTime(context, nextTime);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,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";

Expand Down
9 changes: 9 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,16 @@
<string name="BackupsPreferenceFragment_signal_requires_external_storage_permission_in_order_to_create_backups">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\".</string>
<!-- Title of dialog shown when picking the time to perform a chat backup -->
<string name="BackupsPreferenceFragment__set_backup_time">Set backup time</string>
<string name="BackupsPreferenceFragment__time_label_daily">%1$s every day</string>
<plurals name="BackupsPreferenceFragment__time_label_n_days">
<item quantity="other">%1$s every %2$d days</item>
</plurals>


<!-- BackupFrequencyPickerDialogFragment -->
<string name="BackupFrequencyPickerDialogFragment__enter_frequency">Enter frequency (days)</string>
<string name="BackupFrequencyPickerDialogFragment__ok">OK</string>
<string name="BackupFrequencyPickerDialogFragment__cancel">Cancel</string>

<!-- CustomDefaultPreference -->
<string name="CustomDefaultPreference_using_custom">Using custom: %s</string>
Expand Down