Backup: Use tasks for restore
This commit is contained in:
parent
e6e16999ae
commit
dfc788cd29
6 changed files with 335 additions and 74 deletions
|
@ -58,29 +58,32 @@ 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.EncryptedBackupTask;
|
import org.shadowice.flocke.andotp.Tasks.EncryptedBackupTask;
|
||||||
|
import org.shadowice.flocke.andotp.Tasks.EncryptedRestoreTask;
|
||||||
import org.shadowice.flocke.andotp.Tasks.GenericBackupTask;
|
import org.shadowice.flocke.andotp.Tasks.GenericBackupTask;
|
||||||
|
import org.shadowice.flocke.andotp.Tasks.GenericRestoreTask;
|
||||||
import org.shadowice.flocke.andotp.Tasks.PGPBackupTask;
|
import org.shadowice.flocke.andotp.Tasks.PGPBackupTask;
|
||||||
|
import org.shadowice.flocke.andotp.Tasks.PGPRestoreTask;
|
||||||
import org.shadowice.flocke.andotp.Tasks.PlainTextBackupTask;
|
import org.shadowice.flocke.andotp.Tasks.PlainTextBackupTask;
|
||||||
|
import org.shadowice.flocke.andotp.Tasks.PlainTextRestoreTask;
|
||||||
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;
|
||||||
import org.shadowice.flocke.andotp.Utilities.EncryptionHelper;
|
import org.shadowice.flocke.andotp.Utilities.EncryptionHelper;
|
||||||
import org.shadowice.flocke.andotp.Utilities.StorageAccessHelper;
|
|
||||||
import org.shadowice.flocke.andotp.Utilities.Tools;
|
import org.shadowice.flocke.andotp.Utilities.Tools;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
public class BackupActivity extends BaseActivity {
|
public class BackupActivity extends BaseActivity {
|
||||||
private final static String TAG = BackupActivity.class.getSimpleName();
|
private final static String TAG = BackupActivity.class.getSimpleName();
|
||||||
private static final String TAG_TASK_FRAGMENT = "BackupActivity.TaskFragmentTag";
|
|
||||||
|
private static final String TAG_BACKUP_TASK_FRAGMENT = "BackupActivity.BackupTaskFragmentTag";
|
||||||
|
private static final String TAG_RESTORE_TASK_FRAGMENT = "BackupActivity.RestoreTaskFragmentTag";
|
||||||
|
|
||||||
private Constants.BackupType backupType = Constants.BackupType.ENCRYPTED;
|
private Constants.BackupType backupType = Constants.BackupType.ENCRYPTED;
|
||||||
private SecretKey encryptionKey = null;
|
private SecretKey encryptionKey = null;
|
||||||
|
@ -279,12 +282,15 @@ public class BackupActivity extends BaseActivity {
|
||||||
|
|
||||||
if (result.messageId != 0)
|
if (result.messageId != 0)
|
||||||
notifyBackupState(result.messageId);
|
notifyBackupState(result.messageId);
|
||||||
|
else
|
||||||
|
if (!result.success)
|
||||||
|
notifyBackupState(R.string.backup_toast_export_failed);
|
||||||
|
|
||||||
// Clean up the task fragment
|
// Clean up the task fragment
|
||||||
TaskFragment taskFragment = findTaskFragment();
|
BackupTaskFragment backupTaskFragment = findBackupTaskFragment();
|
||||||
if (taskFragment != null) {
|
if (backupTaskFragment != null) {
|
||||||
getFragmentManager().beginTransaction()
|
getFragmentManager().beginTransaction()
|
||||||
.remove(taskFragment)
|
.remove(backupTaskFragment)
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,17 +298,59 @@ public class BackupActivity extends BaseActivity {
|
||||||
finishWithResult();
|
finishWithResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showBackupProgress(boolean running) {
|
private void handleRestoreTaskResult(GenericRestoreTask.RestoreTaskResult result) {
|
||||||
|
if (result.success) {
|
||||||
|
if (result.isPGP) {
|
||||||
|
InputStream is = new ByteArrayInputStream(result.payload.getBytes(StandardCharsets.UTF_8));
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
OpenPgpApi api = new OpenPgpApi(this, pgpServiceConnection.getService());
|
||||||
|
Intent resultIntent = api.executeApi(result.decryptIntent, is, os);
|
||||||
|
|
||||||
|
handleOpenPGPResult(resultIntent, os, result.uri, Constants.INTENT_BACKUP_DECRYPT_PGP);
|
||||||
|
} else {
|
||||||
|
restoreEntries(result.payload, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (result.messageId != 0)
|
||||||
|
notifyBackupState(result.messageId);
|
||||||
|
else
|
||||||
|
notifyBackupState(R.string.backup_toast_import_failed);
|
||||||
|
}
|
||||||
|
|
||||||
|
showRestoreProgress(false);
|
||||||
|
|
||||||
|
// Clean up the task fragment
|
||||||
|
RestoreTaskFragment restoreTaskFragment = findRestoreTaskFragment();
|
||||||
|
if (restoreTaskFragment != null) {
|
||||||
|
getFragmentManager().beginTransaction()
|
||||||
|
.remove(restoreTaskFragment)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.success && !result.isPGP)
|
||||||
|
finishWithResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleInProgressMode(boolean running) {
|
||||||
allowExit = !running;
|
allowExit = !running;
|
||||||
|
|
||||||
btnBackup.setEnabled(!running);
|
btnBackup.setEnabled(!running);
|
||||||
btnRestore.setEnabled(!running);
|
btnRestore.setEnabled(!running);
|
||||||
chkOldFormat.setEnabled(!running);
|
chkOldFormat.setEnabled(!running);
|
||||||
swReplace.setEnabled(!running);
|
swReplace.setEnabled(!running);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showBackupProgress(boolean running) {
|
||||||
|
toggleInProgressMode(running);
|
||||||
progressBackup.setVisibility(running ? View.VISIBLE : View.GONE);
|
progressBackup.setVisibility(running ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showRestoreProgress(boolean running) {
|
||||||
|
toggleInProgressMode(running);
|
||||||
|
progressRestore.setVisibility(running ? View.VISIBLE : 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) {
|
||||||
|
@ -387,7 +435,7 @@ public class BackupActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restoreEntries(String text) {
|
private void restoreEntries(String text, boolean finish) {
|
||||||
ArrayList<Entry> entries = DatabaseHelper.stringToEntries(text);
|
ArrayList<Entry> entries = DatabaseHelper.stringToEntries(text);
|
||||||
|
|
||||||
if (entries.size() > 0) {
|
if (entries.size() > 0) {
|
||||||
|
@ -401,6 +449,8 @@ public class BackupActivity extends BaseActivity {
|
||||||
if (DatabaseHelper.saveDatabase(this, entries, encryptionKey)) {
|
if (DatabaseHelper.saveDatabase(this, entries, encryptionKey)) {
|
||||||
reload = true;
|
reload = true;
|
||||||
Toast.makeText(this, R.string.backup_toast_import_success, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.backup_toast_import_success, Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
|
if (finish)
|
||||||
finishWithResult();
|
finishWithResult();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, R.string.backup_toast_import_save_failed, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.backup_toast_import_save_failed, Toast.LENGTH_LONG).show();
|
||||||
|
@ -414,9 +464,10 @@ public class BackupActivity extends BaseActivity {
|
||||||
|
|
||||||
private void doRestorePlain(Uri uri) {
|
private void doRestorePlain(Uri uri) {
|
||||||
if (Tools.isExternalStorageReadable()) {
|
if (Tools.isExternalStorageReadable()) {
|
||||||
String content = StorageAccessHelper.loadFileString(this, uri);
|
PlainTextRestoreTask task = new PlainTextRestoreTask(this, uri);
|
||||||
|
task.setCallback(this::handleRestoreTaskResult);
|
||||||
|
|
||||||
restoreEntries(content);
|
startRestoreTask(task);
|
||||||
} 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();
|
||||||
}
|
}
|
||||||
|
@ -475,39 +526,10 @@ public class BackupActivity extends BaseActivity {
|
||||||
|
|
||||||
private void doRestoreCryptWithPassword(Uri uri, String password, boolean old_format) {
|
private void doRestoreCryptWithPassword(Uri uri, String password, boolean old_format) {
|
||||||
if (Tools.isExternalStorageReadable()) {
|
if (Tools.isExternalStorageReadable()) {
|
||||||
boolean success = true;
|
EncryptedRestoreTask task = new EncryptedRestoreTask(this, uri, password, old_format);
|
||||||
String decryptedString = "";
|
task.setCallback(this::handleRestoreTaskResult);
|
||||||
|
|
||||||
try {
|
startRestoreTask(task);
|
||||||
byte[] data = StorageAccessHelper.loadFile(this, uri);
|
|
||||||
|
|
||||||
if (old_format) {
|
|
||||||
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password);
|
|
||||||
byte[] decrypted = EncryptionHelper.decrypt(key, data);
|
|
||||||
|
|
||||||
decryptedString = new String(decrypted, StandardCharsets.UTF_8);
|
|
||||||
} else {
|
|
||||||
byte[] iterBytes = Arrays.copyOfRange(data, 0, Constants.INT_LENGTH);
|
|
||||||
byte[] salt = Arrays.copyOfRange(data, Constants.INT_LENGTH, Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH);
|
|
||||||
byte[] encrypted = Arrays.copyOfRange(data, Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH, data.length);
|
|
||||||
|
|
||||||
int iter = ByteBuffer.wrap(iterBytes).getInt();
|
|
||||||
|
|
||||||
SecretKey key = EncryptionHelper.generateSymmetricKeyPBKDF2(password, iter, salt);
|
|
||||||
|
|
||||||
byte[] decrypted = EncryptionHelper.decrypt(key, encrypted);
|
|
||||||
decryptedString = new String(decrypted, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
success = false;
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
restoreEntries(decryptedString);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this,R.string.backup_toast_import_decryption_failed, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
} 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();
|
||||||
}
|
}
|
||||||
|
@ -548,13 +570,10 @@ public class BackupActivity extends BaseActivity {
|
||||||
if (decryptIntent == null)
|
if (decryptIntent == null)
|
||||||
decryptIntent = new Intent(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
decryptIntent = new Intent(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||||
|
|
||||||
String input = StorageAccessHelper.loadFileString(this, uri);
|
PGPRestoreTask task = new PGPRestoreTask(this, uri, decryptIntent);
|
||||||
|
task.setCallback(this::handleRestoreTaskResult);
|
||||||
|
|
||||||
InputStream is = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));
|
startRestoreTask(task);
|
||||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
||||||
OpenPgpApi api = new OpenPgpApi(this, pgpServiceConnection.getService());
|
|
||||||
Intent result = api.executeApi(decryptIntent, is, os);
|
|
||||||
handleOpenPGPResult(result, os, uri, Constants.INTENT_BACKUP_DECRYPT_PGP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doBackupEncrypted(Uri uri, String data) {
|
private void doBackupEncrypted(Uri uri, String data) {
|
||||||
|
@ -608,12 +627,12 @@ public class BackupActivity extends BaseActivity {
|
||||||
OpenPgpSignatureResult sigResult = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
|
OpenPgpSignatureResult sigResult = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
|
||||||
|
|
||||||
if (sigResult.getResult() == OpenPgpSignatureResult.RESULT_VALID_KEY_CONFIRMED) {
|
if (sigResult.getResult() == OpenPgpSignatureResult.RESULT_VALID_KEY_CONFIRMED) {
|
||||||
restoreEntries(outputStreamToString(os));
|
restoreEntries(outputStreamToString(os), true);
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, R.string.backup_toast_openpgp_not_verified, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.backup_toast_openpgp_not_verified, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
restoreEntries(outputStreamToString(os));
|
restoreEntries(outputStreamToString(os), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -639,42 +658,86 @@ public class BackupActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private TaskFragment findTaskFragment() {
|
private BackupTaskFragment findBackupTaskFragment() {
|
||||||
return (TaskFragment) getFragmentManager().findFragmentByTag(TAG_TASK_FRAGMENT);
|
return (BackupTaskFragment) getFragmentManager().findFragmentByTag(TAG_BACKUP_TASK_FRAGMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private RestoreTaskFragment findRestoreTaskFragment() {
|
||||||
|
return (RestoreTaskFragment) getFragmentManager().findFragmentByTag(TAG_RESTORE_TASK_FRAGMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startBackupTask(GenericBackupTask task) {
|
private void startBackupTask(GenericBackupTask task) {
|
||||||
TaskFragment taskFragment = findTaskFragment();
|
BackupTaskFragment backupTaskFragment = findBackupTaskFragment();
|
||||||
|
RestoreTaskFragment restoreTaskFragment = findRestoreTaskFragment();
|
||||||
|
|
||||||
// Don't start a task if we already have an active task running.
|
// Don't start a task if we already have an active task running (backup or restore).
|
||||||
if (taskFragment == null || taskFragment.task.isCanceled()) {
|
if ((backupTaskFragment == null || backupTaskFragment.task.isCanceled()) && (restoreTaskFragment == null || restoreTaskFragment.task.isCanceled())) {
|
||||||
if (taskFragment == null) {
|
if (backupTaskFragment == null) {
|
||||||
taskFragment = new TaskFragment();
|
backupTaskFragment = new BackupTaskFragment();
|
||||||
getFragmentManager()
|
getFragmentManager()
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
.add(taskFragment, TAG_TASK_FRAGMENT)
|
.add(backupTaskFragment, TAG_BACKUP_TASK_FRAGMENT)
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
taskFragment.startTask(task);
|
backupTaskFragment.startTask(task);
|
||||||
|
|
||||||
showBackupProgress(true);
|
showBackupProgress(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkBackgroundTask() {
|
private void startRestoreTask(GenericRestoreTask task) {
|
||||||
TaskFragment taskFragment = findTaskFragment();
|
BackupTaskFragment backupTaskFragment = findBackupTaskFragment();
|
||||||
if (taskFragment != null) {
|
RestoreTaskFragment restoreTaskFragment = findRestoreTaskFragment();
|
||||||
if (taskFragment.task.isCanceled()) {
|
|
||||||
|
// Don't start a task if we already have an active task running (backup or restore).
|
||||||
|
if ((backupTaskFragment == null || backupTaskFragment.task.isCanceled()) && (restoreTaskFragment == null || restoreTaskFragment.task.isCanceled())) {
|
||||||
|
if (restoreTaskFragment == null) {
|
||||||
|
restoreTaskFragment = new RestoreTaskFragment();
|
||||||
|
getFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.add(restoreTaskFragment, TAG_RESTORE_TASK_FRAGMENT)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreTaskFragment.startTask(task);
|
||||||
|
|
||||||
|
showRestoreProgress(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkBackgroundBackupTask() {
|
||||||
|
BackupTaskFragment backupTaskFragment = findBackupTaskFragment();
|
||||||
|
|
||||||
|
if (backupTaskFragment != null) {
|
||||||
|
if (backupTaskFragment.task.isCanceled()) {
|
||||||
// The task was canceled or has finished, so remove the task fragment.
|
// The task was canceled or has finished, so remove the task fragment.
|
||||||
getFragmentManager().beginTransaction()
|
getFragmentManager().beginTransaction()
|
||||||
.remove(taskFragment)
|
.remove(backupTaskFragment)
|
||||||
.commit();
|
.commit();
|
||||||
} else {
|
} else {
|
||||||
taskFragment.task.setCallback(this::handleBackupTaskResult);
|
backupTaskFragment.task.setCallback(this::handleBackupTaskResult);
|
||||||
showBackupProgress(true);
|
showBackupProgress(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkBackgroundRestoreTask() {
|
||||||
|
RestoreTaskFragment restoreTaskFragment = findRestoreTaskFragment();
|
||||||
|
|
||||||
|
if (restoreTaskFragment != null) {
|
||||||
|
if (restoreTaskFragment.task.isCanceled()) {
|
||||||
|
// The task was canceled or has finished, so remove the task fragment.
|
||||||
|
getFragmentManager().beginTransaction()
|
||||||
|
.remove(restoreTaskFragment)
|
||||||
|
.commit();
|
||||||
|
} else {
|
||||||
|
restoreTaskFragment.task.setCallback(this::handleRestoreTaskResult);
|
||||||
|
showRestoreProgress(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -682,16 +745,22 @@ public class BackupActivity extends BaseActivity {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
|
||||||
// We don't want the task to callback to a dead activity and cause a memory leak, so null it here.
|
// We don't want the task to callback to a dead activity and cause a memory leak, so null it here.
|
||||||
TaskFragment taskFragment = findTaskFragment();
|
BackupTaskFragment backupTaskFragment = findBackupTaskFragment();
|
||||||
if (taskFragment != null) {
|
RestoreTaskFragment restoreTaskFragment = findRestoreTaskFragment();
|
||||||
taskFragment.task.setCallback(null);
|
|
||||||
}
|
if (backupTaskFragment != null)
|
||||||
|
backupTaskFragment.task.setCallback(null);
|
||||||
|
|
||||||
|
if (restoreTaskFragment != null)
|
||||||
|
restoreTaskFragment.task.setCallback(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
checkBackgroundTask();
|
|
||||||
|
checkBackgroundBackupTask();
|
||||||
|
checkBackgroundRestoreTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -700,10 +769,10 @@ public class BackupActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Retained instance fragment to hold a running {@link GenericBackupTask} between configuration changes.*/
|
/** Retained instance fragment to hold a running {@link GenericBackupTask} between configuration changes.*/
|
||||||
public static class TaskFragment extends Fragment {
|
public static class BackupTaskFragment extends Fragment {
|
||||||
GenericBackupTask task;
|
GenericBackupTask task;
|
||||||
|
|
||||||
public TaskFragment() {
|
public BackupTaskFragment() {
|
||||||
super();
|
super();
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
}
|
}
|
||||||
|
@ -713,4 +782,19 @@ public class BackupActivity extends BaseActivity {
|
||||||
this.task.execute();
|
this.task.execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Retained instance fragment to hold a running {@link GenericRestoreTask} between configuration changes.*/
|
||||||
|
public static class RestoreTaskFragment extends Fragment {
|
||||||
|
GenericRestoreTask task;
|
||||||
|
|
||||||
|
public RestoreTaskFragment() {
|
||||||
|
super();
|
||||||
|
setRetainInstance(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startTask(@NonNull GenericRestoreTask task) {
|
||||||
|
this.task = task;
|
||||||
|
this.task.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
package org.shadowice.flocke.andotp.Tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.shadowice.flocke.andotp.R;
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.Constants;
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.EncryptionHelper;
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.StorageAccessHelper;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
public class EncryptedRestoreTask extends GenericRestoreTask {
|
||||||
|
private final String password;
|
||||||
|
private final boolean oldFormat;
|
||||||
|
|
||||||
|
public EncryptedRestoreTask(Context context, Uri uri, String password, boolean oldFormat) {
|
||||||
|
super(context, uri);
|
||||||
|
this.password = password;
|
||||||
|
this.oldFormat = oldFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
protected RestoreTaskResult doInBackground() {
|
||||||
|
boolean success = true;
|
||||||
|
String decryptedString = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] data = StorageAccessHelper.loadFile(applicationContext, uri);
|
||||||
|
|
||||||
|
if (oldFormat) {
|
||||||
|
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password);
|
||||||
|
byte[] decrypted = EncryptionHelper.decrypt(key, data);
|
||||||
|
|
||||||
|
decryptedString = new String(decrypted, StandardCharsets.UTF_8);
|
||||||
|
} else {
|
||||||
|
byte[] iterBytes = Arrays.copyOfRange(data, 0, Constants.INT_LENGTH);
|
||||||
|
byte[] salt = Arrays.copyOfRange(data, Constants.INT_LENGTH, Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH);
|
||||||
|
byte[] encrypted = Arrays.copyOfRange(data, Constants.INT_LENGTH + Constants.ENCRYPTION_IV_LENGTH, data.length);
|
||||||
|
|
||||||
|
int iter = ByteBuffer.wrap(iterBytes).getInt();
|
||||||
|
|
||||||
|
SecretKey key = EncryptionHelper.generateSymmetricKeyPBKDF2(password, iter, salt);
|
||||||
|
|
||||||
|
byte[] decrypted = EncryptionHelper.decrypt(key, encrypted);
|
||||||
|
decryptedString = new String(decrypted, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
success = false;
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
return RestoreTaskResult.success(decryptedString);
|
||||||
|
} else {
|
||||||
|
return RestoreTaskResult.failure(R.string.backup_toast_import_decryption_failed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.shadowice.flocke.andotp.Tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.shadowice.flocke.andotp.R;
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.Settings;
|
||||||
|
|
||||||
|
public abstract class GenericRestoreTask extends UiBasedBackgroundTask<GenericRestoreTask.RestoreTaskResult> {
|
||||||
|
protected final Context applicationContext;
|
||||||
|
protected final Settings settings;
|
||||||
|
protected Uri uri;
|
||||||
|
|
||||||
|
public GenericRestoreTask(Context context, @Nullable Uri uri) {
|
||||||
|
super(GenericRestoreTask.RestoreTaskResult.failure(R.string.backup_toast_import_failed));
|
||||||
|
|
||||||
|
this.applicationContext = context.getApplicationContext();
|
||||||
|
this.settings = new Settings(applicationContext);
|
||||||
|
|
||||||
|
this.uri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
protected abstract RestoreTaskResult doInBackground();
|
||||||
|
|
||||||
|
public static class RestoreTaskResult {
|
||||||
|
public final boolean success;
|
||||||
|
public final String payload;
|
||||||
|
public final int messageId;
|
||||||
|
|
||||||
|
public boolean isPGP = false;
|
||||||
|
public Intent decryptIntent = null;
|
||||||
|
public Uri uri = null;
|
||||||
|
|
||||||
|
public RestoreTaskResult(boolean success, String payload, int messageId) {
|
||||||
|
this.success = success;
|
||||||
|
this.payload = payload;
|
||||||
|
this.messageId = messageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestoreTaskResult(boolean success, String payload, int messageId, boolean isPGP, Intent decryptIntent, Uri uri) {
|
||||||
|
this.success = success;
|
||||||
|
this.payload = payload;
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.isPGP = isPGP;
|
||||||
|
this.decryptIntent = decryptIntent;
|
||||||
|
this.uri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RestoreTaskResult success(String payload) {
|
||||||
|
return new RestoreTaskResult(true, payload, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RestoreTaskResult failure(int messageId) {
|
||||||
|
return new RestoreTaskResult(false, null, messageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.shadowice.flocke.andotp.Tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.StorageAccessHelper;
|
||||||
|
|
||||||
|
public class PGPRestoreTask extends GenericRestoreTask {
|
||||||
|
private final Intent decryptIntent;
|
||||||
|
|
||||||
|
public PGPRestoreTask(Context context, Uri uri, Intent decryptIntent) {
|
||||||
|
super(context, uri);
|
||||||
|
this.decryptIntent = decryptIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
protected RestoreTaskResult doInBackground() {
|
||||||
|
String data = StorageAccessHelper.loadFileString(applicationContext, uri);
|
||||||
|
|
||||||
|
return new RestoreTaskResult(true, data, 0, true, decryptIntent, uri);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.shadowice.flocke.andotp.Tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.StorageAccessHelper;
|
||||||
|
|
||||||
|
public class PlainTextRestoreTask extends GenericRestoreTask {
|
||||||
|
public PlainTextRestoreTask(Context context, Uri uri) {
|
||||||
|
super(context, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
protected RestoreTaskResult doInBackground() {
|
||||||
|
String data = StorageAccessHelper.loadFileString(applicationContext, uri);
|
||||||
|
return RestoreTaskResult.success(data);
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,6 +55,7 @@
|
||||||
<string name="backup_toast_import_save_failed">Failed to save restored entries</string>
|
<string name="backup_toast_import_save_failed">Failed to save restored entries</string>
|
||||||
<string name="backup_toast_import_decryption_failed">Decryption of the backup failed</string>
|
<string name="backup_toast_import_decryption_failed">Decryption of the backup failed</string>
|
||||||
<string name="backup_toast_import_no_entries">No entries found in imported data</string>
|
<string name="backup_toast_import_no_entries">No entries found in imported data</string>
|
||||||
|
<string name="backup_toast_import_failed">Import from external storage failed</string>
|
||||||
<string name="backup_toast_storage_not_accessible">External storage currently not accessible</string>
|
<string name="backup_toast_storage_not_accessible">External storage currently not accessible</string>
|
||||||
<string name="backup_toast_openpgp_error">OpenPGP Error: %s</string>
|
<string name="backup_toast_openpgp_error">OpenPGP Error: %s</string>
|
||||||
<string name="backup_toast_openpgp_not_verified">No verified signature detected</string>
|
<string name="backup_toast_openpgp_not_verified">No verified signature detected</string>
|
||||||
|
|
Loading…
Reference in a new issue