Refactor backup tasks
Base the backup tasks on UiBasedBackgroundTask
This commit is contained in:
parent
7bf6cda6ed
commit
1d3f7af099
7 changed files with 201 additions and 193 deletions
|
@ -53,7 +53,10 @@ 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.Tasks.EncryptedBackupTask;
|
||||||
|
import org.shadowice.flocke.andotp.Tasks.GenericBackupTask;
|
||||||
|
import org.shadowice.flocke.andotp.Tasks.PGPBackupTask;
|
||||||
|
import org.shadowice.flocke.andotp.Tasks.PlainTextBackupTask;
|
||||||
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;
|
||||||
|
@ -68,13 +71,10 @@ 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 java.util.Arrays;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
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 Constants.BackupType backupType = Constants.BackupType.ENCRYPTED;
|
||||||
|
@ -259,36 +259,18 @@ public class BackupActivity extends BaseActivity
|
||||||
pgpServiceConnection.unbindFromService();
|
pgpServiceConnection.unbindFromService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void handleBackupTaskResult(GenericBackupTask.BackupTaskResult result) {
|
||||||
public void onBackupFinished() {
|
Toast.makeText(this, result.messageId, Toast.LENGTH_LONG).show();
|
||||||
hideProgress();
|
showBackupProgress(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void showBackupProgress(boolean running) {
|
||||||
public void onBackupFailed() {
|
btnBackup.setEnabled(!running);
|
||||||
hideProgress();
|
btnRestore.setEnabled(!running);
|
||||||
}
|
chkOldFormat.setEnabled(!running);
|
||||||
|
swReplace.setEnabled(!running);
|
||||||
|
|
||||||
private void showProgress(boolean restore) {
|
progressBackup.setVisibility(running ? View.VISIBLE : View.GONE);
|
||||||
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
|
||||||
|
@ -414,15 +396,11 @@ public class BackupActivity extends BaseActivity
|
||||||
if (Tools.isExternalStorageWritable()) {
|
if (Tools.isExternalStorageWritable()) {
|
||||||
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(this, encryptionKey);
|
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(this, encryptionKey);
|
||||||
|
|
||||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
PlainTextBackupTask task = new PlainTextBackupTask(this, entries, uri);
|
||||||
|
task.setCallback(this::handleBackupTaskResult);
|
||||||
|
|
||||||
BackupTask backupTask = new BackupTask(this, Constants.BackupType.PLAIN_TEXT, DatabaseHelper.entriesToString(entries), this);
|
task.execute();
|
||||||
|
showBackupProgress(true);
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
@ -524,8 +502,13 @@ 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()) {
|
||||||
showProgress(false);
|
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(this, encryptionKey);
|
||||||
BackupHelper.backupToFileAsync(this, uri, password, encryptionKey, this, false);
|
|
||||||
|
EncryptedBackupTask task = new EncryptedBackupTask(this, entries, password, uri);
|
||||||
|
task.setCallback(this::handleBackupTaskResult);
|
||||||
|
|
||||||
|
task.execute();
|
||||||
|
showBackupProgress(true);
|
||||||
} 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,15 +531,11 @@ 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();
|
PGPBackupTask task = new PGPBackupTask(this, data, uri);
|
||||||
|
task.setCallback(this::handleBackupTaskResult);
|
||||||
|
|
||||||
BackupTask backupTask = new BackupTask(this, Constants.BackupType.OPEN_PGP, data, this);
|
task.execute();
|
||||||
|
showBackupProgress(true);
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.shadowice.flocke.andotp.Tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.shadowice.flocke.andotp.Database.Entry;
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.BackupHelper;
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.Constants;
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.DatabaseHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class EncryptedBackupTask extends GenericBackupTask {
|
||||||
|
private final String password;
|
||||||
|
private final ArrayList<Entry> entries;
|
||||||
|
|
||||||
|
public EncryptedBackupTask(Context context, ArrayList<Entry> entries, String password, @Nullable Uri uri) {
|
||||||
|
super(context, uri);
|
||||||
|
this.entries = entries;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
protected Constants.BackupType getBackupType() {
|
||||||
|
return Constants.BackupType.ENCRYPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doBackup() {
|
||||||
|
String payload = DatabaseHelper.entriesToString(entries);
|
||||||
|
return BackupHelper.backupToFile(applicationContext, uri, password, payload);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package org.shadowice.flocke.andotp.Tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public abstract class GenericBackupTask extends UiBasedBackgroundTask<GenericBackupTask.BackupTaskResult> {
|
||||||
|
protected final Context applicationContext;
|
||||||
|
protected final Settings settings;
|
||||||
|
protected final Constants.BackupType type;
|
||||||
|
protected Uri uri;
|
||||||
|
|
||||||
|
public GenericBackupTask(Context context, @Nullable Uri uri) {
|
||||||
|
super(BackupTaskResult.failure());
|
||||||
|
|
||||||
|
this.applicationContext = context.getApplicationContext();
|
||||||
|
this.settings = new Settings(applicationContext);
|
||||||
|
|
||||||
|
this.type = getBackupType();
|
||||||
|
this.uri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
protected BackupTaskResult doInBackground() {
|
||||||
|
if (uri == null) {
|
||||||
|
BackupHelper.BackupFile backupFile = BackupHelper.backupFile(applicationContext, settings.getBackupLocation(), type);
|
||||||
|
|
||||||
|
if (backupFile.file == null)
|
||||||
|
return new BackupTaskResult(backupFile.errorMessage);
|
||||||
|
|
||||||
|
uri = backupFile.file.getUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = doBackup();
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
return BackupTaskResult.success();
|
||||||
|
else
|
||||||
|
return BackupTaskResult.failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
protected abstract Constants.BackupType getBackupType();
|
||||||
|
protected abstract boolean doBackup();
|
||||||
|
|
||||||
|
|
||||||
|
public static class BackupTaskResult {
|
||||||
|
public final int messageId;
|
||||||
|
|
||||||
|
public BackupTaskResult(int messageId) {
|
||||||
|
this.messageId = messageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BackupTaskResult success() {
|
||||||
|
return new BackupTaskResult(R.string.backup_toast_export_success);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BackupTaskResult failure() {
|
||||||
|
return new BackupTaskResult(R.string.backup_toast_export_failed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.shadowice.flocke.andotp.Tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.Constants;
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.StorageAccessHelper;
|
||||||
|
|
||||||
|
public class PGPBackupTask extends GenericBackupTask {
|
||||||
|
private final String payload;
|
||||||
|
|
||||||
|
public PGPBackupTask(Context context, String payload, @Nullable Uri uri) {
|
||||||
|
super(context, uri);
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
protected Constants.BackupType getBackupType() {
|
||||||
|
return Constants.BackupType.OPEN_PGP;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doBackup() {
|
||||||
|
return StorageAccessHelper.saveFile(applicationContext, uri, payload);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.shadowice.flocke.andotp.Tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.shadowice.flocke.andotp.Database.Entry;
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.Constants;
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.DatabaseHelper;
|
||||||
|
import org.shadowice.flocke.andotp.Utilities.StorageAccessHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class PlainTextBackupTask extends GenericBackupTask {
|
||||||
|
private final ArrayList<Entry> entries;
|
||||||
|
|
||||||
|
public PlainTextBackupTask(Context context, ArrayList<Entry> entries, @Nullable Uri uri) {
|
||||||
|
super(context, uri);
|
||||||
|
this.entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
protected Constants.BackupType getBackupType() {
|
||||||
|
return Constants.BackupType.PLAIN_TEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doBackup() {
|
||||||
|
String payload = DatabaseHelper.entriesToString(entries);
|
||||||
|
return StorageAccessHelper.saveFile(applicationContext, uri, payload);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,13 +7,10 @@ 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;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
@ -104,22 +101,6 @@ public class BackupHelper {
|
||||||
return Constants.BackupType.UNAVAILABLE;
|
return Constants.BackupType.UNAVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void backupToFileAsync(Context context, Uri uri, String password, SecretKey encryptionKey, BackupTask.BackupCallback callback, boolean silent) {
|
|
||||||
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(context, encryptionKey);
|
|
||||||
|
|
||||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
||||||
|
|
||||||
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);
|
||||||
String plain = DatabaseHelper.entriesToString(entries);
|
String plain = DatabaseHelper.entriesToString(entries);
|
||||||
|
|
Loading…
Reference in a new issue