diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/AccountUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/account/AccountUtils.java
new file mode 100644
index 000000000..5c719b0d1
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/account/AccountUtils.java
@@ -0,0 +1,93 @@
+package org.briarproject.briar.android.account;
+
+import android.content.Context;
+import android.content.Intent;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.logging.Logger;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.os.Environment.DIRECTORY_DOWNLOADS;
+import static android.os.Environment.getExternalStoragePublicDirectory;
+import static android.widget.Toast.LENGTH_LONG;
+import static java.util.logging.Level.WARNING;
+import static java.util.logging.Logger.getLogger;
+import static org.briarproject.bramble.util.IoUtils.copyAndClose;
+import static org.briarproject.bramble.util.LogUtils.logException;
+import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
+import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI;
+
+public class AccountUtils {
+
+ private static final Logger LOG = getLogger(AccountUtils.class.getName());
+
+ private static final String[] BACKUP_DIRS =
+ {"app_db", "app_key", "shared_prefs"};
+
+ public static void exportAccount(Context ctx) {
+ try {
+ File dataDir = getDataDir(ctx);
+ File backupDir = getBackupDir(ctx);
+ for (String name : BACKUP_DIRS) {
+ copyRecursively(new File(dataDir, name),
+ new File(backupDir, name));
+ }
+ Toast.makeText(ctx, "Account exported to "
+ + backupDir.getCanonicalPath(), LENGTH_LONG).show();
+ } catch (IOException e) {
+ logException(LOG, WARNING, e);
+ Toast.makeText(ctx, "Export failed", LENGTH_LONG).show();
+ }
+ }
+
+ public static void importAccount(Context ctx) {
+ try {
+ File dataDir = getDataDir(ctx);
+ File backupDir = getBackupDir(ctx);
+ for (String name : BACKUP_DIRS) {
+ copyRecursively(new File(backupDir, name),
+ new File(dataDir, name));
+ }
+ Toast.makeText(ctx, "Account imported from "
+ + backupDir.getCanonicalPath(), LENGTH_LONG).show();
+ Intent intent = new Intent(ctx, ENTRY_ACTIVITY);
+ intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
+ intent.setData(SIGN_OUT_URI);
+ ctx.startActivity(intent);
+ } catch (IOException e) {
+ logException(LOG, WARNING, e);
+ Toast.makeText(ctx, "Import failed", LENGTH_LONG).show();
+ }
+ }
+
+ private static File getDataDir(Context ctx) {
+ return new File(ctx.getApplicationInfo().dataDir);
+ }
+
+ private static File getBackupDir(Context ctx) {
+ File downloads = getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS);
+ return new File(downloads, ctx.getPackageName());
+ }
+
+ private static void copyRecursively(File src, File dest)
+ throws IOException {
+ if (src.isDirectory()) {
+ if (!dest.isDirectory() && !dest.mkdirs()) throw new IOException();
+ File[] children = src.listFiles();
+ if (children == null) throw new IOException();
+ for (File child : children) {
+ copyRecursively(child, new File(dest, child.getName()));
+ }
+ } else if (src.isFile()) {
+ InputStream in = new FileInputStream(src);
+ OutputStream out = new FileOutputStream(dest);
+ copyAndClose(in, out);
+ }
+ }
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java
index c09f91190..2a06cf7dd 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java
@@ -32,6 +32,7 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.R;
import org.briarproject.briar.android.Localizer;
+import org.briarproject.briar.android.account.AccountUtils;
import org.briarproject.briar.android.util.UiUtils;
import java.util.ArrayList;
@@ -251,9 +252,23 @@ public class SettingsFragment extends PreferenceFragmentCompat
throw new RuntimeException("Boom!");
}
);
+ findPreference("pref_key_export").setOnPreferenceClickListener(
+ preference -> {
+ AccountUtils.exportAccount(requireContext());
+ return true;
+ }
+ );
+ findPreference("pref_key_import").setOnPreferenceClickListener(
+ preference -> {
+ AccountUtils.importAccount(requireContext());
+ return true;
+ }
+ );
} else {
findPreference("pref_key_explode").setVisible(false);
findPreference("pref_key_test_data").setVisible(false);
+ findPreference("pref_key_export").setVisible(false);
+ findPreference("pref_key_import").setVisible(false);
PreferenceGroup testing =
findPreference("pref_key_explode").getParent();
if (testing == null) throw new AssertionError();
diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml
index 21acdd98f..4a1d5d4d7 100644
--- a/briar-android/src/main/res/xml/settings.xml
+++ b/briar-android/src/main/res/xml/settings.xml
@@ -232,6 +232,16 @@
android:title="Crash"
app:iconSpaceReserved="false"/>
+
+
+
+