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}