That fuck shit the fascists are using
1package org.tm.archive.preferences;
2
3import android.Manifest;
4import android.app.Activity;
5import android.content.Intent;
6import android.net.Uri;
7import android.os.Build;
8import android.os.Bundle;
9import android.text.format.DateFormat;
10import android.text.method.LinkMovementMethod;
11import android.view.LayoutInflater;
12import android.view.View;
13import android.view.ViewGroup;
14import android.widget.ProgressBar;
15import android.widget.TextView;
16
17import androidx.annotation.NonNull;
18import androidx.annotation.Nullable;
19import androidx.annotation.RequiresApi;
20import androidx.core.text.HtmlCompat;
21import androidx.fragment.app.Fragment;
22
23import com.google.android.material.timepicker.MaterialTimePicker;
24import com.google.android.material.timepicker.TimeFormat;
25
26import org.greenrobot.eventbus.EventBus;
27import org.greenrobot.eventbus.Subscribe;
28import org.greenrobot.eventbus.ThreadMode;
29import org.signal.core.util.ThreadUtil;
30import org.signal.core.util.logging.Log;
31import org.tm.archive.R;
32import org.tm.archive.backup.BackupDialog;
33import org.tm.archive.backup.BackupEvent;
34import org.tm.archive.database.NoExternalStorageException;
35import org.tm.archive.dependencies.ApplicationDependencies;
36import org.tm.archive.jobs.LocalBackupJob;
37import org.tm.archive.keyvalue.SignalStore;
38import org.tm.archive.permissions.Permissions;
39import org.tm.archive.service.LocalBackupListener;
40import org.tm.archive.util.BackupUtil;
41import org.tm.archive.util.JavaTimeExtensionsKt;
42import org.tm.archive.util.StorageUtil;
43import org.tm.archive.util.TextSecurePreferences;
44
45import java.text.NumberFormat;
46import java.time.LocalTime;
47import java.util.Locale;
48import java.util.Objects;
49
50public class BackupsPreferenceFragment extends Fragment {
51
52 private static final String TAG = Log.tag(BackupsPreferenceFragment.class);
53
54 private static final short CHOOSE_BACKUPS_LOCATION_REQUEST_CODE = 26212;
55
56 private View create;
57 private View folder;
58 private View verify;
59 private View timer;
60 private TextView timeLabel;
61 private TextView toggle;
62 private TextView info;
63 private TextView summary;
64 private TextView folderName;
65 private ProgressBar progress;
66 private TextView progressSummary;
67
68 private final NumberFormat formatter = NumberFormat.getInstance();
69
70 @Override
71 public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
72 return inflater.inflate(R.layout.fragment_backups, container, false);
73 }
74
75 @Override
76 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
77 create = view.findViewById(R.id.fragment_backup_create);
78 folder = view.findViewById(R.id.fragment_backup_folder);
79 verify = view.findViewById(R.id.fragment_backup_verify);
80 timer = view.findViewById(R.id.fragment_backup_time);
81 timeLabel = view.findViewById(R.id.fragment_backup_time_value);
82 toggle = view.findViewById(R.id.fragment_backup_toggle);
83 info = view.findViewById(R.id.fragment_backup_info);
84 summary = view.findViewById(R.id.fragment_backup_create_summary);
85 folderName = view.findViewById(R.id.fragment_backup_folder_name);
86 progress = view.findViewById(R.id.fragment_backup_progress);
87 progressSummary = view.findViewById(R.id.fragment_backup_progress_summary);
88
89 toggle.setOnClickListener(unused -> onToggleClicked());
90 create.setOnClickListener(unused -> onCreateClicked());
91 verify.setOnClickListener(unused -> BackupDialog.showVerifyBackupPassphraseDialog(requireContext()));
92 timer.setOnClickListener(unused -> pickTime());
93
94 formatter.setMinimumFractionDigits(1);
95 formatter.setMaximumFractionDigits(1);
96
97 EventBus.getDefault().register(this);
98 }
99
100 @Override
101 public void onResume() {
102 super.onResume();
103
104 setBackupStatus();
105 setBackupSummary();
106 setInfo();
107 }
108
109 @Override
110 public void onDestroyView() {
111 super.onDestroyView();
112 EventBus.getDefault().unregister(this);
113 }
114
115 @Override
116 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
117 Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
118 }
119
120 @Override
121 public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
122 if (Build.VERSION.SDK_INT >= 29 &&
123 requestCode == CHOOSE_BACKUPS_LOCATION_REQUEST_CODE &&
124 resultCode == Activity.RESULT_OK &&
125 data != null &&
126 data.getData() != null)
127 {
128 BackupDialog.showEnableBackupDialog(requireContext(),
129 data,
130 StorageUtil.getDisplayPath(requireContext(), data.getData()),
131 this::setBackupsEnabled);
132 } else {
133 Log.w(TAG, "Unknown activity result. code: " + requestCode + " resultCode: " + resultCode + " data present: " + (data != null));
134 }
135 }
136
137 @Subscribe(threadMode = ThreadMode.MAIN)
138 public void onEvent(BackupEvent event) {
139 if (event.getType() == BackupEvent.Type.PROGRESS || event.getType() == BackupEvent.Type.PROGRESS_VERIFYING) {
140 create.setEnabled(false);
141 summary.setText(getString(event.getType() == BackupEvent.Type.PROGRESS ? R.string.BackupsPreferenceFragment__in_progress
142 : R.string.BackupsPreferenceFragment__verifying_backup));
143 progress.setVisibility(View.VISIBLE);
144 progressSummary.setVisibility(event.getCount() > 0 ? View.VISIBLE : View.GONE);
145
146 if (event.getEstimatedTotalCount() == 0) {
147 progress.setIndeterminate(true);
148 progressSummary.setText(getString(R.string.BackupsPreferenceFragment__d_so_far, event.getCount()));
149 } else {
150 double completionPercentage = event.getCompletionPercentage();
151
152 progress.setIndeterminate(false);
153 progress.setMax(100);
154 progress.setProgress((int) completionPercentage);
155 progressSummary.setText(getString(R.string.BackupsPreferenceFragment__s_so_far, formatter.format(completionPercentage)));
156 }
157 } else if (event.getType() == BackupEvent.Type.FINISHED) {
158 create.setEnabled(true);
159 progress.setVisibility(View.GONE);
160 progressSummary.setVisibility(View.GONE);
161 setBackupSummary();
162 ThreadUtil.runOnMainDelayed(this::setBackupSummary, 100);
163 }
164 }
165
166 private void setBackupStatus() {
167 if (SignalStore.settings().isBackupEnabled()) {
168 if (BackupUtil.canUserAccessBackupDirectory(requireContext())) {
169 setBackupsEnabled();
170 } else {
171 Log.w(TAG, "Cannot access backup directory. Disabling backups.");
172
173 BackupUtil.disableBackups(requireContext());
174 setBackupsDisabled();
175 }
176 } else {
177 setBackupsDisabled();
178 }
179 }
180
181 private void setBackupSummary() {
182 summary.setText(getString(R.string.BackupsPreferenceFragment__last_backup, BackupUtil.getLastBackupTime(requireContext(), Locale.getDefault())));
183 }
184
185 private void setBackupFolderName() {
186 folder.setVisibility(View.GONE);
187
188 if (BackupUtil.canUserAccessBackupDirectory(requireContext())) {
189 if (BackupUtil.isUserSelectionRequired(requireContext()) &&
190 BackupUtil.canUserAccessBackupDirectory(requireContext()))
191 {
192 Uri backupUri = Objects.requireNonNull(SignalStore.settings().getSignalBackupDirectory());
193
194 folder.setVisibility(View.VISIBLE);
195 folderName.setText(StorageUtil.getDisplayPath(requireContext(), backupUri));
196 } else if (StorageUtil.canWriteInSignalStorageDir()) {
197 try {
198 folder.setVisibility(View.VISIBLE);
199 folderName.setText(StorageUtil.getOrCreateBackupDirectory().getPath());
200 } catch (NoExternalStorageException e) {
201 Log.w(TAG, "Could not display folder name.", e);
202 }
203 }
204 }
205 }
206
207 private void setInfo() {
208 String link = String.format("<a href=\"%s\">%s</a>", getString(R.string.backup_support_url), getString(R.string.BackupsPreferenceFragment__learn_more));
209 String infoText = getString(R.string.BackupsPreferenceFragment__to_restore_a_backup, link);
210
211 info.setText(HtmlCompat.fromHtml(infoText, 0));
212 info.setMovementMethod(LinkMovementMethod.getInstance());
213 }
214
215 private void onToggleClicked() {
216 if (BackupUtil.isUserSelectionRequired(requireContext())) {
217 onToggleClickedApi29();
218 } else {
219 onToggleClickedLegacy();
220 }
221 }
222
223 @RequiresApi(29)
224 private void onToggleClickedApi29() {
225 if (!SignalStore.settings().isBackupEnabled()) {
226 BackupDialog.showChooseBackupLocationDialog(this, CHOOSE_BACKUPS_LOCATION_REQUEST_CODE);
227 } else {
228 BackupDialog.showDisableBackupDialog(requireContext(), this::setBackupsDisabled);
229 }
230 }
231
232 private void onToggleClickedLegacy() {
233 Permissions.with(this)
234 .request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
235 .ifNecessary()
236 .onAllGranted(() -> {
237 if (!SignalStore.settings().isBackupEnabled()) {
238 BackupDialog.showEnableBackupDialog(requireContext(), null, null, this::setBackupsEnabled);
239 } else {
240 BackupDialog.showDisableBackupDialog(requireContext(), this::setBackupsDisabled);
241 }
242 })
243 .withPermanentDenialDialog(getString(R.string.BackupsPreferenceFragment_signal_requires_external_storage_permission_in_order_to_create_backups))
244 .execute();
245 }
246
247 private void onCreateClicked() {
248 if (BackupUtil.isUserSelectionRequired(requireContext())) {
249 onCreateClickedApi29();
250 } else {
251 onCreateClickedLegacy();
252 }
253 }
254
255 @RequiresApi(29)
256 private void onCreateClickedApi29() {
257 Log.i(TAG, "Queueing backup...");
258 LocalBackupJob.enqueue(true);
259 }
260
261 private void pickTime() {
262 int timeFormat = DateFormat.is24HourFormat(requireContext()) ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H;
263 final MaterialTimePicker timePickerFragment = new MaterialTimePicker.Builder()
264 .setTimeFormat(timeFormat)
265 .setHour(SignalStore.settings().getBackupHour())
266 .setMinute(SignalStore.settings().getBackupMinute())
267 .setTitleText(R.string.BackupsPreferenceFragment__set_backup_time)
268 .build();
269 timePickerFragment.addOnPositiveButtonClickListener(v -> {
270 int hour = timePickerFragment.getHour();
271 int minute = timePickerFragment.getMinute();
272 SignalStore.settings().setBackupSchedule(hour, minute);
273 updateTimeLabel();
274 TextSecurePreferences.setNextBackupTime(requireContext(), 0);
275 LocalBackupListener.schedule(requireContext());
276 });
277 timePickerFragment.show(getChildFragmentManager(), "TIME_PICKER");
278 }
279
280 private void onCreateClickedLegacy() {
281 Permissions.with(this)
282 .request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
283 .ifNecessary()
284 .onAllGranted(() -> {
285 Log.i(TAG, "Queuing backup...");
286 LocalBackupJob.enqueue(true);
287 })
288 .withPermanentDenialDialog(getString(R.string.BackupsPreferenceFragment_signal_requires_external_storage_permission_in_order_to_create_backups))
289 .execute();
290 }
291
292 private void updateTimeLabel() {
293 final int backupHour = SignalStore.settings().getBackupHour();
294 final int backupMinute = SignalStore.settings().getBackupMinute();
295 LocalTime time = LocalTime.of(backupHour, backupMinute);
296 timeLabel.setText(JavaTimeExtensionsKt.formatHours(time, requireContext()));
297 }
298
299 private void setBackupsEnabled() {
300 toggle.setText(R.string.BackupsPreferenceFragment__turn_off);
301 create.setVisibility(View.VISIBLE);
302 verify.setVisibility(View.VISIBLE);
303 timer.setVisibility(View.VISIBLE);
304 updateTimeLabel();
305 setBackupFolderName();
306 }
307
308 private void setBackupsDisabled() {
309 toggle.setText(R.string.BackupsPreferenceFragment__turn_on);
310 create.setVisibility(View.GONE);
311 folder.setVisibility(View.GONE);
312 verify.setVisibility(View.GONE);
313 timer.setVisibility(View.GONE);
314 ApplicationDependencies.getJobManager().cancelAllInQueue(LocalBackupJob.QUEUE);
315 }
316}