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        setValues(
 75                recurringBackup,
 76                R.array.recurring_backup_values,
 77                value -> timeframeValueToName(requireContext(), value));
 78    }
 79
 80    @Override
 81    protected void onSharedPreferenceChanged(@NonNull String key) {
 82        super.onSharedPreferenceChanged(key);
 83        if (RECURRING_BACKUP.equals(key)) {
 84            final var sharedPreferences = getPreferenceManager().getSharedPreferences();
 85            if (sharedPreferences == null) {
 86                return;
 87            }
 88            final Long recurringBackupInterval =
 89                    Longs.tryParse(
 90                            Strings.nullToEmpty(
 91                                    sharedPreferences.getString(RECURRING_BACKUP, null)));
 92            if (recurringBackupInterval == null) {
 93                return;
 94            }
 95            Log.d(
 96                    Config.LOGTAG,
 97                    "recurring backup interval changed to: " + recurringBackupInterval);
 98            final var workManager = WorkManager.getInstance(requireContext());
 99            if (recurringBackupInterval <= 0) {
100                workManager.cancelUniqueWork(RECURRING_BACKUP);
101            } else {
102                final Constraints constraints =
103                        new Constraints.Builder()
104                                .setRequiresBatteryNotLow(true)
105                                .setRequiresStorageNotLow(true)
106                                .build();
107
108                final PeriodicWorkRequest periodicWorkRequest =
109                        new PeriodicWorkRequest.Builder(
110                                        ExportBackupWorker.class,
111                                        recurringBackupInterval,
112                                        TimeUnit.SECONDS)
113                                .setConstraints(constraints)
114                                .setInputData(
115                                        new Data.Builder()
116                                                .putBoolean("recurring_backup", true)
117                                                .build())
118                                .build();
119                workManager.enqueueUniquePeriodicWork(
120                        RECURRING_BACKUP, ExistingPeriodicWorkPolicy.UPDATE, periodicWorkRequest);
121            }
122        }
123    }
124
125    @Override
126    public void onStart() {
127        super.onStart();
128        requireActivity().setTitle(R.string.backup);
129    }
130
131    private boolean onBackupPreferenceClicked(final Preference preference) {
132        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
133            if (ContextCompat.checkSelfPermission(
134                            requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
135                    != PackageManager.PERMISSION_GRANTED) {
136                requestStorageForBackupLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
137            } else {
138                startOneOffBackup();
139            }
140        } else {
141            startOneOffBackup();
142        }
143        return true;
144    }
145
146    private void startOneOffBackup() {
147        final OneTimeWorkRequest exportBackupWorkRequest =
148                new OneTimeWorkRequest.Builder(ExportBackupWorker.class)
149                        .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
150                        .build();
151        WorkManager.getInstance(requireContext())
152                .enqueueUniqueWork(
153                        CREATE_ONE_OFF_BACKUP, ExistingWorkPolicy.KEEP, exportBackupWorkRequest);
154        final MaterialAlertDialogBuilder builder =
155                new MaterialAlertDialogBuilder(requireActivity());
156        builder.setMessage(R.string.backup_started_message);
157        builder.setPositiveButton(R.string.ok, null);
158        builder.create().show();
159    }
160}