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 var export = findPreference("export");
 64        final ListPreference recurringBackup = findPreference(RECURRING_BACKUP);
 65        final var backupDirectory = findPreference("backup_directory");
 66        if (createOneOffBackup == null || recurringBackup == null || backupDirectory == null) {
 67            throw new IllegalStateException(
 68                    "The preference resource file is missing some preferences");
 69        }
 70        backupDirectory.setSummary(
 71                getString(
 72                        R.string.pref_create_backup_summary,
 73                        FileBackend.getBackupDirectory(requireContext()).getAbsolutePath()));
 74        createOneOffBackup.setOnPreferenceClickListener(this::onBackupPreferenceClicked);
 75        export.setOnPreferenceClickListener(this::onExportClicked);
 76        setValues(
 77                recurringBackup,
 78                R.array.recurring_backup_values,
 79                value -> timeframeValueToName(requireContext(), value));
 80    }
 81
 82    @Override
 83    protected void onSharedPreferenceChanged(@NonNull String key) {
 84        super.onSharedPreferenceChanged(key);
 85        if (RECURRING_BACKUP.equals(key)) {
 86            final var sharedPreferences = getPreferenceManager().getSharedPreferences();
 87            if (sharedPreferences == null) {
 88                return;
 89            }
 90            final Long recurringBackupInterval =
 91                    Longs.tryParse(
 92                            Strings.nullToEmpty(
 93                                    sharedPreferences.getString(RECURRING_BACKUP, null)));
 94            if (recurringBackupInterval == null) {
 95                return;
 96            }
 97            Log.d(
 98                    Config.LOGTAG,
 99                    "recurring backup interval changed to: " + recurringBackupInterval);
100            final var workManager = WorkManager.getInstance(requireContext());
101            if (recurringBackupInterval <= 0) {
102                workManager.cancelUniqueWork(RECURRING_BACKUP);
103            } else {
104                final Constraints constraints =
105                        new Constraints.Builder()
106                                .setRequiresBatteryNotLow(true)
107                                .setRequiresStorageNotLow(true)
108                                .build();
109
110                final PeriodicWorkRequest periodicWorkRequest =
111                        new PeriodicWorkRequest.Builder(
112                                        ExportBackupWorker.class,
113                                        recurringBackupInterval,
114                                        TimeUnit.SECONDS)
115                                .setConstraints(constraints)
116                                .setInputData(
117                                        new Data.Builder()
118                                                .putBoolean("recurring_backup", true)
119                                                .build())
120                                .build();
121                workManager.enqueueUniquePeriodicWork(
122                        RECURRING_BACKUP, ExistingPeriodicWorkPolicy.UPDATE, periodicWorkRequest);
123            }
124        }
125    }
126
127    @Override
128    public void onStart() {
129        super.onStart();
130        requireActivity().setTitle(R.string.backup);
131    }
132
133    private boolean onBackupPreferenceClicked(final Preference preference) {
134        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
135            if (ContextCompat.checkSelfPermission(
136                            requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
137                    != PackageManager.PERMISSION_GRANTED) {
138                requestStorageForBackupLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
139            } else {
140                startOneOffBackup();
141            }
142        } else {
143            startOneOffBackup();
144        }
145        return true;
146    }
147
148    private void startOneOffBackup() {
149        final OneTimeWorkRequest exportBackupWorkRequest =
150                new OneTimeWorkRequest.Builder(ExportBackupWorker.class)
151                        .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
152                        .build();
153        WorkManager.getInstance(requireContext())
154                .enqueueUniqueWork(
155                        CREATE_ONE_OFF_BACKUP, ExistingWorkPolicy.KEEP, exportBackupWorkRequest);
156        final MaterialAlertDialogBuilder builder =
157                new MaterialAlertDialogBuilder(requireActivity());
158        builder.setMessage(R.string.backup_started_message);
159        builder.setPositiveButton(R.string.ok, null);
160        builder.create().show();
161    }
162
163    private boolean onExportClicked(final Preference preference) {
164        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
165            if (ContextCompat.checkSelfPermission(
166                            requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
167                    != PackageManager.PERMISSION_GRANTED) {
168                requestStorageForBackupLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
169            } else {
170                startExport();
171            }
172        } else {
173            startExport();
174        }
175        return true;
176    }
177
178    private void startExport() {
179        final OneTimeWorkRequest exportBackupWorkRequest =
180                new OneTimeWorkRequest.Builder(com.cheogram.android.ExportBackupService.class)
181                        .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
182                        .build();
183        WorkManager.getInstance(requireContext())
184                .enqueueUniqueWork(
185                        CREATE_ONE_OFF_BACKUP, ExistingWorkPolicy.KEEP, exportBackupWorkRequest);
186        final MaterialAlertDialogBuilder builder =
187                new MaterialAlertDialogBuilder(requireActivity());
188        builder.setMessage(R.string.backup_started_message);
189        builder.setPositiveButton(R.string.ok, null);
190        builder.create().show();
191    }
192}