Refactor backup activity to better work with background tasks

This commit is contained in:
Jakob Nixdorf 2021-02-01 20:28:34 +01:00
parent e1ced76b55
commit feeeba7294
No known key found for this signature in database
GPG key ID: BE99BF86574A7DBC
7 changed files with 417 additions and 408 deletions

View file

@ -36,11 +36,16 @@ import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewStub; import android.view.ViewStub;
import android.widget.LinearLayout; import android.widget.AdapterView;
import android.widget.Switch; import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.material.switchmaterial.SwitchMaterial;
import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi;
@ -48,6 +53,7 @@ import org.openintents.openpgp.util.OpenPgpServiceConnection;
import org.shadowice.flocke.andotp.Database.Entry; import org.shadowice.flocke.andotp.Database.Entry;
import org.shadowice.flocke.andotp.Dialogs.PasswordEntryDialog; import org.shadowice.flocke.andotp.Dialogs.PasswordEntryDialog;
import org.shadowice.flocke.andotp.R; import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Tasks.BackupTask;
import org.shadowice.flocke.andotp.Utilities.BackupHelper; import org.shadowice.flocke.andotp.Utilities.BackupHelper;
import org.shadowice.flocke.andotp.Utilities.Constants; import org.shadowice.flocke.andotp.Utilities.Constants;
import org.shadowice.flocke.andotp.Utilities.DatabaseHelper; import org.shadowice.flocke.andotp.Utilities.DatabaseHelper;
@ -67,9 +73,11 @@ import java.util.concurrent.Executors;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
public class BackupActivity extends BaseActivity { public class BackupActivity extends BaseActivity
implements BackupTask.BackupCallback {
private final static String TAG = BackupActivity.class.getSimpleName(); private final static String TAG = BackupActivity.class.getSimpleName();
private Constants.BackupType backupType = Constants.BackupType.ENCRYPTED;
private SecretKey encryptionKey = null; private SecretKey encryptionKey = null;
private OpenPgpServiceConnection pgpServiceConnection; private OpenPgpServiceConnection pgpServiceConnection;
@ -78,7 +86,14 @@ public class BackupActivity extends BaseActivity {
private Uri encryptTargetFile; private Uri encryptTargetFile;
private Uri decryptSourceFile; private Uri decryptSourceFile;
private Switch replace; private Button btnBackup;
private Button btnRestore;
private TextView txtBackupLabel;
private TextView txtBackupWarning;
private SwitchMaterial swReplace;
private CheckBox chkOldFormat;
private ProgressBar progressBackup;
private ProgressBar progressRestore;
private boolean reload = false; private boolean reload = false;
@ -100,96 +115,119 @@ public class BackupActivity extends BaseActivity {
byte[] keyMaterial = callingIntent.getByteArrayExtra(Constants.EXTRA_BACKUP_ENCRYPTION_KEY); byte[] keyMaterial = callingIntent.getByteArrayExtra(Constants.EXTRA_BACKUP_ENCRYPTION_KEY);
encryptionKey = EncryptionHelper.generateSymmetricKey(keyMaterial); encryptionKey = EncryptionHelper.generateSymmetricKey(keyMaterial);
// Plain-text Spinner spBackupType = v.findViewById(R.id.backupType);
btnBackup = v.findViewById(R.id.buttonBackup);
btnRestore = v.findViewById(R.id.buttonRestore);
txtBackupLabel = v.findViewById(R.id.backupLabel);
txtBackupWarning = v.findViewById(R.id.backupErrorLabel);
swReplace = v.findViewById(R.id.backup_replace);
chkOldFormat = v.findViewById(R.id.restoreOldCrypt);
progressBackup = v.findViewById(R.id.progressBarBackup);
progressRestore = v.findViewById(R.id.progressBarRestore);
LinearLayout backupPlain = v.findViewById(R.id.button_backup_plain); setupBackupType(settings.getDefaultBackupType());
LinearLayout restorePlain = v.findViewById(R.id.button_restore_plain); spBackupType.setSelection(backupType.ordinal());
backupPlain.setOnClickListener(new View.OnClickListener() { spBackupType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onClick(View view) { public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
backupPlainWithWarning(); Constants.BackupType type = Constants.BackupType.values()[i];
setupBackupType(type);
} }
@Override
public void onNothingSelected(AdapterView<?> adapterView) { }
}); });
restorePlain.setOnClickListener(new View.OnClickListener() { btnBackup.setOnClickListener(view -> {
@Override switch (backupType) {
public void onClick(View view) { case PLAIN_TEXT:
showOpenFileSelector(Constants.INTENT_BACKUP_OPEN_DOCUMENT_PLAIN); backupPlainWithWarning();
} break;
}); case ENCRYPTED:
showSaveFileSelector(Constants.BACKUP_MIMETYPE_CRYPT, Constants.BackupType.ENCRYPTED, Constants.INTENT_BACKUP_SAVE_DOCUMENT_CRYPT);
// Password break;
case OPEN_PGP:
TextView cryptSetup = v.findViewById(R.id.msg_crypt_setup);
LinearLayout backupCrypt = v.findViewById(R.id.button_backup_crypt);
LinearLayout restoreCrypt = v.findViewById(R.id.button_restore_crypt);
LinearLayout restoreCryptOld = v.findViewById(R.id.button_restore_crypt_old);
if (settings.getBackupPasswordEnc().isEmpty()) {
cryptSetup.setVisibility(View.VISIBLE);
} else {
cryptSetup.setVisibility(View.GONE);
}
backupCrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showSaveFileSelector(Constants.BACKUP_MIMETYPE_CRYPT, Constants.BackupType.ENCRYPTED, Constants.INTENT_BACKUP_SAVE_DOCUMENT_CRYPT);
}
});
restoreCrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showOpenFileSelector(Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT);
}
});
restoreCryptOld.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showOpenFileSelector(Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT_OLD);
}
});
// OpenPGP
String PGPProvider = settings.getOpenPGPProvider();
pgpEncryptionUserIDs = settings.getOpenPGPEncryptionUserIDs();
TextView setupPGP = v.findViewById(R.id.msg_openpgp_setup);
LinearLayout backupPGP = v.findViewById(R.id.button_backup_openpgp);
LinearLayout restorePGP = v.findViewById(R.id.button_restore_openpgp);
if (TextUtils.isEmpty(PGPProvider)) {
setupPGP.setVisibility(View.VISIBLE);
backupPGP.setVisibility(View.GONE);
restorePGP.setVisibility(View.GONE);
} else if (TextUtils.isEmpty(pgpEncryptionUserIDs)){
setupPGP.setVisibility(View.VISIBLE);
setupPGP.setText(R.string.backup_desc_openpgp_keyid);
backupPGP.setVisibility(View.GONE);
} else {
pgpServiceConnection = new OpenPgpServiceConnection(BackupActivity.this.getApplicationContext(), PGPProvider);
pgpServiceConnection.bindToService();
backupPGP.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showSaveFileSelector(Constants.BACKUP_MIMETYPE_PGP, Constants.BackupType.OPEN_PGP, Constants.INTENT_BACKUP_SAVE_DOCUMENT_PGP); showSaveFileSelector(Constants.BACKUP_MIMETYPE_PGP, Constants.BackupType.OPEN_PGP, Constants.INTENT_BACKUP_SAVE_DOCUMENT_PGP);
} break;
}); }
});
restorePGP.setOnClickListener(new View.OnClickListener() { btnRestore.setOnClickListener(view -> {
@Override switch (backupType) {
public void onClick(View view) { case PLAIN_TEXT:
showOpenFileSelector(Constants.INTENT_BACKUP_OPEN_DOCUMENT_PLAIN);
break;
case ENCRYPTED:
if (chkOldFormat.isChecked())
showOpenFileSelector(Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT_OLD);
else
showOpenFileSelector(Constants.INTENT_BACKUP_OPEN_DOCUMENT_CRYPT);
break;
case OPEN_PGP:
showOpenFileSelector(Constants.INTENT_BACKUP_OPEN_DOCUMENT_PGP); showOpenFileSelector(Constants.INTENT_BACKUP_OPEN_DOCUMENT_PGP);
break;
}
});
}
private void setupBackupType(Constants.BackupType type) {
switch (type) {
case PLAIN_TEXT:
txtBackupLabel.setText(R.string.backup_label_warning_plain);
chkOldFormat.setVisibility(View.GONE);
txtBackupWarning.setVisibility(View.GONE);
btnBackup.setEnabled(true);
btnRestore.setEnabled(true);
break;
case ENCRYPTED:
txtBackupLabel.setText(R.string.backup_label_crypt);
chkOldFormat.setVisibility(View.VISIBLE);
txtBackupWarning.setVisibility(View.GONE);
btnBackup.setEnabled(true);
btnRestore.setEnabled(true);
break;
case OPEN_PGP:
txtBackupLabel.setText(R.string.backup_label_pgp);
chkOldFormat.setVisibility(View.GONE);
String PGPProvider = settings.getOpenPGPProvider();
pgpEncryptionUserIDs = settings.getOpenPGPEncryptionUserIDs();
if (TextUtils.isEmpty(PGPProvider)) {
txtBackupWarning.setText(R.string.backup_desc_openpgp_provider);
txtBackupWarning.setVisibility(View.VISIBLE);
btnBackup.setEnabled(false);
btnRestore.setEnabled(false);
} else if (TextUtils.isEmpty(pgpEncryptionUserIDs)){
txtBackupWarning.setText(R.string.backup_desc_openpgp_keyid);
txtBackupWarning.setVisibility(View.VISIBLE);
btnBackup.setEnabled(false);
btnRestore.setEnabled(false);
} else {
txtBackupWarning.setVisibility(View.GONE);
btnBackup.setEnabled(true);
btnRestore.setEnabled(true);
pgpServiceConnection = new OpenPgpServiceConnection(BackupActivity.this.getApplicationContext(), PGPProvider);
pgpServiceConnection.bindToService();
} }
});
break;
} }
replace = v.findViewById(R.id.backup_replace); backupType = type;
settings.setDefaultBackupType(type);
} }
// End with a result // End with a result
@ -221,6 +259,38 @@ public class BackupActivity extends BaseActivity {
pgpServiceConnection.unbindFromService(); pgpServiceConnection.unbindFromService();
} }
@Override
public void onBackupFinished() {
hideProgress();
}
@Override
public void onBackupFailed() {
hideProgress();
}
private void showProgress(boolean restore) {
btnBackup.setEnabled(false);
btnRestore.setEnabled(false);
chkOldFormat.setEnabled(false);
swReplace.setEnabled(false);
if (restore)
progressRestore.setVisibility(View.VISIBLE);
else
progressBackup.setVisibility(View.VISIBLE);
}
private void hideProgress() {
btnBackup.setEnabled(true);
btnRestore.setEnabled(true);
chkOldFormat.setEnabled(true);
swReplace.setEnabled(true);
progressRestore.setVisibility(View.GONE);
progressBackup.setVisibility(View.GONE);
}
// Get the result from external activities // Get the result from external activities
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) { protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
@ -293,26 +363,11 @@ public class BackupActivity extends BaseActivity {
} else { } else {
if (settings.isBackupLocationSet()) { if (settings.isBackupLocationSet()) {
if (intentId == Constants.INTENT_BACKUP_SAVE_DOCUMENT_PLAIN) { if (intentId == Constants.INTENT_BACKUP_SAVE_DOCUMENT_PLAIN) {
BackupHelper.BackupFile plainBackupFile = BackupHelper.backupFile(this, settings.getBackupLocation(), Constants.BackupType.PLAIN_TEXT); doBackupPlain(null);
if (plainBackupFile.file != null)
doBackupPlain(plainBackupFile.file.getUri());
else
Toast.makeText(this, plainBackupFile.errorMessage, Toast.LENGTH_LONG).show();
} else if (intentId == Constants.INTENT_BACKUP_SAVE_DOCUMENT_CRYPT) { } else if (intentId == Constants.INTENT_BACKUP_SAVE_DOCUMENT_CRYPT) {
BackupHelper.BackupFile cryptBackupFile = BackupHelper.backupFile(this, settings.getBackupLocation(), Constants.BackupType.ENCRYPTED); doBackupCrypt(null);
if (cryptBackupFile.file != null)
doBackupCrypt(cryptBackupFile.file.getUri());
else
Toast.makeText(this, cryptBackupFile.errorMessage, Toast.LENGTH_LONG).show();
} else if (intentId == Constants.INTENT_BACKUP_SAVE_DOCUMENT_PGP) { } else if (intentId == Constants.INTENT_BACKUP_SAVE_DOCUMENT_PGP) {
BackupHelper.BackupFile pgpBackupFile = BackupHelper.backupFile(this, settings.getBackupLocation(), Constants.BackupType.OPEN_PGP); backupEncryptedWithPGP(null, null);
if (pgpBackupFile.file != null)
backupEncryptedWithPGP(pgpBackupFile.file.getUri(), null);
else
Toast.makeText(this, pgpBackupFile.errorMessage, Toast.LENGTH_LONG).show();
} }
} else { } else {
Toast.makeText(this, R.string.backup_toast_no_location, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.backup_toast_no_location, Toast.LENGTH_LONG).show();
@ -324,7 +379,7 @@ public class BackupActivity extends BaseActivity {
ArrayList<Entry> entries = DatabaseHelper.stringToEntries(text); ArrayList<Entry> entries = DatabaseHelper.stringToEntries(text);
if (entries.size() > 0) { if (entries.size() > 0) {
if (! replace.isChecked()) { if (! swReplace.isChecked()) {
ArrayList<Entry> currentEntries = DatabaseHelper.loadDatabase(this, encryptionKey); ArrayList<Entry> currentEntries = DatabaseHelper.loadDatabase(this, encryptionKey);
entries.removeAll(currentEntries); entries.removeAll(currentEntries);
@ -360,14 +415,17 @@ public class BackupActivity extends BaseActivity {
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(this, encryptionKey); ArrayList<Entry> entries = DatabaseHelper.loadDatabase(this, encryptionKey);
ExecutorService executor = Executors.newSingleThreadExecutor(); ExecutorService executor = Executors.newSingleThreadExecutor();
BackupHelper.SaveStringToFile runnable = new BackupHelper.SaveStringToFile(this, uri, DatabaseHelper.entriesToString(entries));
executor.execute(runnable); BackupTask backupTask = new BackupTask(this, Constants.BackupType.PLAIN_TEXT, DatabaseHelper.entriesToString(entries), this);
if (uri != null)
backupTask.setUri(uri);
showProgress(false);
executor.execute(backupTask);
} else { } else {
Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show();
} }
finishWithResult();
} }
private void backupPlainWithWarning() { private void backupPlainWithWarning() {
@ -466,12 +524,11 @@ public class BackupActivity extends BaseActivity {
private void doBackupCryptWithPassword(Uri uri, String password) { private void doBackupCryptWithPassword(Uri uri, String password) {
if (Tools.isExternalStorageWritable()) { if (Tools.isExternalStorageWritable()) {
BackupHelper.backupToFileAsync(this, uri, password, encryptionKey, false); showProgress(false);
BackupHelper.backupToFileAsync(this, uri, password, encryptionKey, this, false);
} else { } else {
Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show();
} }
finishWithResult();
} }
/* OpenPGP backup functions */ /* OpenPGP backup functions */
@ -492,14 +549,17 @@ public class BackupActivity extends BaseActivity {
private void doBackupEncrypted(Uri uri, String data) { private void doBackupEncrypted(Uri uri, String data) {
if (Tools.isExternalStorageWritable()) { if (Tools.isExternalStorageWritable()) {
ExecutorService executor = Executors.newSingleThreadExecutor(); ExecutorService executor = Executors.newSingleThreadExecutor();
BackupHelper.SaveStringToFile runnable = new BackupHelper.SaveStringToFile(this, uri, data);
executor.execute(runnable); BackupTask backupTask = new BackupTask(this, Constants.BackupType.OPEN_PGP, data, this);
if (uri != null)
backupTask.setUri(uri);
showProgress(false);
executor.execute(backupTask);
} else { } else {
Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show();
} }
finishWithResult();
} }
private void backupEncryptedWithPGP(Uri uri, Intent encryptIntent) { private void backupEncryptedWithPGP(Uri uri, Intent encryptIntent) {

View file

@ -0,0 +1,124 @@
package org.shadowice.flocke.andotp.Tasks;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Utilities.BackupHelper;
import org.shadowice.flocke.andotp.Utilities.Constants;
import org.shadowice.flocke.andotp.Utilities.Settings;
import org.shadowice.flocke.andotp.Utilities.StorageAccessHelper;
public class BackupTask implements Runnable {
final private Context context;
final private String payload;
final private Constants.BackupType type;
final private BackupCallback callback;
final private Handler handler;
private boolean silent = false;
private Uri uri = null;
private String password = null;
public BackupTask(Context context, Constants.BackupType type, String payload, BackupCallback callback) {
this.context = context;
this.payload = payload;
this.type = type;
this.callback = callback;
this.handler = new Handler(Looper.getMainLooper());
}
public void setUri(Uri uri) {
this.uri = uri;
}
public void setPassword(String password) {
this.password = password;
}
public void setSilent(boolean silent) {
this.silent = silent;
}
@Override
public void run() {
boolean success;
if (uri == null)
uri = getTargetUri();
if (uri == null)
return;
if (type == Constants.BackupType.PLAIN_TEXT) {
success = StorageAccessHelper.saveFile(context, uri, payload);
} else if (type == Constants.BackupType.ENCRYPTED) {
success = BackupHelper.backupToFile(context, uri, password, payload);
} else if (type == Constants.BackupType.OPEN_PGP) {
success = StorageAccessHelper.saveFile(context, uri, payload);
} else {
success = false;
}
if (success)
onFinished();
else
onFailed();
}
private Uri getTargetUri() {
Settings settings = new Settings(context);
BackupHelper.BackupFile backupFile = BackupHelper.backupFile(context, settings.getBackupLocation(), type);
if (backupFile.file != null) {
return backupFile.file.getUri();
} else {
showToast(context, backupFile.errorMessage);
return null;
}
}
private void showToast(Context context, int msgId) {
if (!this.silent) {
this.handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, msgId, Toast.LENGTH_LONG).show();
}
});
}
}
private void onFinished() {
this.handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, R.string.backup_toast_export_success, Toast.LENGTH_LONG).show();
if (callback != null)
callback.onBackupFinished();
}
});
}
private void onFailed() {
this.handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, R.string.backup_toast_export_failed, Toast.LENGTH_LONG).show();
if (callback != null)
callback.onBackupFailed();
}
});
}
public interface BackupCallback {
void onBackupFinished();
void onBackupFailed();
}
}

View file

@ -2,14 +2,12 @@ package org.shadowice.flocke.andotp.Utilities;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
import org.shadowice.flocke.andotp.Database.Entry; import org.shadowice.flocke.andotp.Database.Entry;
import org.shadowice.flocke.andotp.R; import org.shadowice.flocke.andotp.R;
import org.shadowice.flocke.andotp.Tasks.BackupTask;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -106,25 +104,31 @@ public class BackupHelper {
return Constants.BackupType.UNAVAILABLE; return Constants.BackupType.UNAVAILABLE;
} }
public static void backupToFileAsync(Context context, Uri uri, String password, SecretKey encryptionKey, boolean silent) { public static void backupToFileAsync(Context context, Uri uri, String password, SecretKey encryptionKey, BackupTask.BackupCallback callback, boolean silent) {
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(context, encryptionKey); ArrayList<Entry> entries = DatabaseHelper.loadDatabase(context, encryptionKey);
ExecutorService executor = Executors.newSingleThreadExecutor(); ExecutorService executor = Executors.newSingleThreadExecutor();
BackupCrypt runnable = new BackupCrypt(context, uri, password, entries, silent);
executor.execute(runnable); BackupTask backupTask = new BackupTask(context, Constants.BackupType.ENCRYPTED, DatabaseHelper.entriesToString(entries), callback);
if (uri != null)
backupTask.setUri(uri);
backupTask.setPassword(password);
backupTask.setSilent(silent);
executor.execute(backupTask);
} }
public static boolean backupToFile(Context context, Uri uri, String password, SecretKey encryptionKey) { public static boolean backupToFile(Context context, Uri uri, String password, SecretKey encryptionKey) {
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(context, encryptionKey); ArrayList<Entry> entries = DatabaseHelper.loadDatabase(context, encryptionKey);
return backupToFile(context, uri, password, entries);
}
public static boolean backupToFile(Context context, Uri uri, String password, ArrayList<Entry> entries)
{
String plain = DatabaseHelper.entriesToString(entries); String plain = DatabaseHelper.entriesToString(entries);
return backupToFile(context, uri, password, plain);
}
public static boolean backupToFile(Context context, Uri uri, String password, String plain)
{
boolean success = true; boolean success = true;
try { try {
@ -149,73 +153,4 @@ public class BackupHelper {
return success; return success;
} }
public static class BackupCrypt implements Runnable {
final private Context context;
final private Uri uri;
final private String password;
final private ArrayList<Entry> entries;
final private boolean silent;
public BackupCrypt(Context context, Uri uri, String password, ArrayList<Entry> entries, boolean silent) {
this.context = context;
this.uri = uri;
this.password = password;
this.entries = entries;
this.silent = silent;
}
@Override
public void run() {
boolean success = backupToFile(context, uri, password, entries);
if (!silent) {
if (success) {
postMessage(R.string.backup_toast_export_success);
} else {
postMessage(R.string.backup_toast_export_failed);
}
}
}
void postMessage(int msgId) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, msgId, Toast.LENGTH_LONG).show();
}
});
}
}
public static class SaveStringToFile implements Runnable {
final private Context context;
final private Uri uri;
final private String data;
public SaveStringToFile(Context context, Uri uri, String data) {
this.context = context;
this.uri = uri;
this.data = data;
}
@Override
public void run() {
boolean success = StorageAccessHelper.saveFile(context, uri, data);
if (success)
postMessage(R.string.backup_toast_export_success);
else
postMessage(R.string.backup_toast_export_failed);
}
void postMessage(int msgId) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, msgId, Toast.LENGTH_LONG).show();
}
});
}
}
} }

View file

@ -605,4 +605,13 @@ public class Settings {
public boolean getBlockAutofill() { public boolean getBlockAutofill() {
return getBoolean(R.string.settings_key_block_autofill, false); return getBoolean(R.string.settings_key_block_autofill, false);
} }
public void setDefaultBackupType(Constants.BackupType type) {
setString(R.string.settings_key_backup_default_type, type.name().toLowerCase(Locale.ENGLISH));
}
public Constants.BackupType getDefaultBackupType() {
String defaultType = getString(R.string.settings_key_backup_default_type, Constants.BackupType.ENCRYPTED.name());
return Constants.BackupType.valueOf(defaultType.toUpperCase(Locale.ENGLISH));
}
} }

View file

@ -2,222 +2,78 @@
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/scroll_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:overScrollMode="never" android:overScrollMode="never"
app:layout_behavior="@string/appbar_scrolling_view_behavior" > app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/activity_margin"
<TextView android:layout_marginTop="@dimen/activity_margin_large"
android:layout_width="match_parent" android:gravity="center_horizontal">
android:layout_height="wrap_content"
android:paddingStart="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingTop="@dimen/activity_margin"
android:textColor="@color/colorAccent"
android:textStyle="bold"
android:text="@string/backup_category_plain" />
<LinearLayout <LinearLayout
android:id="@+id/button_backup_plain" android:orientation="horizontal"
android:orientation="vertical" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:padding="@dimen/activity_margin"
android:background="?android:attr/selectableItemBackground" >
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem" android:textColor="@color/colorAccent"
android:text="@string/backup_title_export_plain" /> android:textStyle="bold"
android:textAppearance="@style/TextAppearance.AppCompat.Widget.TextView.SpinnerItem"
android:text="@string/backup_label_type"/>
<TextView <Spinner
android:id="@+id/backupType"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" android:entries="@array/backup_list_type_names" />
android:text="@string/backup_desc_export_plain"/>
</LinearLayout>
<LinearLayout
android:id="@+id/button_restore_plain"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/activity_margin"
android:background="?android:attr/selectableItemBackground" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:text="@string/backup_title_import_plain" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/backup_desc_import_plain"/>
</LinearLayout> </LinearLayout>
<TextView <TextView
android:layout_width="match_parent" android:id="@+id/backupLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="@dimen/activity_margin" android:layout_marginTop="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin" android:padding="@dimen/activity_margin"
android:paddingTop="@dimen/activity_margin" android:text="@string/backup_label_warning_plain"/>
android:textColor="@color/colorAccent"
android:textStyle="bold"
android:text="@string/backup_category_crypt" />
<TextView <TextView
android:id="@+id/msg_crypt_setup" android:id="@+id/backupErrorLabel"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="@dimen/activity_margin" android:padding="@dimen/activity_margin"
android:visibility="gone" android:textColor="?attr/colorExpiring"
android:text="@string/backup_desc_crypt_setup"/> android:text="@string/backup_desc_openpgp_provider"
android:visibility="gone"/>
<LinearLayout <Button
android:id="@+id/button_backup_crypt" android:id="@+id/buttonBackup"
android:orientation="vertical" android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="@dimen/activity_margin" android:text="@string/backup_button_backup"/>
android:background="?android:attr/selectableItemBackground" >
<TextView <ProgressBar
android:layout_width="wrap_content" android:id="@+id/progressBarBackup"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:text="@string/backup_title_export_crypt" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/backup_desc_export_crypt"/>
</LinearLayout>
<LinearLayout
android:id="@+id/button_restore_crypt"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="@dimen/activity_margin" android:layout_marginTop="@dimen/activity_margin"
android:background="?android:attr/selectableItemBackground" > android:indeterminate="true"
style="?android:attr/progressBarStyle"
<TextView android:visibility="gone"/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:text="@string/backup_title_import_crypt" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/backup_desc_import_crypt"/>
</LinearLayout>
<LinearLayout
android:id="@+id/button_restore_crypt_old"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/activity_margin"
android:background="?android:attr/selectableItemBackground" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:text="@string/backup_title_import_crypt_old" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/backup_desc_import_crypt_old"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingTop="@dimen/activity_margin"
android:textColor="@color/colorAccent"
android:textStyle="bold"
android:text="@string/backup_category_openpgp" />
<TextView
android:id="@+id/msg_openpgp_setup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/activity_margin"
android:visibility="gone"
android:text="@string/backup_desc_openpgp_provider"/>
<LinearLayout
android:id="@+id/button_backup_openpgp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/activity_margin"
android:background="?android:attr/selectableItemBackground" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:text="@string/backup_title_export_openpgp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/backup_desc_export_openpgp"/>
</LinearLayout>
<LinearLayout
android:id="@+id/button_restore_openpgp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/activity_margin"
android:background="?android:attr/selectableItemBackground" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:text="@string/backup_title_import_openpgp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/backup_desc_import_openpgp"/>
</LinearLayout>
<RelativeLayout <RelativeLayout
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin" android:layout_marginTop="@dimen/activity_margin_large"
android:layout_marginBottom="@dimen/activity_margin"
android:padding="@dimen/activity_margin" android:padding="@dimen/activity_margin"
android:background="?android:attr/selectableItemBackground" > android:background="?android:attr/selectableItemBackground" >
@ -242,7 +98,7 @@
</LinearLayout> </LinearLayout>
<Switch <com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/backup_replace" android:id="@+id/backup_replace"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -252,5 +108,30 @@
</RelativeLayout> </RelativeLayout>
<CheckBox
android:id="@+id/restoreOldCrypt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/activity_margin"
android:layout_marginBottom="@dimen/activity_margin"
android:text="@string/backup_check_restore_old"
android:visibility="gone" />
<Button
android:id="@+id/buttonRestore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/backup_button_restore" />
<ProgressBar
android:id="@+id/progressBarRestore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin"
android:indeterminate="true"
style="?android:attr/progressBarStyle"
android:visibility="gone"/>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View file

@ -53,6 +53,7 @@
<string name="settings_key_openpgp_key_sign" translatable="false">pref_openpgp_key_sign</string> <string name="settings_key_openpgp_key_sign" translatable="false">pref_openpgp_key_sign</string>
<string name="settings_key_openpgp_verify" translatable="false">pref_openpgp_verify</string> <string name="settings_key_openpgp_verify" translatable="false">pref_openpgp_verify</string>
<string name="settings_key_new_backup_format_dialog_shown" translatable="false">pref_new_backup_dialog_shown</string> <!--Deprecated --> <string name="settings_key_new_backup_format_dialog_shown" translatable="false">pref_new_backup_dialog_shown</string> <!--Deprecated -->
<string name="settings_key_backup_default_type" translatable="false">pref_backup_default_type</string>
<string name="settings_key_security_backup_warning" translatable="false">pref_security_backup_warning_shown</string> <string name="settings_key_security_backup_warning" translatable="false">pref_security_backup_warning_shown</string>
<string name="settings_key_sort_mode" translatable="false">pref_sort_mode</string> <string name="settings_key_sort_mode" translatable="false">pref_sort_mode</string>

View file

@ -2,33 +2,10 @@
<resources> <resources>
<string name="backup_activity_title">Backups</string> <string name="backup_activity_title">Backups</string>
<string name="backup_category_plain">Plain-text backups</string>
<string name="backup_category_crypt">Encrypted backups</string>
<string name="backup_category_openpgp">OpenPGP backups</string>
<string name="backup_title_export_plain">Backup (plain-text)</string>
<string name="backup_title_export_crypt">Backup (encrypted)</string>
<string name="backup_title_export_openpgp">Backup (OpenPGP)</string>
<string name="backup_title_import_plain">Restore (plain-text)</string>
<string name="backup_title_import_crypt">Restore (encrypted)</string>
<string name="backup_title_import_crypt_old">Restore (encrypted, old encryption)</string>
<string name="backup_title_import_openpgp">Restore (OpenPGP)</string>
<string name="backup_title_replace">Replace existing entries</string> <string name="backup_title_replace">Replace existing entries</string>
<string name="backup_desc_replace">If enabled all old entries are replaced when importing a
<string name="backup_desc_export_plain">Backup all accounts in a plain-text JSON file</string> backup and only the backup is present. If disabled the old entries and the backups content
<string name="backup_desc_export_crypt">Backup all accounts in a password-protected JSON file</string> are merged.</string>
<string name="backup_desc_export_openpgp">Backup all accounts in an OpenPGP-encrypted JSON file</string>
<string name="backup_desc_import_plain">Restore accounts from a plain-text JSON file</string>
<string name="backup_desc_import_crypt">Restore accounts from a password-protected JSON file</string>
<string name="backup_desc_import_crypt_old">Restore accounts from a password-protected JSON file
created with an <b>andOTP version lower than 0.6.3</b></string>
<string name="backup_desc_import_openpgp">Restore accounts from an OpenPGP-encrypted JSON file</string>
<string name="backup_desc_crypt_setup">Failed to load the backup password from the <b>Settings</b>,
this either means no password was set or something went wrong. You will be asked to enter
the password manually when creating or importing a backup.
</string>
<string name="backup_desc_openpgp_provider">You need to install an OpenPGP provider and enable <string name="backup_desc_openpgp_provider">You need to install an OpenPGP provider and enable
it in the <b>Settings</b> to use this feature. it in the <b>Settings</b> to use this feature.
@ -37,18 +14,12 @@
before you can create encrypted backups. before you can create encrypted backups.
</string> </string>
<string name="backup_desc_replace">If enabled all old entries are replaced when importing a
backup and only the backup is present. If disabled the old entries and the backups content
are merged.</string>
<!-- Dialogs --> <!-- Dialogs -->
<string name="backup_dialog_title_security_warning">Security warning</string> <string name="backup_dialog_title_security_warning">Security warning</string>
<string name="backup_dialog_msg_export_warning">Do you really want to export the database as <string name="backup_dialog_msg_export_warning">Do you really want to export the database as
plain-text JSON file? This file contains all your secret keys, please <b>keep it safe</b>! plain-text JSON file? This file contains all your secret keys, please <b>keep it safe</b>!
</string> </string>
<string name="backup_receiver_title_backup_failed">Backup failed</string> <string name="backup_receiver_title_backup_failed">Backup failed</string>
<string name="backup_receiver_title_backup_success">Backup successful</string> <string name="backup_receiver_title_backup_success">Backup successful</string>
@ -89,4 +60,32 @@
<string name="backup_toast_openpgp_not_verified">No verified signature detected</string> <string name="backup_toast_openpgp_not_verified">No verified signature detected</string>
<string name="backup_toast_crypt_password_not_set">Password not set, check the <b>Settings</b></string> <string name="backup_toast_crypt_password_not_set">Password not set, check the <b>Settings</b></string>
<string name="backup_toast_file_selection_failed">Can\'t open file selection dialog!</string> <string name="backup_toast_file_selection_failed">Can\'t open file selection dialog!</string>
<string name="backup_label_type">Backup type: </string>
<string name="backup_label_warning_plain">The backup will be stored in an unencrypted plain-text
file. Please only use if strictly necessary and <b>keep the file safe</b>!</string>
<string name="backup_label_crypt">The backup will be stored encrypted with a custom password
using AES 256-bit encryption. You can setup a default password in the settings if you
don\'t want to have to enter it every time you create a backup.</string>
<string name="backup_label_pgp">The backup will be stored encrypted using the PGP key you
specified in the settings. The encryption will be handled by a 3rd party app.</string>
<string name="backup_check_restore_old">Restore old backup format (created with an andOTP version
before 0.6.3)</string>
<string name="backup_button_backup">Create backup</string>
<string name="backup_button_restore">Restore backup</string>
<string-array name="backup_list_type_names">
<item>Plain-text</item>
<item>Encrypted</item>
<item>OpenPGP</item>
</string-array>
<string-array name="backup_list_type_values" translatable="false">
<item>plain</item>
<item>crypt</item>
<item>pgp</item>
</string-array>
</resources> </resources>