mirror of
https://github.com/moparisthebest/k-9
synced 2024-12-26 01:28:50 -05:00
Build a structure to allow for more easily creating new versions of
preferences Storage importers/exporters. Password/encryption key prompting is now down in centralized place. On import, the password prompt is given if the file to be imported uses an importer implementation that requires a password and no password is provided. On export, the password prompt is given if the chosen version is for an exporter that requires a password and no password was provided. For instance, for automatic backups, a password could be stored in preferences and provided to the exporter, so no password prompt would be given.
This commit is contained in:
parent
19bff64672
commit
89bdbdce94
@ -1039,12 +1039,14 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
|
|||||||
<string name="settings_import_success_multiple">Imported <xliff:g id="numAccounts">%s</xliff:g> accounts from <xliff:g id="filename">%s</xliff:g></string>
|
<string name="settings_import_success_multiple">Imported <xliff:g id="numAccounts">%s</xliff:g> accounts from <xliff:g id="filename">%s</xliff:g></string>
|
||||||
<string name="settings_import_success_single">Imported 1 account from <xliff:g id="filename">%s</xliff:g></string>
|
<string name="settings_import_success_single">Imported 1 account from <xliff:g id="filename">%s</xliff:g></string>
|
||||||
<string name="settings_export_failure">Failed to export settings: <xliff:g id="reason">%s</xliff:g></string>
|
<string name="settings_export_failure">Failed to export settings: <xliff:g id="reason">%s</xliff:g></string>
|
||||||
<string name="settings_import_failure">Failed from import settings from <xliff:g id="filename">%s</xliff:g>:<xliff:g id="reason">%s</xliff:g></string>
|
<string name="settings_import_failure">Failed to import settings from <xliff:g id="filename">%s</xliff:g>:<xliff:g id="reason">%s</xliff:g></string>
|
||||||
<string name="settings_export_success_header">Export succeeded</string>
|
<string name="settings_export_success_header">Export succeeded</string>
|
||||||
<string name="settings_export_failed_header">Export failed</string>
|
<string name="settings_export_failed_header">Export failed</string>
|
||||||
<string name="settings_import_success_header">Import succeeded</string>
|
<string name="settings_import_success_header">Import succeeded</string>
|
||||||
<string name="settings_import_failed_header">Import failed</string>
|
<string name="settings_import_failed_header">Import failed</string>
|
||||||
|
|
||||||
|
<string name="settings_unknown_version">Unable to handle file of version <xliff:g id="version">%s</xliff:g></string>
|
||||||
|
|
||||||
<string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>
|
<string name="account_unavailable">Account \"<xliff:g id="account">%s</xliff:g>\" is unavailable; check storage</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity;
|
package com.fsck.k9.activity;
|
||||||
|
|
||||||
import android.app.Activity;
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
@ -15,31 +24,46 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.*;
|
import android.view.ContextMenu;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.Window;
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.widget.*;
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
|
|
||||||
import com.fsck.k9.*;
|
import com.fsck.k9.Account;
|
||||||
import com.fsck.k9.helper.SizeFormatter;
|
import com.fsck.k9.AccountStats;
|
||||||
|
import com.fsck.k9.BaseAccount;
|
||||||
|
import com.fsck.k9.FontSizes;
|
||||||
|
import com.fsck.k9.K9;
|
||||||
|
import com.fsck.k9.Preferences;
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
import com.fsck.k9.SearchAccount;
|
||||||
|
import com.fsck.k9.SearchSpecification;
|
||||||
import com.fsck.k9.activity.setup.AccountSettings;
|
import com.fsck.k9.activity.setup.AccountSettings;
|
||||||
import com.fsck.k9.activity.setup.AccountSetupBasics;
|
import com.fsck.k9.activity.setup.AccountSetupBasics;
|
||||||
import com.fsck.k9.activity.setup.Prefs;
|
import com.fsck.k9.activity.setup.Prefs;
|
||||||
import com.fsck.k9.controller.MessagingController;
|
import com.fsck.k9.controller.MessagingController;
|
||||||
import com.fsck.k9.controller.MessagingListener;
|
import com.fsck.k9.controller.MessagingListener;
|
||||||
|
import com.fsck.k9.helper.SizeFormatter;
|
||||||
import com.fsck.k9.mail.Flag;
|
import com.fsck.k9.mail.Flag;
|
||||||
import com.fsck.k9.mail.internet.MimeUtility;
|
import com.fsck.k9.mail.internet.MimeUtility;
|
||||||
import com.fsck.k9.mail.store.StorageManager;
|
import com.fsck.k9.mail.store.StorageManager;
|
||||||
import com.fsck.k9.view.ColorChip;
|
import com.fsck.k9.view.ColorChip;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public class Accounts extends K9ListActivity implements OnItemClickListener, OnClickListener {
|
public class Accounts extends K9ListActivity implements OnItemClickListener, OnClickListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -832,68 +856,76 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
|
|||||||
|
|
||||||
private void onImport(Uri uri) {
|
private void onImport(Uri uri) {
|
||||||
Log.i(K9.LOG_TAG, "onImport importing from URI " + uri.getPath());
|
Log.i(K9.LOG_TAG, "onImport importing from URI " + uri.getPath());
|
||||||
try {
|
|
||||||
final String fileName = uri.getPath();
|
|
||||||
ContentResolver resolver = getContentResolver();
|
|
||||||
final InputStream is = resolver.openInputStream(uri);
|
|
||||||
|
|
||||||
PasswordEntryDialog dialog = new PasswordEntryDialog(this, getString(R.string.settings_encryption_password_prompt),
|
final String fileName = uri.getPath();
|
||||||
new PasswordEntryDialog.PasswordEntryListener() {
|
AsyncUIProcessor.getInstance(Accounts.this.getApplication()).importSettings(this, uri, new ImportListener()
|
||||||
public void passwordChosen(String chosenPassword) {
|
{
|
||||||
String toastText = Accounts.this.getString(R.string.settings_importing);
|
|
||||||
Toast toast = Toast.makeText(Accounts.this.getApplication(), toastText, Toast.LENGTH_SHORT);
|
|
||||||
toast.show();
|
|
||||||
mHandler.progress(true);
|
|
||||||
AsyncUIProcessor.getInstance(Accounts.this.getApplication()).importSettings(is, chosenPassword, new ImportListener() {
|
|
||||||
public void failure(final String message, Exception e) {
|
|
||||||
Accounts.this.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
mHandler.progress(false);
|
|
||||||
showDialog(Accounts.this, R.string.settings_import_failed_header, Accounts.this.getString(R.string.settings_import_failure, fileName, message));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void importSuccess(final int numAccounts) {
|
|
||||||
Accounts.this.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
mHandler.progress(false);
|
|
||||||
String messageText =
|
|
||||||
numAccounts != 1
|
|
||||||
? Accounts.this.getString(R.string.settings_import_success_multiple, numAccounts, fileName)
|
|
||||||
: Accounts.this.getString(R.string.settings_import_success_single, fileName);
|
|
||||||
showDialog(Accounts.this, R.string.settings_import_success_header, messageText);
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel() {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dialog.show();
|
|
||||||
} catch (FileNotFoundException fnfe) {
|
|
||||||
String toastText = Accounts.this.getString(R.string.settings_import_failure, uri.getPath(), fnfe.getMessage());
|
|
||||||
Toast toast = Toast.makeText(Accounts.this.getApplication(), toastText, 1);
|
|
||||||
toast.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void showDialog(final Activity activity, int headerRes, String message) {
|
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
||||||
builder.setTitle(headerRes);
|
|
||||||
builder.setMessage(message);
|
|
||||||
builder.setPositiveButton(R.string.okay_action,
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void success(int numAccounts)
|
||||||
dialog.dismiss();
|
{
|
||||||
|
mHandler.progress(false);
|
||||||
|
String messageText =
|
||||||
|
numAccounts != 1
|
||||||
|
? Accounts.this.getString(R.string.settings_import_success_multiple, numAccounts, fileName)
|
||||||
|
: Accounts.this.getString(R.string.settings_import_success_single, fileName);
|
||||||
|
showDialog(Accounts.this, R.string.settings_import_success_header, messageText);
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failure(String message, Exception e)
|
||||||
|
{
|
||||||
|
mHandler.progress(false);
|
||||||
|
showDialog(Accounts.this, R.string.settings_import_failed_header, Accounts.this.getString(R.string.settings_import_failure, fileName, e.getLocalizedMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void canceled()
|
||||||
|
{
|
||||||
|
mHandler.progress(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void started()
|
||||||
|
{
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
mHandler.progress(true);
|
||||||
|
String toastText = Accounts.this.getString(R.string.settings_importing);
|
||||||
|
Toast toast = Toast.makeText(Accounts.this, toastText, Toast.LENGTH_SHORT);
|
||||||
|
toast.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private void showDialog(final Context context, final int headerRes, final String message) {
|
||||||
|
this.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
|
builder.setTitle(headerRes);
|
||||||
|
builder.setMessage(message);
|
||||||
|
builder.setPositiveButton(R.string.okay_action,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccountsAdapter extends ArrayAdapter<BaseAccount> {
|
class AccountsAdapter extends ArrayAdapter<BaseAccount> {
|
||||||
|
@ -5,8 +5,12 @@ import java.io.InputStream;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.helper.Utility;
|
import com.fsck.k9.helper.Utility;
|
||||||
@ -33,7 +37,10 @@ public class AsyncUIProcessor {
|
|||||||
}
|
}
|
||||||
return inst;
|
return inst;
|
||||||
}
|
}
|
||||||
public void exportSettings(final String uuid, final String encryptionKey, final ExportListener listener) {
|
public void execute(Runnable runnable) {
|
||||||
|
threadPool.execute(runnable);
|
||||||
|
}
|
||||||
|
public void exportSettings(final Activity activity, final String version, final String uuid, final ExportListener listener) {
|
||||||
threadPool.execute(new Runnable() {
|
threadPool.execute(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -46,11 +53,9 @@ public class AsyncUIProcessor {
|
|||||||
dir.mkdirs();
|
dir.mkdirs();
|
||||||
File file = Utility.createUniqueFile(dir, "settings.k9s");
|
File file = Utility.createUniqueFile(dir, "settings.k9s");
|
||||||
String fileName = file.getAbsolutePath();
|
String fileName = file.getAbsolutePath();
|
||||||
StorageExporter.exportPreferences(mApplication, uuid, fileName, encryptionKey);
|
StorageExporter.exportPreferences(activity, version, uuid, fileName, null, listener);
|
||||||
if (listener != null) {
|
|
||||||
listener.exportSuccess(fileName);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
Log.w(K9.LOG_TAG, "Exception during export", e);
|
||||||
listener.failure(e.getLocalizedMessage(), e);
|
listener.failure(e.getLocalizedMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,43 +63,73 @@ public class AsyncUIProcessor {
|
|||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
public void importSettings(final String fileName, final String encryptionKey, final ImportListener listener) {
|
|
||||||
threadPool.execute(new Runnable() {
|
|
||||||
|
|
||||||
|
public void importSettings(final Activity activity, final Uri uri, final ImportListener listener) {
|
||||||
|
threadPool.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
InputStream is = null;
|
||||||
try {
|
try {
|
||||||
int numAccounts = StorageImporter.importPreferences(mApplication, fileName, encryptionKey);
|
ContentResolver resolver = mApplication.getContentResolver();
|
||||||
K9.setServicesEnabled(mApplication);
|
is = resolver.openInputStream(uri);
|
||||||
if (listener != null) {
|
|
||||||
listener.importSuccess(numAccounts);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
listener.failure(e.getLocalizedMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
Log.w(K9.LOG_TAG, "Exception while resolving Uri to InputStream", e);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.failure(e.getLocalizedMessage(), e);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final InputStream myIs = is;
|
||||||
|
StorageImporter.importPreferences(activity, is, null, new ImportListener() {
|
||||||
|
@Override
|
||||||
|
public void failure(String message, Exception e) {
|
||||||
|
quietClose(myIs);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.failure(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success(int numAccounts) {
|
||||||
|
quietClose(myIs);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.success(numAccounts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void canceled() {
|
||||||
|
quietClose(myIs);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.canceled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void started()
|
||||||
|
{
|
||||||
|
if (listener != null) {
|
||||||
|
listener.started();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
public void importSettings(final InputStream inputStream, final String encryptionKey, final ImportListener listener) {
|
|
||||||
threadPool.execute(new Runnable() {
|
|
||||||
|
|
||||||
@Override
|
private void quietClose(InputStream is)
|
||||||
public void run() {
|
{
|
||||||
try {
|
if (is != null) {
|
||||||
int numAccounts = StorageImporter.importPreferences(mApplication, inputStream, encryptionKey);
|
try {
|
||||||
K9.setServicesEnabled(mApplication);
|
is.close();
|
||||||
if (listener != null) {
|
}
|
||||||
listener.importSuccess(numAccounts);
|
catch (Exception e) {
|
||||||
}
|
Log.w(K9.LOG_TAG, "Unable to close inputStream", e);
|
||||||
} catch (Exception e) {
|
|
||||||
listener.failure(e.getLocalizedMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,59 +7,90 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import com.fsck.k9.Account;
|
import com.fsck.k9.Account;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
|
import com.fsck.k9.preferences.StorageVersioning;
|
||||||
|
|
||||||
public class ExportHelper {
|
public class ExportHelper {
|
||||||
public static void exportSettings(final Activity activity, final Progressable progressable, final Account account) {
|
public static void exportSettings(final Activity activity, final Account account, final ExportListener listener) {
|
||||||
PasswordEntryDialog dialog = new PasswordEntryDialog(activity, activity.getString(R.string.settings_encryption_password_prompt),
|
// Once there are more versions, build a UI to select which one to use. For now, use the encrypted/encoded version:
|
||||||
new PasswordEntryDialog.PasswordEntryListener() {
|
String version = StorageVersioning.STORAGE_VERSION.VERSION1.getVersionString();
|
||||||
public void passwordChosen(String chosenPassword) {
|
String uuid = null;
|
||||||
String toastText = activity.getString(R.string.settings_exporting);
|
if (account != null) {
|
||||||
Toast toast = Toast.makeText(activity, toastText, Toast.LENGTH_SHORT);
|
uuid = account.getUuid();
|
||||||
toast.show();
|
}
|
||||||
progressable.setProgress(true);
|
AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, version, uuid, new ExportListener() {
|
||||||
String uuid = null;
|
|
||||||
if (account != null) {
|
|
||||||
uuid = account.getUuid();
|
|
||||||
}
|
|
||||||
AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(uuid, chosenPassword,
|
|
||||||
new ExportListener() {
|
|
||||||
public void failure(final String message, Exception e) {
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
progressable.setProgress(false);
|
|
||||||
showDialog(activity, R.string.settings_export_failed_header, activity.getString(R.string.settings_export_failure, message));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void exportSuccess(final String fileName) {
|
@Override
|
||||||
activity.runOnUiThread(new Runnable() {
|
public void canceled()
|
||||||
public void run() {
|
{
|
||||||
progressable.setProgress(false);
|
if (listener != null) {
|
||||||
showDialog(activity, R.string.settings_export_success_header, activity.getString(R.string.settings_export_success, fileName));
|
listener.canceled();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failure(String message, Exception e)
|
||||||
|
{
|
||||||
|
if (listener != null) {
|
||||||
|
listener.failure(message, e);
|
||||||
|
}
|
||||||
|
showDialog(activity, R.string.settings_export_failed_header, activity.getString(R.string.settings_export_failure, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void started()
|
||||||
|
{
|
||||||
|
if (listener != null) {
|
||||||
|
listener.started();
|
||||||
|
}
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
String toastText = activity.getString(R.string.settings_exporting);
|
||||||
|
Toast toast = Toast.makeText(activity, toastText, Toast.LENGTH_SHORT);
|
||||||
|
toast.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
@Override
|
||||||
|
public void success(String fileName)
|
||||||
|
{
|
||||||
|
if (listener != null) {
|
||||||
|
listener.success(fileName);
|
||||||
|
}
|
||||||
|
showDialog(activity, R.string.settings_export_success_header, activity.getString(R.string.settings_export_success, fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success()
|
||||||
|
{
|
||||||
|
// This one should never be called here because the AsyncUIProcessor will generate a filename
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dialog.show();
|
|
||||||
}
|
}
|
||||||
private static void showDialog(final Activity activity, int headerRes, String message) {
|
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
private static void showDialog(final Activity activity, final int headerRes, final String message) {
|
||||||
builder.setTitle(headerRes);
|
activity.runOnUiThread(new Runnable() {
|
||||||
builder.setMessage(message);
|
|
||||||
builder.setPositiveButton(R.string.okay_action,
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void run()
|
||||||
dialog.dismiss();
|
{
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||||
|
builder.setTitle(headerRes);
|
||||||
|
builder.setMessage(message);
|
||||||
|
builder.setPositiveButton(R.string.okay_action,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
package com.fsck.k9.activity;
|
package com.fsck.k9.activity;
|
||||||
|
|
||||||
public interface ExportListener {
|
public interface ExportListener {
|
||||||
public void exportSuccess(String fileName);
|
public void success(String fileName);
|
||||||
|
public void success();
|
||||||
|
|
||||||
public void failure(String message, Exception e);
|
public void failure(String message, Exception e);
|
||||||
|
|
||||||
|
public void canceled();
|
||||||
|
|
||||||
|
public void started();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package com.fsck.k9.activity;
|
package com.fsck.k9.activity;
|
||||||
|
|
||||||
public interface ImportListener {
|
public interface ImportListener {
|
||||||
public void importSuccess(int numAccounts);
|
public void success(int numAccounts);
|
||||||
|
|
||||||
public void failure(String message, Exception e);
|
public void failure(String message, Exception e);
|
||||||
|
|
||||||
|
public void canceled();
|
||||||
|
|
||||||
|
public void started();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import com.fsck.k9.K9;
|
|||||||
import com.fsck.k9.helper.DateFormatter;
|
import com.fsck.k9.helper.DateFormatter;
|
||||||
|
|
||||||
|
|
||||||
public class K9Activity extends Activity implements Progressable {
|
public class K9Activity extends Activity {
|
||||||
private GestureDetector gestureDetector;
|
private GestureDetector gestureDetector;
|
||||||
|
|
||||||
protected ScrollView mTopView;
|
protected ScrollView mTopView;
|
||||||
@ -167,7 +167,39 @@ public class K9Activity extends Activity implements Progressable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onExport(final Account account) {
|
public void onExport(final Account account) {
|
||||||
ExportHelper.exportSettings(this, this, account);
|
ExportHelper.exportSettings(this, account, new ExportListener()
|
||||||
|
{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void canceled()
|
||||||
|
{
|
||||||
|
setProgress(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failure(String message, Exception e)
|
||||||
|
{
|
||||||
|
setProgress(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void started()
|
||||||
|
{
|
||||||
|
setProgress(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success(String fileName)
|
||||||
|
{
|
||||||
|
setProgress(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success()
|
||||||
|
{
|
||||||
|
setProgress(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import com.fsck.k9.Account;
|
|||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.helper.DateFormatter;
|
import com.fsck.k9.helper.DateFormatter;
|
||||||
|
|
||||||
public class K9ListActivity extends ListActivity implements Progressable {
|
public class K9ListActivity extends ListActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
K9Activity.setLanguage(this, K9.getK9Language());
|
K9Activity.setLanguage(this, K9.getK9Language());
|
||||||
@ -94,7 +94,39 @@ public class K9ListActivity extends ListActivity implements Progressable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onExport(final Account account) {
|
public void onExport(final Account account) {
|
||||||
ExportHelper.exportSettings(this, this, account);
|
ExportHelper.exportSettings(this, account, new ExportListener()
|
||||||
|
{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void canceled()
|
||||||
|
{
|
||||||
|
setProgress(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failure(String message, Exception e)
|
||||||
|
{
|
||||||
|
setProgress(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void started()
|
||||||
|
{
|
||||||
|
setProgress(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success(String fileName)
|
||||||
|
{
|
||||||
|
setProgress(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success()
|
||||||
|
{
|
||||||
|
setProgress(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package com.fsck.k9.activity;
|
|
||||||
|
|
||||||
public interface Progressable {
|
|
||||||
public void setProgress(boolean progress);
|
|
||||||
}
|
|
11
src/com/fsck/k9/preferences/IStorageExporter.java
Normal file
11
src/com/fsck/k9/preferences/IStorageExporter.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package com.fsck.k9.preferences;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public interface IStorageExporter
|
||||||
|
{
|
||||||
|
public boolean needsKey();
|
||||||
|
public void exportPreferences(Context context, String uuid, OutputStream os, String encryptionKey) throws StorageImportExportException;
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
package com.fsck.k9.preferences;
|
package com.fsck.k9.preferences;
|
||||||
|
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
|
import com.fsck.k9.preferences.StorageImporter.ImportElement;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
public interface IStorageImporter {
|
public interface IStorageImporter {
|
||||||
public abstract int importPreferences(Preferences preferences, SharedPreferences.Editor context, String data, String encryptionKey) throws StorageImportExportException;
|
public boolean needsKey();
|
||||||
|
public abstract int importPreferences(Preferences preferences, SharedPreferences.Editor context, ImportElement dataset, String encryptionKey) throws StorageImportExportException;
|
||||||
}
|
}
|
@ -1,112 +1,134 @@
|
|||||||
package com.fsck.k9.preferences;
|
package com.fsck.k9.preferences;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.app.Activity;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.R;
|
||||||
|
import com.fsck.k9.activity.AsyncUIProcessor;
|
||||||
|
import com.fsck.k9.activity.ExportListener;
|
||||||
|
import com.fsck.k9.activity.PasswordEntryDialog;
|
||||||
|
|
||||||
public class StorageExporter {
|
public class StorageExporter
|
||||||
public static void exportPreferences(Context context, String uuid, String fileName, String encryptionKey) throws StorageImportExportException {
|
{
|
||||||
|
private static void exportPreferences(Activity activity, String version, String uuid, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) {
|
||||||
Log.i(K9.LOG_TAG, "Exporting preferences for account " + uuid + " to file " + fileName);
|
|
||||||
File outFile = new File(fileName);
|
|
||||||
OutputStream os = null;
|
|
||||||
try {
|
try {
|
||||||
os = new FileOutputStream(outFile);
|
IStorageExporter storageExporter = StorageVersioning.createExporter(version);
|
||||||
} catch (FileNotFoundException e) {
|
if (storageExporter == null) {
|
||||||
throw new StorageImportExportException("Unable to export settings", e);
|
throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, version), null);
|
||||||
|
}
|
||||||
|
if (storageExporter.needsKey() && encryptionKey == null) {
|
||||||
|
gatherPassword(activity, storageExporter, uuid, fileName, os, listener);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
finishExport(activity, storageExporter, uuid, fileName, os, encryptionKey, listener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (listener != null) {
|
||||||
|
listener.failure(e.getLocalizedMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exportPreferences(Activity activity, String version, String uuid, String fileName, String encryptionKey, final ExportListener listener) throws StorageImportExportException {
|
||||||
|
exportPreferences(activity, version, uuid, fileName, null, encryptionKey, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exportPrefererences(Activity activity, String version, String uuid, OutputStream os, String encryptionKey, final ExportListener listener) throws StorageImportExportException {
|
||||||
|
exportPreferences(activity, version, uuid, null, os, encryptionKey, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void gatherPassword(final Activity activity, final IStorageExporter storageExporter, final String uuid, final String fileName, final OutputStream os, final ExportListener listener) {
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
PasswordEntryDialog dialog = new PasswordEntryDialog(activity, activity.getString(R.string.settings_encryption_password_prompt),
|
||||||
|
new PasswordEntryDialog.PasswordEntryListener() {
|
||||||
|
public void passwordChosen(final String chosenPassword) {
|
||||||
|
|
||||||
|
AsyncUIProcessor.getInstance(activity.getApplication()).execute(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
finishExport(activity, storageExporter, uuid, fileName, os, chosenPassword, listener);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
Log.w(K9.LOG_TAG, "Exception while finishing export", e);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.failure(e.getLocalizedMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
if (listener != null) {
|
||||||
|
listener.canceled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void finishExport(Activity activity, IStorageExporter storageExporter, String uuid, String fileName, OutputStream os, String encryptionKey, ExportListener listener) throws StorageImportExportException {
|
||||||
|
boolean needToClose = false;
|
||||||
|
if (listener != null) {
|
||||||
|
listener.started();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
exportPrefererences(context, uuid, os, encryptionKey);
|
// This needs to be after the password prompt. If the user cancels the password, we do not want
|
||||||
} finally {
|
// to create the file needlessly
|
||||||
|
if (os == null && fileName != null) {
|
||||||
|
needToClose = true;
|
||||||
|
File outFile = new File(fileName);
|
||||||
|
os = new FileOutputStream(outFile);
|
||||||
|
}
|
||||||
if (os != null) {
|
if (os != null) {
|
||||||
|
storageExporter.exportPreferences(activity, uuid, os, encryptionKey);
|
||||||
|
if (listener != null) {
|
||||||
|
if (fileName != null) {
|
||||||
|
listener.success(fileName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
listener.success();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new StorageImportExportException("Internal error; no fileName or OutputStream", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new StorageImportExportException(e.getLocalizedMessage(), e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (needToClose && os != null) {
|
||||||
try {
|
try {
|
||||||
os.close();
|
os.close();
|
||||||
} catch (Exception e) {
|
}
|
||||||
Log.i(K9.LOG_TAG, "Unable to close OutputStream for file " + fileName + ": " + e.getLocalizedMessage());
|
catch (Exception e) {
|
||||||
|
Log.w(K9.LOG_TAG, "Unable to close OutputStream", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i(K9.LOG_TAG, "Exported preferences for account " + uuid + " to file " + fileName + " which is size " + outFile.length());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void exportPrefererences(Context context, String uuid, OutputStream os, String encryptionKey) throws StorageImportExportException {
|
|
||||||
try {
|
|
||||||
Log.i(K9.LOG_TAG, "Exporting preferences for account " + uuid + " to OutputStream");
|
|
||||||
K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.ENCRYPT);
|
|
||||||
OutputStreamWriter sw = new OutputStreamWriter(os);
|
|
||||||
PrintWriter pf = new PrintWriter(sw);
|
|
||||||
long keysEvaluated = 0;
|
|
||||||
long keysExported = 0;
|
|
||||||
pf.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
|
||||||
|
|
||||||
pf.print("<k9settings version=\"1\"");
|
|
||||||
pf.println(">");
|
|
||||||
|
|
||||||
Preferences preferences = Preferences.getPreferences(context);
|
|
||||||
SharedPreferences storage = preferences.getPreferences();
|
|
||||||
|
|
||||||
Account[] accounts = preferences.getAccounts();
|
|
||||||
Set<String> accountUuids = new HashSet<String>();
|
|
||||||
for (Account account : accounts) {
|
|
||||||
accountUuids.add(account.getUuid());
|
|
||||||
}
|
|
||||||
|
|
||||||
Map < String, ? extends Object > prefs = storage.getAll();
|
|
||||||
for (Map.Entry < String, ? extends Object > entry : prefs.entrySet()) {
|
|
||||||
String key = entry.getKey();
|
|
||||||
String value = entry.getValue().toString();
|
|
||||||
//Log.i(K9.LOG_TAG, "Evaluating key " + key);
|
|
||||||
keysEvaluated++;
|
|
||||||
if (uuid != null) {
|
|
||||||
String[] comps = key.split("\\.");
|
|
||||||
String keyUuid = comps[0];
|
|
||||||
//Log.i(K9.LOG_TAG, "Got key uuid " + keyUuid);
|
|
||||||
if (uuid.equals(keyUuid) == false) {
|
|
||||||
//Log.i(K9.LOG_TAG, "Skipping key " + key + " which is for another account or global");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String[] comps = key.split("\\.");
|
|
||||||
if (comps.length > 1) {
|
|
||||||
String keyUuid = comps[0];
|
|
||||||
if (accountUuids.contains(keyUuid) == false) {
|
|
||||||
//Log.i(K9.LOG_TAG, "Skipping key " + key + " which is not for any current account");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String keyEnc = krypto.encrypt(key);
|
|
||||||
String valueEnc = krypto.encrypt(value);
|
|
||||||
String output = keyEnc + ":" + valueEnc;
|
|
||||||
//Log.i(K9.LOG_TAG, "For key " + key + ", output is " + output);
|
|
||||||
pf.println(output);
|
|
||||||
keysExported++;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pf.println("</k9settings>");
|
|
||||||
pf.flush();
|
|
||||||
|
|
||||||
Log.i(K9.LOG_TAG, "Exported " + keysExported + " settings of " + keysEvaluated
|
|
||||||
+ " total for preferences for account " + uuid);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new StorageImportExportException("Unable to encrypt settings", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
92
src/com/fsck/k9/preferences/StorageExporterVersion1.java
Normal file
92
src/com/fsck/k9/preferences/StorageExporterVersion1.java
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package com.fsck.k9.preferences;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.fsck.k9.Account;
|
||||||
|
import com.fsck.k9.K9;
|
||||||
|
import com.fsck.k9.Preferences;
|
||||||
|
|
||||||
|
public class StorageExporterVersion1 implements IStorageExporter {
|
||||||
|
public void exportPreferences(Context context, String uuid, OutputStream os, String encryptionKey) throws StorageImportExportException {
|
||||||
|
try {
|
||||||
|
Log.i(K9.LOG_TAG, "Exporting preferences for account " + uuid + " to OutputStream");
|
||||||
|
K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.ENCRYPT);
|
||||||
|
OutputStreamWriter sw = new OutputStreamWriter(os);
|
||||||
|
PrintWriter pf = new PrintWriter(sw);
|
||||||
|
long keysEvaluated = 0;
|
||||||
|
long keysExported = 0;
|
||||||
|
pf.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
||||||
|
|
||||||
|
pf.print("<k9settings version=\"1\"");
|
||||||
|
pf.println(">");
|
||||||
|
|
||||||
|
Preferences preferences = Preferences.getPreferences(context);
|
||||||
|
SharedPreferences storage = preferences.getPreferences();
|
||||||
|
|
||||||
|
Account[] accounts = preferences.getAccounts();
|
||||||
|
Set<String> accountUuids = new HashSet<String>();
|
||||||
|
for (Account account : accounts) {
|
||||||
|
accountUuids.add(account.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map < String, ? extends Object > prefs = storage.getAll();
|
||||||
|
for (Map.Entry < String, ? extends Object > entry : prefs.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
String value = entry.getValue().toString();
|
||||||
|
//Log.i(K9.LOG_TAG, "Evaluating key " + key);
|
||||||
|
keysEvaluated++;
|
||||||
|
if (uuid != null) {
|
||||||
|
String[] comps = key.split("\\.");
|
||||||
|
String keyUuid = comps[0];
|
||||||
|
//Log.i(K9.LOG_TAG, "Got key uuid " + keyUuid);
|
||||||
|
if (uuid.equals(keyUuid) == false) {
|
||||||
|
//Log.i(K9.LOG_TAG, "Skipping key " + key + " which is for another account or global");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String[] comps = key.split("\\.");
|
||||||
|
if (comps.length > 1) {
|
||||||
|
String keyUuid = comps[0];
|
||||||
|
if (accountUuids.contains(keyUuid) == false) {
|
||||||
|
//Log.i(K9.LOG_TAG, "Skipping key " + key + " which is not for any current account");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String keyEnc = krypto.encrypt(key);
|
||||||
|
String valueEnc = krypto.encrypt(value);
|
||||||
|
String output = keyEnc + ":" + valueEnc;
|
||||||
|
//Log.i(K9.LOG_TAG, "For key " + key + ", output is " + output);
|
||||||
|
pf.println(output);
|
||||||
|
keysExported++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pf.println("</k9settings>");
|
||||||
|
pf.flush();
|
||||||
|
|
||||||
|
Log.i(K9.LOG_TAG, "Exported " + keysExported + " settings of " + keysEvaluated
|
||||||
|
+ " total for preferences for account " + uuid);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new StorageImportExportException("Unable to encrypt settings", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsKey()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,10 @@
|
|||||||
package com.fsck.k9.preferences;
|
package com.fsck.k9.preferences;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import javax.xml.parsers.SAXParser;
|
import javax.xml.parsers.SAXParser;
|
||||||
import javax.xml.parsers.SAXParserFactory;
|
import javax.xml.parsers.SAXParserFactory;
|
||||||
|
|
||||||
@ -18,43 +14,22 @@ import org.xml.sax.SAXException;
|
|||||||
import org.xml.sax.XMLReader;
|
import org.xml.sax.XMLReader;
|
||||||
import org.xml.sax.helpers.DefaultHandler;
|
import org.xml.sax.helpers.DefaultHandler;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.app.Activity;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
import com.fsck.k9.activity.AsyncUIProcessor;
|
||||||
|
import com.fsck.k9.activity.ImportListener;
|
||||||
|
import com.fsck.k9.activity.PasswordEntryDialog;
|
||||||
import com.fsck.k9.helper.DateFormatter;
|
import com.fsck.k9.helper.DateFormatter;
|
||||||
|
|
||||||
public class StorageImporter {
|
public class StorageImporter {
|
||||||
public static int importPreferences(Context context, String fileName, String encryptionKey) throws StorageImportExportException {
|
|
||||||
InputStream is = null;
|
|
||||||
try {
|
|
||||||
is = new FileInputStream(fileName);
|
|
||||||
} catch (FileNotFoundException fnfe) {
|
|
||||||
throw new StorageImportExportException("Failure opening settings file " + fileName, fnfe);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public static void importPreferences(Activity activity, InputStream is, String providedEncryptionKey, ImportListener listener) {
|
||||||
try {
|
try {
|
||||||
int count = importPreferences(context, is, encryptionKey);
|
|
||||||
return count;
|
|
||||||
} finally {
|
|
||||||
if (is != null) {
|
|
||||||
try {
|
|
||||||
is.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.i(K9.LOG_TAG, "Unable to close InputStream for file " + fileName + ": " + e.getLocalizedMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
public static int importPreferences(Context context, InputStream is, String encryptionKey) throws StorageImportExportException {
|
|
||||||
try {
|
|
||||||
Preferences preferences = Preferences.getPreferences(context);
|
|
||||||
SharedPreferences storage = preferences.getPreferences();
|
|
||||||
SharedPreferences.Editor editor = storage.edit();
|
|
||||||
|
|
||||||
SAXParserFactory spf = SAXParserFactory.newInstance();
|
SAXParserFactory spf = SAXParserFactory.newInstance();
|
||||||
SAXParser sp = spf.newSAXParser();
|
SAXParser sp = spf.newSAXParser();
|
||||||
XMLReader xr = sp.getXMLReader();
|
XMLReader xr = sp.getXMLReader();
|
||||||
@ -63,48 +38,104 @@ public class StorageImporter {
|
|||||||
|
|
||||||
xr.parse(new InputSource(is));
|
xr.parse(new InputSource(is));
|
||||||
|
|
||||||
Element dataset = handler.getRootElement();
|
ImportElement dataset = handler.getRootElement();
|
||||||
String version = dataset.attributes.get("version");
|
String version = dataset.attributes.get("version");
|
||||||
Log.i(K9.LOG_TAG, "Got settings file version " + version);
|
Log.i(K9.LOG_TAG, "Got settings file version " + version);
|
||||||
|
|
||||||
IStorageImporter storageImporter = null;
|
IStorageImporter storageImporter = StorageVersioning.createImporter(version);
|
||||||
if ("1".equals(version)) {
|
if (storageImporter == null)
|
||||||
storageImporter = new StorageImporterVersion1();
|
{
|
||||||
} else {
|
throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, version));
|
||||||
throw new StorageImportExportException("Unable to read file of version " + version
|
|
||||||
+ "; (only version 1 is readable)");
|
|
||||||
}
|
}
|
||||||
int numAccounts = 0;
|
if (providedEncryptionKey != null || storageImporter.needsKey() == false) {
|
||||||
if (storageImporter != null) {
|
Log.i(K9.LOG_TAG, "Version " + version + " settings file needs encryption key");
|
||||||
String data = dataset.data.toString();
|
finishImport(activity, storageImporter, dataset, providedEncryptionKey, listener);
|
||||||
numAccounts = storageImporter.importPreferences(preferences, editor, data, encryptionKey);
|
}
|
||||||
|
else {
|
||||||
|
gatherPassword(activity, storageImporter, dataset, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (listener != null) {
|
||||||
|
listener.failure(e.getLocalizedMessage(), e);
|
||||||
}
|
}
|
||||||
editor.commit();
|
|
||||||
Preferences.getPreferences(context).refreshAccounts();
|
|
||||||
DateFormatter.clearChosenFormat();
|
|
||||||
K9.loadPrefs(Preferences.getPreferences(context));
|
|
||||||
return numAccounts;
|
|
||||||
} catch (SAXException se) {
|
|
||||||
throw new StorageImportExportException("Failure reading settings file", se);
|
|
||||||
} catch (IOException ie) {
|
|
||||||
throw new StorageImportExportException("Failure reading settings file", ie);
|
|
||||||
} catch (ParserConfigurationException pce) {
|
|
||||||
throw new StorageImportExportException("Failure reading settings file", pce);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Element {
|
private static void finishImport(Activity context, IStorageImporter storageImporter, ImportElement dataset, String encryptionKey, ImportListener listener) throws StorageImportExportException {
|
||||||
|
if (listener != null) {
|
||||||
|
listener.started();
|
||||||
|
}
|
||||||
|
Preferences preferences = Preferences.getPreferences(context);
|
||||||
|
SharedPreferences storage = preferences.getPreferences();
|
||||||
|
SharedPreferences.Editor editor = storage.edit();
|
||||||
|
int numAccounts = 0;
|
||||||
|
if (storageImporter != null) {
|
||||||
|
numAccounts = storageImporter.importPreferences(preferences, editor, dataset, encryptionKey);
|
||||||
|
}
|
||||||
|
editor.commit();
|
||||||
|
Preferences.getPreferences(context).refreshAccounts();
|
||||||
|
DateFormatter.clearChosenFormat();
|
||||||
|
K9.loadPrefs(Preferences.getPreferences(context));
|
||||||
|
K9.setServicesEnabled(context);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.success(numAccounts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void gatherPassword(final Activity activity, final IStorageImporter storageImporter, final ImportElement dataset, final ImportListener listener) {
|
||||||
|
activity.runOnUiThread(new Runnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
PasswordEntryDialog dialog = new PasswordEntryDialog(activity, activity.getString(R.string.settings_encryption_password_prompt),
|
||||||
|
new PasswordEntryDialog.PasswordEntryListener() {
|
||||||
|
public void passwordChosen(final String chosenPassword) {
|
||||||
|
AsyncUIProcessor.getInstance(activity.getApplication()).execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
finishImport(activity, storageImporter, dataset, chosenPassword, listener);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
Log.w(K9.LOG_TAG, "Failure during import", e);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.failure(e.getLocalizedMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
if (listener != null) {
|
||||||
|
listener.canceled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public static class ImportElement {
|
||||||
String name;
|
String name;
|
||||||
Map<String, String> attributes = new HashMap<String, String>();
|
Map<String, String> attributes = new HashMap<String, String>();
|
||||||
Map<String, Element> subElements = new HashMap<String, Element>();
|
Map<String, ImportElement> subElements = new HashMap<String, ImportElement>();
|
||||||
StringBuilder data = new StringBuilder();
|
StringBuilder data = new StringBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class StorageImporterHandler extends DefaultHandler {
|
private static class StorageImporterHandler extends DefaultHandler {
|
||||||
private Element rootElement = new Element();
|
private ImportElement rootElement = new ImportElement();
|
||||||
private Stack<Element> mOpenTags = new Stack<Element>();
|
private Stack<ImportElement> mOpenTags = new Stack<ImportElement>();
|
||||||
|
|
||||||
public Element getRootElement() {
|
public ImportElement getRootElement() {
|
||||||
return this.rootElement;
|
return this.rootElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +152,7 @@ public class StorageImporter {
|
|||||||
public void startElement(String namespaceURI, String localName,
|
public void startElement(String namespaceURI, String localName,
|
||||||
String qName, Attributes attributes) throws SAXException {
|
String qName, Attributes attributes) throws SAXException {
|
||||||
Log.i(K9.LOG_TAG, "Starting element " + localName);
|
Log.i(K9.LOG_TAG, "Starting element " + localName);
|
||||||
Element element = new Element();
|
ImportElement element = new ImportElement();
|
||||||
element.name = localName;
|
element.name = localName;
|
||||||
mOpenTags.push(element);
|
mOpenTags.push(element);
|
||||||
for (int i = 0; i < attributes.getLength(); i++) {
|
for (int i = 0; i < attributes.getLength(); i++) {
|
||||||
@ -135,8 +166,8 @@ public class StorageImporter {
|
|||||||
@Override
|
@Override
|
||||||
public void endElement(String namespaceURI, String localName, String qName) {
|
public void endElement(String namespaceURI, String localName, String qName) {
|
||||||
Log.i(K9.LOG_TAG, "Ending element " + localName);
|
Log.i(K9.LOG_TAG, "Ending element " + localName);
|
||||||
Element element = mOpenTags.pop();
|
ImportElement element = mOpenTags.pop();
|
||||||
Element superElement = mOpenTags.empty() ? null : mOpenTags.peek();
|
ImportElement superElement = mOpenTags.empty() ? null : mOpenTags.peek();
|
||||||
if (superElement != null) {
|
if (superElement != null) {
|
||||||
superElement.subElements.put(element.name, element);
|
superElement.subElements.put(element.name, element);
|
||||||
} else {
|
} else {
|
||||||
|
@ -14,10 +14,13 @@ import android.util.Log;
|
|||||||
import com.fsck.k9.Account;
|
import com.fsck.k9.Account;
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
|
import com.fsck.k9.preferences.StorageImporter.ImportElement;
|
||||||
|
|
||||||
public class StorageImporterVersion1 implements IStorageImporter {
|
public class StorageImporterVersion1 implements IStorageImporter {
|
||||||
public int importPreferences(Preferences preferences, SharedPreferences.Editor editor, String data, String encryptionKey) throws StorageImportExportException {
|
public int importPreferences(Preferences preferences, SharedPreferences.Editor editor, ImportElement dataset, String encryptionKey) throws StorageImportExportException {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
String data = dataset.data.toString();
|
||||||
List<Integer> accountNumbers = Account.getExistingAccountNumbers(preferences);
|
List<Integer> accountNumbers = Account.getExistingAccountNumbers(preferences);
|
||||||
Log.i(K9.LOG_TAG, "Existing accountNumbers = " + accountNumbers);
|
Log.i(K9.LOG_TAG, "Existing accountNumbers = " + accountNumbers);
|
||||||
Map<String, String> uuidMapping = new HashMap<String, String>();
|
Map<String, String> uuidMapping = new HashMap<String, String>();
|
||||||
@ -83,4 +86,10 @@ public class StorageImporterVersion1 implements IStorageImporter {
|
|||||||
throw new StorageImportExportException("Unable to decrypt settings", e);
|
throw new StorageImportExportException("Unable to decrypt settings", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needsKey()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
85
src/com/fsck/k9/preferences/StorageVersioning.java
Normal file
85
src/com/fsck/k9/preferences/StorageVersioning.java
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package com.fsck.k9.preferences;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
public class StorageVersioning
|
||||||
|
{
|
||||||
|
public enum STORAGE_VERSION {
|
||||||
|
VERSION1(StorageImporterVersion1.class, StorageExporterVersion1.class, true, STORAGE_VERSION_1);
|
||||||
|
|
||||||
|
private Class<? extends IStorageImporter> importerClass;
|
||||||
|
private Class<? extends IStorageExporter> exporterClass;
|
||||||
|
private boolean needsKey;
|
||||||
|
private String versionString;
|
||||||
|
|
||||||
|
|
||||||
|
private STORAGE_VERSION(Class<? extends IStorageImporter> imclass, Class<? extends IStorageExporter> exclass, boolean nk, String vs) {
|
||||||
|
importerClass = imclass;
|
||||||
|
exporterClass = exclass;
|
||||||
|
needsKey = nk;
|
||||||
|
versionString = vs;
|
||||||
|
|
||||||
|
}
|
||||||
|
public Class<? extends IStorageImporter> getImporterClass() {
|
||||||
|
return importerClass;
|
||||||
|
}
|
||||||
|
public IStorageImporter createImporter() throws InstantiationException, IllegalAccessException {
|
||||||
|
IStorageImporter storageImporter = importerClass.newInstance();
|
||||||
|
return storageImporter;
|
||||||
|
}
|
||||||
|
public Class<? extends IStorageExporter> getExporterClass() {
|
||||||
|
return exporterClass;
|
||||||
|
}
|
||||||
|
public IStorageExporter createExporter() throws InstantiationException, IllegalAccessException {
|
||||||
|
IStorageExporter storageExporter = exporterClass.newInstance();
|
||||||
|
return storageExporter;
|
||||||
|
}
|
||||||
|
public boolean needsKey() {
|
||||||
|
return needsKey;
|
||||||
|
}
|
||||||
|
public String getVersionString() {
|
||||||
|
return versionString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never, ever re-use these numbers!
|
||||||
|
private static final String STORAGE_VERSION_1 = "1";
|
||||||
|
|
||||||
|
public static Map<String, STORAGE_VERSION> versionMap = new HashMap<String, STORAGE_VERSION>();
|
||||||
|
static {
|
||||||
|
versionMap.put(STORAGE_VERSION.VERSION1.getVersionString(), STORAGE_VERSION.VERSION1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IStorageImporter createImporter(String version) throws InstantiationException, IllegalAccessException
|
||||||
|
{
|
||||||
|
STORAGE_VERSION storageVersion = versionMap.get(version);
|
||||||
|
if (storageVersion == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return storageVersion.createImporter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IStorageExporter createExporter(String version) throws InstantiationException, IllegalAccessException
|
||||||
|
{
|
||||||
|
STORAGE_VERSION storageVersion = versionMap.get(version);
|
||||||
|
if (storageVersion == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return storageVersion.createExporter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean needsKey(String version)
|
||||||
|
{
|
||||||
|
STORAGE_VERSION storageVersion = versionMap.get(version);
|
||||||
|
if (storageVersion == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return storageVersion.needsKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user