Implement new workflow for scoped storage backup selection.

This commit is contained in:
Alex Hart
2020-10-15 16:12:53 -03:00
committed by Greyson Parrelli
parent 9a1c869efe
commit ee3d7a9a35
39 changed files with 1582 additions and 280 deletions

View File

@@ -0,0 +1,259 @@
package org.thoughtcrime.securesms.preferences;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.text.HtmlCompat;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.Fragment;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.backup.BackupDialog;
import org.thoughtcrime.securesms.backup.FullBackupBase;
import org.thoughtcrime.securesms.database.NoExternalStorageException;
import org.thoughtcrime.securesms.jobs.LocalBackupJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.BackupUtil;
import org.thoughtcrime.securesms.util.StorageUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.Locale;
import java.util.Objects;
public class BackupsPreferenceFragment extends Fragment {
private static final String TAG = Log.tag(BackupsPreferenceFragment.class);
private static final short CHOOSE_BACKUPS_LOCATION_REQUEST_CODE = 26212;
private View create;
private View folder;
private View verify;
private TextView toggle;
private TextView info;
private TextView summary;
private TextView folderName;
private ProgressBar progress;
private TextView progressSummary;
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_backups, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
create = view.findViewById(R.id.fragment_backup_create);
folder = view.findViewById(R.id.fragment_backup_folder);
verify = view.findViewById(R.id.fragment_backup_verify);
toggle = view.findViewById(R.id.fragment_backup_toggle);
info = view.findViewById(R.id.fragment_backup_info);
summary = view.findViewById(R.id.fragment_backup_create_summary);
folderName = view.findViewById(R.id.fragment_backup_folder_name);
progress = view.findViewById(R.id.fragment_backup_progress);
progressSummary = view.findViewById(R.id.fragment_backup_progress_summary);
toggle.setOnClickListener(unused -> onToggleClicked());
create.setOnClickListener(unused -> onCreateClicked());
verify.setOnClickListener(unused -> BackupDialog.showVerifyBackupPassphraseDialog(requireContext()));
EventBus.getDefault().register(this);
}
@SuppressWarnings("ConstantConditions")
@Override
public void onResume() {
super.onResume();
((AppCompatActivity) getActivity()).getSupportActionBar().setTitle(R.string.BackupsPreferenceFragment__chat_backups);
setBackupStatus();
setBackupSummary();
setInfo();
}
@Override
public void onDestroyView() {
super.onDestroyView();
EventBus.getDefault().unregister(this);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == CHOOSE_BACKUPS_LOCATION_REQUEST_CODE && resultCode == Activity.RESULT_OK && data != null && data.getData() != null) {
DocumentFile backupDirectory = DocumentFile.fromTreeUri(requireContext(), data.getData());
if (backupDirectory == null || !backupDirectory.isDirectory()) {
Log.w(TAG, "Could not open backup directory.");
return;
}
BackupDialog.showEnableBackupDialog(requireContext(),
data,
backupDirectory.getName(),
this::setBackupsEnabled);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(FullBackupBase.BackupEvent event) {
if (event.getType() == FullBackupBase.BackupEvent.Type.PROGRESS) {
create.setEnabled(false);
summary.setText(getString(R.string.BackupsPreferenceFragment__in_progress));
progress.setVisibility(View.VISIBLE);
progressSummary.setVisibility(View.VISIBLE);
progressSummary.setText(getString(R.string.BackupsPreferenceFragment__d_so_far, event.getCount()));
} else if (event.getType() == FullBackupBase.BackupEvent.Type.FINISHED) {
create.setEnabled(true);
progress.setVisibility(View.GONE);
progressSummary.setVisibility(View.GONE);
setBackupSummary();
}
}
private void setBackupStatus() {
if (TextSecurePreferences.isBackupEnabled(requireContext())) {
if (BackupUtil.canUserAccessBackupDirectory(requireContext())) {
setBackupsEnabled();
} else {
Log.w(TAG, "Cannot access backup directory. Disabling backups.");
BackupUtil.disableBackups(requireContext());
setBackupsDisabled();
}
} else {
setBackupsDisabled();
}
}
private void setBackupSummary() {
summary.setText(getString(R.string.BackupsPreferenceFragment__last_backup, BackupUtil.getLastBackupTime(requireContext(), Locale.getDefault())));
}
private void setBackupFolderName() {
folder.setVisibility(View.GONE);
if (BackupUtil.canUserAccessBackupDirectory(requireContext())) {
if (BackupUtil.isUserSelectionRequired(requireContext()) &&
BackupUtil.canUserAccessBackupDirectory(requireContext()))
{
Uri backupUri = Objects.requireNonNull(SignalStore.settings().getSignalBackupDirectory());
DocumentFile backupFile = Objects.requireNonNull(DocumentFile.fromTreeUri(requireContext(), backupUri));
if (backupFile.getName() != null) {
folder.setVisibility(View.VISIBLE);
folderName.setText(backupFile.getName());
}
} else if (StorageUtil.canWriteInSignalStorageDir()) {
try {
folder.setVisibility(View.VISIBLE);
folderName.setText(StorageUtil.getBackupDirectory().getPath());
} catch (NoExternalStorageException e) {
Log.w(TAG, "Could not display folder name.", e);
}
}
}
}
private void setInfo() {
String link = String.format("<a href=\"%s\">%s</a>", getString(R.string.backup_support_url), getString(R.string.BackupsPreferenceFragment__learn_more));
String infoText = getString(R.string.BackupsPreferenceFragment__to_restore_a_backup, link);
info.setText(HtmlCompat.fromHtml(infoText, 0));
info.setMovementMethod(LinkMovementMethod.getInstance());
}
private void onToggleClicked() {
if (BackupUtil.isUserSelectionRequired(requireContext())) {
onToggleClickedApi29();
} else {
onToggleClickedLegacy();
}
}
@RequiresApi(29)
private void onToggleClickedApi29() {
if (!TextSecurePreferences.isBackupEnabled(requireContext())) {
BackupDialog.showChooseBackupLocationDialog(this, CHOOSE_BACKUPS_LOCATION_REQUEST_CODE);
} else {
BackupDialog.showDisableBackupDialog(requireContext(), this::setBackupsDisabled);
}
}
private void onToggleClickedLegacy() {
Permissions.with(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.ifNecessary()
.onAllGranted(() -> {
if (!TextSecurePreferences.isBackupEnabled(requireContext())) {
BackupDialog.showEnableBackupDialog(requireContext(), null, null, this::setBackupsEnabled);
} else {
BackupDialog.showDisableBackupDialog(requireContext(), this::setBackupsDisabled);
}
})
.withPermanentDenialDialog(getString(R.string.BackupsPreferenceFragment_signal_requires_external_storage_permission_in_order_to_create_backups))
.execute();
}
private void onCreateClicked() {
if (BackupUtil.isUserSelectionRequired(requireContext())) {
onCreateClickedApi29();
} else {
onCreateClickedLegacy();
}
}
@RequiresApi(29)
private void onCreateClickedApi29() {
Log.i(TAG, "Queing backup...");
LocalBackupJob.enqueue(true);
}
private void onCreateClickedLegacy() {
Permissions.with(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.ifNecessary()
.onAllGranted(() -> {
Log.i(TAG, "Queuing backup...");
LocalBackupJob.enqueue(true);
})
.withPermanentDenialDialog(getString(R.string.BackupsPreferenceFragment_signal_requires_external_storage_permission_in_order_to_create_backups))
.execute();
}
private void setBackupsEnabled() {
toggle.setText(R.string.BackupsPreferenceFragment__turn_off);
create.setVisibility(View.VISIBLE);
verify.setVisibility(View.VISIBLE);
setBackupFolderName();
}
private void setBackupsDisabled() {
toggle.setText(R.string.BackupsPreferenceFragment__turn_on);
create.setVisibility(View.GONE);
folder.setVisibility(View.GONE);
verify.setVisibility(View.GONE);
}
}

View File

@@ -1,33 +1,25 @@
package org.thoughtcrime.securesms.preferences;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityOptionsCompat;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.backup.BackupDialog;
import org.thoughtcrime.securesms.backup.FullBackupBase.BackupEvent;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.jobs.LocalBackupJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.preferences.widgets.ProgressPreference;
import org.thoughtcrime.securesms.util.BackupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
@@ -46,16 +38,12 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
findPreference(TextSecurePreferences.MESSAGE_BODY_TEXT_SIZE_PREF)
.setOnPreferenceChangeListener(new ListSummaryListener());
findPreference(TextSecurePreferences.BACKUP_ENABLED)
.setOnPreferenceClickListener(new BackupClickListener());
findPreference(TextSecurePreferences.BACKUP_NOW)
.setOnPreferenceClickListener(new BackupCreateListener());
findPreference(TextSecurePreferences.BACKUP_PASSPHRASE_VERIFY)
.setOnPreferenceClickListener(new BackupVerifyListener());
findPreference(TextSecurePreferences.BACKUP).setOnPreferenceClickListener(unused -> {
goToBackupsPreferenceFragment();
return true;
});
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.MESSAGE_BODY_TEXT_SIZE_PREF));
EventBus.getDefault().register(this);
}
@Override
@@ -68,7 +56,6 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
super.onResume();
((ApplicationPreferencesActivity)getActivity()).getSupportActionBar().setTitle(R.string.preferences__chats);
setMediaDownloadSummaries();
setBackupSummary();
}
@Override
@@ -82,24 +69,8 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(BackupEvent event) {
ProgressPreference preference = (ProgressPreference)findPreference(TextSecurePreferences.BACKUP_NOW);
if (event.getType() == BackupEvent.Type.PROGRESS) {
preference.setEnabled(false);
preference.setSummary(getString(R.string.ChatsPreferenceFragment_in_progress));
preference.setProgress(event.getCount());
} else if (event.getType() == BackupEvent.Type.FINISHED) {
preference.setEnabled(true);
preference.setProgressVisible(false);
setBackupSummary();
}
}
private void setBackupSummary() {
findPreference(TextSecurePreferences.BACKUP_NOW)
.setSummary(String.format(getString(R.string.ChatsPreferenceFragment_last_backup_s), BackupUtil.getLastBackupTime(getContext(), Locale.getDefault())));
private void goToBackupsPreferenceFragment() {
((ApplicationPreferencesActivity) requireActivity()).pushFragment(new BackupsPreferenceFragment());
}
private void setMediaDownloadSummaries() {
@@ -124,51 +95,6 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
: TextUtils.join(", ", outValues);
}
private class BackupClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
Permissions.with(ChatsPreferenceFragment.this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.ifNecessary()
.onAllGranted(() -> {
if (!((SwitchPreferenceCompat)preference).isChecked()) {
BackupDialog.showEnableBackupDialog(getActivity(), (SwitchPreferenceCompat)preference);
} else {
BackupDialog.showDisableBackupDialog(getActivity(), (SwitchPreferenceCompat)preference);
}
})
.withPermanentDenialDialog(getString(R.string.ChatsPreferenceFragment_signal_requires_external_storage_permission_in_order_to_create_backups))
.execute();
return true;
}
}
private class BackupCreateListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
Permissions.with(ChatsPreferenceFragment.this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.ifNecessary()
.onAllGranted(() -> {
Log.i(TAG, "Starting backup from user");
LocalBackupJob.enqueue(true);
})
.withPermanentDenialDialog(getString(R.string.ChatsPreferenceFragment_signal_requires_external_storage_permission_in_order_to_create_backups))
.execute();
return true;
}
}
private class BackupVerifyListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
BackupDialog.showVerifyBackupPassphraseDialog(requireContext());
return true;
}
}
private class MediaDownloadChangeListener implements Preference.OnPreferenceChangeListener {
@SuppressWarnings("unchecked")
@Override public boolean onPreferenceChange(Preference preference, Object newValue) {