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}