BackupSettingsFragment.java

  1package eu.siacs.conversations.ui.fragment.settings;
  2
  3import android.Manifest;
  4import android.content.pm.PackageManager;
  5import android.os.Build;
  6import android.os.Bundle;
  7import android.util.Log;
  8import android.widget.Toast;
  9
 10import androidx.activity.result.ActivityResultLauncher;
 11import androidx.activity.result.contract.ActivityResultContracts;
 12import androidx.annotation.NonNull;
 13import androidx.annotation.Nullable;
 14import androidx.core.content.ContextCompat;
 15import androidx.preference.ListPreference;
 16import androidx.preference.Preference;
 17import androidx.work.Constraints;
 18import androidx.work.Data;
 19import androidx.work.ExistingPeriodicWorkPolicy;
 20import androidx.work.ExistingWorkPolicy;
 21import androidx.work.OneTimeWorkRequest;
 22import androidx.work.OutOfQuotaPolicy;
 23import androidx.work.PeriodicWorkRequest;
 24import androidx.work.WorkManager;
 25
 26import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 27import com.google.common.base.Strings;
 28import com.google.common.primitives.Longs;
 29
 30import eu.siacs.conversations.Config;
 31import eu.siacs.conversations.R;
 32import eu.siacs.conversations.persistance.FileBackend;
 33import eu.siacs.conversations.worker.ExportBackupWorker;
 34
 35import java.util.concurrent.TimeUnit;
 36
 37public class BackupSettingsFragment extends XmppPreferenceFragment {
 38
 39    public static final String CREATE_ONE_OFF_BACKUP = "create_one_off_backup";
 40    private static final String RECURRING_BACKUP = "recurring_backup";
 41
 42    private final ActivityResultLauncher<String> requestStorageForBackupLauncher =
 43            registerForActivityResult(
 44                    new ActivityResultContracts.RequestPermission(),
 45                    isGranted -> {
 46                        if (isGranted) {
 47                            startOneOffBackup();
 48                        } else {
 49                            Toast.makeText(
 50                                            requireActivity(),
 51                                            getString(
 52                                                    R.string.no_storage_permission,
 53                                                    getString(R.string.app_name)),
 54                                            Toast.LENGTH_LONG)
 55                                    .show();
 56                        }
 57                    });
 58
 59    @Override
 60    public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
 61        setPreferencesFromResource(R.xml.preferences_backup, rootKey);
 62        final var createOneOffBackup = findPreference(CREATE_ONE_OFF_BACKUP);
 63        final ListPreference recurringBackup = findPreference(RECURRING_BACKUP);
 64        final var backupDirectory = findPreference("backup_directory");
 65        if (createOneOffBackup == null || recurringBackup == null || backupDirectory == null) {
 66            throw new IllegalStateException(
 67                    "The preference resource file is missing some preferences");
 68        }
 69        backupDirectory.setSummary(
 70                getString(
 71                        R.string.pref_create_backup_summary,
 72                        FileBackend.getBackupDirectory(requireContext()).getAbsolutePath()));
 73        createOneOffBackup.setOnPreferenceClickListener(this::onBackupPreferenceClicked);
 74        final int[] choices = getResources().getIntArray(R.array.recurring_backup_values);
 75        final CharSequence[] entries = new CharSequence[choices.length];
 76        final CharSequence[] entryValues = new CharSequence[choices.length];
 77        for (int i = 0; i < choices.length; ++i) {
 78            entryValues[i] = String.valueOf(choices[i]);
 79            entries[i] = timeframeValueToName(requireContext(), choices[i]);
 80        }
 81        recurringBackup.setEntries(entries);
 82        recurringBackup.setEntryValues(entryValues);
 83        recurringBackup.setSummaryProvider(new TimeframeSummaryProvider());
 84    }
 85
 86    @Override
 87    protected void onSharedPreferenceChanged(@NonNull String key) {
 88        super.onSharedPreferenceChanged(key);
 89        if (RECURRING_BACKUP.equals(key)) {
 90            final var sharedPreferences = getPreferenceManager().getSharedPreferences();
 91            if (sharedPreferences == null) {
 92                return;
 93            }
 94            final Long recurringBackupInterval =
 95                    Longs.tryParse(
 96                            Strings.nullToEmpty(
 97                                    sharedPreferences.getString(RECURRING_BACKUP, null)));
 98            if (recurringBackupInterval == null) {
 99                return;
100            }
101            Log.d(
102                    Config.LOGTAG,
103                    "recurring backup interval changed to: " + recurringBackupInterval);
104            final var workManager = WorkManager.getInstance(requireContext());
105            if (recurringBackupInterval <= 0) {
106                workManager.cancelUniqueWork(RECURRING_BACKUP);
107            } else {
108                final Constraints constraints =
109                        new Constraints.Builder()
110                                .setRequiresBatteryNotLow(true)
111                                .setRequiresStorageNotLow(true)
112                                .build();
113
114                final PeriodicWorkRequest periodicWorkRequest =
115                        new PeriodicWorkRequest.Builder(
116                                        ExportBackupWorker.class,
117                                        recurringBackupInterval,
118                                        TimeUnit.SECONDS)
119                                .setConstraints(constraints)
120                                .setInputData(
121                                        new Data.Builder()
122                                                .putBoolean("recurring_backup", true)
123                                                .build())
124                                .build();
125                workManager.enqueueUniquePeriodicWork(
126                        RECURRING_BACKUP, ExistingPeriodicWorkPolicy.UPDATE, periodicWorkRequest);
127            }
128        }
129    }
130
131    @Override
132    public void onStart() {
133        super.onStart();
134        requireActivity().setTitle(R.string.backup);
135    }
136
137    private boolean onBackupPreferenceClicked(final Preference preference) {
138        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
139            if (ContextCompat.checkSelfPermission(
140                            requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
141                    != PackageManager.PERMISSION_GRANTED) {
142                requestStorageForBackupLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
143            } else {
144                startOneOffBackup();
145            }
146        } else {
147            startOneOffBackup();
148        }
149        return true;
150    }
151
152    private void startOneOffBackup() {
153        final OneTimeWorkRequest exportBackupWorkRequest =
154                new OneTimeWorkRequest.Builder(ExportBackupWorker.class)
155                        .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
156                        .build();
157        WorkManager.getInstance(requireContext())
158                .enqueueUniqueWork(
159                        CREATE_ONE_OFF_BACKUP, ExistingWorkPolicy.KEEP, exportBackupWorkRequest);
160        final MaterialAlertDialogBuilder builder =
161                new MaterialAlertDialogBuilder(requireActivity());
162        builder.setMessage(R.string.backup_started_message);
163        builder.setPositiveButton(R.string.ok, null);
164        builder.create().show();
165    }
166}