From 8a3e1336e0998e64650b040417ee8abfcb70bf4b Mon Sep 17 00:00:00 2001 From: danapple Date: Mon, 28 Feb 2011 21:27:58 -0600 Subject: [PATCH 001/116] Although I believe SimplyCrypto was made available without restriction, with no license, K9Krypto is a completely new, completely taint-free implementation of encryption for K-9 Mail settings files. Also, K9Krypto reuses the cryptography infrastructure between strings, so should be more efficient. --- src/com/fsck/k9/preferences/K9Krypto.java | 67 +++++++++++++ src/com/fsck/k9/preferences/SimpleCrypto.java | 94 ------------------- .../fsck/k9/preferences/StorageExporter.java | 6 +- .../preferences/StorageImporterVersion1.java | 6 +- 4 files changed, 73 insertions(+), 100 deletions(-) create mode 100644 src/com/fsck/k9/preferences/K9Krypto.java delete mode 100644 src/com/fsck/k9/preferences/SimpleCrypto.java diff --git a/src/com/fsck/k9/preferences/K9Krypto.java b/src/com/fsck/k9/preferences/K9Krypto.java new file mode 100644 index 000000000..e362a94df --- /dev/null +++ b/src/com/fsck/k9/preferences/K9Krypto.java @@ -0,0 +1,67 @@ +package com.fsck.k9.preferences; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.binary.Base64; + +public class K9Krypto +{ + final Base64 mBase64; + final Cipher mCipher; + + private final static String AES = "AES"; + private final static String SECURE_RANDOM_TYPE = "SHA1PRNG"; + + public enum MODE + { + ENCRYPT(Cipher.ENCRYPT_MODE), DECRYPT(Cipher.DECRYPT_MODE); + + int mode; + private MODE(int nMode) + { + mode = nMode; + } + } + + public K9Krypto(String key, MODE mode) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException { + mBase64 = new Base64(); + KeyGenerator keyGenerator = KeyGenerator.getInstance(AES); + SecureRandom secureRandom = SecureRandom.getInstance(SECURE_RANDOM_TYPE); + secureRandom.setSeed(key.getBytes()); + keyGenerator.init(128, secureRandom); + SecretKey secretKey = keyGenerator.generateKey(); + byte[] processedKey = secretKey.getEncoded(); + mCipher = setupCipher(mode.mode, processedKey); + } + + public String encrypt(String plainText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { + byte[] encryptedText = mCipher.doFinal(plainText.getBytes()); + byte[] encryptedEncodedText = mBase64.encode(encryptedText); + return new String(encryptedEncodedText); + } + + public String decrypt(String encryptedEncodedText) throws IllegalBlockSizeException, BadPaddingException { + byte[] encryptedText = mBase64.decode(encryptedEncodedText.getBytes()); + byte[] plainText = mCipher.doFinal(encryptedText); + return new String(plainText); + } + + private Cipher setupCipher(int mode, byte[] processedKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException + { + SecretKeySpec secretKeySpec = new SecretKeySpec(processedKey, AES); + Cipher cipher = Cipher.getInstance(AES); + cipher.init(mode, secretKeySpec); + return cipher; + } + +} diff --git a/src/com/fsck/k9/preferences/SimpleCrypto.java b/src/com/fsck/k9/preferences/SimpleCrypto.java deleted file mode 100644 index f496c9b30..000000000 --- a/src/com/fsck/k9/preferences/SimpleCrypto.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.fsck.k9.preferences; - -import java.security.SecureRandom; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.codec.binary.Base64; - - - -/** - * Copied from: - * http://www.androidsnippets.org/snippets/39/index.html - * a page which had no licensing or copyright notice - * and appeared to be intended for public use - * package net.sf.andhsli.hotspotlogin; - * Usage: - *
- * String crypto = SimpleCrypto.encrypt(masterpassword, cleartext)
- * ...
- * String cleartext = SimpleCrypto.decrypt(masterpassword, crypto)
- * 
- * @author ferenc.hechler - */ -public class SimpleCrypto { - - public static String encrypt(String seed, String cleartext, Base64 base64) throws Exception { - byte[] rawKey = getRawKey(seed.getBytes()); - byte[] result = encrypt(rawKey, cleartext.getBytes()); - return new String(base64.encode(result)); - } - - public static String decrypt(String seed, String encrypted, Base64 base64) throws Exception { - byte[] rawKey = getRawKey(seed.getBytes()); - byte[] enc = base64.decode(encrypted.getBytes()); - byte[] result = decrypt(rawKey, enc); - return new String(result); - } - - private static byte[] getRawKey(byte[] seed) throws Exception { - KeyGenerator kgen = KeyGenerator.getInstance("AES"); - SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); - sr.setSeed(seed); - kgen.init(128, sr); // 192 and 256 bits may not be available - SecretKey skey = kgen.generateKey(); - byte[] raw = skey.getEncoded(); - return raw; - } - - - private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception { - SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); - Cipher cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.ENCRYPT_MODE, skeySpec); - byte[] encrypted = cipher.doFinal(clear); - return encrypted; - } - - private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception { - SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); - Cipher cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.DECRYPT_MODE, skeySpec); - byte[] decrypted = cipher.doFinal(encrypted); - return decrypted; - } - -// -// public static byte[] toByte(String hexString) { -// int len = hexString.length()/2; -// byte[] result = new byte[len]; -// for (int i = 0; i < len; i++) -// result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue(); -// return result; -// } -// -// public static String toHex(byte[] buf) { -// if (buf == null) -// return ""; -// StringBuffer result = new StringBuffer(2*buf.length); -// for (int i = 0; i < buf.length; i++) { -// appendHex(result, buf[i]); -// } -// return result.toString(); -// } -// private final static String HEX = "0123456789ABCDEF"; -// private static void appendHex(StringBuffer sb, byte b) { -// sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f)); -// } -// -} - diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index acf5447a8..5a41042b7 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -21,7 +21,7 @@ public class StorageExporter { //public static String VALIDITY = "K-9MailExport"; // Does outputting a fixed string in a known location make the encrypted data easier to break? public static void exportPreferences(Context context, String uuid, String fileName, String encryptionKey) throws StorageImportExportException { try { - Base64 base64 = new Base64(); + K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.ENCRYPT); File outFile = new File(fileName); PrintWriter pf = new PrintWriter(outFile); long keysEvaluated = 0; @@ -68,8 +68,8 @@ public class StorageExporter { } } } - String keyEnc = SimpleCrypto.encrypt(encryptionKey, key, base64); - String valueEnc = SimpleCrypto.encrypt(encryptionKey, value, base64); + 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); diff --git a/src/com/fsck/k9/preferences/StorageImporterVersion1.java b/src/com/fsck/k9/preferences/StorageImporterVersion1.java index cfa2fc1f1..d06c08819 100644 --- a/src/com/fsck/k9/preferences/StorageImporterVersion1.java +++ b/src/com/fsck/k9/preferences/StorageImporterVersion1.java @@ -20,7 +20,6 @@ import com.fsck.k9.Preferences; public class StorageImporterVersion1 implements IStorageImporter { public int importPreferences(Preferences preferences, SharedPreferences.Editor editor, String data, String encryptionKey) throws StorageImportExportException { try { - Base64 base64 = new Base64(); List accountNumbers = Account.getExistingAccountNumbers(preferences); Log.i(K9.LOG_TAG, "Existing accountNumbers = " + accountNumbers); Map uuidMapping = new HashMap(); @@ -31,6 +30,7 @@ public class StorageImporterVersion1 implements IStorageImporter { String line = null; int settingsImported = 0; int numAccounts = 0; + K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.DECRYPT); do { line = br.readLine(); if (line != null) { @@ -39,8 +39,8 @@ public class StorageImporterVersion1 implements IStorageImporter { if (comps.length > 1) { String keyEnc = comps[0]; String valueEnc = comps[1]; - String key = SimpleCrypto.decrypt(encryptionKey, keyEnc, base64); - String value = SimpleCrypto.decrypt(encryptionKey, valueEnc, base64); + String key = krypto.decrypt(keyEnc); + String value = krypto.decrypt(valueEnc); String[] keyParts = key.split("\\."); if (keyParts.length > 1) { String oldUuid = keyParts[0]; From 9cd5f61539e900963ca3a32282160965ed2e35ac Mon Sep 17 00:00:00 2001 From: danapple Date: Tue, 1 Mar 2011 21:21:00 -0600 Subject: [PATCH 002/116] Change import/export completion Toasts to Dialogs. --- res/values/strings.xml | 5 ++++- src/com/fsck/k9/activity/Accounts.java | 26 +++++++++++++++++----- src/com/fsck/k9/activity/ExportHelper.java | 26 ++++++++++++++++------ 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 4679163f6..b20d5c56b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1040,6 +1040,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Imported 1 account from %s Failed to export settings: %s Failed from import settings from %s:%s - + Export succeeded + Export failed + Import succeeded + Import failed diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index e7a9213ce..c1f81bd4e 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1,6 +1,7 @@ package com.fsck.k9.activity; +import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.ContentResolver; @@ -815,9 +816,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC public void run() { mHandler.progress(false); - String toastText = Accounts.this.getString(R.string.settings_import_failure, fileName, message ); - Toast toast = Toast.makeText(Accounts.this.getApplication(), toastText, 1); - toast.show(); + showDialog(Accounts.this, R.string.settings_import_failed_header, Accounts.this.getString(R.string.settings_import_failure, fileName, message)); } }); } @@ -829,12 +828,11 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC public void run() { mHandler.progress(false); - String toastText = + 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 ); - Toast toast = Toast.makeText(Accounts.this.getApplication(), toastText, 1); - toast.show(); + showDialog(Accounts.this, R.string.settings_import_success_header, messageText); refresh(); } }); @@ -855,6 +853,22 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC 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 + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + + builder.show(); + } class AccountsAdapter extends ArrayAdapter { public AccountsAdapter(BaseAccount[] accounts) { diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java index 864689935..2b39a432f 100644 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ b/src/com/fsck/k9/activity/ExportHelper.java @@ -1,6 +1,8 @@ package com.fsck.k9.activity; import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.widget.Toast; import com.fsck.k9.Account; @@ -25,9 +27,7 @@ public class ExportHelper { activity.runOnUiThread(new Runnable() { public void run() { progressable.setProgress(false); - String toastText = activity.getString(R.string.settings_export_failure, message); - Toast toast = Toast.makeText(activity.getApplication(), toastText, Toast.LENGTH_LONG); - toast.show(); + showDialog(activity, R.string.settings_export_failed_header, activity.getString(R.string.settings_export_failure, message)); } }); } @@ -36,9 +36,7 @@ public class ExportHelper { activity.runOnUiThread(new Runnable() { public void run() { progressable.setProgress(false); - String toastText = activity.getString(R.string.settings_export_success, fileName); - Toast toast = Toast.makeText(activity.getApplication(), toastText, Toast.LENGTH_LONG); - toast.show(); + showDialog(activity, R.string.settings_export_success_header, activity.getString(R.string.settings_export_success, fileName)); } }); } @@ -50,5 +48,19 @@ public class ExportHelper { }); dialog.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 + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + + builder.show(); + } } From 88f6034cbbddd38293cb376501aca1111c2ccf68 Mon Sep 17 00:00:00 2001 From: danapple Date: Tue, 1 Mar 2011 21:43:11 -0600 Subject: [PATCH 003/116] When emailing a K-9 settings export file, use specific special content type. --- src/com/fsck/k9/mail/internet/MimeUtility.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index be318d998..253b2b082 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -341,6 +341,7 @@ public class MimeUtility { { "jpm", "video/jpm"}, { "js", "application/x-javascript"}, { "json", "application/json"}, + { "k9s", "application/x-k9settings"}, { "kar", "audio/midi"}, { "karbon", "application/vnd.kde.karbon"}, { "kfo", "application/vnd.kde.kformula"}, From d9cb84047b8da29c9af3d4c09e783590003a1b76 Mon Sep 17 00:00:00 2001 From: danapple Date: Tue, 1 Mar 2011 21:43:43 -0600 Subject: [PATCH 004/116] Delete obsolete import --- src/com/fsck/k9/preferences/StorageImporterVersion1.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageImporterVersion1.java b/src/com/fsck/k9/preferences/StorageImporterVersion1.java index d06c08819..7b286c6af 100644 --- a/src/com/fsck/k9/preferences/StorageImporterVersion1.java +++ b/src/com/fsck/k9/preferences/StorageImporterVersion1.java @@ -8,8 +8,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import org.apache.commons.codec.binary.Base64; - import android.content.SharedPreferences; import android.util.Log; From 107408c0d9bd977e15a3f60c43f184d55f281c38 Mon Sep 17 00:00:00 2001 From: danapple Date: Tue, 1 Mar 2011 22:34:41 -0600 Subject: [PATCH 005/116] K-9 Mail now opens the Accounts Activity when started with an Intent with an enclosed URI with a content type of application/x-k9settings. This allows a user to bootstrap K-9 Mail configuration by email a settings file from one device to another, perhaps using a GMail account on the receiving end. --- AndroidManifest.xml | 6 +++++ src/com/fsck/k9/activity/Accounts.java | 22 ++++++++++++++++++- .../fsck/k9/mail/internet/MimeUtility.java | 4 ++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d35644a58..3fe7c986d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -75,6 +75,12 @@ + + + + + + Date: Wed, 2 Mar 2011 07:34:54 -0600 Subject: [PATCH 006/116] astyle --- src/com/fsck/k9/activity/Accounts.java | 111 +++++++----------- .../fsck/k9/activity/ActivityListener.java | 3 +- src/com/fsck/k9/activity/ExportHelper.java | 5 +- src/com/fsck/k9/activity/FolderList.java | 13 +- src/com/fsck/k9/activity/MessageList.java | 9 +- src/com/fsck/k9/activity/MessageView.java | 14 +-- .../k9/activity/setup/AccountSettings.java | 8 +- .../fsck/k9/mail/internet/EncoderUtil.java | 4 +- src/com/fsck/k9/mail/internet/MimeHeader.java | 3 +- src/com/fsck/k9/preferences/K9Krypto.java | 28 ++--- 10 files changed, 83 insertions(+), 115 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index d05f4cbc5..6afd1a4f2 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -66,7 +66,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private SearchAccount unreadAccount = null; private SearchAccount integratedInboxAccount = null; private FontSizes mFontSizes = K9.getFontSizes(); - + private static final int ACTIVITY_REQUEST_PICK_SETTINGS_FILE = 1; class AccountsHandler extends Handler { @@ -138,12 +138,11 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC }); } } - - public void setProgress(boolean progress) - { + + public void setProgress(boolean progress) { mHandler.progress(progress); } - + ActivityListener mListener = new ActivityListener() { @Override public void informUserOfStatus() { @@ -240,15 +239,14 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC intent.putExtra(EXTRA_STARTUP, false); context.startActivity(intent); } - + @Override - public void onNewIntent(Intent intent) - { + public void onNewIntent(Intent intent) { Uri uri = intent.getData(); Log.i(K9.LOG_TAG, "Accounts Activity got uri " + uri); if (uri != null) { ContentResolver contentResolver = getContentResolver(); - + Log.i(K9.LOG_TAG, "Accounts Activity got content of type " + contentResolver.getType(uri)); String contentType = contentResolver.getType(uri); @@ -273,7 +271,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC Account[] accounts = Preferences.getPreferences(this).getAccounts(); Intent intent = getIntent(); boolean startup = intent.getData() == null && intent.getBooleanExtra(EXTRA_STARTUP, true); - onNewIntent(intent); + onNewIntent(intent); if (startup && K9.startIntegratedInbox()) { onOpenAccount(integratedInboxAccount); finish(); @@ -299,7 +297,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC restoreAccountStats(icicle); } - + } @SuppressWarnings("unchecked") @@ -782,76 +780,61 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } } } - - private void onImport() - { + + private void onImport() { Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); startActivityForResult(Intent.createChooser(i, null), ACTIVITY_REQUEST_PICK_SETTINGS_FILE); } - + @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { + protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.i(K9.LOG_TAG, "onActivityResult requestCode = " + requestCode + ", resultCode = " + resultCode + ", data = " + data); if (resultCode != RESULT_OK) return; - if (data == null) - { + if (data == null) { return; } - switch (requestCode) - { - case ACTIVITY_REQUEST_PICK_SETTINGS_FILE: - onImport(data.getData()); - break; + switch (requestCode) { + case ACTIVITY_REQUEST_PICK_SETTINGS_FILE: + onImport(data.getData()); + break; } } - - private void onImport(Uri uri) - { + + private void onImport(Uri uri) { Log.i(K9.LOG_TAG, "onImport importing from URI " + uri.getPath()); - try - { + 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), - new PasswordEntryDialog.PasswordEntryListener() - { - public void passwordChosen(String chosenPassword) - { - String toastText = Accounts.this.getString(R.string.settings_importing ); + + PasswordEntryDialog dialog = new PasswordEntryDialog(this, getString(R.string.settings_encryption_password_prompt), + new PasswordEntryDialog.PasswordEntryListener() { + 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() - { + 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() - { + + 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 ); + 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(); } @@ -859,23 +842,19 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } }); } - - public void cancel() - { + + public void cancel() { } }); dialog.show(); - } - catch (FileNotFoundException fnfe) - { - String toastText = Accounts.this.getString(R.string.settings_import_failure, uri.getPath(), fnfe.getMessage() ); + } 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) - { + + 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); @@ -886,7 +865,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC dialog.dismiss(); } }); - + builder.show(); } diff --git a/src/com/fsck/k9/activity/ActivityListener.java b/src/com/fsck/k9/activity/ActivityListener.java index 732ffda92..9647e3352 100644 --- a/src/com/fsck/k9/activity/ActivityListener.java +++ b/src/com/fsck/k9/activity/ActivityListener.java @@ -37,8 +37,7 @@ public class ActivityListener extends MessagingListener { String displayName = mLoadingFolderName; if (K9.INBOX.equalsIgnoreCase(displayName)) { displayName = context.getString(R.string.special_mailbox_name_inbox); - } - else if ((mAccount != null) && mAccount.getOutboxFolderName().equals(displayName)) { + } else if ((mAccount != null) && mAccount.getOutboxFolderName().equals(displayName)) { displayName = context.getString(R.string.special_mailbox_name_outbox); } diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java index 2b39a432f..bed90d323 100644 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ b/src/com/fsck/k9/activity/ExportHelper.java @@ -48,8 +48,7 @@ public class ExportHelper { }); dialog.show(); } - private static void showDialog(final Activity activity, int headerRes, String message) - { + 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); @@ -60,7 +59,7 @@ public class ExportHelper { dialog.dismiss(); } }); - + builder.show(); } } diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java index b5900bc84..e1da2a781 100644 --- a/src/com/fsck/k9/activity/FolderList.java +++ b/src/com/fsck/k9/activity/FolderList.java @@ -150,12 +150,11 @@ public class FolderList extends K9ListActivity { }); } } - - public void setProgress(boolean progress) - { + + public void setProgress(boolean progress) { mHandler.progress(progress); } - + /** * This class is responsible for reloading the list of local messages for a * given folder, notifying the adapter that the message have been loaded and @@ -543,15 +542,15 @@ public class FolderList extends K9ListActivity { onCompact(mAccount); return true; - + case R.id.export: onExport(mAccount); return true; - + case R.id.export_all: onExport(null); return true; - + case R.id.display_1st_class: { setDisplayMode(FolderMode.FIRST_CLASS); return true; diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java index edfc255d4..971a0e57d 100644 --- a/src/com/fsck/k9/activity/MessageList.java +++ b/src/com/fsck/k9/activity/MessageList.java @@ -518,12 +518,11 @@ public class MessageList }); } } - - public void setProgress(boolean progress) - { + + public void setProgress(boolean progress) { mHandler.progress(progress); } - + public static void actionHandleFolder(Context context, Account account, String folder) { Intent intent = actionHandleFolderIntent(context, account, folder); context.startActivity(intent); @@ -1405,7 +1404,7 @@ public class MessageList case R.id.export: { onExport(mAccount); return true; - } + } case R.id.export_all: { onExport(null); return true; diff --git a/src/com/fsck/k9/activity/MessageView.java b/src/com/fsck/k9/activity/MessageView.java index ae63d9a17..b937a66ab 100644 --- a/src/com/fsck/k9/activity/MessageView.java +++ b/src/com/fsck/k9/activity/MessageView.java @@ -943,13 +943,13 @@ public class MessageView extends K9Activity implements OnClickListener { @Override protected Dialog onCreateDialog(final int id) { switch (id) { - case R.id.dialog_confirm_delete: - return createConfirmDeleteDialog(id); - case R.id.dialog_attachment_progress: - ProgressDialog d = new ProgressDialog(this); - d.setIndeterminate(true); - d.setTitle(R.string.dialog_attachment_progress_title); - return d; + case R.id.dialog_confirm_delete: + return createConfirmDeleteDialog(id); + case R.id.dialog_attachment_progress: + ProgressDialog d = new ProgressDialog(this); + d.setIndeterminate(true); + d.setTitle(R.string.dialog_attachment_progress_title); + return d; } return super.onCreateDialog(id); } diff --git a/src/com/fsck/k9/activity/setup/AccountSettings.java b/src/com/fsck/k9/activity/setup/AccountSettings.java index 8980b072f..e2877b989 100644 --- a/src/com/fsck/k9/activity/setup/AccountSettings.java +++ b/src/com/fsck/k9/activity/setup/AccountSettings.java @@ -853,12 +853,10 @@ public class AccountSettings extends K9PreferenceActivity { // TODO: In the future the call above should be changed to only return remote folders. // For now we just remove the Outbox folder if present. - Iterator iter = folders.iterator(); - while (iter.hasNext()) - { + Iterator iter = folders.iterator(); + while (iter.hasNext()) { Folder folder = iter.next(); - if (mAccount.getOutboxFolderName().equals(folder.getName())) - { + if (mAccount.getOutboxFolderName().equals(folder.getName())) { iter.remove(); } } diff --git a/src/com/fsck/k9/mail/internet/EncoderUtil.java b/src/com/fsck/k9/mail/internet/EncoderUtil.java index 8b1428f65..2665ee7e8 100644 --- a/src/com/fsck/k9/mail/internet/EncoderUtil.java +++ b/src/com/fsck/k9/mail/internet/EncoderUtil.java @@ -87,7 +87,7 @@ public class EncoderUtil { int encodedLength = bEncodedLength(bytes); int totalLength = prefix.length() + encodedLength - + ENC_WORD_SUFFIX.length(); + + ENC_WORD_SUFFIX.length(); if (totalLength <= ENCODED_WORD_MAX_LENGTH) { return prefix + org.apache.james.mime4j.codec.EncoderUtil.encodeB(bytes) + ENC_WORD_SUFFIX; } else { @@ -111,7 +111,7 @@ public class EncoderUtil { int encodedLength = qEncodedLength(bytes); int totalLength = prefix.length() + encodedLength - + ENC_WORD_SUFFIX.length(); + + ENC_WORD_SUFFIX.length(); if (totalLength <= ENCODED_WORD_MAX_LENGTH) { return prefix + org.apache.james.mime4j.codec.EncoderUtil.encodeQ(bytes, org.apache.james.mime4j.codec.EncoderUtil.Usage.WORD_ENTITY) + ENC_WORD_SUFFIX; } else { diff --git a/src/com/fsck/k9/mail/internet/MimeHeader.java b/src/com/fsck/k9/mail/internet/MimeHeader.java index 68f4e334f..3140f7e59 100644 --- a/src/com/fsck/k9/mail/internet/MimeHeader.java +++ b/src/com/fsck/k9/mail/internet/MimeHeader.java @@ -142,8 +142,7 @@ public class MimeHeader { } } - public void setCharset(String charset) - { + public void setCharset(String charset) { mCharset = charset; } } diff --git a/src/com/fsck/k9/preferences/K9Krypto.java b/src/com/fsck/k9/preferences/K9Krypto.java index e362a94df..adacbf1fb 100644 --- a/src/com/fsck/k9/preferences/K9Krypto.java +++ b/src/com/fsck/k9/preferences/K9Krypto.java @@ -14,25 +14,22 @@ import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; -public class K9Krypto -{ +public class K9Krypto { final Base64 mBase64; final Cipher mCipher; - + private final static String AES = "AES"; private final static String SECURE_RANDOM_TYPE = "SHA1PRNG"; - - public enum MODE - { + + public enum MODE { ENCRYPT(Cipher.ENCRYPT_MODE), DECRYPT(Cipher.DECRYPT_MODE); - + int mode; - private MODE(int nMode) - { + private MODE(int nMode) { mode = nMode; } } - + public K9Krypto(String key, MODE mode) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException { mBase64 = new Base64(); KeyGenerator keyGenerator = KeyGenerator.getInstance(AES); @@ -43,25 +40,24 @@ public class K9Krypto byte[] processedKey = secretKey.getEncoded(); mCipher = setupCipher(mode.mode, processedKey); } - + public String encrypt(String plainText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { byte[] encryptedText = mCipher.doFinal(plainText.getBytes()); byte[] encryptedEncodedText = mBase64.encode(encryptedText); return new String(encryptedEncodedText); } - + public String decrypt(String encryptedEncodedText) throws IllegalBlockSizeException, BadPaddingException { byte[] encryptedText = mBase64.decode(encryptedEncodedText.getBytes()); byte[] plainText = mCipher.doFinal(encryptedText); return new String(plainText); } - - private Cipher setupCipher(int mode, byte[] processedKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException - { + + private Cipher setupCipher(int mode, byte[] processedKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException { SecretKeySpec secretKeySpec = new SecretKeySpec(processedKey, AES); Cipher cipher = Cipher.getInstance(AES); cipher.init(mode, secretKeySpec); return cipher; } - + } From fdb38da2e5697d7f21cfa53907f7fedc235e9df0 Mon Sep 17 00:00:00 2001 From: danapple Date: Wed, 2 Mar 2011 19:28:36 -0600 Subject: [PATCH 007/116] Discourage accidental deletion of essential mime types. --- src/com/fsck/k9/mail/internet/MimeUtility.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index 1cb593aa7..c404a3be3 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -27,7 +27,10 @@ public class MimeUtility { * http://www.stdicon.com/mimetypes */ public static final String[][] MIME_TYPE_BY_EXTENSION_MAP = new String[][] { - { "", "application/octet-stream" }, + //* Do not delete the next two lines + { "", DEFAULT_ATTACHMENT_MIME_TYPE }, + { "k9s", K9_SETTINGS_MIME_TYPE}, + //* Do not delete the previous two lines { "123", "application/vnd.lotus-1-2-3"}, { "323", "text/h323"}, { "3dml", "text/vnd.in3d.3dml"}, @@ -341,7 +344,6 @@ public class MimeUtility { { "jpm", "video/jpm"}, { "js", "application/x-javascript"}, { "json", "application/json"}, - { "k9s", K9_SETTINGS_MIME_TYPE}, { "kar", "audio/midi"}, { "karbon", "application/vnd.kde.karbon"}, { "kfo", "application/vnd.kde.kformula"}, From 922487676888844fd28ef96e11133f1af83bbae8 Mon Sep 17 00:00:00 2001 From: danapple Date: Thu, 3 Mar 2011 10:00:58 -0600 Subject: [PATCH 008/116] Make exporter usable by other things handling OutputStreams, like a facility to use the Android-wide Backup Service. --- .../fsck/k9/preferences/StorageExporter.java | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 5a41042b7..a70f37caf 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -1,39 +1,62 @@ package com.fsck.k9.preferences; import java.io.File; -import java.io.IOException; +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 org.apache.commons.codec.binary.Base64; +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; -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; - public class StorageExporter { - //public static String VALIDITY = "K-9MailExport"; // Does outputting a fixed string in a known location make the encrypted data easier to break? public static void exportPreferences(Context context, String uuid, String fileName, String encryptionKey) throws StorageImportExportException { + + Log.i(K9.LOG_TAG, "Exporting preferences for account " + uuid + " to file " + fileName); + File outFile = new File(fileName); + OutputStream os = null; try { + os = new FileOutputStream(outFile); + } catch (FileNotFoundException e) { + throw new StorageImportExportException("Unable to export settings", e); + } + + try { + exportPrefererences(context, uuid, os, encryptionKey); + } finally { + if (os != null) { + try { + os.close(); + } catch (Exception e) { + Log.i(K9.LOG_TAG, "Unable to close OutputStream for file " + fileName + ": " + e.getLocalizedMessage()); + } + } + } + + 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); - File outFile = new File(fileName); - PrintWriter pf = new PrintWriter(outFile); + OutputStreamWriter sw = new OutputStreamWriter(os); + PrintWriter pf = new PrintWriter(sw); long keysEvaluated = 0; long keysExported = 0; pf.println(""); - // String testval = SimpleCrypto.encrypt(encryptionKey, VALIDITY); - pf.print(""); - Log.i(K9.LOG_TAG, "Exporting preferences for account " + uuid + " to file " + fileName); Preferences preferences = Preferences.getPreferences(context); SharedPreferences storage = preferences.getPreferences(); @@ -78,12 +101,10 @@ public class StorageExporter { } pf.println(""); - pf.close(); + pf.flush(); Log.i(K9.LOG_TAG, "Exported " + keysExported + " settings of " + keysEvaluated - + " total for preferences for account " + uuid + " to file " + fileName + " which is size " + outFile.length()); - } catch (IOException ie) { - throw new StorageImportExportException("Unable to export settings", ie); + + " total for preferences for account " + uuid); } catch (Exception e) { throw new StorageImportExportException("Unable to encrypt settings", e); } From 4f59a04fd5feecb5503ba16697b78864dd5cd4ab Mon Sep 17 00:00:00 2001 From: danapple Date: Thu, 3 Mar 2011 10:14:19 -0600 Subject: [PATCH 009/116] Make importer usable by other things handling InputStreams, like a facility to use the Android-wide Backup Service. --- .../fsck/k9/preferences/StorageImporter.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index 4991d266d..2d4f5467e 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -28,12 +28,26 @@ import com.fsck.k9.helper.DateFormatter; public class StorageImporter { public static int importPreferences(Context context, String fileName, String encryptionKey) throws StorageImportExportException { + InputStream is = null; try { - InputStream is = new FileInputStream(fileName); - return importPreferences(context, is, encryptionKey); + is = new FileInputStream(fileName); } catch (FileNotFoundException fnfe) { - throw new StorageImportExportException("Failure reading settings file " + fileName, fnfe); + throw new StorageImportExportException("Failure opening settings file " + fileName, fnfe); } + + 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 { @@ -48,13 +62,11 @@ public class StorageImporter { xr.setContentHandler(handler); xr.parse(new InputSource(is)); - is.close(); Element dataset = handler.getRootElement(); String version = dataset.attributes.get("version"); Log.i(K9.LOG_TAG, "Got settings file version " + version); - IStorageImporter storageImporter = null; if ("1".equals(version)) { storageImporter = new StorageImporterVersion1(); From 7891b24c310b7843f0036553474fcfe681ff5f00 Mon Sep 17 00:00:00 2001 From: danapple Date: Wed, 9 Mar 2011 23:04:05 -0600 Subject: [PATCH 010/116] Fixes Issue 3102 in cooperation with vincent...@gmail.com Reset service enablement and MailService scheduling whenever a storage medium is mounted or unmounted. --- src/com/fsck/k9/K9.java | 6 +----- src/com/fsck/k9/mail/store/StorageManager.java | 4 +++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java index e08cb02fb..b23aec55d 100644 --- a/src/com/fsck/k9/K9.java +++ b/src/com/fsck/k9/K9.java @@ -320,11 +320,7 @@ public class K9 extends Application { } - public static void setServicesEnabled(Context context, Integer wakeLockId) { - setServicesEnabled(context, Preferences.getPreferences(context).getAvailableAccounts().size() > 0, wakeLockId); - } - - public static void setServicesEnabled(Context context, boolean enabled, Integer wakeLockId) { + private static void setServicesEnabled(Context context, boolean enabled, Integer wakeLockId) { PackageManager pm = context.getPackageManager(); diff --git a/src/com/fsck/k9/mail/store/StorageManager.java b/src/com/fsck/k9/mail/store/StorageManager.java index 9c52b7114..ab861ecb8 100644 --- a/src/com/fsck/k9/mail/store/StorageManager.java +++ b/src/com/fsck/k9/mail/store/StorageManager.java @@ -659,6 +659,8 @@ public class StorageManager { sync.writeLock.lock(); sync.unmounting = false; sync.writeLock.unlock(); + + K9.setServicesEnabled(K9.app); } /** @@ -684,7 +686,7 @@ public class StorageManager { } // XXX we should reset mail service ONLY if there are accounts using the storage (this is not done in a regular listener because it has to be invoked afterward) - MailService.actionReset(mApplication, null); + K9.setServicesEnabled(K9.app); } /** From c3cc43675ba29492532c789bc2ba9670f6f154ca Mon Sep 17 00:00:00 2001 From: danapple Date: Wed, 16 Mar 2011 17:05:41 -0500 Subject: [PATCH 011/116] Grey out unavailable accounts. Display a short toast when attempting to open an unavailable account. --- res/layout/accounts_item.xml | 6 ++-- res/values/strings.xml | 2 ++ src/com/fsck/k9/AccountStats.java | 1 + src/com/fsck/k9/activity/Accounts.java | 48 ++++++++++++++++---------- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/res/layout/accounts_item.xml b/res/layout/accounts_item.xml index b25c398a8..8c76ac48e 100644 --- a/res/layout/accounts_item.xml +++ b/res/layout/accounts_item.xml @@ -9,7 +9,8 @@ android:paddingRight="6dip" android:paddingBottom="2dip" android:descendantFocusability="blocksDescendants" - android:gravity="center_vertical" > + android:gravity="center_vertical" + android:background="#ccc" > + android:layout_alignParentLeft="true" + android:background="@android:color/transparent" /> Import succeeded Import failed + Account \"%s\" is unavailable; check storage + diff --git a/src/com/fsck/k9/AccountStats.java b/src/com/fsck/k9/AccountStats.java index d69685e66..146a8c94b 100644 --- a/src/com/fsck/k9/AccountStats.java +++ b/src/com/fsck/k9/AccountStats.java @@ -10,4 +10,5 @@ public class AccountStats implements Serializable { public long size = -1; public int unreadMessageCount = 0; public int flaggedMessageCount = 0; + public boolean available = true; } diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 6afd1a4f2..7076615c0 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -32,6 +32,7 @@ import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.internet.MimeUtility; +import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.view.ColorChip; import java.io.FileNotFoundException; @@ -171,6 +172,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } if (stats == null) { stats = new AccountStats(); // empty stats for unavailable accounts + stats.available = false; } accountStats.put(account.getUuid(), stats); if (account instanceof Account) { @@ -297,6 +299,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC restoreAccountStats(icicle); } + + } @@ -319,18 +323,37 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC outState.putSerializable(ACCOUNT_STATS, accountStats); } + private StorageManager.StorageListener storageListener = new StorageManager.StorageListener() + { + + @Override + public void onUnmount(String providerId) + { + refresh(); + } + + @Override + public void onMount(String providerId) + { + refresh(); + } + }; + @Override public void onResume() { super.onResume(); refresh(); MessagingController.getInstance(getApplication()).addListener(mListener); + StorageManager.getInstance(getApplication()).addListener(storageListener); } @Override public void onPause() { super.onPause(); MessagingController.getInstance(getApplication()).removeListener(mListener); + StorageManager.getInstance(getApplication()).removeListener(storageListener); + } private void refresh() { @@ -431,6 +454,10 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } else { Account realAccount = (Account)account; if (!realAccount.isAvailable(this)) { + String toastText = getString(R.string.account_unavailable, account.getDescription()); + Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_SHORT); + toast.show(); + Log.i(K9.LOG_TAG, "refusing to open account that is not available"); return false; } @@ -900,24 +927,6 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } AccountStats stats = accountStats.get(account.getUuid()); - /* - // 20101024/fiouzy: the following code throws NullPointerException because Background is null - - // display unavailable accounts translucent - if (account instanceof Account) { - Account realAccount = (Account) account; - if (realAccount.isAvailable(Accounts.this)) { - holder.email.getBackground().setAlpha(255); - holder.description.getBackground().setAlpha(255); - } else { - holder.email.getBackground().setAlpha(127); - holder.description.getBackground().setAlpha(127); - } - } else { - holder.email.getBackground().setAlpha(255); - holder.description.getBackground().setAlpha(255); - } - */ if (stats != null && account instanceof Account && stats.size >= 0) { holder.email.setText(SizeFormatter.formatSize(Accounts.this, stats.size)); holder.email.setVisibility(View.VISIBLE); @@ -948,6 +957,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC holder.flaggedMessageCount.setOnClickListener(new AccountClickListener(account, SearchModifier.FLAGGED)); holder.newMessageCount.setOnClickListener(new AccountClickListener(account, SearchModifier.UNREAD)); + + view.getBackground().setAlpha(stats.available ? 0 : 127); holder.activeIcons.setOnClickListener(new OnClickListener() { public void onClick(View v) { @@ -960,6 +971,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } else { holder.newMessageCount.setVisibility(View.GONE); holder.flaggedMessageCount.setVisibility(View.GONE); + view.getBackground().setAlpha(0); } if (account instanceof Account) { Account realAccount = (Account)account; From 19bff64672afaa0daa1ea0a6a3f9bdbcc742be02 Mon Sep 17 00:00:00 2001 From: danapple Date: Sat, 19 Mar 2011 12:45:08 -0500 Subject: [PATCH 012/116] Remove ability to export settings from the folder and message lists. However, leaving the .xml in place so that this can be easily undone later without recourse to revision control, as I suspect this might be changed again soon. --- res/menu/folder_list_option.xml | 4 +++- res/menu/message_list_option.xml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/res/menu/folder_list_option.xml b/res/menu/folder_list_option.xml index d768acdf0..a83822eda 100644 --- a/res/menu/folder_list_option.xml +++ b/res/menu/folder_list_option.xml @@ -70,10 +70,12 @@ android:title="@string/global_settings_action" android:icon="@android:drawable/ic_menu_preferences" /> + + Date: Sun, 20 Mar 2011 11:52:13 -0500 Subject: [PATCH 013/116] 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. --- res/values/strings.xml | 4 +- src/com/fsck/k9/activity/Accounts.java | 172 +++++++++------ .../fsck/k9/activity/AsyncUIProcessor.java | 101 ++++++--- src/com/fsck/k9/activity/ExportHelper.java | 117 ++++++---- src/com/fsck/k9/activity/ExportListener.java | 7 +- src/com/fsck/k9/activity/ImportListener.java | 6 +- src/com/fsck/k9/activity/K9Activity.java | 36 +++- src/com/fsck/k9/activity/K9ListActivity.java | 36 +++- src/com/fsck/k9/activity/Progressable.java | 5 - .../fsck/k9/preferences/IStorageExporter.java | 11 + .../fsck/k9/preferences/IStorageImporter.java | 4 +- .../fsck/k9/preferences/StorageExporter.java | 202 ++++++++++-------- .../preferences/StorageExporterVersion1.java | 92 ++++++++ .../fsck/k9/preferences/StorageImporter.java | 153 +++++++------ .../preferences/StorageImporterVersion1.java | 11 +- .../k9/preferences/StorageVersioning.java | 85 ++++++++ 16 files changed, 731 insertions(+), 311 deletions(-) delete mode 100644 src/com/fsck/k9/activity/Progressable.java create mode 100644 src/com/fsck/k9/preferences/IStorageExporter.java create mode 100644 src/com/fsck/k9/preferences/StorageExporterVersion1.java create mode 100644 src/com/fsck/k9/preferences/StorageVersioning.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 3f88579e2..1f4aaf395 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1039,12 +1039,14 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Imported %s accounts from %s Imported 1 account from %s Failed to export settings: %s - Failed from import settings from %s:%s + Failed to import settings from %s:%s Export succeeded Export failed Import succeeded Import failed + Unable to handle file of version %s + Account \"%s\" is unavailable; check storage diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 7076615c0..8a6373554 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1,7 +1,16 @@ 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.Dialog; import android.content.ContentResolver; @@ -15,31 +24,46 @@ import android.os.Bundle; import android.os.Handler; import android.util.Log; 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.View.OnClickListener; 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.OnItemClickListener; -import com.fsck.k9.*; -import com.fsck.k9.helper.SizeFormatter; +import com.fsck.k9.Account; +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.AccountSetupBasics; import com.fsck.k9.activity.setup.Prefs; import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingListener; +import com.fsck.k9.helper.SizeFormatter; import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.StorageManager; 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 { /** @@ -832,70 +856,78 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private void onImport(Uri uri) { 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), - new PasswordEntryDialog.PasswordEntryListener() { - 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() { + + final String fileName = uri.getPath(); + AsyncUIProcessor.getInstance(Accounts.this.getApplication()).importSettings(this, uri, new ImportListener() + { @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); + public void success(int numAccounts) + { + 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(); + } + }); + } }); - - builder.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(); + } + }); + } + class AccountsAdapter extends ArrayAdapter { public AccountsAdapter(BaseAccount[] accounts) { super(Accounts.this, 0, accounts); diff --git a/src/com/fsck/k9/activity/AsyncUIProcessor.java b/src/com/fsck/k9/activity/AsyncUIProcessor.java index 4492b4869..a2447e8df 100644 --- a/src/com/fsck/k9/activity/AsyncUIProcessor.java +++ b/src/com/fsck/k9/activity/AsyncUIProcessor.java @@ -5,8 +5,12 @@ import java.io.InputStream; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import android.app.Activity; import android.app.Application; +import android.content.ContentResolver; +import android.net.Uri; import android.os.Environment; +import android.util.Log; import com.fsck.k9.K9; import com.fsck.k9.helper.Utility; @@ -33,7 +37,10 @@ public class AsyncUIProcessor { } 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() { @Override @@ -46,11 +53,9 @@ public class AsyncUIProcessor { dir.mkdirs(); File file = Utility.createUniqueFile(dir, "settings.k9s"); String fileName = file.getAbsolutePath(); - StorageExporter.exportPreferences(mApplication, uuid, fileName, encryptionKey); - if (listener != null) { - listener.exportSuccess(fileName); - } + StorageExporter.exportPreferences(activity, version, uuid, fileName, null, listener); } catch (Exception e) { + Log.w(K9.LOG_TAG, "Exception during export", 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) { + + public void importSettings(final Activity activity, final Uri uri, final ImportListener listener) { threadPool.execute(new Runnable() { - @Override public void run() { + InputStream is = null; try { - int numAccounts = StorageImporter.importPreferences(mApplication, fileName, encryptionKey); - K9.setServicesEnabled(mApplication); - if (listener != null) { - listener.importSuccess(numAccounts); - } - } catch (Exception e) { - listener.failure(e.getLocalizedMessage(), e); + ContentResolver resolver = mApplication.getContentResolver(); + is = resolver.openInputStream(uri); } + 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 - public void run() { - try { - int numAccounts = StorageImporter.importPreferences(mApplication, inputStream, encryptionKey); - K9.setServicesEnabled(mApplication); - if (listener != null) { - listener.importSuccess(numAccounts); - } - } catch (Exception e) { - listener.failure(e.getLocalizedMessage(), e); - } + + private void quietClose(InputStream is) + { + if (is != null) { + try { + is.close(); + } + catch (Exception e) { + Log.w(K9.LOG_TAG, "Unable to close inputStream", e); } } - ); - } - + + } diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java index bed90d323..18abc92e9 100644 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ b/src/com/fsck/k9/activity/ExportHelper.java @@ -7,59 +7,90 @@ import android.widget.Toast; import com.fsck.k9.Account; import com.fsck.k9.R; +import com.fsck.k9.preferences.StorageVersioning; public class ExportHelper { - public static void exportSettings(final Activity activity, final Progressable progressable, final Account account) { - PasswordEntryDialog dialog = new PasswordEntryDialog(activity, activity.getString(R.string.settings_encryption_password_prompt), - new PasswordEntryDialog.PasswordEntryListener() { - public void passwordChosen(String chosenPassword) { - String toastText = activity.getString(R.string.settings_exporting); - Toast toast = Toast.makeText(activity, toastText, Toast.LENGTH_SHORT); - toast.show(); - progressable.setProgress(true); - 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 static void exportSettings(final Activity activity, final Account account, final ExportListener listener) { + // Once there are more versions, build a UI to select which one to use. For now, use the encrypted/encoded version: + String version = StorageVersioning.STORAGE_VERSION.VERSION1.getVersionString(); + String uuid = null; + if (account != null) { + uuid = account.getUuid(); + } + AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, version, uuid, new ExportListener() { - public void exportSuccess(final String fileName) { - activity.runOnUiThread(new Runnable() { - public void run() { - progressable.setProgress(false); - showDialog(activity, R.string.settings_export_success_header, activity.getString(R.string.settings_export_success, fileName)); - } - }); + @Override + public void canceled() + { + if (listener != null) { + 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() { - } - }); - dialog.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 - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); + 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 } }); + } + + private static void showDialog(final Activity activity, final int headerRes, final String message) { + activity.runOnUiThread(new Runnable() { - builder.show(); + @Override + public void run() + { + 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(); + } + }); + } } diff --git a/src/com/fsck/k9/activity/ExportListener.java b/src/com/fsck/k9/activity/ExportListener.java index 6c2b6f3a6..2fe10f413 100644 --- a/src/com/fsck/k9/activity/ExportListener.java +++ b/src/com/fsck/k9/activity/ExportListener.java @@ -1,8 +1,13 @@ package com.fsck.k9.activity; 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 canceled(); + + public void started(); } diff --git a/src/com/fsck/k9/activity/ImportListener.java b/src/com/fsck/k9/activity/ImportListener.java index 442b612ee..3d9d65553 100644 --- a/src/com/fsck/k9/activity/ImportListener.java +++ b/src/com/fsck/k9/activity/ImportListener.java @@ -1,8 +1,12 @@ package com.fsck.k9.activity; public interface ImportListener { - public void importSuccess(int numAccounts); + public void success(int numAccounts); public void failure(String message, Exception e); + + public void canceled(); + + public void started(); } diff --git a/src/com/fsck/k9/activity/K9Activity.java b/src/com/fsck/k9/activity/K9Activity.java index d9b29817d..7d926c2b1 100644 --- a/src/com/fsck/k9/activity/K9Activity.java +++ b/src/com/fsck/k9/activity/K9Activity.java @@ -21,7 +21,7 @@ import com.fsck.k9.K9; import com.fsck.k9.helper.DateFormatter; -public class K9Activity extends Activity implements Progressable { +public class K9Activity extends Activity { private GestureDetector gestureDetector; protected ScrollView mTopView; @@ -167,7 +167,39 @@ public class K9Activity extends Activity implements Progressable { } 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); + } + }); } } diff --git a/src/com/fsck/k9/activity/K9ListActivity.java b/src/com/fsck/k9/activity/K9ListActivity.java index 52887bd95..a8c0f83c3 100644 --- a/src/com/fsck/k9/activity/K9ListActivity.java +++ b/src/com/fsck/k9/activity/K9ListActivity.java @@ -11,7 +11,7 @@ import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.helper.DateFormatter; -public class K9ListActivity extends ListActivity implements Progressable { +public class K9ListActivity extends ListActivity { @Override public void onCreate(Bundle icicle) { K9Activity.setLanguage(this, K9.getK9Language()); @@ -94,7 +94,39 @@ public class K9ListActivity extends ListActivity implements Progressable { } 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); + } + }); } } diff --git a/src/com/fsck/k9/activity/Progressable.java b/src/com/fsck/k9/activity/Progressable.java deleted file mode 100644 index 5201e99b3..000000000 --- a/src/com/fsck/k9/activity/Progressable.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.fsck.k9.activity; - -public interface Progressable { - public void setProgress(boolean progress); -} diff --git a/src/com/fsck/k9/preferences/IStorageExporter.java b/src/com/fsck/k9/preferences/IStorageExporter.java new file mode 100644 index 000000000..5ee140403 --- /dev/null +++ b/src/com/fsck/k9/preferences/IStorageExporter.java @@ -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; +} \ No newline at end of file diff --git a/src/com/fsck/k9/preferences/IStorageImporter.java b/src/com/fsck/k9/preferences/IStorageImporter.java index 6702c4f80..415ad9bdc 100644 --- a/src/com/fsck/k9/preferences/IStorageImporter.java +++ b/src/com/fsck/k9/preferences/IStorageImporter.java @@ -1,9 +1,11 @@ package com.fsck.k9.preferences; import com.fsck.k9.Preferences; +import com.fsck.k9.preferences.StorageImporter.ImportElement; import android.content.SharedPreferences; 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; } \ No newline at end of file diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index a70f37caf..c5156baf6 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -1,112 +1,134 @@ 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.app.Activity; import android.util.Log; -import com.fsck.k9.Account; 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 static void exportPreferences(Context context, String uuid, String fileName, String encryptionKey) throws StorageImportExportException { - - Log.i(K9.LOG_TAG, "Exporting preferences for account " + uuid + " to file " + fileName); - File outFile = new File(fileName); - OutputStream os = null; +public class StorageExporter +{ + private static void exportPreferences(Activity activity, String version, String uuid, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { try { - os = new FileOutputStream(outFile); - } catch (FileNotFoundException e) { - throw new StorageImportExportException("Unable to export settings", e); + IStorageExporter storageExporter = StorageVersioning.createExporter(version); + if (storageExporter == null) { + 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 { - exportPrefererences(context, uuid, os, encryptionKey); - } finally { + // This needs to be after the password prompt. If the user cancels the password, we do not want + // to create the file needlessly + if (os == null && fileName != null) { + needToClose = true; + File outFile = new File(fileName); + os = new FileOutputStream(outFile); + } 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 { 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(""); - - pf.print(""); - - Preferences preferences = Preferences.getPreferences(context); - SharedPreferences storage = preferences.getPreferences(); - - Account[] accounts = preferences.getAccounts(); - Set accountUuids = new HashSet(); - 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(""); - 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); - } + } + } diff --git a/src/com/fsck/k9/preferences/StorageExporterVersion1.java b/src/com/fsck/k9/preferences/StorageExporterVersion1.java new file mode 100644 index 000000000..58124cd23 --- /dev/null +++ b/src/com/fsck/k9/preferences/StorageExporterVersion1.java @@ -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(""); + + pf.print(""); + + Preferences preferences = Preferences.getPreferences(context); + SharedPreferences storage = preferences.getPreferences(); + + Account[] accounts = preferences.getAccounts(); + Set accountUuids = new HashSet(); + 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(""); + 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; + } +} diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index 2d4f5467e..db5028cea 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -1,14 +1,10 @@ package com.fsck.k9.preferences; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.Stack; -import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; @@ -18,43 +14,22 @@ import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; -import android.content.Context; +import android.app.Activity; import android.content.SharedPreferences; import android.util.Log; 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.ImportListener; +import com.fsck.k9.activity.PasswordEntryDialog; import com.fsck.k9.helper.DateFormatter; 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 { - 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(); SAXParser sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); @@ -63,48 +38,104 @@ public class StorageImporter { xr.parse(new InputSource(is)); - Element dataset = handler.getRootElement(); + ImportElement dataset = handler.getRootElement(); String version = dataset.attributes.get("version"); Log.i(K9.LOG_TAG, "Got settings file version " + version); - IStorageImporter storageImporter = null; - if ("1".equals(version)) { - storageImporter = new StorageImporterVersion1(); - } else { - throw new StorageImportExportException("Unable to read file of version " + version - + "; (only version 1 is readable)"); + IStorageImporter storageImporter = StorageVersioning.createImporter(version); + if (storageImporter == null) + { + throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, version)); } - int numAccounts = 0; - if (storageImporter != null) { - String data = dataset.data.toString(); - numAccounts = storageImporter.importPreferences(preferences, editor, data, encryptionKey); + if (providedEncryptionKey != null || storageImporter.needsKey() == false) { + Log.i(K9.LOG_TAG, "Version " + version + " settings file needs encryption key"); + finishImport(activity, storageImporter, dataset, providedEncryptionKey, listener); + } + 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 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(); + } + }); + + }; + - private static class Element { + public static class ImportElement { String name; Map attributes = new HashMap(); - Map subElements = new HashMap(); + Map subElements = new HashMap(); StringBuilder data = new StringBuilder(); } private static class StorageImporterHandler extends DefaultHandler { - private Element rootElement = new Element(); - private Stack mOpenTags = new Stack(); + private ImportElement rootElement = new ImportElement(); + private Stack mOpenTags = new Stack(); - public Element getRootElement() { + public ImportElement getRootElement() { return this.rootElement; } @@ -121,7 +152,7 @@ public class StorageImporter { public void startElement(String namespaceURI, String localName, String qName, Attributes attributes) throws SAXException { Log.i(K9.LOG_TAG, "Starting element " + localName); - Element element = new Element(); + ImportElement element = new ImportElement(); element.name = localName; mOpenTags.push(element); for (int i = 0; i < attributes.getLength(); i++) { @@ -135,8 +166,8 @@ public class StorageImporter { @Override public void endElement(String namespaceURI, String localName, String qName) { Log.i(K9.LOG_TAG, "Ending element " + localName); - Element element = mOpenTags.pop(); - Element superElement = mOpenTags.empty() ? null : mOpenTags.peek(); + ImportElement element = mOpenTags.pop(); + ImportElement superElement = mOpenTags.empty() ? null : mOpenTags.peek(); if (superElement != null) { superElement.subElements.put(element.name, element); } else { diff --git a/src/com/fsck/k9/preferences/StorageImporterVersion1.java b/src/com/fsck/k9/preferences/StorageImporterVersion1.java index 7b286c6af..e19c65eb0 100644 --- a/src/com/fsck/k9/preferences/StorageImporterVersion1.java +++ b/src/com/fsck/k9/preferences/StorageImporterVersion1.java @@ -14,10 +14,13 @@ import android.util.Log; import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.Preferences; +import com.fsck.k9.preferences.StorageImporter.ImportElement; 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 { + + String data = dataset.data.toString(); List accountNumbers = Account.getExistingAccountNumbers(preferences); Log.i(K9.LOG_TAG, "Existing accountNumbers = " + accountNumbers); Map uuidMapping = new HashMap(); @@ -83,4 +86,10 @@ public class StorageImporterVersion1 implements IStorageImporter { throw new StorageImportExportException("Unable to decrypt settings", e); } } + + @Override + public boolean needsKey() + { + return true; + } } diff --git a/src/com/fsck/k9/preferences/StorageVersioning.java b/src/com/fsck/k9/preferences/StorageVersioning.java new file mode 100644 index 000000000..f78f8e281 --- /dev/null +++ b/src/com/fsck/k9/preferences/StorageVersioning.java @@ -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 importerClass; + private Class exporterClass; + private boolean needsKey; + private String versionString; + + + private STORAGE_VERSION(Class imclass, Class exclass, boolean nk, String vs) { + importerClass = imclass; + exporterClass = exclass; + needsKey = nk; + versionString = vs; + + } + public Class getImporterClass() { + return importerClass; + } + public IStorageImporter createImporter() throws InstantiationException, IllegalAccessException { + IStorageImporter storageImporter = importerClass.newInstance(); + return storageImporter; + } + public Class 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 versionMap = new HashMap(); + 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(); + } + +} From 2e7a78520942c38fd89f4388dc0980b93b4ffb3a Mon Sep 17 00:00:00 2001 From: danapple Date: Sun, 20 Mar 2011 12:01:29 -0500 Subject: [PATCH 014/116] Clarify logic for when an importer needs a password prompt. --- src/com/fsck/k9/preferences/StorageImporter.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index db5028cea..2c2ff33ad 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -47,12 +47,11 @@ public class StorageImporter { { throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, version)); } - if (providedEncryptionKey != null || storageImporter.needsKey() == false) { - Log.i(K9.LOG_TAG, "Version " + version + " settings file needs encryption key"); - finishImport(activity, storageImporter, dataset, providedEncryptionKey, listener); + if (storageImporter.needsKey() && providedEncryptionKey == null) { + gatherPassword(activity, storageImporter, dataset, listener); } else { - gatherPassword(activity, storageImporter, dataset, listener); + finishImport(activity, storageImporter, dataset, providedEncryptionKey, listener); } } catch (Exception e) From 8e5c50a8efe66ea198b3826e7cdcecde68245f16 Mon Sep 17 00:00:00 2001 From: danapple Date: Sun, 20 Mar 2011 15:21:24 -0500 Subject: [PATCH 015/116] astyle --- src/com/fsck/k9/activity/Accounts.java | 73 +- .../fsck/k9/activity/AsyncUIProcessor.java | 26 +- src/com/fsck/k9/activity/ExportHelper.java | 25 +- src/com/fsck/k9/activity/ExportListener.java | 4 +- src/com/fsck/k9/activity/ImportListener.java | 4 +- src/com/fsck/k9/activity/K9Activity.java | 18 +- src/com/fsck/k9/activity/K9ListActivity.java | 18 +- .../fsck/k9/mail/internet/MimeUtility.java | 1698 ++++++++--------- .../fsck/k9/preferences/IStorageExporter.java | 3 +- .../fsck/k9/preferences/StorageExporter.java | 92 +- .../preferences/StorageExporterVersion1.java | 3 +- .../fsck/k9/preferences/StorageImporter.java | 66 +- .../preferences/StorageImporterVersion1.java | 3 +- .../k9/preferences/StorageVersioning.java | 51 +- 14 files changed, 1010 insertions(+), 1074 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 8a6373554..0f46665c2 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -323,8 +323,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC restoreAccountStats(icicle); } - - + + } @@ -347,22 +347,19 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC outState.putSerializable(ACCOUNT_STATS, accountStats); } - private StorageManager.StorageListener storageListener = new StorageManager.StorageListener() - { - - @Override - public void onUnmount(String providerId) - { - refresh(); - } - - @Override - public void onMount(String providerId) - { - refresh(); - } - }; - + private StorageManager.StorageListener storageListener = new StorageManager.StorageListener() { + + @Override + public void onUnmount(String providerId) { + refresh(); + } + + @Override + public void onMount(String providerId) { + refresh(); + } + }; + @Override public void onResume() { super.onResume(); @@ -377,7 +374,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC super.onPause(); MessagingController.getInstance(getApplication()).removeListener(mListener); StorageManager.getInstance(getApplication()).removeListener(storageListener); - + } private void refresh() { @@ -481,7 +478,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC String toastText = getString(R.string.account_unavailable, account.getDescription()); Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_SHORT); toast.show(); - + Log.i(K9.LOG_TAG, "refusing to open account that is not available"); return false; } @@ -856,13 +853,11 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private void onImport(Uri uri) { Log.i(K9.LOG_TAG, "onImport importing from URI " + uri.getPath()); - + final String fileName = uri.getPath(); - AsyncUIProcessor.getInstance(Accounts.this.getApplication()).importSettings(this, uri, new ImportListener() - { + AsyncUIProcessor.getInstance(Accounts.this.getApplication()).importSettings(this, uri, new ImportListener() { @Override - public void success(int numAccounts) - { + public void success(int numAccounts) { mHandler.progress(false); String messageText = numAccounts != 1 @@ -871,48 +866,42 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC showDialog(Accounts.this, R.string.settings_import_success_header, messageText); runOnUiThread(new Runnable() { @Override - public void run() - { + public void run() { refresh(); } }); } - + @Override - public void failure(String message, Exception e) - { + 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() - { + public void canceled() { mHandler.progress(false); } @Override - public void started() - { + public void started() { runOnUiThread(new Runnable() { @Override - public void run() - { + 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() - { + public void run() { final AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(headerRes); builder.setMessage(message); @@ -927,7 +916,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } }); } - + class AccountsAdapter extends ArrayAdapter { public AccountsAdapter(BaseAccount[] accounts) { super(Accounts.this, 0, accounts); @@ -989,7 +978,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC holder.flaggedMessageCount.setOnClickListener(new AccountClickListener(account, SearchModifier.FLAGGED)); holder.newMessageCount.setOnClickListener(new AccountClickListener(account, SearchModifier.UNREAD)); - + view.getBackground().setAlpha(stats.available ? 0 : 127); holder.activeIcons.setOnClickListener(new OnClickListener() { diff --git a/src/com/fsck/k9/activity/AsyncUIProcessor.java b/src/com/fsck/k9/activity/AsyncUIProcessor.java index a2447e8df..9ac776aae 100644 --- a/src/com/fsck/k9/activity/AsyncUIProcessor.java +++ b/src/com/fsck/k9/activity/AsyncUIProcessor.java @@ -63,7 +63,7 @@ public class AsyncUIProcessor { ); } - + public void importSettings(final Activity activity, final Uri uri, final ImportListener listener) { threadPool.execute(new Runnable() { @Override @@ -72,12 +72,11 @@ public class AsyncUIProcessor { try { ContentResolver resolver = mApplication.getContentResolver(); is = resolver.openInputStream(uri); - } - catch (Exception 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; @@ -107,29 +106,26 @@ public class AsyncUIProcessor { } @Override - public void started() - { + public void started() { if (listener != null) { listener.started(); } } - }); + }); } } - ); + ); } - - private void quietClose(InputStream is) - { + + private void quietClose(InputStream is) { if (is != null) { try { is.close(); - } - catch (Exception e) { + } catch (Exception e) { Log.w(K9.LOG_TAG, "Unable to close inputStream", e); } } } - - + + } diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java index 18abc92e9..b019175b0 100644 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ b/src/com/fsck/k9/activity/ExportHelper.java @@ -20,16 +20,14 @@ public class ExportHelper { AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, version, uuid, new ExportListener() { @Override - public void canceled() - { + public void canceled() { if (listener != null) { listener.canceled(); } } @Override - public void failure(String message, Exception e) - { + public void failure(String message, Exception e) { if (listener != null) { listener.failure(message, e); } @@ -37,16 +35,14 @@ public class ExportHelper { } @Override - public void started() - { + public void started() { if (listener != null) { listener.started(); } activity.runOnUiThread(new Runnable() { @Override - public void run() - { + public void run() { String toastText = activity.getString(R.string.settings_exporting); Toast toast = Toast.makeText(activity, toastText, Toast.LENGTH_SHORT); toast.show(); @@ -55,8 +51,7 @@ public class ExportHelper { } @Override - public void success(String fileName) - { + public void success(String fileName) { if (listener != null) { listener.success(fileName); } @@ -64,19 +59,17 @@ public class ExportHelper { } @Override - public void success() - { + public void success() { // This one should never be called here because the AsyncUIProcessor will generate a filename } }); } - + private static void showDialog(final Activity activity, final int headerRes, final String message) { activity.runOnUiThread(new Runnable() { @Override - public void run() - { + public void run() { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(headerRes); builder.setMessage(message); @@ -91,6 +84,6 @@ public class ExportHelper { builder.show(); } }); - + } } diff --git a/src/com/fsck/k9/activity/ExportListener.java b/src/com/fsck/k9/activity/ExportListener.java index 2fe10f413..2ec062545 100644 --- a/src/com/fsck/k9/activity/ExportListener.java +++ b/src/com/fsck/k9/activity/ExportListener.java @@ -5,9 +5,9 @@ public interface ExportListener { public void success(); public void failure(String message, Exception e); - + public void canceled(); - + public void started(); } diff --git a/src/com/fsck/k9/activity/ImportListener.java b/src/com/fsck/k9/activity/ImportListener.java index 3d9d65553..255d31ebd 100644 --- a/src/com/fsck/k9/activity/ImportListener.java +++ b/src/com/fsck/k9/activity/ImportListener.java @@ -4,9 +4,9 @@ public interface ImportListener { public void success(int numAccounts); public void failure(String message, Exception e); - + public void canceled(); - + public void started(); } diff --git a/src/com/fsck/k9/activity/K9Activity.java b/src/com/fsck/k9/activity/K9Activity.java index 7d926c2b1..2a90801b7 100644 --- a/src/com/fsck/k9/activity/K9Activity.java +++ b/src/com/fsck/k9/activity/K9Activity.java @@ -167,36 +167,30 @@ public class K9Activity extends Activity { } public void onExport(final Account account) { - ExportHelper.exportSettings(this, account, new ExportListener() - { + ExportHelper.exportSettings(this, account, new ExportListener() { @Override - public void canceled() - { + public void canceled() { setProgress(false); } @Override - public void failure(String message, Exception e) - { + public void failure(String message, Exception e) { setProgress(false); } @Override - public void started() - { + public void started() { setProgress(true); } @Override - public void success(String fileName) - { + public void success(String fileName) { setProgress(false); } @Override - public void success() - { + public void success() { setProgress(false); } }); diff --git a/src/com/fsck/k9/activity/K9ListActivity.java b/src/com/fsck/k9/activity/K9ListActivity.java index a8c0f83c3..5b99ac22b 100644 --- a/src/com/fsck/k9/activity/K9ListActivity.java +++ b/src/com/fsck/k9/activity/K9ListActivity.java @@ -94,36 +94,30 @@ public class K9ListActivity extends ListActivity { } public void onExport(final Account account) { - ExportHelper.exportSettings(this, account, new ExportListener() - { + ExportHelper.exportSettings(this, account, new ExportListener() { @Override - public void canceled() - { + public void canceled() { setProgress(false); } @Override - public void failure(String message, Exception e) - { + public void failure(String message, Exception e) { setProgress(false); } @Override - public void started() - { + public void started() { setProgress(true); } @Override - public void success(String fileName) - { + public void success(String fileName) { setProgress(false); } @Override - public void success() - { + public void success() { setProgress(false); } }); diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index c404a3be3..34a75ce4c 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -28,855 +28,855 @@ public class MimeUtility { */ public static final String[][] MIME_TYPE_BY_EXTENSION_MAP = new String[][] { //* Do not delete the next two lines - { "", DEFAULT_ATTACHMENT_MIME_TYPE }, - { "k9s", K9_SETTINGS_MIME_TYPE}, - //* Do not delete the previous two lines - { "123", "application/vnd.lotus-1-2-3"}, - { "323", "text/h323"}, - { "3dml", "text/vnd.in3d.3dml"}, - { "3g2", "video/3gpp2"}, - { "3gp", "video/3gpp"}, - { "aab", "application/x-authorware-bin"}, - { "aac", "audio/x-aac"}, - { "aam", "application/x-authorware-map"}, - { "a", "application/octet-stream"}, - { "aas", "application/x-authorware-seg"}, - { "abw", "application/x-abiword"}, - { "acc", "application/vnd.americandynamics.acc"}, - { "ace", "application/x-ace-compressed"}, - { "acu", "application/vnd.acucobol"}, - { "acutc", "application/vnd.acucorp"}, - { "acx", "application/internet-property-stream"}, - { "adp", "audio/adpcm"}, - { "aep", "application/vnd.audiograph"}, - { "afm", "application/x-font-type1"}, - { "afp", "application/vnd.ibm.modcap"}, - { "ai", "application/postscript"}, - { "aif", "audio/x-aiff"}, - { "aifc", "audio/x-aiff"}, - { "aiff", "audio/x-aiff"}, - { "air", "application/vnd.adobe.air-application-installer-package+zip"}, - { "ami", "application/vnd.amiga.ami"}, - { "apk", "application/vnd.android.package-archive"}, - { "application", "application/x-ms-application"}, - { "apr", "application/vnd.lotus-approach"}, - { "asc", "application/pgp-signature"}, - { "asf", "video/x-ms-asf"}, - { "asm", "text/x-asm"}, - { "aso", "application/vnd.accpac.simply.aso"}, - { "asr", "video/x-ms-asf"}, - { "asx", "video/x-ms-asf"}, - { "atc", "application/vnd.acucorp"}, - { "atom", "application/atom+xml"}, - { "atomcat", "application/atomcat+xml"}, - { "atomsvc", "application/atomsvc+xml"}, - { "atx", "application/vnd.antix.game-component"}, - { "au", "audio/basic"}, - { "avi", "video/x-msvideo"}, - { "aw", "application/applixware"}, - { "axs", "application/olescript"}, - { "azf", "application/vnd.airzip.filesecure.azf"}, - { "azs", "application/vnd.airzip.filesecure.azs"}, - { "azw", "application/vnd.amazon.ebook"}, - { "bas", "text/plain"}, - { "bat", "application/x-msdownload"}, - { "bcpio", "application/x-bcpio"}, - { "bdf", "application/x-font-bdf"}, - { "bdm", "application/vnd.syncml.dm+wbxml"}, - { "bh2", "application/vnd.fujitsu.oasysprs"}, - { "bin", "application/octet-stream"}, - { "bmi", "application/vnd.bmi"}, - { "bmp", "image/bmp"}, - { "book", "application/vnd.framemaker"}, - { "box", "application/vnd.previewsystems.box"}, - { "boz", "application/x-bzip2"}, - { "bpk", "application/octet-stream"}, - { "btif", "image/prs.btif"}, - { "bz2", "application/x-bzip2"}, - { "bz", "application/x-bzip"}, - { "c4d", "application/vnd.clonk.c4group"}, - { "c4f", "application/vnd.clonk.c4group"}, - { "c4g", "application/vnd.clonk.c4group"}, - { "c4p", "application/vnd.clonk.c4group"}, - { "c4u", "application/vnd.clonk.c4group"}, - { "cab", "application/vnd.ms-cab-compressed"}, - { "car", "application/vnd.curl.car"}, - { "cat", "application/vnd.ms-pki.seccat"}, - { "cct", "application/x-director"}, - { "cc", "text/x-c"}, - { "ccxml", "application/ccxml+xml"}, - { "cdbcmsg", "application/vnd.contact.cmsg"}, - { "cdf", "application/x-cdf"}, - { "cdkey", "application/vnd.mediastation.cdkey"}, - { "cdx", "chemical/x-cdx"}, - { "cdxml", "application/vnd.chemdraw+xml"}, - { "cdy", "application/vnd.cinderella"}, - { "cer", "application/x-x509-ca-cert"}, - { "cgm", "image/cgm"}, - { "chat", "application/x-chat"}, - { "chm", "application/vnd.ms-htmlhelp"}, - { "chrt", "application/vnd.kde.kchart"}, - { "cif", "chemical/x-cif"}, - { "cii", "application/vnd.anser-web-certificate-issue-initiation"}, - { "cla", "application/vnd.claymore"}, - { "class", "application/java-vm"}, - { "clkk", "application/vnd.crick.clicker.keyboard"}, - { "clkp", "application/vnd.crick.clicker.palette"}, - { "clkt", "application/vnd.crick.clicker.template"}, - { "clkw", "application/vnd.crick.clicker.wordbank"}, - { "clkx", "application/vnd.crick.clicker"}, - { "clp", "application/x-msclip"}, - { "cmc", "application/vnd.cosmocaller"}, - { "cmdf", "chemical/x-cmdf"}, - { "cml", "chemical/x-cml"}, - { "cmp", "application/vnd.yellowriver-custom-menu"}, - { "cmx", "image/x-cmx"}, - { "cod", "application/vnd.rim.cod"}, - { "com", "application/x-msdownload"}, - { "conf", "text/plain"}, - { "cpio", "application/x-cpio"}, - { "cpp", "text/x-c"}, - { "cpt", "application/mac-compactpro"}, - { "crd", "application/x-mscardfile"}, - { "crl", "application/pkix-crl"}, - { "crt", "application/x-x509-ca-cert"}, - { "csh", "application/x-csh"}, - { "csml", "chemical/x-csml"}, - { "csp", "application/vnd.commonspace"}, - { "css", "text/css"}, - { "cst", "application/x-director"}, - { "csv", "text/csv"}, - { "c", "text/plain"}, - { "cu", "application/cu-seeme"}, - { "curl", "text/vnd.curl"}, - { "cww", "application/prs.cww"}, - { "cxt", "application/x-director"}, - { "cxx", "text/x-c"}, - { "daf", "application/vnd.mobius.daf"}, - { "dataless", "application/vnd.fdsn.seed"}, - { "davmount", "application/davmount+xml"}, - { "dcr", "application/x-director"}, - { "dcurl", "text/vnd.curl.dcurl"}, - { "dd2", "application/vnd.oma.dd2+xml"}, - { "ddd", "application/vnd.fujixerox.ddd"}, - { "deb", "application/x-debian-package"}, - { "def", "text/plain"}, - { "deploy", "application/octet-stream"}, - { "der", "application/x-x509-ca-cert"}, - { "dfac", "application/vnd.dreamfactory"}, - { "dic", "text/x-c"}, - { "diff", "text/plain"}, - { "dir", "application/x-director"}, - { "dis", "application/vnd.mobius.dis"}, - { "dist", "application/octet-stream"}, - { "distz", "application/octet-stream"}, - { "djv", "image/vnd.djvu"}, - { "djvu", "image/vnd.djvu"}, - { "dll", "application/x-msdownload"}, - { "dmg", "application/octet-stream"}, - { "dms", "application/octet-stream"}, - { "dna", "application/vnd.dna"}, - { "doc", "application/msword"}, - { "docm", "application/vnd.ms-word.document.macroenabled.12"}, - { "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, - { "dot", "application/msword"}, - { "dotm", "application/vnd.ms-word.template.macroenabled.12"}, - { "dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, - { "dp", "application/vnd.osgi.dp"}, - { "dpg", "application/vnd.dpgraph"}, - { "dsc", "text/prs.lines.tag"}, - { "dtb", "application/x-dtbook+xml"}, - { "dtd", "application/xml-dtd"}, - { "dts", "audio/vnd.dts"}, - { "dtshd", "audio/vnd.dts.hd"}, - { "dump", "application/octet-stream"}, - { "dvi", "application/x-dvi"}, - { "dwf", "model/vnd.dwf"}, - { "dwg", "image/vnd.dwg"}, - { "dxf", "image/vnd.dxf"}, - { "dxp", "application/vnd.spotfire.dxp"}, - { "dxr", "application/x-director"}, - { "ecelp4800", "audio/vnd.nuera.ecelp4800"}, - { "ecelp7470", "audio/vnd.nuera.ecelp7470"}, - { "ecelp9600", "audio/vnd.nuera.ecelp9600"}, - { "ecma", "application/ecmascript"}, - { "edm", "application/vnd.novadigm.edm"}, - { "edx", "application/vnd.novadigm.edx"}, - { "efif", "application/vnd.picsel"}, - { "ei6", "application/vnd.pg.osasli"}, - { "elc", "application/octet-stream"}, - { "eml", "message/rfc822"}, - { "emma", "application/emma+xml"}, - { "eol", "audio/vnd.digital-winds"}, - { "eot", "application/vnd.ms-fontobject"}, - { "eps", "application/postscript"}, - { "epub", "application/epub+zip"}, - { "es3", "application/vnd.eszigno3+xml"}, - { "esf", "application/vnd.epson.esf"}, - { "et3", "application/vnd.eszigno3+xml"}, - { "etx", "text/x-setext"}, - { "evy", "application/envoy"}, - { "exe", "application/octet-stream"}, - { "ext", "application/vnd.novadigm.ext"}, - { "ez2", "application/vnd.ezpix-album"}, - { "ez3", "application/vnd.ezpix-package"}, - { "ez", "application/andrew-inset"}, - { "f4v", "video/x-f4v"}, - { "f77", "text/x-fortran"}, - { "f90", "text/x-fortran"}, - { "fbs", "image/vnd.fastbidsheet"}, - { "fdf", "application/vnd.fdf"}, - { "fe_launch", "application/vnd.denovo.fcselayout-link"}, - { "fg5", "application/vnd.fujitsu.oasysgp"}, - { "fgd", "application/x-director"}, - { "fh4", "image/x-freehand"}, - { "fh5", "image/x-freehand"}, - { "fh7", "image/x-freehand"}, - { "fhc", "image/x-freehand"}, - { "fh", "image/x-freehand"}, - { "fif", "application/fractals"}, - { "fig", "application/x-xfig"}, - { "fli", "video/x-fli"}, - { "flo", "application/vnd.micrografx.flo"}, - { "flr", "x-world/x-vrml"}, - { "flv", "video/x-flv"}, - { "flw", "application/vnd.kde.kivio"}, - { "flx", "text/vnd.fmi.flexstor"}, - { "fly", "text/vnd.fly"}, - { "fm", "application/vnd.framemaker"}, - { "fnc", "application/vnd.frogans.fnc"}, - { "for", "text/x-fortran"}, - { "fpx", "image/vnd.fpx"}, - { "frame", "application/vnd.framemaker"}, - { "fsc", "application/vnd.fsc.weblaunch"}, - { "fst", "image/vnd.fst"}, - { "ftc", "application/vnd.fluxtime.clip"}, - { "f", "text/x-fortran"}, - { "fti", "application/vnd.anser-web-funds-transfer-initiation"}, - { "fvt", "video/vnd.fvt"}, - { "fzs", "application/vnd.fuzzysheet"}, - { "g3", "image/g3fax"}, - { "gac", "application/vnd.groove-account"}, - { "gdl", "model/vnd.gdl"}, - { "geo", "application/vnd.dynageo"}, - { "gex", "application/vnd.geometry-explorer"}, - { "ggb", "application/vnd.geogebra.file"}, - { "ggt", "application/vnd.geogebra.tool"}, - { "ghf", "application/vnd.groove-help"}, - { "gif", "image/gif"}, - { "gim", "application/vnd.groove-identity-message"}, - { "gmx", "application/vnd.gmx"}, - { "gnumeric", "application/x-gnumeric"}, - { "gph", "application/vnd.flographit"}, - { "gqf", "application/vnd.grafeq"}, - { "gqs", "application/vnd.grafeq"}, - { "gram", "application/srgs"}, - { "gre", "application/vnd.geometry-explorer"}, - { "grv", "application/vnd.groove-injector"}, - { "grxml", "application/srgs+xml"}, - { "gsf", "application/x-font-ghostscript"}, - { "gtar", "application/x-gtar"}, - { "gtm", "application/vnd.groove-tool-message"}, - { "gtw", "model/vnd.gtw"}, - { "gv", "text/vnd.graphviz"}, - { "gz", "application/x-gzip"}, - { "h261", "video/h261"}, - { "h263", "video/h263"}, - { "h264", "video/h264"}, - { "hbci", "application/vnd.hbci"}, - { "hdf", "application/x-hdf"}, - { "hh", "text/x-c"}, - { "hlp", "application/winhlp"}, - { "hpgl", "application/vnd.hp-hpgl"}, - { "hpid", "application/vnd.hp-hpid"}, - { "hps", "application/vnd.hp-hps"}, - { "hqx", "application/mac-binhex40"}, - { "hta", "application/hta"}, - { "htc", "text/x-component"}, - { "h", "text/plain"}, - { "htke", "application/vnd.kenameaapp"}, - { "html", "text/html"}, - { "htm", "text/html"}, - { "htt", "text/webviewhtml"}, - { "hvd", "application/vnd.yamaha.hv-dic"}, - { "hvp", "application/vnd.yamaha.hv-voice"}, - { "hvs", "application/vnd.yamaha.hv-script"}, - { "icc", "application/vnd.iccprofile"}, - { "ice", "x-conference/x-cooltalk"}, - { "icm", "application/vnd.iccprofile"}, - { "ico", "image/x-icon"}, - { "ics", "text/calendar"}, - { "ief", "image/ief"}, - { "ifb", "text/calendar"}, - { "ifm", "application/vnd.shana.informed.formdata"}, - { "iges", "model/iges"}, - { "igl", "application/vnd.igloader"}, - { "igs", "model/iges"}, - { "igx", "application/vnd.micrografx.igx"}, - { "iif", "application/vnd.shana.informed.interchange"}, - { "iii", "application/x-iphone"}, - { "imp", "application/vnd.accpac.simply.imp"}, - { "ims", "application/vnd.ms-ims"}, - { "ins", "application/x-internet-signup"}, - { "in", "text/plain"}, - { "ipk", "application/vnd.shana.informed.package"}, - { "irm", "application/vnd.ibm.rights-management"}, - { "irp", "application/vnd.irepository.package+xml"}, - { "iso", "application/octet-stream"}, - { "isp", "application/x-internet-signup"}, - { "itp", "application/vnd.shana.informed.formtemplate"}, - { "ivp", "application/vnd.immervision-ivp"}, - { "ivu", "application/vnd.immervision-ivu"}, - { "jad", "text/vnd.sun.j2me.app-descriptor"}, - { "jam", "application/vnd.jam"}, - { "jar", "application/java-archive"}, - { "java", "text/x-java-source"}, - { "jfif", "image/pipeg"}, - { "jisp", "application/vnd.jisp"}, - { "jlt", "application/vnd.hp-jlyt"}, - { "jnlp", "application/x-java-jnlp-file"}, - { "joda", "application/vnd.joost.joda-archive"}, - { "jpeg", "image/jpeg"}, - { "jpe", "image/jpeg"}, - { "jpg", "image/jpeg"}, - { "jpgm", "video/jpm"}, - { "jpgv", "video/jpeg"}, - { "jpm", "video/jpm"}, - { "js", "application/x-javascript"}, - { "json", "application/json"}, - { "kar", "audio/midi"}, - { "karbon", "application/vnd.kde.karbon"}, - { "kfo", "application/vnd.kde.kformula"}, - { "kia", "application/vnd.kidspiration"}, - { "kil", "application/x-killustrator"}, - { "kml", "application/vnd.google-earth.kml+xml"}, - { "kmz", "application/vnd.google-earth.kmz"}, - { "kne", "application/vnd.kinar"}, - { "knp", "application/vnd.kinar"}, - { "kon", "application/vnd.kde.kontour"}, - { "kpr", "application/vnd.kde.kpresenter"}, - { "kpt", "application/vnd.kde.kpresenter"}, - { "ksh", "text/plain"}, - { "ksp", "application/vnd.kde.kspread"}, - { "ktr", "application/vnd.kahootz"}, - { "ktz", "application/vnd.kahootz"}, - { "kwd", "application/vnd.kde.kword"}, - { "kwt", "application/vnd.kde.kword"}, - { "latex", "application/x-latex"}, - { "lbd", "application/vnd.llamagraphics.life-balance.desktop"}, - { "lbe", "application/vnd.llamagraphics.life-balance.exchange+xml"}, - { "les", "application/vnd.hhe.lesson-player"}, - { "lha", "application/octet-stream"}, - { "link66", "application/vnd.route66.link66+xml"}, - { "list3820", "application/vnd.ibm.modcap"}, - { "listafp", "application/vnd.ibm.modcap"}, - { "list", "text/plain"}, - { "log", "text/plain"}, - { "lostxml", "application/lost+xml"}, - { "lrf", "application/octet-stream"}, - { "lrm", "application/vnd.ms-lrm"}, - { "lsf", "video/x-la-asf"}, - { "lsx", "video/x-la-asf"}, - { "ltf", "application/vnd.frogans.ltf"}, - { "lvp", "audio/vnd.lucent.voice"}, - { "lwp", "application/vnd.lotus-wordpro"}, - { "lzh", "application/octet-stream"}, - { "m13", "application/x-msmediaview"}, - { "m14", "application/x-msmediaview"}, - { "m1v", "video/mpeg"}, - { "m2a", "audio/mpeg"}, - { "m2v", "video/mpeg"}, - { "m3a", "audio/mpeg"}, - { "m3u", "audio/x-mpegurl"}, - { "m4u", "video/vnd.mpegurl"}, - { "m4v", "video/x-m4v"}, - { "ma", "application/mathematica"}, - { "mag", "application/vnd.ecowin.chart"}, - { "maker", "application/vnd.framemaker"}, - { "man", "text/troff"}, - { "mathml", "application/mathml+xml"}, - { "mb", "application/mathematica"}, - { "mbk", "application/vnd.mobius.mbk"}, - { "mbox", "application/mbox"}, - { "mc1", "application/vnd.medcalcdata"}, - { "mcd", "application/vnd.mcd"}, - { "mcurl", "text/vnd.curl.mcurl"}, - { "mdb", "application/x-msaccess"}, - { "mdi", "image/vnd.ms-modi"}, - { "mesh", "model/mesh"}, - { "me", "text/troff"}, - { "mfm", "application/vnd.mfmp"}, - { "mgz", "application/vnd.proteus.magazine"}, - { "mht", "message/rfc822"}, - { "mhtml", "message/rfc822"}, - { "mid", "audio/midi"}, - { "midi", "audio/midi"}, - { "mif", "application/vnd.mif"}, - { "mime", "message/rfc822"}, - { "mj2", "video/mj2"}, - { "mjp2", "video/mj2"}, - { "mlp", "application/vnd.dolby.mlp"}, - { "mmd", "application/vnd.chipnuts.karaoke-mmd"}, - { "mmf", "application/vnd.smaf"}, - { "mmr", "image/vnd.fujixerox.edmics-mmr"}, - { "mny", "application/x-msmoney"}, - { "mobi", "application/x-mobipocket-ebook"}, - { "movie", "video/x-sgi-movie"}, - { "mov", "video/quicktime"}, - { "mp2a", "audio/mpeg"}, - { "mp2", "video/mpeg"}, - { "mp3", "audio/mpeg"}, - { "mp4a", "audio/mp4"}, - { "mp4s", "application/mp4"}, - { "mp4", "video/mp4"}, - { "mp4v", "video/mp4"}, - { "mpa", "video/mpeg"}, - { "mpc", "application/vnd.mophun.certificate"}, - { "mpeg", "video/mpeg"}, - { "mpe", "video/mpeg"}, - { "mpg4", "video/mp4"}, - { "mpga", "audio/mpeg"}, - { "mpg", "video/mpeg"}, - { "mpkg", "application/vnd.apple.installer+xml"}, - { "mpm", "application/vnd.blueice.multipass"}, - { "mpn", "application/vnd.mophun.application"}, - { "mpp", "application/vnd.ms-project"}, - { "mpt", "application/vnd.ms-project"}, - { "mpv2", "video/mpeg"}, - { "mpy", "application/vnd.ibm.minipay"}, - { "mqy", "application/vnd.mobius.mqy"}, - { "mrc", "application/marc"}, - { "mscml", "application/mediaservercontrol+xml"}, - { "mseed", "application/vnd.fdsn.mseed"}, - { "mseq", "application/vnd.mseq"}, - { "msf", "application/vnd.epson.msf"}, - { "msh", "model/mesh"}, - { "msi", "application/x-msdownload"}, - { "ms", "text/troff"}, - { "msty", "application/vnd.muvee.style"}, - { "mts", "model/vnd.mts"}, - { "mus", "application/vnd.musician"}, - { "musicxml", "application/vnd.recordare.musicxml+xml"}, - { "mvb", "application/x-msmediaview"}, - { "mxf", "application/mxf"}, - { "mxl", "application/vnd.recordare.musicxml"}, - { "mxml", "application/xv+xml"}, - { "mxs", "application/vnd.triscape.mxs"}, - { "mxu", "video/vnd.mpegurl"}, - { "nb", "application/mathematica"}, - { "nc", "application/x-netcdf"}, - { "ncx", "application/x-dtbncx+xml"}, - { "n-gage", "application/vnd.nokia.n-gage.symbian.install"}, - { "ngdat", "application/vnd.nokia.n-gage.data"}, - { "nlu", "application/vnd.neurolanguage.nlu"}, - { "nml", "application/vnd.enliven"}, - { "nnd", "application/vnd.noblenet-directory"}, - { "nns", "application/vnd.noblenet-sealer"}, - { "nnw", "application/vnd.noblenet-web"}, - { "npx", "image/vnd.net-fpx"}, - { "nsf", "application/vnd.lotus-notes"}, - { "nws", "message/rfc822"}, - { "oa2", "application/vnd.fujitsu.oasys2"}, - { "oa3", "application/vnd.fujitsu.oasys3"}, - { "o", "application/octet-stream"}, - { "oas", "application/vnd.fujitsu.oasys"}, - { "obd", "application/x-msbinder"}, - { "obj", "application/octet-stream"}, - { "oda", "application/oda"}, - { "odb", "application/vnd.oasis.opendocument.database"}, - { "odc", "application/vnd.oasis.opendocument.chart"}, - { "odf", "application/vnd.oasis.opendocument.formula"}, - { "odft", "application/vnd.oasis.opendocument.formula-template"}, - { "odg", "application/vnd.oasis.opendocument.graphics"}, - { "odi", "application/vnd.oasis.opendocument.image"}, - { "odp", "application/vnd.oasis.opendocument.presentation"}, - { "ods", "application/vnd.oasis.opendocument.spreadsheet"}, - { "odt", "application/vnd.oasis.opendocument.text"}, - { "oga", "audio/ogg"}, - { "ogg", "audio/ogg"}, - { "ogv", "video/ogg"}, - { "ogx", "application/ogg"}, - { "onepkg", "application/onenote"}, - { "onetmp", "application/onenote"}, - { "onetoc2", "application/onenote"}, - { "onetoc", "application/onenote"}, - { "opf", "application/oebps-package+xml"}, - { "oprc", "application/vnd.palm"}, - { "org", "application/vnd.lotus-organizer"}, - { "osf", "application/vnd.yamaha.openscoreformat"}, - { "osfpvg", "application/vnd.yamaha.openscoreformat.osfpvg+xml"}, - { "otc", "application/vnd.oasis.opendocument.chart-template"}, - { "otf", "application/x-font-otf"}, - { "otg", "application/vnd.oasis.opendocument.graphics-template"}, - { "oth", "application/vnd.oasis.opendocument.text-web"}, - { "oti", "application/vnd.oasis.opendocument.image-template"}, - { "otm", "application/vnd.oasis.opendocument.text-master"}, - { "otp", "application/vnd.oasis.opendocument.presentation-template"}, - { "ots", "application/vnd.oasis.opendocument.spreadsheet-template"}, - { "ott", "application/vnd.oasis.opendocument.text-template"}, - { "oxt", "application/vnd.openofficeorg.extension"}, - { "p10", "application/pkcs10"}, - { "p12", "application/x-pkcs12"}, - { "p7b", "application/x-pkcs7-certificates"}, - { "p7c", "application/x-pkcs7-mime"}, - { "p7m", "application/x-pkcs7-mime"}, - { "p7r", "application/x-pkcs7-certreqresp"}, - { "p7s", "application/x-pkcs7-signature"}, - { "pas", "text/x-pascal"}, - { "pbd", "application/vnd.powerbuilder6"}, - { "pbm", "image/x-portable-bitmap"}, - { "pcf", "application/x-font-pcf"}, - { "pcl", "application/vnd.hp-pcl"}, - { "pclxl", "application/vnd.hp-pclxl"}, - { "pct", "image/x-pict"}, - { "pcurl", "application/vnd.curl.pcurl"}, - { "pcx", "image/x-pcx"}, - { "pdb", "application/vnd.palm"}, - { "pdf", "application/pdf"}, - { "pfa", "application/x-font-type1"}, - { "pfb", "application/x-font-type1"}, - { "pfm", "application/x-font-type1"}, - { "pfr", "application/font-tdpfr"}, - { "pfx", "application/x-pkcs12"}, - { "pgm", "image/x-portable-graymap"}, - { "pgn", "application/x-chess-pgn"}, - { "pgp", "application/pgp-encrypted"}, - { "pic", "image/x-pict"}, - { "pkg", "application/octet-stream"}, - { "pki", "application/pkixcmp"}, - { "pkipath", "application/pkix-pkipath"}, - { "pko", "application/ynd.ms-pkipko"}, - { "plb", "application/vnd.3gpp.pic-bw-large"}, - { "plc", "application/vnd.mobius.plc"}, - { "plf", "application/vnd.pocketlearn"}, - { "pls", "application/pls+xml"}, - { "pl", "text/plain"}, - { "pma", "application/x-perfmon"}, - { "pmc", "application/x-perfmon"}, - { "pml", "application/x-perfmon"}, - { "pmr", "application/x-perfmon"}, - { "pmw", "application/x-perfmon"}, - { "png", "image/png"}, - { "pnm", "image/x-portable-anymap"}, - { "portpkg", "application/vnd.macports.portpkg"}, - { "pot,", "application/vnd.ms-powerpoint"}, - { "pot", "application/vnd.ms-powerpoint"}, - { "potm", "application/vnd.ms-powerpoint.template.macroenabled.12"}, - { "potx", "application/vnd.openxmlformats-officedocument.presentationml.template"}, - { "ppa", "application/vnd.ms-powerpoint"}, - { "ppam", "application/vnd.ms-powerpoint.addin.macroenabled.12"}, - { "ppd", "application/vnd.cups-ppd"}, - { "ppm", "image/x-portable-pixmap"}, - { "pps", "application/vnd.ms-powerpoint"}, - { "ppsm", "application/vnd.ms-powerpoint.slideshow.macroenabled.12"}, - { "ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, - { "ppt", "application/vnd.ms-powerpoint"}, - { "pptm", "application/vnd.ms-powerpoint.presentation.macroenabled.12"}, - { "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, - { "pqa", "application/vnd.palm"}, - { "prc", "application/x-mobipocket-ebook"}, - { "pre", "application/vnd.lotus-freelance"}, - { "prf", "application/pics-rules"}, - { "ps", "application/postscript"}, - { "psb", "application/vnd.3gpp.pic-bw-small"}, - { "psd", "image/vnd.adobe.photoshop"}, - { "psf", "application/x-font-linux-psf"}, - { "p", "text/x-pascal"}, - { "ptid", "application/vnd.pvi.ptid1"}, - { "pub", "application/x-mspublisher"}, - { "pvb", "application/vnd.3gpp.pic-bw-var"}, - { "pwn", "application/vnd.3m.post-it-notes"}, - { "pwz", "application/vnd.ms-powerpoint"}, - { "pya", "audio/vnd.ms-playready.media.pya"}, - { "pyc", "application/x-python-code"}, - { "pyo", "application/x-python-code"}, - { "py", "text/x-python"}, - { "pyv", "video/vnd.ms-playready.media.pyv"}, - { "qam", "application/vnd.epson.quickanime"}, - { "qbo", "application/vnd.intu.qbo"}, - { "qfx", "application/vnd.intu.qfx"}, - { "qps", "application/vnd.publishare-delta-tree"}, - { "qt", "video/quicktime"}, - { "qwd", "application/vnd.quark.quarkxpress"}, - { "qwt", "application/vnd.quark.quarkxpress"}, - { "qxb", "application/vnd.quark.quarkxpress"}, - { "qxd", "application/vnd.quark.quarkxpress"}, - { "qxl", "application/vnd.quark.quarkxpress"}, - { "qxt", "application/vnd.quark.quarkxpress"}, - { "ra", "audio/x-pn-realaudio"}, - { "ram", "audio/x-pn-realaudio"}, - { "rar", "application/x-rar-compressed"}, - { "ras", "image/x-cmu-raster"}, - { "rcprofile", "application/vnd.ipunplugged.rcprofile"}, - { "rdf", "application/rdf+xml"}, - { "rdz", "application/vnd.data-vision.rdz"}, - { "rep", "application/vnd.businessobjects"}, - { "res", "application/x-dtbresource+xml"}, - { "rgb", "image/x-rgb"}, - { "rif", "application/reginfo+xml"}, - { "rl", "application/resource-lists+xml"}, - { "rlc", "image/vnd.fujixerox.edmics-rlc"}, - { "rld", "application/resource-lists-diff+xml"}, - { "rm", "application/vnd.rn-realmedia"}, - { "rmi", "audio/midi"}, - { "rmp", "audio/x-pn-realaudio-plugin"}, - { "rms", "application/vnd.jcp.javame.midlet-rms"}, - { "rnc", "application/relax-ng-compact-syntax"}, - { "roff", "text/troff"}, - { "rpm", "application/x-rpm"}, - { "rpss", "application/vnd.nokia.radio-presets"}, - { "rpst", "application/vnd.nokia.radio-preset"}, - { "rq", "application/sparql-query"}, - { "rs", "application/rls-services+xml"}, - { "rsd", "application/rsd+xml"}, - { "rss", "application/rss+xml"}, - { "rtf", "application/rtf"}, - { "rtx", "text/richtext"}, - { "saf", "application/vnd.yamaha.smaf-audio"}, - { "sbml", "application/sbml+xml"}, - { "sc", "application/vnd.ibm.secure-container"}, - { "scd", "application/x-msschedule"}, - { "scm", "application/vnd.lotus-screencam"}, - { "scq", "application/scvp-cv-request"}, - { "scs", "application/scvp-cv-response"}, - { "sct", "text/scriptlet"}, - { "scurl", "text/vnd.curl.scurl"}, - { "sda", "application/vnd.stardivision.draw"}, - { "sdc", "application/vnd.stardivision.calc"}, - { "sdd", "application/vnd.stardivision.impress"}, - { "sdkd", "application/vnd.solent.sdkm+xml"}, - { "sdkm", "application/vnd.solent.sdkm+xml"}, - { "sdp", "application/sdp"}, - { "sdw", "application/vnd.stardivision.writer"}, - { "see", "application/vnd.seemail"}, - { "seed", "application/vnd.fdsn.seed"}, - { "sema", "application/vnd.sema"}, - { "semd", "application/vnd.semd"}, - { "semf", "application/vnd.semf"}, - { "ser", "application/java-serialized-object"}, - { "setpay", "application/set-payment-initiation"}, - { "setreg", "application/set-registration-initiation"}, - { "sfd-hdstx", "application/vnd.hydrostatix.sof-data"}, - { "sfs", "application/vnd.spotfire.sfs"}, - { "sgl", "application/vnd.stardivision.writer-global"}, - { "sgml", "text/sgml"}, - { "sgm", "text/sgml"}, - { "sh", "application/x-sh"}, - { "shar", "application/x-shar"}, - { "shf", "application/shf+xml"}, - { "sic", "application/vnd.wap.sic"}, - { "sig", "application/pgp-signature"}, - { "silo", "model/mesh"}, - { "sis", "application/vnd.symbian.install"}, - { "sisx", "application/vnd.symbian.install"}, - { "sit", "application/x-stuffit"}, - { "si", "text/vnd.wap.si"}, - { "sitx", "application/x-stuffitx"}, - { "skd", "application/vnd.koan"}, - { "skm", "application/vnd.koan"}, - { "skp", "application/vnd.koan"}, - { "skt", "application/vnd.koan"}, - { "slc", "application/vnd.wap.slc"}, - { "sldm", "application/vnd.ms-powerpoint.slide.macroenabled.12"}, - { "sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"}, - { "slt", "application/vnd.epson.salt"}, - { "sl", "text/vnd.wap.sl"}, - { "smf", "application/vnd.stardivision.math"}, - { "smi", "application/smil+xml"}, - { "smil", "application/smil+xml"}, - { "snd", "audio/basic"}, - { "snf", "application/x-font-snf"}, - { "so", "application/octet-stream"}, - { "spc", "application/x-pkcs7-certificates"}, - { "spf", "application/vnd.yamaha.smaf-phrase"}, - { "spl", "application/x-futuresplash"}, - { "spot", "text/vnd.in3d.spot"}, - { "spp", "application/scvp-vp-response"}, - { "spq", "application/scvp-vp-request"}, - { "spx", "audio/ogg"}, - { "src", "application/x-wais-source"}, - { "srx", "application/sparql-results+xml"}, - { "sse", "application/vnd.kodak-descriptor"}, - { "ssf", "application/vnd.epson.ssf"}, - { "ssml", "application/ssml+xml"}, - { "sst", "application/vnd.ms-pkicertstore"}, - { "stc", "application/vnd.sun.xml.calc.template"}, - { "std", "application/vnd.sun.xml.draw.template"}, - { "s", "text/x-asm"}, - { "stf", "application/vnd.wt.stf"}, - { "sti", "application/vnd.sun.xml.impress.template"}, - { "stk", "application/hyperstudio"}, - { "stl", "application/vnd.ms-pki.stl"}, - { "stm", "text/html"}, - { "str", "application/vnd.pg.format"}, - { "stw", "application/vnd.sun.xml.writer.template"}, - { "sus", "application/vnd.sus-calendar"}, - { "susp", "application/vnd.sus-calendar"}, - { "sv4cpio", "application/x-sv4cpio"}, - { "sv4crc", "application/x-sv4crc"}, - { "svd", "application/vnd.svd"}, - { "svg", "image/svg+xml"}, - { "svgz", "image/svg+xml"}, - { "swa", "application/x-director"}, - { "swf", "application/x-shockwave-flash"}, - { "swi", "application/vnd.arastra.swi"}, - { "sxc", "application/vnd.sun.xml.calc"}, - { "sxd", "application/vnd.sun.xml.draw"}, - { "sxg", "application/vnd.sun.xml.writer.global"}, - { "sxi", "application/vnd.sun.xml.impress"}, - { "sxm", "application/vnd.sun.xml.math"}, - { "sxw", "application/vnd.sun.xml.writer"}, - { "tao", "application/vnd.tao.intent-module-archive"}, - { "t", "application/x-troff"}, - { "tar", "application/x-tar"}, - { "tcap", "application/vnd.3gpp2.tcap"}, - { "tcl", "application/x-tcl"}, - { "teacher", "application/vnd.smart.teacher"}, - { "tex", "application/x-tex"}, - { "texi", "application/x-texinfo"}, - { "texinfo", "application/x-texinfo"}, - { "text", "text/plain"}, - { "tfm", "application/x-tex-tfm"}, - { "tgz", "application/x-gzip"}, - { "tiff", "image/tiff"}, - { "tif", "image/tiff"}, - { "tmo", "application/vnd.tmobile-livetv"}, - { "torrent", "application/x-bittorrent"}, - { "tpl", "application/vnd.groove-tool-template"}, - { "tpt", "application/vnd.trid.tpt"}, - { "tra", "application/vnd.trueapp"}, - { "trm", "application/x-msterminal"}, - { "tr", "text/troff"}, - { "tsv", "text/tab-separated-values"}, - { "ttc", "application/x-font-ttf"}, - { "ttf", "application/x-font-ttf"}, - { "twd", "application/vnd.simtech-mindmapper"}, - { "twds", "application/vnd.simtech-mindmapper"}, - { "txd", "application/vnd.genomatix.tuxedo"}, - { "txf", "application/vnd.mobius.txf"}, - { "txt", "text/plain"}, - { "u32", "application/x-authorware-bin"}, - { "udeb", "application/x-debian-package"}, - { "ufd", "application/vnd.ufdl"}, - { "ufdl", "application/vnd.ufdl"}, - { "uls", "text/iuls"}, - { "umj", "application/vnd.umajin"}, - { "unityweb", "application/vnd.unity"}, - { "uoml", "application/vnd.uoml+xml"}, - { "uris", "text/uri-list"}, - { "uri", "text/uri-list"}, - { "urls", "text/uri-list"}, - { "ustar", "application/x-ustar"}, - { "utz", "application/vnd.uiq.theme"}, - { "uu", "text/x-uuencode"}, - { "vcd", "application/x-cdlink"}, - { "vcf", "text/x-vcard"}, - { "vcg", "application/vnd.groove-vcard"}, - { "vcs", "text/x-vcalendar"}, - { "vcx", "application/vnd.vcx"}, - { "vis", "application/vnd.visionary"}, - { "viv", "video/vnd.vivo"}, - { "vor", "application/vnd.stardivision.writer"}, - { "vox", "application/x-authorware-bin"}, - { "vrml", "x-world/x-vrml"}, - { "vsd", "application/vnd.visio"}, - { "vsf", "application/vnd.vsf"}, - { "vss", "application/vnd.visio"}, - { "vst", "application/vnd.visio"}, - { "vsw", "application/vnd.visio"}, - { "vtu", "model/vnd.vtu"}, - { "vxml", "application/voicexml+xml"}, - { "w3d", "application/x-director"}, - { "wad", "application/x-doom"}, - { "wav", "audio/x-wav"}, - { "wax", "audio/x-ms-wax"}, - { "wbmp", "image/vnd.wap.wbmp"}, - { "wbs", "application/vnd.criticaltools.wbs+xml"}, - { "wbxml", "application/vnd.wap.wbxml"}, - { "wcm", "application/vnd.ms-works"}, - { "wdb", "application/vnd.ms-works"}, - { "wiz", "application/msword"}, - { "wks", "application/vnd.ms-works"}, - { "wma", "audio/x-ms-wma"}, - { "wmd", "application/x-ms-wmd"}, - { "wmf", "application/x-msmetafile"}, - { "wmlc", "application/vnd.wap.wmlc"}, - { "wmlsc", "application/vnd.wap.wmlscriptc"}, - { "wmls", "text/vnd.wap.wmlscript"}, - { "wml", "text/vnd.wap.wml"}, - { "wm", "video/x-ms-wm"}, - { "wmv", "video/x-ms-wmv"}, - { "wmx", "video/x-ms-wmx"}, - { "wmz", "application/x-ms-wmz"}, - { "wpd", "application/vnd.wordperfect"}, - { "wpl", "application/vnd.ms-wpl"}, - { "wps", "application/vnd.ms-works"}, - { "wqd", "application/vnd.wqd"}, - { "wri", "application/x-mswrite"}, - { "wrl", "x-world/x-vrml"}, - { "wrz", "x-world/x-vrml"}, - { "wsdl", "application/wsdl+xml"}, - { "wspolicy", "application/wspolicy+xml"}, - { "wtb", "application/vnd.webturbo"}, - { "wvx", "video/x-ms-wvx"}, - { "x32", "application/x-authorware-bin"}, - { "x3d", "application/vnd.hzn-3d-crossword"}, - { "xaf", "x-world/x-vrml"}, - { "xap", "application/x-silverlight-app"}, - { "xar", "application/vnd.xara"}, - { "xbap", "application/x-ms-xbap"}, - { "xbd", "application/vnd.fujixerox.docuworks.binder"}, - { "xbm", "image/x-xbitmap"}, - { "xdm", "application/vnd.syncml.dm+xml"}, - { "xdp", "application/vnd.adobe.xdp+xml"}, - { "xdw", "application/vnd.fujixerox.docuworks"}, - { "xenc", "application/xenc+xml"}, - { "xer", "application/patch-ops-error+xml"}, - { "xfdf", "application/vnd.adobe.xfdf"}, - { "xfdl", "application/vnd.xfdl"}, - { "xht", "application/xhtml+xml"}, - { "xhtml", "application/xhtml+xml"}, - { "xhvml", "application/xv+xml"}, - { "xif", "image/vnd.xiff"}, - { "xla", "application/vnd.ms-excel"}, - { "xlam", "application/vnd.ms-excel.addin.macroenabled.12"}, - { "xlb", "application/vnd.ms-excel"}, - { "xlc", "application/vnd.ms-excel"}, - { "xlm", "application/vnd.ms-excel"}, - { "xls", "application/vnd.ms-excel"}, - { "xlsb", "application/vnd.ms-excel.sheet.binary.macroenabled.12"}, - { "xlsm", "application/vnd.ms-excel.sheet.macroenabled.12"}, - { "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, - { "xlt", "application/vnd.ms-excel"}, - { "xltm", "application/vnd.ms-excel.template.macroenabled.12"}, - { "xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"}, - { "xlw", "application/vnd.ms-excel"}, - { "xml", "application/xml"}, - { "xo", "application/vnd.olpc-sugar"}, - { "xof", "x-world/x-vrml"}, - { "xop", "application/xop+xml"}, - { "xpdl", "application/xml"}, - { "xpi", "application/x-xpinstall"}, - { "xpm", "image/x-xpixmap"}, - { "xpr", "application/vnd.is-xpr"}, - { "xps", "application/vnd.ms-xpsdocument"}, - { "xpw", "application/vnd.intercon.formnet"}, - { "xpx", "application/vnd.intercon.formnet"}, - { "xsl", "application/xml"}, - { "xslt", "application/xslt+xml"}, - { "xsm", "application/vnd.syncml+xml"}, - { "xspf", "application/xspf+xml"}, - { "xul", "application/vnd.mozilla.xul+xml"}, - { "xvm", "application/xv+xml"}, - { "xvml", "application/xv+xml"}, - { "xwd", "image/x-xwindowdump"}, - { "xyz", "chemical/x-xyz"}, - { "z", "application/x-compress"}, - { "zaz", "application/vnd.zzazz.deck+xml"}, - { "zip", "application/zip"}, - { "zir", "application/vnd.zul"}, - { "zirz", "application/vnd.zul"}, - { "zmm", "application/vnd.handheld-entertainment+xml"} + { "", DEFAULT_ATTACHMENT_MIME_TYPE }, + { "k9s", K9_SETTINGS_MIME_TYPE}, + //* Do not delete the previous two lines + { "123", "application/vnd.lotus-1-2-3"}, + { "323", "text/h323"}, + { "3dml", "text/vnd.in3d.3dml"}, + { "3g2", "video/3gpp2"}, + { "3gp", "video/3gpp"}, + { "aab", "application/x-authorware-bin"}, + { "aac", "audio/x-aac"}, + { "aam", "application/x-authorware-map"}, + { "a", "application/octet-stream"}, + { "aas", "application/x-authorware-seg"}, + { "abw", "application/x-abiword"}, + { "acc", "application/vnd.americandynamics.acc"}, + { "ace", "application/x-ace-compressed"}, + { "acu", "application/vnd.acucobol"}, + { "acutc", "application/vnd.acucorp"}, + { "acx", "application/internet-property-stream"}, + { "adp", "audio/adpcm"}, + { "aep", "application/vnd.audiograph"}, + { "afm", "application/x-font-type1"}, + { "afp", "application/vnd.ibm.modcap"}, + { "ai", "application/postscript"}, + { "aif", "audio/x-aiff"}, + { "aifc", "audio/x-aiff"}, + { "aiff", "audio/x-aiff"}, + { "air", "application/vnd.adobe.air-application-installer-package+zip"}, + { "ami", "application/vnd.amiga.ami"}, + { "apk", "application/vnd.android.package-archive"}, + { "application", "application/x-ms-application"}, + { "apr", "application/vnd.lotus-approach"}, + { "asc", "application/pgp-signature"}, + { "asf", "video/x-ms-asf"}, + { "asm", "text/x-asm"}, + { "aso", "application/vnd.accpac.simply.aso"}, + { "asr", "video/x-ms-asf"}, + { "asx", "video/x-ms-asf"}, + { "atc", "application/vnd.acucorp"}, + { "atom", "application/atom+xml"}, + { "atomcat", "application/atomcat+xml"}, + { "atomsvc", "application/atomsvc+xml"}, + { "atx", "application/vnd.antix.game-component"}, + { "au", "audio/basic"}, + { "avi", "video/x-msvideo"}, + { "aw", "application/applixware"}, + { "axs", "application/olescript"}, + { "azf", "application/vnd.airzip.filesecure.azf"}, + { "azs", "application/vnd.airzip.filesecure.azs"}, + { "azw", "application/vnd.amazon.ebook"}, + { "bas", "text/plain"}, + { "bat", "application/x-msdownload"}, + { "bcpio", "application/x-bcpio"}, + { "bdf", "application/x-font-bdf"}, + { "bdm", "application/vnd.syncml.dm+wbxml"}, + { "bh2", "application/vnd.fujitsu.oasysprs"}, + { "bin", "application/octet-stream"}, + { "bmi", "application/vnd.bmi"}, + { "bmp", "image/bmp"}, + { "book", "application/vnd.framemaker"}, + { "box", "application/vnd.previewsystems.box"}, + { "boz", "application/x-bzip2"}, + { "bpk", "application/octet-stream"}, + { "btif", "image/prs.btif"}, + { "bz2", "application/x-bzip2"}, + { "bz", "application/x-bzip"}, + { "c4d", "application/vnd.clonk.c4group"}, + { "c4f", "application/vnd.clonk.c4group"}, + { "c4g", "application/vnd.clonk.c4group"}, + { "c4p", "application/vnd.clonk.c4group"}, + { "c4u", "application/vnd.clonk.c4group"}, + { "cab", "application/vnd.ms-cab-compressed"}, + { "car", "application/vnd.curl.car"}, + { "cat", "application/vnd.ms-pki.seccat"}, + { "cct", "application/x-director"}, + { "cc", "text/x-c"}, + { "ccxml", "application/ccxml+xml"}, + { "cdbcmsg", "application/vnd.contact.cmsg"}, + { "cdf", "application/x-cdf"}, + { "cdkey", "application/vnd.mediastation.cdkey"}, + { "cdx", "chemical/x-cdx"}, + { "cdxml", "application/vnd.chemdraw+xml"}, + { "cdy", "application/vnd.cinderella"}, + { "cer", "application/x-x509-ca-cert"}, + { "cgm", "image/cgm"}, + { "chat", "application/x-chat"}, + { "chm", "application/vnd.ms-htmlhelp"}, + { "chrt", "application/vnd.kde.kchart"}, + { "cif", "chemical/x-cif"}, + { "cii", "application/vnd.anser-web-certificate-issue-initiation"}, + { "cla", "application/vnd.claymore"}, + { "class", "application/java-vm"}, + { "clkk", "application/vnd.crick.clicker.keyboard"}, + { "clkp", "application/vnd.crick.clicker.palette"}, + { "clkt", "application/vnd.crick.clicker.template"}, + { "clkw", "application/vnd.crick.clicker.wordbank"}, + { "clkx", "application/vnd.crick.clicker"}, + { "clp", "application/x-msclip"}, + { "cmc", "application/vnd.cosmocaller"}, + { "cmdf", "chemical/x-cmdf"}, + { "cml", "chemical/x-cml"}, + { "cmp", "application/vnd.yellowriver-custom-menu"}, + { "cmx", "image/x-cmx"}, + { "cod", "application/vnd.rim.cod"}, + { "com", "application/x-msdownload"}, + { "conf", "text/plain"}, + { "cpio", "application/x-cpio"}, + { "cpp", "text/x-c"}, + { "cpt", "application/mac-compactpro"}, + { "crd", "application/x-mscardfile"}, + { "crl", "application/pkix-crl"}, + { "crt", "application/x-x509-ca-cert"}, + { "csh", "application/x-csh"}, + { "csml", "chemical/x-csml"}, + { "csp", "application/vnd.commonspace"}, + { "css", "text/css"}, + { "cst", "application/x-director"}, + { "csv", "text/csv"}, + { "c", "text/plain"}, + { "cu", "application/cu-seeme"}, + { "curl", "text/vnd.curl"}, + { "cww", "application/prs.cww"}, + { "cxt", "application/x-director"}, + { "cxx", "text/x-c"}, + { "daf", "application/vnd.mobius.daf"}, + { "dataless", "application/vnd.fdsn.seed"}, + { "davmount", "application/davmount+xml"}, + { "dcr", "application/x-director"}, + { "dcurl", "text/vnd.curl.dcurl"}, + { "dd2", "application/vnd.oma.dd2+xml"}, + { "ddd", "application/vnd.fujixerox.ddd"}, + { "deb", "application/x-debian-package"}, + { "def", "text/plain"}, + { "deploy", "application/octet-stream"}, + { "der", "application/x-x509-ca-cert"}, + { "dfac", "application/vnd.dreamfactory"}, + { "dic", "text/x-c"}, + { "diff", "text/plain"}, + { "dir", "application/x-director"}, + { "dis", "application/vnd.mobius.dis"}, + { "dist", "application/octet-stream"}, + { "distz", "application/octet-stream"}, + { "djv", "image/vnd.djvu"}, + { "djvu", "image/vnd.djvu"}, + { "dll", "application/x-msdownload"}, + { "dmg", "application/octet-stream"}, + { "dms", "application/octet-stream"}, + { "dna", "application/vnd.dna"}, + { "doc", "application/msword"}, + { "docm", "application/vnd.ms-word.document.macroenabled.12"}, + { "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + { "dot", "application/msword"}, + { "dotm", "application/vnd.ms-word.template.macroenabled.12"}, + { "dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, + { "dp", "application/vnd.osgi.dp"}, + { "dpg", "application/vnd.dpgraph"}, + { "dsc", "text/prs.lines.tag"}, + { "dtb", "application/x-dtbook+xml"}, + { "dtd", "application/xml-dtd"}, + { "dts", "audio/vnd.dts"}, + { "dtshd", "audio/vnd.dts.hd"}, + { "dump", "application/octet-stream"}, + { "dvi", "application/x-dvi"}, + { "dwf", "model/vnd.dwf"}, + { "dwg", "image/vnd.dwg"}, + { "dxf", "image/vnd.dxf"}, + { "dxp", "application/vnd.spotfire.dxp"}, + { "dxr", "application/x-director"}, + { "ecelp4800", "audio/vnd.nuera.ecelp4800"}, + { "ecelp7470", "audio/vnd.nuera.ecelp7470"}, + { "ecelp9600", "audio/vnd.nuera.ecelp9600"}, + { "ecma", "application/ecmascript"}, + { "edm", "application/vnd.novadigm.edm"}, + { "edx", "application/vnd.novadigm.edx"}, + { "efif", "application/vnd.picsel"}, + { "ei6", "application/vnd.pg.osasli"}, + { "elc", "application/octet-stream"}, + { "eml", "message/rfc822"}, + { "emma", "application/emma+xml"}, + { "eol", "audio/vnd.digital-winds"}, + { "eot", "application/vnd.ms-fontobject"}, + { "eps", "application/postscript"}, + { "epub", "application/epub+zip"}, + { "es3", "application/vnd.eszigno3+xml"}, + { "esf", "application/vnd.epson.esf"}, + { "et3", "application/vnd.eszigno3+xml"}, + { "etx", "text/x-setext"}, + { "evy", "application/envoy"}, + { "exe", "application/octet-stream"}, + { "ext", "application/vnd.novadigm.ext"}, + { "ez2", "application/vnd.ezpix-album"}, + { "ez3", "application/vnd.ezpix-package"}, + { "ez", "application/andrew-inset"}, + { "f4v", "video/x-f4v"}, + { "f77", "text/x-fortran"}, + { "f90", "text/x-fortran"}, + { "fbs", "image/vnd.fastbidsheet"}, + { "fdf", "application/vnd.fdf"}, + { "fe_launch", "application/vnd.denovo.fcselayout-link"}, + { "fg5", "application/vnd.fujitsu.oasysgp"}, + { "fgd", "application/x-director"}, + { "fh4", "image/x-freehand"}, + { "fh5", "image/x-freehand"}, + { "fh7", "image/x-freehand"}, + { "fhc", "image/x-freehand"}, + { "fh", "image/x-freehand"}, + { "fif", "application/fractals"}, + { "fig", "application/x-xfig"}, + { "fli", "video/x-fli"}, + { "flo", "application/vnd.micrografx.flo"}, + { "flr", "x-world/x-vrml"}, + { "flv", "video/x-flv"}, + { "flw", "application/vnd.kde.kivio"}, + { "flx", "text/vnd.fmi.flexstor"}, + { "fly", "text/vnd.fly"}, + { "fm", "application/vnd.framemaker"}, + { "fnc", "application/vnd.frogans.fnc"}, + { "for", "text/x-fortran"}, + { "fpx", "image/vnd.fpx"}, + { "frame", "application/vnd.framemaker"}, + { "fsc", "application/vnd.fsc.weblaunch"}, + { "fst", "image/vnd.fst"}, + { "ftc", "application/vnd.fluxtime.clip"}, + { "f", "text/x-fortran"}, + { "fti", "application/vnd.anser-web-funds-transfer-initiation"}, + { "fvt", "video/vnd.fvt"}, + { "fzs", "application/vnd.fuzzysheet"}, + { "g3", "image/g3fax"}, + { "gac", "application/vnd.groove-account"}, + { "gdl", "model/vnd.gdl"}, + { "geo", "application/vnd.dynageo"}, + { "gex", "application/vnd.geometry-explorer"}, + { "ggb", "application/vnd.geogebra.file"}, + { "ggt", "application/vnd.geogebra.tool"}, + { "ghf", "application/vnd.groove-help"}, + { "gif", "image/gif"}, + { "gim", "application/vnd.groove-identity-message"}, + { "gmx", "application/vnd.gmx"}, + { "gnumeric", "application/x-gnumeric"}, + { "gph", "application/vnd.flographit"}, + { "gqf", "application/vnd.grafeq"}, + { "gqs", "application/vnd.grafeq"}, + { "gram", "application/srgs"}, + { "gre", "application/vnd.geometry-explorer"}, + { "grv", "application/vnd.groove-injector"}, + { "grxml", "application/srgs+xml"}, + { "gsf", "application/x-font-ghostscript"}, + { "gtar", "application/x-gtar"}, + { "gtm", "application/vnd.groove-tool-message"}, + { "gtw", "model/vnd.gtw"}, + { "gv", "text/vnd.graphviz"}, + { "gz", "application/x-gzip"}, + { "h261", "video/h261"}, + { "h263", "video/h263"}, + { "h264", "video/h264"}, + { "hbci", "application/vnd.hbci"}, + { "hdf", "application/x-hdf"}, + { "hh", "text/x-c"}, + { "hlp", "application/winhlp"}, + { "hpgl", "application/vnd.hp-hpgl"}, + { "hpid", "application/vnd.hp-hpid"}, + { "hps", "application/vnd.hp-hps"}, + { "hqx", "application/mac-binhex40"}, + { "hta", "application/hta"}, + { "htc", "text/x-component"}, + { "h", "text/plain"}, + { "htke", "application/vnd.kenameaapp"}, + { "html", "text/html"}, + { "htm", "text/html"}, + { "htt", "text/webviewhtml"}, + { "hvd", "application/vnd.yamaha.hv-dic"}, + { "hvp", "application/vnd.yamaha.hv-voice"}, + { "hvs", "application/vnd.yamaha.hv-script"}, + { "icc", "application/vnd.iccprofile"}, + { "ice", "x-conference/x-cooltalk"}, + { "icm", "application/vnd.iccprofile"}, + { "ico", "image/x-icon"}, + { "ics", "text/calendar"}, + { "ief", "image/ief"}, + { "ifb", "text/calendar"}, + { "ifm", "application/vnd.shana.informed.formdata"}, + { "iges", "model/iges"}, + { "igl", "application/vnd.igloader"}, + { "igs", "model/iges"}, + { "igx", "application/vnd.micrografx.igx"}, + { "iif", "application/vnd.shana.informed.interchange"}, + { "iii", "application/x-iphone"}, + { "imp", "application/vnd.accpac.simply.imp"}, + { "ims", "application/vnd.ms-ims"}, + { "ins", "application/x-internet-signup"}, + { "in", "text/plain"}, + { "ipk", "application/vnd.shana.informed.package"}, + { "irm", "application/vnd.ibm.rights-management"}, + { "irp", "application/vnd.irepository.package+xml"}, + { "iso", "application/octet-stream"}, + { "isp", "application/x-internet-signup"}, + { "itp", "application/vnd.shana.informed.formtemplate"}, + { "ivp", "application/vnd.immervision-ivp"}, + { "ivu", "application/vnd.immervision-ivu"}, + { "jad", "text/vnd.sun.j2me.app-descriptor"}, + { "jam", "application/vnd.jam"}, + { "jar", "application/java-archive"}, + { "java", "text/x-java-source"}, + { "jfif", "image/pipeg"}, + { "jisp", "application/vnd.jisp"}, + { "jlt", "application/vnd.hp-jlyt"}, + { "jnlp", "application/x-java-jnlp-file"}, + { "joda", "application/vnd.joost.joda-archive"}, + { "jpeg", "image/jpeg"}, + { "jpe", "image/jpeg"}, + { "jpg", "image/jpeg"}, + { "jpgm", "video/jpm"}, + { "jpgv", "video/jpeg"}, + { "jpm", "video/jpm"}, + { "js", "application/x-javascript"}, + { "json", "application/json"}, + { "kar", "audio/midi"}, + { "karbon", "application/vnd.kde.karbon"}, + { "kfo", "application/vnd.kde.kformula"}, + { "kia", "application/vnd.kidspiration"}, + { "kil", "application/x-killustrator"}, + { "kml", "application/vnd.google-earth.kml+xml"}, + { "kmz", "application/vnd.google-earth.kmz"}, + { "kne", "application/vnd.kinar"}, + { "knp", "application/vnd.kinar"}, + { "kon", "application/vnd.kde.kontour"}, + { "kpr", "application/vnd.kde.kpresenter"}, + { "kpt", "application/vnd.kde.kpresenter"}, + { "ksh", "text/plain"}, + { "ksp", "application/vnd.kde.kspread"}, + { "ktr", "application/vnd.kahootz"}, + { "ktz", "application/vnd.kahootz"}, + { "kwd", "application/vnd.kde.kword"}, + { "kwt", "application/vnd.kde.kword"}, + { "latex", "application/x-latex"}, + { "lbd", "application/vnd.llamagraphics.life-balance.desktop"}, + { "lbe", "application/vnd.llamagraphics.life-balance.exchange+xml"}, + { "les", "application/vnd.hhe.lesson-player"}, + { "lha", "application/octet-stream"}, + { "link66", "application/vnd.route66.link66+xml"}, + { "list3820", "application/vnd.ibm.modcap"}, + { "listafp", "application/vnd.ibm.modcap"}, + { "list", "text/plain"}, + { "log", "text/plain"}, + { "lostxml", "application/lost+xml"}, + { "lrf", "application/octet-stream"}, + { "lrm", "application/vnd.ms-lrm"}, + { "lsf", "video/x-la-asf"}, + { "lsx", "video/x-la-asf"}, + { "ltf", "application/vnd.frogans.ltf"}, + { "lvp", "audio/vnd.lucent.voice"}, + { "lwp", "application/vnd.lotus-wordpro"}, + { "lzh", "application/octet-stream"}, + { "m13", "application/x-msmediaview"}, + { "m14", "application/x-msmediaview"}, + { "m1v", "video/mpeg"}, + { "m2a", "audio/mpeg"}, + { "m2v", "video/mpeg"}, + { "m3a", "audio/mpeg"}, + { "m3u", "audio/x-mpegurl"}, + { "m4u", "video/vnd.mpegurl"}, + { "m4v", "video/x-m4v"}, + { "ma", "application/mathematica"}, + { "mag", "application/vnd.ecowin.chart"}, + { "maker", "application/vnd.framemaker"}, + { "man", "text/troff"}, + { "mathml", "application/mathml+xml"}, + { "mb", "application/mathematica"}, + { "mbk", "application/vnd.mobius.mbk"}, + { "mbox", "application/mbox"}, + { "mc1", "application/vnd.medcalcdata"}, + { "mcd", "application/vnd.mcd"}, + { "mcurl", "text/vnd.curl.mcurl"}, + { "mdb", "application/x-msaccess"}, + { "mdi", "image/vnd.ms-modi"}, + { "mesh", "model/mesh"}, + { "me", "text/troff"}, + { "mfm", "application/vnd.mfmp"}, + { "mgz", "application/vnd.proteus.magazine"}, + { "mht", "message/rfc822"}, + { "mhtml", "message/rfc822"}, + { "mid", "audio/midi"}, + { "midi", "audio/midi"}, + { "mif", "application/vnd.mif"}, + { "mime", "message/rfc822"}, + { "mj2", "video/mj2"}, + { "mjp2", "video/mj2"}, + { "mlp", "application/vnd.dolby.mlp"}, + { "mmd", "application/vnd.chipnuts.karaoke-mmd"}, + { "mmf", "application/vnd.smaf"}, + { "mmr", "image/vnd.fujixerox.edmics-mmr"}, + { "mny", "application/x-msmoney"}, + { "mobi", "application/x-mobipocket-ebook"}, + { "movie", "video/x-sgi-movie"}, + { "mov", "video/quicktime"}, + { "mp2a", "audio/mpeg"}, + { "mp2", "video/mpeg"}, + { "mp3", "audio/mpeg"}, + { "mp4a", "audio/mp4"}, + { "mp4s", "application/mp4"}, + { "mp4", "video/mp4"}, + { "mp4v", "video/mp4"}, + { "mpa", "video/mpeg"}, + { "mpc", "application/vnd.mophun.certificate"}, + { "mpeg", "video/mpeg"}, + { "mpe", "video/mpeg"}, + { "mpg4", "video/mp4"}, + { "mpga", "audio/mpeg"}, + { "mpg", "video/mpeg"}, + { "mpkg", "application/vnd.apple.installer+xml"}, + { "mpm", "application/vnd.blueice.multipass"}, + { "mpn", "application/vnd.mophun.application"}, + { "mpp", "application/vnd.ms-project"}, + { "mpt", "application/vnd.ms-project"}, + { "mpv2", "video/mpeg"}, + { "mpy", "application/vnd.ibm.minipay"}, + { "mqy", "application/vnd.mobius.mqy"}, + { "mrc", "application/marc"}, + { "mscml", "application/mediaservercontrol+xml"}, + { "mseed", "application/vnd.fdsn.mseed"}, + { "mseq", "application/vnd.mseq"}, + { "msf", "application/vnd.epson.msf"}, + { "msh", "model/mesh"}, + { "msi", "application/x-msdownload"}, + { "ms", "text/troff"}, + { "msty", "application/vnd.muvee.style"}, + { "mts", "model/vnd.mts"}, + { "mus", "application/vnd.musician"}, + { "musicxml", "application/vnd.recordare.musicxml+xml"}, + { "mvb", "application/x-msmediaview"}, + { "mxf", "application/mxf"}, + { "mxl", "application/vnd.recordare.musicxml"}, + { "mxml", "application/xv+xml"}, + { "mxs", "application/vnd.triscape.mxs"}, + { "mxu", "video/vnd.mpegurl"}, + { "nb", "application/mathematica"}, + { "nc", "application/x-netcdf"}, + { "ncx", "application/x-dtbncx+xml"}, + { "n-gage", "application/vnd.nokia.n-gage.symbian.install"}, + { "ngdat", "application/vnd.nokia.n-gage.data"}, + { "nlu", "application/vnd.neurolanguage.nlu"}, + { "nml", "application/vnd.enliven"}, + { "nnd", "application/vnd.noblenet-directory"}, + { "nns", "application/vnd.noblenet-sealer"}, + { "nnw", "application/vnd.noblenet-web"}, + { "npx", "image/vnd.net-fpx"}, + { "nsf", "application/vnd.lotus-notes"}, + { "nws", "message/rfc822"}, + { "oa2", "application/vnd.fujitsu.oasys2"}, + { "oa3", "application/vnd.fujitsu.oasys3"}, + { "o", "application/octet-stream"}, + { "oas", "application/vnd.fujitsu.oasys"}, + { "obd", "application/x-msbinder"}, + { "obj", "application/octet-stream"}, + { "oda", "application/oda"}, + { "odb", "application/vnd.oasis.opendocument.database"}, + { "odc", "application/vnd.oasis.opendocument.chart"}, + { "odf", "application/vnd.oasis.opendocument.formula"}, + { "odft", "application/vnd.oasis.opendocument.formula-template"}, + { "odg", "application/vnd.oasis.opendocument.graphics"}, + { "odi", "application/vnd.oasis.opendocument.image"}, + { "odp", "application/vnd.oasis.opendocument.presentation"}, + { "ods", "application/vnd.oasis.opendocument.spreadsheet"}, + { "odt", "application/vnd.oasis.opendocument.text"}, + { "oga", "audio/ogg"}, + { "ogg", "audio/ogg"}, + { "ogv", "video/ogg"}, + { "ogx", "application/ogg"}, + { "onepkg", "application/onenote"}, + { "onetmp", "application/onenote"}, + { "onetoc2", "application/onenote"}, + { "onetoc", "application/onenote"}, + { "opf", "application/oebps-package+xml"}, + { "oprc", "application/vnd.palm"}, + { "org", "application/vnd.lotus-organizer"}, + { "osf", "application/vnd.yamaha.openscoreformat"}, + { "osfpvg", "application/vnd.yamaha.openscoreformat.osfpvg+xml"}, + { "otc", "application/vnd.oasis.opendocument.chart-template"}, + { "otf", "application/x-font-otf"}, + { "otg", "application/vnd.oasis.opendocument.graphics-template"}, + { "oth", "application/vnd.oasis.opendocument.text-web"}, + { "oti", "application/vnd.oasis.opendocument.image-template"}, + { "otm", "application/vnd.oasis.opendocument.text-master"}, + { "otp", "application/vnd.oasis.opendocument.presentation-template"}, + { "ots", "application/vnd.oasis.opendocument.spreadsheet-template"}, + { "ott", "application/vnd.oasis.opendocument.text-template"}, + { "oxt", "application/vnd.openofficeorg.extension"}, + { "p10", "application/pkcs10"}, + { "p12", "application/x-pkcs12"}, + { "p7b", "application/x-pkcs7-certificates"}, + { "p7c", "application/x-pkcs7-mime"}, + { "p7m", "application/x-pkcs7-mime"}, + { "p7r", "application/x-pkcs7-certreqresp"}, + { "p7s", "application/x-pkcs7-signature"}, + { "pas", "text/x-pascal"}, + { "pbd", "application/vnd.powerbuilder6"}, + { "pbm", "image/x-portable-bitmap"}, + { "pcf", "application/x-font-pcf"}, + { "pcl", "application/vnd.hp-pcl"}, + { "pclxl", "application/vnd.hp-pclxl"}, + { "pct", "image/x-pict"}, + { "pcurl", "application/vnd.curl.pcurl"}, + { "pcx", "image/x-pcx"}, + { "pdb", "application/vnd.palm"}, + { "pdf", "application/pdf"}, + { "pfa", "application/x-font-type1"}, + { "pfb", "application/x-font-type1"}, + { "pfm", "application/x-font-type1"}, + { "pfr", "application/font-tdpfr"}, + { "pfx", "application/x-pkcs12"}, + { "pgm", "image/x-portable-graymap"}, + { "pgn", "application/x-chess-pgn"}, + { "pgp", "application/pgp-encrypted"}, + { "pic", "image/x-pict"}, + { "pkg", "application/octet-stream"}, + { "pki", "application/pkixcmp"}, + { "pkipath", "application/pkix-pkipath"}, + { "pko", "application/ynd.ms-pkipko"}, + { "plb", "application/vnd.3gpp.pic-bw-large"}, + { "plc", "application/vnd.mobius.plc"}, + { "plf", "application/vnd.pocketlearn"}, + { "pls", "application/pls+xml"}, + { "pl", "text/plain"}, + { "pma", "application/x-perfmon"}, + { "pmc", "application/x-perfmon"}, + { "pml", "application/x-perfmon"}, + { "pmr", "application/x-perfmon"}, + { "pmw", "application/x-perfmon"}, + { "png", "image/png"}, + { "pnm", "image/x-portable-anymap"}, + { "portpkg", "application/vnd.macports.portpkg"}, + { "pot,", "application/vnd.ms-powerpoint"}, + { "pot", "application/vnd.ms-powerpoint"}, + { "potm", "application/vnd.ms-powerpoint.template.macroenabled.12"}, + { "potx", "application/vnd.openxmlformats-officedocument.presentationml.template"}, + { "ppa", "application/vnd.ms-powerpoint"}, + { "ppam", "application/vnd.ms-powerpoint.addin.macroenabled.12"}, + { "ppd", "application/vnd.cups-ppd"}, + { "ppm", "image/x-portable-pixmap"}, + { "pps", "application/vnd.ms-powerpoint"}, + { "ppsm", "application/vnd.ms-powerpoint.slideshow.macroenabled.12"}, + { "ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, + { "ppt", "application/vnd.ms-powerpoint"}, + { "pptm", "application/vnd.ms-powerpoint.presentation.macroenabled.12"}, + { "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + { "pqa", "application/vnd.palm"}, + { "prc", "application/x-mobipocket-ebook"}, + { "pre", "application/vnd.lotus-freelance"}, + { "prf", "application/pics-rules"}, + { "ps", "application/postscript"}, + { "psb", "application/vnd.3gpp.pic-bw-small"}, + { "psd", "image/vnd.adobe.photoshop"}, + { "psf", "application/x-font-linux-psf"}, + { "p", "text/x-pascal"}, + { "ptid", "application/vnd.pvi.ptid1"}, + { "pub", "application/x-mspublisher"}, + { "pvb", "application/vnd.3gpp.pic-bw-var"}, + { "pwn", "application/vnd.3m.post-it-notes"}, + { "pwz", "application/vnd.ms-powerpoint"}, + { "pya", "audio/vnd.ms-playready.media.pya"}, + { "pyc", "application/x-python-code"}, + { "pyo", "application/x-python-code"}, + { "py", "text/x-python"}, + { "pyv", "video/vnd.ms-playready.media.pyv"}, + { "qam", "application/vnd.epson.quickanime"}, + { "qbo", "application/vnd.intu.qbo"}, + { "qfx", "application/vnd.intu.qfx"}, + { "qps", "application/vnd.publishare-delta-tree"}, + { "qt", "video/quicktime"}, + { "qwd", "application/vnd.quark.quarkxpress"}, + { "qwt", "application/vnd.quark.quarkxpress"}, + { "qxb", "application/vnd.quark.quarkxpress"}, + { "qxd", "application/vnd.quark.quarkxpress"}, + { "qxl", "application/vnd.quark.quarkxpress"}, + { "qxt", "application/vnd.quark.quarkxpress"}, + { "ra", "audio/x-pn-realaudio"}, + { "ram", "audio/x-pn-realaudio"}, + { "rar", "application/x-rar-compressed"}, + { "ras", "image/x-cmu-raster"}, + { "rcprofile", "application/vnd.ipunplugged.rcprofile"}, + { "rdf", "application/rdf+xml"}, + { "rdz", "application/vnd.data-vision.rdz"}, + { "rep", "application/vnd.businessobjects"}, + { "res", "application/x-dtbresource+xml"}, + { "rgb", "image/x-rgb"}, + { "rif", "application/reginfo+xml"}, + { "rl", "application/resource-lists+xml"}, + { "rlc", "image/vnd.fujixerox.edmics-rlc"}, + { "rld", "application/resource-lists-diff+xml"}, + { "rm", "application/vnd.rn-realmedia"}, + { "rmi", "audio/midi"}, + { "rmp", "audio/x-pn-realaudio-plugin"}, + { "rms", "application/vnd.jcp.javame.midlet-rms"}, + { "rnc", "application/relax-ng-compact-syntax"}, + { "roff", "text/troff"}, + { "rpm", "application/x-rpm"}, + { "rpss", "application/vnd.nokia.radio-presets"}, + { "rpst", "application/vnd.nokia.radio-preset"}, + { "rq", "application/sparql-query"}, + { "rs", "application/rls-services+xml"}, + { "rsd", "application/rsd+xml"}, + { "rss", "application/rss+xml"}, + { "rtf", "application/rtf"}, + { "rtx", "text/richtext"}, + { "saf", "application/vnd.yamaha.smaf-audio"}, + { "sbml", "application/sbml+xml"}, + { "sc", "application/vnd.ibm.secure-container"}, + { "scd", "application/x-msschedule"}, + { "scm", "application/vnd.lotus-screencam"}, + { "scq", "application/scvp-cv-request"}, + { "scs", "application/scvp-cv-response"}, + { "sct", "text/scriptlet"}, + { "scurl", "text/vnd.curl.scurl"}, + { "sda", "application/vnd.stardivision.draw"}, + { "sdc", "application/vnd.stardivision.calc"}, + { "sdd", "application/vnd.stardivision.impress"}, + { "sdkd", "application/vnd.solent.sdkm+xml"}, + { "sdkm", "application/vnd.solent.sdkm+xml"}, + { "sdp", "application/sdp"}, + { "sdw", "application/vnd.stardivision.writer"}, + { "see", "application/vnd.seemail"}, + { "seed", "application/vnd.fdsn.seed"}, + { "sema", "application/vnd.sema"}, + { "semd", "application/vnd.semd"}, + { "semf", "application/vnd.semf"}, + { "ser", "application/java-serialized-object"}, + { "setpay", "application/set-payment-initiation"}, + { "setreg", "application/set-registration-initiation"}, + { "sfd-hdstx", "application/vnd.hydrostatix.sof-data"}, + { "sfs", "application/vnd.spotfire.sfs"}, + { "sgl", "application/vnd.stardivision.writer-global"}, + { "sgml", "text/sgml"}, + { "sgm", "text/sgml"}, + { "sh", "application/x-sh"}, + { "shar", "application/x-shar"}, + { "shf", "application/shf+xml"}, + { "sic", "application/vnd.wap.sic"}, + { "sig", "application/pgp-signature"}, + { "silo", "model/mesh"}, + { "sis", "application/vnd.symbian.install"}, + { "sisx", "application/vnd.symbian.install"}, + { "sit", "application/x-stuffit"}, + { "si", "text/vnd.wap.si"}, + { "sitx", "application/x-stuffitx"}, + { "skd", "application/vnd.koan"}, + { "skm", "application/vnd.koan"}, + { "skp", "application/vnd.koan"}, + { "skt", "application/vnd.koan"}, + { "slc", "application/vnd.wap.slc"}, + { "sldm", "application/vnd.ms-powerpoint.slide.macroenabled.12"}, + { "sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"}, + { "slt", "application/vnd.epson.salt"}, + { "sl", "text/vnd.wap.sl"}, + { "smf", "application/vnd.stardivision.math"}, + { "smi", "application/smil+xml"}, + { "smil", "application/smil+xml"}, + { "snd", "audio/basic"}, + { "snf", "application/x-font-snf"}, + { "so", "application/octet-stream"}, + { "spc", "application/x-pkcs7-certificates"}, + { "spf", "application/vnd.yamaha.smaf-phrase"}, + { "spl", "application/x-futuresplash"}, + { "spot", "text/vnd.in3d.spot"}, + { "spp", "application/scvp-vp-response"}, + { "spq", "application/scvp-vp-request"}, + { "spx", "audio/ogg"}, + { "src", "application/x-wais-source"}, + { "srx", "application/sparql-results+xml"}, + { "sse", "application/vnd.kodak-descriptor"}, + { "ssf", "application/vnd.epson.ssf"}, + { "ssml", "application/ssml+xml"}, + { "sst", "application/vnd.ms-pkicertstore"}, + { "stc", "application/vnd.sun.xml.calc.template"}, + { "std", "application/vnd.sun.xml.draw.template"}, + { "s", "text/x-asm"}, + { "stf", "application/vnd.wt.stf"}, + { "sti", "application/vnd.sun.xml.impress.template"}, + { "stk", "application/hyperstudio"}, + { "stl", "application/vnd.ms-pki.stl"}, + { "stm", "text/html"}, + { "str", "application/vnd.pg.format"}, + { "stw", "application/vnd.sun.xml.writer.template"}, + { "sus", "application/vnd.sus-calendar"}, + { "susp", "application/vnd.sus-calendar"}, + { "sv4cpio", "application/x-sv4cpio"}, + { "sv4crc", "application/x-sv4crc"}, + { "svd", "application/vnd.svd"}, + { "svg", "image/svg+xml"}, + { "svgz", "image/svg+xml"}, + { "swa", "application/x-director"}, + { "swf", "application/x-shockwave-flash"}, + { "swi", "application/vnd.arastra.swi"}, + { "sxc", "application/vnd.sun.xml.calc"}, + { "sxd", "application/vnd.sun.xml.draw"}, + { "sxg", "application/vnd.sun.xml.writer.global"}, + { "sxi", "application/vnd.sun.xml.impress"}, + { "sxm", "application/vnd.sun.xml.math"}, + { "sxw", "application/vnd.sun.xml.writer"}, + { "tao", "application/vnd.tao.intent-module-archive"}, + { "t", "application/x-troff"}, + { "tar", "application/x-tar"}, + { "tcap", "application/vnd.3gpp2.tcap"}, + { "tcl", "application/x-tcl"}, + { "teacher", "application/vnd.smart.teacher"}, + { "tex", "application/x-tex"}, + { "texi", "application/x-texinfo"}, + { "texinfo", "application/x-texinfo"}, + { "text", "text/plain"}, + { "tfm", "application/x-tex-tfm"}, + { "tgz", "application/x-gzip"}, + { "tiff", "image/tiff"}, + { "tif", "image/tiff"}, + { "tmo", "application/vnd.tmobile-livetv"}, + { "torrent", "application/x-bittorrent"}, + { "tpl", "application/vnd.groove-tool-template"}, + { "tpt", "application/vnd.trid.tpt"}, + { "tra", "application/vnd.trueapp"}, + { "trm", "application/x-msterminal"}, + { "tr", "text/troff"}, + { "tsv", "text/tab-separated-values"}, + { "ttc", "application/x-font-ttf"}, + { "ttf", "application/x-font-ttf"}, + { "twd", "application/vnd.simtech-mindmapper"}, + { "twds", "application/vnd.simtech-mindmapper"}, + { "txd", "application/vnd.genomatix.tuxedo"}, + { "txf", "application/vnd.mobius.txf"}, + { "txt", "text/plain"}, + { "u32", "application/x-authorware-bin"}, + { "udeb", "application/x-debian-package"}, + { "ufd", "application/vnd.ufdl"}, + { "ufdl", "application/vnd.ufdl"}, + { "uls", "text/iuls"}, + { "umj", "application/vnd.umajin"}, + { "unityweb", "application/vnd.unity"}, + { "uoml", "application/vnd.uoml+xml"}, + { "uris", "text/uri-list"}, + { "uri", "text/uri-list"}, + { "urls", "text/uri-list"}, + { "ustar", "application/x-ustar"}, + { "utz", "application/vnd.uiq.theme"}, + { "uu", "text/x-uuencode"}, + { "vcd", "application/x-cdlink"}, + { "vcf", "text/x-vcard"}, + { "vcg", "application/vnd.groove-vcard"}, + { "vcs", "text/x-vcalendar"}, + { "vcx", "application/vnd.vcx"}, + { "vis", "application/vnd.visionary"}, + { "viv", "video/vnd.vivo"}, + { "vor", "application/vnd.stardivision.writer"}, + { "vox", "application/x-authorware-bin"}, + { "vrml", "x-world/x-vrml"}, + { "vsd", "application/vnd.visio"}, + { "vsf", "application/vnd.vsf"}, + { "vss", "application/vnd.visio"}, + { "vst", "application/vnd.visio"}, + { "vsw", "application/vnd.visio"}, + { "vtu", "model/vnd.vtu"}, + { "vxml", "application/voicexml+xml"}, + { "w3d", "application/x-director"}, + { "wad", "application/x-doom"}, + { "wav", "audio/x-wav"}, + { "wax", "audio/x-ms-wax"}, + { "wbmp", "image/vnd.wap.wbmp"}, + { "wbs", "application/vnd.criticaltools.wbs+xml"}, + { "wbxml", "application/vnd.wap.wbxml"}, + { "wcm", "application/vnd.ms-works"}, + { "wdb", "application/vnd.ms-works"}, + { "wiz", "application/msword"}, + { "wks", "application/vnd.ms-works"}, + { "wma", "audio/x-ms-wma"}, + { "wmd", "application/x-ms-wmd"}, + { "wmf", "application/x-msmetafile"}, + { "wmlc", "application/vnd.wap.wmlc"}, + { "wmlsc", "application/vnd.wap.wmlscriptc"}, + { "wmls", "text/vnd.wap.wmlscript"}, + { "wml", "text/vnd.wap.wml"}, + { "wm", "video/x-ms-wm"}, + { "wmv", "video/x-ms-wmv"}, + { "wmx", "video/x-ms-wmx"}, + { "wmz", "application/x-ms-wmz"}, + { "wpd", "application/vnd.wordperfect"}, + { "wpl", "application/vnd.ms-wpl"}, + { "wps", "application/vnd.ms-works"}, + { "wqd", "application/vnd.wqd"}, + { "wri", "application/x-mswrite"}, + { "wrl", "x-world/x-vrml"}, + { "wrz", "x-world/x-vrml"}, + { "wsdl", "application/wsdl+xml"}, + { "wspolicy", "application/wspolicy+xml"}, + { "wtb", "application/vnd.webturbo"}, + { "wvx", "video/x-ms-wvx"}, + { "x32", "application/x-authorware-bin"}, + { "x3d", "application/vnd.hzn-3d-crossword"}, + { "xaf", "x-world/x-vrml"}, + { "xap", "application/x-silverlight-app"}, + { "xar", "application/vnd.xara"}, + { "xbap", "application/x-ms-xbap"}, + { "xbd", "application/vnd.fujixerox.docuworks.binder"}, + { "xbm", "image/x-xbitmap"}, + { "xdm", "application/vnd.syncml.dm+xml"}, + { "xdp", "application/vnd.adobe.xdp+xml"}, + { "xdw", "application/vnd.fujixerox.docuworks"}, + { "xenc", "application/xenc+xml"}, + { "xer", "application/patch-ops-error+xml"}, + { "xfdf", "application/vnd.adobe.xfdf"}, + { "xfdl", "application/vnd.xfdl"}, + { "xht", "application/xhtml+xml"}, + { "xhtml", "application/xhtml+xml"}, + { "xhvml", "application/xv+xml"}, + { "xif", "image/vnd.xiff"}, + { "xla", "application/vnd.ms-excel"}, + { "xlam", "application/vnd.ms-excel.addin.macroenabled.12"}, + { "xlb", "application/vnd.ms-excel"}, + { "xlc", "application/vnd.ms-excel"}, + { "xlm", "application/vnd.ms-excel"}, + { "xls", "application/vnd.ms-excel"}, + { "xlsb", "application/vnd.ms-excel.sheet.binary.macroenabled.12"}, + { "xlsm", "application/vnd.ms-excel.sheet.macroenabled.12"}, + { "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + { "xlt", "application/vnd.ms-excel"}, + { "xltm", "application/vnd.ms-excel.template.macroenabled.12"}, + { "xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"}, + { "xlw", "application/vnd.ms-excel"}, + { "xml", "application/xml"}, + { "xo", "application/vnd.olpc-sugar"}, + { "xof", "x-world/x-vrml"}, + { "xop", "application/xop+xml"}, + { "xpdl", "application/xml"}, + { "xpi", "application/x-xpinstall"}, + { "xpm", "image/x-xpixmap"}, + { "xpr", "application/vnd.is-xpr"}, + { "xps", "application/vnd.ms-xpsdocument"}, + { "xpw", "application/vnd.intercon.formnet"}, + { "xpx", "application/vnd.intercon.formnet"}, + { "xsl", "application/xml"}, + { "xslt", "application/xslt+xml"}, + { "xsm", "application/vnd.syncml+xml"}, + { "xspf", "application/xspf+xml"}, + { "xul", "application/vnd.mozilla.xul+xml"}, + { "xvm", "application/xv+xml"}, + { "xvml", "application/xv+xml"}, + { "xwd", "image/x-xwindowdump"}, + { "xyz", "chemical/x-xyz"}, + { "z", "application/x-compress"}, + { "zaz", "application/vnd.zzazz.deck+xml"}, + { "zip", "application/zip"}, + { "zir", "application/vnd.zul"}, + { "zirz", "application/vnd.zul"}, + { "zmm", "application/vnd.handheld-entertainment+xml"} }; diff --git a/src/com/fsck/k9/preferences/IStorageExporter.java b/src/com/fsck/k9/preferences/IStorageExporter.java index 5ee140403..ae682e2a9 100644 --- a/src/com/fsck/k9/preferences/IStorageExporter.java +++ b/src/com/fsck/k9/preferences/IStorageExporter.java @@ -4,8 +4,7 @@ import java.io.OutputStream; import android.content.Context; -public interface IStorageExporter -{ +public interface IStorageExporter { public boolean needsKey(); public void exportPreferences(Context context, String uuid, OutputStream os, String encryptionKey) throws StorageImportExportException; } \ No newline at end of file diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index c5156baf6..62bb61f8f 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -13,8 +13,7 @@ import com.fsck.k9.activity.AsyncUIProcessor; import com.fsck.k9.activity.ExportListener; import com.fsck.k9.activity.PasswordEntryDialog; -public class StorageExporter -{ +public class StorageExporter { private static void exportPreferences(Activity activity, String version, String uuid, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { try { IStorageExporter storageExporter = StorageVersioning.createExporter(version); @@ -23,77 +22,71 @@ public class StorageExporter } if (storageExporter.needsKey() && encryptionKey == null) { gatherPassword(activity, storageExporter, uuid, fileName, os, listener); - } - else - { + } else { finishExport(activity, storageExporter, uuid, fileName, os, encryptionKey, listener); } } - - catch (Exception e) - { + + 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() - { + 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() { + new PasswordEntryDialog.PasswordEntryListener() { + public void passwordChosen(final String chosenPassword) { - @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); - } + 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(); } + }); + + } + + public void cancel() { + if (listener != null) { + listener.canceled(); } - }); - dialog.show(); + } + }); + 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 { - // This needs to be after the password prompt. If the user cancels the password, we do not want + // This needs to be after the password prompt. If the user cancels the password, we do not want // to create the file needlessly if (os == null && fileName != null) { needToClose = true; @@ -105,30 +98,25 @@ public class StorageExporter if (listener != null) { if (fileName != null) { listener.success(fileName); - } - else { + } else { listener.success(); } } - } - else { + } else { throw new StorageImportExportException("Internal error; no fileName or OutputStream", null); } - } - catch (Exception e) { + } catch (Exception e) { throw new StorageImportExportException(e.getLocalizedMessage(), e); - } - finally { + } finally { if (needToClose && os != null) { try { os.close(); - } - catch (Exception e) { + } catch (Exception e) { Log.w(K9.LOG_TAG, "Unable to close OutputStream", e); } } } - + } - + } diff --git a/src/com/fsck/k9/preferences/StorageExporterVersion1.java b/src/com/fsck/k9/preferences/StorageExporterVersion1.java index 58124cd23..5959c2d46 100644 --- a/src/com/fsck/k9/preferences/StorageExporterVersion1.java +++ b/src/com/fsck/k9/preferences/StorageExporterVersion1.java @@ -85,8 +85,7 @@ public class StorageExporterVersion1 implements IStorageExporter { } @Override - public boolean needsKey() - { + public boolean needsKey() { return true; } } diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index 2c2ff33ad..fdeecc656 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -43,25 +43,21 @@ public class StorageImporter { Log.i(K9.LOG_TAG, "Got settings file version " + version); IStorageImporter storageImporter = StorageVersioning.createImporter(version); - if (storageImporter == null) - { + if (storageImporter == null) { throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, version)); } if (storageImporter.needsKey() && providedEncryptionKey == null) { gatherPassword(activity, storageImporter, dataset, listener); - } - else { + } else { finishImport(activity, storageImporter, dataset, providedEncryptionKey, listener); } - } - catch (Exception e) - { + } catch (Exception e) { if (listener != null) { listener.failure(e.getLocalizedMessage(), e); } } } - + private static void finishImport(Activity context, IStorageImporter storageImporter, ImportElement dataset, String encryptionKey, ImportListener listener) throws StorageImportExportException { if (listener != null) { listener.started(); @@ -82,46 +78,42 @@ public class StorageImporter { listener.success(numAccounts); } } - + private static void gatherPassword(final Activity activity, final IStorageImporter storageImporter, final ImportElement dataset, final ImportListener listener) { - activity.runOnUiThread(new Runnable() - { + activity.runOnUiThread(new Runnable() { @Override - public void run() - { + 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); - } - } + 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(); } } }); - + } + + public void cancel() { + if (listener != null) { + listener.canceled(); + } + } + }); + dialog.show(); } }); - + }; - + public static class ImportElement { String name; diff --git a/src/com/fsck/k9/preferences/StorageImporterVersion1.java b/src/com/fsck/k9/preferences/StorageImporterVersion1.java index e19c65eb0..1a8c28a7f 100644 --- a/src/com/fsck/k9/preferences/StorageImporterVersion1.java +++ b/src/com/fsck/k9/preferences/StorageImporterVersion1.java @@ -88,8 +88,7 @@ public class StorageImporterVersion1 implements IStorageImporter { } @Override - public boolean needsKey() - { + public boolean needsKey() { return true; } } diff --git a/src/com/fsck/k9/preferences/StorageVersioning.java b/src/com/fsck/k9/preferences/StorageVersioning.java index f78f8e281..53604070a 100644 --- a/src/com/fsck/k9/preferences/StorageVersioning.java +++ b/src/com/fsck/k9/preferences/StorageVersioning.java @@ -4,32 +4,31 @@ import java.util.HashMap; import java.util.Map; -public class StorageVersioning -{ +public class StorageVersioning { public enum STORAGE_VERSION { VERSION1(StorageImporterVersion1.class, StorageExporterVersion1.class, true, STORAGE_VERSION_1); - - private Class importerClass; - private Class exporterClass; + + private Class importerClass; + private Class exporterClass; private boolean needsKey; private String versionString; - - - private STORAGE_VERSION(Class imclass, Class exclass, boolean nk, String vs) { + + + private STORAGE_VERSION(Class imclass, Class exclass, boolean nk, String vs) { importerClass = imclass; exporterClass = exclass; needsKey = nk; versionString = vs; - + } - public Class getImporterClass() { + public Class getImporterClass() { return importerClass; } public IStorageImporter createImporter() throws InstantiationException, IllegalAccessException { IStorageImporter storageImporter = importerClass.newInstance(); return storageImporter; } - public Class getExporterClass() { + public Class getExporterClass() { return exporterClass; } public IStorageExporter createExporter() throws InstantiationException, IllegalAccessException { @@ -43,43 +42,37 @@ public class StorageVersioning return versionString; } } - + // Never, ever re-use these numbers! private static final String STORAGE_VERSION_1 = "1"; - + public static Map versionMap = new HashMap(); static { versionMap.put(STORAGE_VERSION.VERSION1.getVersionString(), STORAGE_VERSION.VERSION1); } - - public static IStorageImporter createImporter(String version) throws InstantiationException, IllegalAccessException - { + + public static IStorageImporter createImporter(String version) throws InstantiationException, IllegalAccessException { STORAGE_VERSION storageVersion = versionMap.get(version); - if (storageVersion == null) - { + if (storageVersion == null) { return null; } return storageVersion.createImporter(); } - - public static IStorageExporter createExporter(String version) throws InstantiationException, IllegalAccessException - { + + public static IStorageExporter createExporter(String version) throws InstantiationException, IllegalAccessException { STORAGE_VERSION storageVersion = versionMap.get(version); - if (storageVersion == null) - { + if (storageVersion == null) { return null; } return storageVersion.createExporter(); } - - public Boolean needsKey(String version) - { + + public Boolean needsKey(String version) { STORAGE_VERSION storageVersion = versionMap.get(version); - if (storageVersion == null) - { + if (storageVersion == null) { return null; } return storageVersion.needsKey(); } - + } From 56efd48ac274cdb0003336c9b264401f2f72fed7 Mon Sep 17 00:00:00 2001 From: danapple Date: Mon, 21 Mar 2011 20:09:55 -0500 Subject: [PATCH 016/116] Removed commented out sections. --- res/menu/folder_list_option.xml | 6 ------ res/menu/message_list_option.xml | 6 ------ 2 files changed, 12 deletions(-) diff --git a/res/menu/folder_list_option.xml b/res/menu/folder_list_option.xml index a83822eda..0e77995b1 100644 --- a/res/menu/folder_list_option.xml +++ b/res/menu/folder_list_option.xml @@ -70,12 +70,6 @@ android:title="@string/global_settings_action" android:icon="@android:drawable/ic_menu_preferences" /> - - Date: Mon, 21 Mar 2011 20:15:22 -0500 Subject: [PATCH 017/116] Re-add polite space. --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 1f4aaf395..0226756b1 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1039,7 +1039,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Imported %s accounts from %s Imported 1 account from %s Failed to export settings: %s - Failed to import settings from %s:%s + Failed to import settings from %s: %s Export succeeded Export failed Import succeeded From 0a0cfac1cbbbed7b36eaf18998903c398c3736a9 Mon Sep 17 00:00:00 2001 From: danapple Date: Tue, 22 Mar 2011 22:59:30 -0500 Subject: [PATCH 018/116] Better internationalization, using real Android pluralization API. --- res/values/strings.xml | 7 +++++-- src/com/fsck/k9/activity/Accounts.java | 6 ++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 0226756b1..9889c0bbb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1036,8 +1036,11 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Exporting settings... Importing settings... Exported settings to %s - Imported %s accounts from %s - Imported 1 account from %s + Imported %s from %s + + 1 account + %s accounts + Failed to export settings: %s Failed to import settings from %s: %s Export succeeded diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 0f46665c2..9d3542eb1 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -859,10 +859,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override public void success(int numAccounts) { 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); + String accountQtyText = getResources().getQuantityString(R.plurals.settings_import_success, numAccounts, numAccounts); + String messageText = getString(R.string.settings_import_success, accountQtyText, fileName); showDialog(Accounts.this, R.string.settings_import_success_header, messageText); runOnUiThread(new Runnable() { @Override From fd468e95fb5040756b704103bdf4b85ed801ea37 Mon Sep 17 00:00:00 2001 From: danapple Date: Wed, 23 Mar 2011 00:00:24 -0500 Subject: [PATCH 019/116] Specify a particular mime type. Although the K-9 settings file mime type will not be generally known, specifying something in particular means that only file browsers that can handle */* will used as options in the chooser. In my tests, OI File Manager responds immediately (and the chooser is bypassed) and the camera, music and ringtone selectors are not provided as options. --- src/com/fsck/k9/activity/Accounts.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 9d3542eb1..550213868 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -832,7 +832,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private void onImport() { Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); - i.setType("*/*"); + i.setType(MimeUtility.K9_SETTINGS_MIME_TYPE); startActivityForResult(Intent.createChooser(i, null), ACTIVITY_REQUEST_PICK_SETTINGS_FILE); } From b8949abaca4eb6c7949af48732e11bcdf5244797 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Fri, 25 Mar 2011 15:56:25 +1100 Subject: [PATCH 020/116] Kill some now-dead and duplicate code --- src/com/fsck/k9/activity/Accounts.java | 30 ++++++++++++++++++ src/com/fsck/k9/activity/FolderList.java | 11 ------- src/com/fsck/k9/activity/K9Activity.java | 32 -------------------- src/com/fsck/k9/activity/K9ListActivity.java | 32 -------------------- src/com/fsck/k9/activity/MessageList.java | 12 -------- 5 files changed, 30 insertions(+), 87 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 550213868..3662fd748 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1112,4 +1112,34 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } + public void onExport(final Account 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); + } + }); + } + } diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java index e1da2a781..61c38969d 100644 --- a/src/com/fsck/k9/activity/FolderList.java +++ b/src/com/fsck/k9/activity/FolderList.java @@ -151,9 +151,6 @@ public class FolderList extends K9ListActivity { } } - public void setProgress(boolean progress) { - mHandler.progress(progress); - } /** * This class is responsible for reloading the list of local messages for a @@ -543,14 +540,6 @@ public class FolderList extends K9ListActivity { return true; - case R.id.export: - onExport(mAccount); - return true; - - case R.id.export_all: - onExport(null); - return true; - case R.id.display_1st_class: { setDisplayMode(FolderMode.FIRST_CLASS); return true; diff --git a/src/com/fsck/k9/activity/K9Activity.java b/src/com/fsck/k9/activity/K9Activity.java index 2a90801b7..3997f1608 100644 --- a/src/com/fsck/k9/activity/K9Activity.java +++ b/src/com/fsck/k9/activity/K9Activity.java @@ -163,37 +163,5 @@ public class K9Activity extends Activity { return false; } } - public void setProgress(boolean progress) { - } - - public void onExport(final Account 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); - } - }); - } } diff --git a/src/com/fsck/k9/activity/K9ListActivity.java b/src/com/fsck/k9/activity/K9ListActivity.java index 5b99ac22b..c63781c82 100644 --- a/src/com/fsck/k9/activity/K9ListActivity.java +++ b/src/com/fsck/k9/activity/K9ListActivity.java @@ -90,37 +90,5 @@ public class K9ListActivity extends ListActivity { return super.onKeyUp(keyCode, event); } - public void setProgress(boolean progress) { - } - - public void onExport(final Account 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); - } - }); - } } diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java index 971a0e57d..3627a9efe 100644 --- a/src/com/fsck/k9/activity/MessageList.java +++ b/src/com/fsck/k9/activity/MessageList.java @@ -519,10 +519,6 @@ public class MessageList } } - public void setProgress(boolean progress) { - mHandler.progress(progress); - } - public static void actionHandleFolder(Context context, Account account, String folder) { Intent intent = actionHandleFolderIntent(context, account, folder); context.startActivity(intent); @@ -1401,14 +1397,6 @@ public class MessageList onEditPrefs(); return true; } - case R.id.export: { - onExport(mAccount); - return true; - } - case R.id.export_all: { - onExport(null); - return true; - } } if (mQueryString != null) { From b4a43893a35eac7becfb21be8a0328aa82662012 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Fri, 25 Mar 2011 16:28:14 +1100 Subject: [PATCH 021/116] Lift the choice of which accounts to export all the way up to Accounts.java in advance of adding a dialog --- src/com/fsck/k9/activity/Accounts.java | 10 +++++- .../fsck/k9/activity/AsyncUIProcessor.java | 5 +-- src/com/fsck/k9/activity/ExportHelper.java | 9 ++--- .../fsck/k9/preferences/IStorageExporter.java | 5 +-- .../fsck/k9/preferences/StorageExporter.java | 23 ++++++------ .../preferences/StorageExporterVersion1.java | 35 +++++++------------ 6 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 3662fd748..435b50d1c 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1113,7 +1113,15 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } public void onExport(final Account account) { - ExportHelper.exportSettings(this, account, new ExportListener() { + + // TODO, prompt to allow a user to choose which accounts to export + HashSet accountUuids; + accountUuids = new HashSet(); + if (account != null) { + accountUuids.add(account.getUuid()); + } + + ExportHelper.exportSettings(this, accountUuids, new ExportListener() { @Override public void canceled() { diff --git a/src/com/fsck/k9/activity/AsyncUIProcessor.java b/src/com/fsck/k9/activity/AsyncUIProcessor.java index 9ac776aae..52d047296 100644 --- a/src/com/fsck/k9/activity/AsyncUIProcessor.java +++ b/src/com/fsck/k9/activity/AsyncUIProcessor.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.InputStream; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.HashSet; import android.app.Activity; import android.app.Application; @@ -40,7 +41,7 @@ public class AsyncUIProcessor { public void execute(Runnable runnable) { threadPool.execute(runnable); } - public void exportSettings(final Activity activity, final String version, final String uuid, final ExportListener listener) { + public void exportSettings(final Activity activity, final String version, final HashSet accountUuids, final ExportListener listener) { threadPool.execute(new Runnable() { @Override @@ -53,7 +54,7 @@ public class AsyncUIProcessor { dir.mkdirs(); File file = Utility.createUniqueFile(dir, "settings.k9s"); String fileName = file.getAbsolutePath(); - StorageExporter.exportPreferences(activity, version, uuid, fileName, null, listener); + StorageExporter.exportPreferences(activity, version, accountUuids, fileName, null, listener); } catch (Exception e) { Log.w(K9.LOG_TAG, "Exception during export", e); listener.failure(e.getLocalizedMessage(), e); diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java index b019175b0..e9899fc19 100644 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ b/src/com/fsck/k9/activity/ExportHelper.java @@ -1,5 +1,6 @@ package com.fsck.k9.activity; +import java.util.HashSet; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; @@ -10,14 +11,10 @@ import com.fsck.k9.R; import com.fsck.k9.preferences.StorageVersioning; public class ExportHelper { - public static void exportSettings(final Activity activity, final Account account, final ExportListener listener) { + public static void exportSettings(final Activity activity, final HashSet accountUuids, final ExportListener listener) { // Once there are more versions, build a UI to select which one to use. For now, use the encrypted/encoded version: String version = StorageVersioning.STORAGE_VERSION.VERSION1.getVersionString(); - String uuid = null; - if (account != null) { - uuid = account.getUuid(); - } - AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, version, uuid, new ExportListener() { + AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, version, accountUuids, new ExportListener() { @Override public void canceled() { diff --git a/src/com/fsck/k9/preferences/IStorageExporter.java b/src/com/fsck/k9/preferences/IStorageExporter.java index ae682e2a9..5cf94afad 100644 --- a/src/com/fsck/k9/preferences/IStorageExporter.java +++ b/src/com/fsck/k9/preferences/IStorageExporter.java @@ -1,10 +1,11 @@ package com.fsck.k9.preferences; import java.io.OutputStream; +import java.util.HashSet; import android.content.Context; public interface IStorageExporter { public boolean needsKey(); - public void exportPreferences(Context context, String uuid, OutputStream os, String encryptionKey) throws StorageImportExportException; -} \ No newline at end of file + public void exportPreferences(Context context, HashSet accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException; +} diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 62bb61f8f..4375bed1c 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -3,6 +3,7 @@ package com.fsck.k9.preferences; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; +import java.util.HashSet; import android.app.Activity; import android.util.Log; @@ -14,16 +15,16 @@ import com.fsck.k9.activity.ExportListener; import com.fsck.k9.activity.PasswordEntryDialog; public class StorageExporter { - private static void exportPreferences(Activity activity, String version, String uuid, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { + private static void exportPreferences(Activity activity, String version, HashSet accountUuids, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { try { IStorageExporter storageExporter = StorageVersioning.createExporter(version); if (storageExporter == null) { throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, version), null); } if (storageExporter.needsKey() && encryptionKey == null) { - gatherPassword(activity, storageExporter, uuid, fileName, os, listener); + gatherPassword(activity, storageExporter, accountUuids, fileName, os, listener); } else { - finishExport(activity, storageExporter, uuid, fileName, os, encryptionKey, listener); + finishExport(activity, storageExporter, accountUuids, fileName, os, encryptionKey, listener); } } @@ -34,15 +35,15 @@ public class StorageExporter { } } - 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 exportPreferences(Activity activity, String version, HashSet accountUuids, String fileName, String encryptionKey, final ExportListener listener) throws StorageImportExportException { + exportPreferences(activity, version, accountUuids, 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); + public static void exportPrefererences(Activity activity, String version, HashSet accountUuids, OutputStream os, String encryptionKey, final ExportListener listener) throws StorageImportExportException { + exportPreferences(activity, version, accountUuids, 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) { + private static void gatherPassword(final Activity activity, final IStorageExporter storageExporter, final HashSet accountUuids, final String fileName, final OutputStream os, final ExportListener listener) { activity.runOnUiThread(new Runnable() { @Override @@ -56,7 +57,7 @@ public class StorageExporter { @Override public void run() { try { - finishExport(activity, storageExporter, uuid, fileName, os, chosenPassword, listener); + finishExport(activity, storageExporter, accountUuids, fileName, os, chosenPassword, listener); } catch (Exception e) { Log.w(K9.LOG_TAG, "Exception while finishing export", e); if (listener != null) { @@ -80,7 +81,7 @@ public class StorageExporter { } - private static void finishExport(Activity activity, IStorageExporter storageExporter, String uuid, String fileName, OutputStream os, String encryptionKey, ExportListener listener) throws StorageImportExportException { + private static void finishExport(Activity activity, IStorageExporter storageExporter, HashSet accountUuids, String fileName, OutputStream os, String encryptionKey, ExportListener listener) throws StorageImportExportException { boolean needToClose = false; if (listener != null) { listener.started(); @@ -94,7 +95,7 @@ public class StorageExporter { os = new FileOutputStream(outFile); } if (os != null) { - storageExporter.exportPreferences(activity, uuid, os, encryptionKey); + storageExporter.exportPreferences(activity, accountUuids, os, encryptionKey); if (listener != null) { if (fileName != null) { listener.success(fileName); diff --git a/src/com/fsck/k9/preferences/StorageExporterVersion1.java b/src/com/fsck/k9/preferences/StorageExporterVersion1.java index 5959c2d46..9c4cf8aa1 100644 --- a/src/com/fsck/k9/preferences/StorageExporterVersion1.java +++ b/src/com/fsck/k9/preferences/StorageExporterVersion1.java @@ -19,9 +19,9 @@ 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 { + public void exportPreferences(Context context, HashSet accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException { try { - Log.i(K9.LOG_TAG, "Exporting preferences for account " + uuid + " to OutputStream"); + Log.i(K9.LOG_TAG, "Exporting preferences"); K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.ENCRYPT); OutputStreamWriter sw = new OutputStreamWriter(os); PrintWriter pf = new PrintWriter(sw); @@ -35,10 +35,12 @@ public class StorageExporterVersion1 implements IStorageExporter { Preferences preferences = Preferences.getPreferences(context); SharedPreferences storage = preferences.getPreferences(); - Account[] accounts = preferences.getAccounts(); - Set accountUuids = new HashSet(); - for (Account account : accounts) { - accountUuids.add(account.getUuid()); + if (accountUuids == null) { + Account[] accounts = preferences.getAccounts(); + accountUuids = new HashSet(); + for (Account account : accounts) { + accountUuids.add(account.getUuid()); + } } Map < String, ? extends Object > prefs = storage.getAll(); @@ -47,23 +49,13 @@ public class StorageExporterVersion1 implements IStorageExporter { String value = entry.getValue().toString(); //Log.i(K9.LOG_TAG, "Evaluating key " + key); keysEvaluated++; - if (uuid != null) { - String[] comps = key.split("\\."); + String[] comps = key.split("\\."); + if (comps.length > 1) { 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"); + if (accountUuids.contains(keyUuid) == false) { + //Log.i(K9.LOG_TAG, "Skipping key " + key + " which is not for any current account"); 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); @@ -77,8 +69,7 @@ public class StorageExporterVersion1 implements IStorageExporter { pf.println(""); pf.flush(); - Log.i(K9.LOG_TAG, "Exported " + keysExported + " settings of " + keysEvaluated - + " total for preferences for account " + uuid); + Log.i(K9.LOG_TAG, "Exported " + keysExported + " of " + keysEvaluated + " settings."); } catch (Exception e) { throw new StorageImportExportException("Unable to encrypt settings", e); } From 59f3106ebe716a8242b99db35a0849495361b6d3 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Fri, 25 Mar 2011 17:14:45 +1100 Subject: [PATCH 022/116] rejigger export menus --- res/menu/accounts_context.xml | 16 +++++----------- res/menu/accounts_option.xml | 7 +++---- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/res/menu/accounts_context.xml b/res/menu/accounts_context.xml index afd94bcb4..e1b486c30 100644 --- a/res/menu/accounts_context.xml +++ b/res/menu/accounts_context.xml @@ -6,17 +6,9 @@ android:title="@string/check_mail_action" /> - - - - - - + @@ -30,6 +22,8 @@ android:title="@string/remove_account_action" /> + diff --git a/res/menu/accounts_option.xml b/res/menu/accounts_option.xml index f03ef2c5b..7e782ac19 100644 --- a/res/menu/accounts_option.xml +++ b/res/menu/accounts_option.xml @@ -31,12 +31,11 @@ --> - - + + Date: Fri, 25 Mar 2011 17:15:02 +1100 Subject: [PATCH 023/116] Restore the more-concise naming of our settings menus --- res/values/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 9889c0bbb..599722441 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -79,9 +79,9 @@ Search results Settings Open - Edit account settings - Edit folder settings - Edit global settings + Account settings + Folder settings + Global settings Remove account Clear pending actions (danger!) From fd38dc35193ac3cdfc3e3413417d1a3a82010f71 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Fri, 25 Mar 2011 17:15:46 +1100 Subject: [PATCH 024/116] Further wordings cleanups --- res/values/strings.xml | 10 ++++++---- src/com/fsck/k9/preferences/StorageExporter.java | 2 +- src/com/fsck/k9/preferences/StorageImporter.java | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 599722441..d7e8f4902 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1028,14 +1028,16 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin » › Unable to connect. - - Enter settings encryption password: + + Settings Import & Export + Please enter a password to protect your exported settings: + Please enter the password you used when exporting your settings: Export account settings - Export all settings + Export settings and accounts Import settings Exporting settings... Importing settings... - Exported settings to %s + Saved exported settings to %s Imported %s from %s 1 account diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 4375bed1c..ed311d45a 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -48,7 +48,7 @@ public class StorageExporter { @Override public void run() { - PasswordEntryDialog dialog = new PasswordEntryDialog(activity, activity.getString(R.string.settings_encryption_password_prompt), + PasswordEntryDialog dialog = new PasswordEntryDialog(activity, activity.getString(R.string.settings_export_encryption_password_prompt), new PasswordEntryDialog.PasswordEntryListener() { public void passwordChosen(final String chosenPassword) { diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index fdeecc656..57038af80 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -83,7 +83,7 @@ public class StorageImporter { activity.runOnUiThread(new Runnable() { @Override public void run() { - PasswordEntryDialog dialog = new PasswordEntryDialog(activity, activity.getString(R.string.settings_encryption_password_prompt), + PasswordEntryDialog dialog = new PasswordEntryDialog(activity, activity.getString(R.string.settings_import_encryption_password_prompt), new PasswordEntryDialog.PasswordEntryListener() { public void passwordChosen(final String chosenPassword) { AsyncUIProcessor.getInstance(activity.getApplication()).execute(new Runnable() { From 26258d76669d6d350f8c62be010e7ecee68c2988 Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 26 Mar 2011 06:43:39 +0100 Subject: [PATCH 025/116] Removed unused imports --- src/com/fsck/k9/Account.java | 1 - src/com/fsck/k9/Preferences.java | 1 - src/com/fsck/k9/activity/Accounts.java | 2 -- src/com/fsck/k9/activity/ExportHelper.java | 1 - src/com/fsck/k9/activity/K9Activity.java | 1 - src/com/fsck/k9/activity/K9ListActivity.java | 1 - src/com/fsck/k9/mail/store/StorageManager.java | 1 - src/com/fsck/k9/preferences/StorageExporterVersion1.java | 4 ---- 8 files changed, 12 deletions(-) diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java index 4bbacd1dc..f868e1a88 100644 --- a/src/com/fsck/k9/Account.java +++ b/src/com/fsck/k9/Account.java @@ -19,7 +19,6 @@ import com.fsck.k9.mail.store.StorageManager.StorageProvider; import com.fsck.k9.view.ColorChip; import java.util.ArrayList; -import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; diff --git a/src/com/fsck/k9/Preferences.java b/src/com/fsck/k9/Preferences.java index 4910150b8..6ee07f538 100644 --- a/src/com/fsck/k9/Preferences.java +++ b/src/com/fsck/k9/Preferences.java @@ -3,7 +3,6 @@ package com.fsck.k9; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 435b50d1c..0fdab7d86 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1,8 +1,6 @@ package com.fsck.k9.activity; -import java.io.FileNotFoundException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java index e9899fc19..932f64b1a 100644 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ b/src/com/fsck/k9/activity/ExportHelper.java @@ -6,7 +6,6 @@ import android.app.AlertDialog; import android.content.DialogInterface; import android.widget.Toast; -import com.fsck.k9.Account; import com.fsck.k9.R; import com.fsck.k9.preferences.StorageVersioning; diff --git a/src/com/fsck/k9/activity/K9Activity.java b/src/com/fsck/k9/activity/K9Activity.java index 3997f1608..59ebaa5e4 100644 --- a/src/com/fsck/k9/activity/K9Activity.java +++ b/src/com/fsck/k9/activity/K9Activity.java @@ -16,7 +16,6 @@ import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import android.widget.ScrollView; -import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.helper.DateFormatter; diff --git a/src/com/fsck/k9/activity/K9ListActivity.java b/src/com/fsck/k9/activity/K9ListActivity.java index c63781c82..a9aeaadb4 100644 --- a/src/com/fsck/k9/activity/K9ListActivity.java +++ b/src/com/fsck/k9/activity/K9ListActivity.java @@ -7,7 +7,6 @@ import android.view.KeyEvent; import android.widget.AdapterView; import android.widget.ListView; -import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.helper.DateFormatter; diff --git a/src/com/fsck/k9/mail/store/StorageManager.java b/src/com/fsck/k9/mail/store/StorageManager.java index ab861ecb8..6537e637c 100644 --- a/src/com/fsck/k9/mail/store/StorageManager.java +++ b/src/com/fsck/k9/mail/store/StorageManager.java @@ -20,7 +20,6 @@ import android.util.Log; import com.fsck.k9.K9; import com.fsck.k9.R; -import com.fsck.k9.service.MailService; /** * Manager for different {@link StorageProvider} -classes that abstract access diff --git a/src/com/fsck/k9/preferences/StorageExporterVersion1.java b/src/com/fsck/k9/preferences/StorageExporterVersion1.java index 9c4cf8aa1..742b7896b 100644 --- a/src/com/fsck/k9/preferences/StorageExporterVersion1.java +++ b/src/com/fsck/k9/preferences/StorageExporterVersion1.java @@ -1,14 +1,10 @@ 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; From 18cefedf32403e8352bfa5b7acaaddf9bef08b33 Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 26 Mar 2011 06:59:45 +0100 Subject: [PATCH 026/116] Get rid of the STORAGE_VERSION enum in StorageVersioning --- src/com/fsck/k9/activity/ExportHelper.java | 2 +- .../k9/preferences/StorageVersioning.java | 68 ++++++------------- 2 files changed, 21 insertions(+), 49 deletions(-) diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java index 932f64b1a..fe8557dd0 100644 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ b/src/com/fsck/k9/activity/ExportHelper.java @@ -12,7 +12,7 @@ import com.fsck.k9.preferences.StorageVersioning; public class ExportHelper { public static void exportSettings(final Activity activity, final HashSet accountUuids, final ExportListener listener) { // Once there are more versions, build a UI to select which one to use. For now, use the encrypted/encoded version: - String version = StorageVersioning.STORAGE_VERSION.VERSION1.getVersionString(); + String version = StorageVersioning.ENCRYPTED_XML_FILE; AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, version, accountUuids, new ExportListener() { @Override diff --git a/src/com/fsck/k9/preferences/StorageVersioning.java b/src/com/fsck/k9/preferences/StorageVersioning.java index 53604070a..95080c1d2 100644 --- a/src/com/fsck/k9/preferences/StorageVersioning.java +++ b/src/com/fsck/k9/preferences/StorageVersioning.java @@ -5,74 +5,46 @@ import java.util.Map; public class StorageVersioning { - public enum STORAGE_VERSION { - VERSION1(StorageImporterVersion1.class, StorageExporterVersion1.class, true, STORAGE_VERSION_1); - - private Class importerClass; - private Class exporterClass; - private boolean needsKey; - private String versionString; - - - private STORAGE_VERSION(Class imclass, Class exclass, boolean nk, String vs) { - importerClass = imclass; - exporterClass = exclass; - needsKey = nk; - versionString = vs; - - } - public Class getImporterClass() { - return importerClass; - } - public IStorageImporter createImporter() throws InstantiationException, IllegalAccessException { - IStorageImporter storageImporter = importerClass.newInstance(); - return storageImporter; - } - public Class 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 final String ENCRYPTED_XML_FILE = "1"; - public static Map versionMap = new HashMap(); + public static Map versionMap = new HashMap(); static { - versionMap.put(STORAGE_VERSION.VERSION1.getVersionString(), STORAGE_VERSION.VERSION1); + versionMap.put(ENCRYPTED_XML_FILE, new StorageVersioning(StorageImporterVersion1.class, StorageExporterVersion1.class, true)); } public static IStorageImporter createImporter(String version) throws InstantiationException, IllegalAccessException { - STORAGE_VERSION storageVersion = versionMap.get(version); + StorageVersioning storageVersion = versionMap.get(version); if (storageVersion == null) { return null; } - return storageVersion.createImporter(); + return storageVersion.importerClass.newInstance(); } public static IStorageExporter createExporter(String version) throws InstantiationException, IllegalAccessException { - STORAGE_VERSION storageVersion = versionMap.get(version); + StorageVersioning storageVersion = versionMap.get(version); if (storageVersion == null) { return null; } - return storageVersion.createExporter(); + return storageVersion.exporterClass.newInstance(); } - public Boolean needsKey(String version) { - STORAGE_VERSION storageVersion = versionMap.get(version); + public static Boolean needsKey(String version) { + StorageVersioning storageVersion = versionMap.get(version); if (storageVersion == null) { return null; } - return storageVersion.needsKey(); + return storageVersion.needsKey; } + + private final Class importerClass; + private final Class exporterClass; + private final boolean needsKey; + + private StorageVersioning(Class imclass, Class exclass, boolean nk) { + importerClass = imclass; + exporterClass = exclass; + needsKey = nk; + } } From 4bdc20c1277a8a4c09119af86c9d38f57f5f9c0e Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 26 Mar 2011 07:00:53 +0100 Subject: [PATCH 027/116] Rename StorageVersioning to StorageFormat --- src/com/fsck/k9/activity/ExportHelper.java | 4 ++-- src/com/fsck/k9/preferences/StorageExporter.java | 2 +- .../{StorageVersioning.java => StorageFormat.java} | 14 +++++++------- src/com/fsck/k9/preferences/StorageImporter.java | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) rename src/com/fsck/k9/preferences/{StorageVersioning.java => StorageFormat.java} (65%) diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java index fe8557dd0..c3414c667 100644 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ b/src/com/fsck/k9/activity/ExportHelper.java @@ -7,12 +7,12 @@ import android.content.DialogInterface; import android.widget.Toast; import com.fsck.k9.R; -import com.fsck.k9.preferences.StorageVersioning; +import com.fsck.k9.preferences.StorageFormat; public class ExportHelper { public static void exportSettings(final Activity activity, final HashSet accountUuids, final ExportListener listener) { // Once there are more versions, build a UI to select which one to use. For now, use the encrypted/encoded version: - String version = StorageVersioning.ENCRYPTED_XML_FILE; + String version = StorageFormat.ENCRYPTED_XML_FILE; AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, version, accountUuids, new ExportListener() { @Override diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index ed311d45a..01c606df7 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -17,7 +17,7 @@ import com.fsck.k9.activity.PasswordEntryDialog; public class StorageExporter { private static void exportPreferences(Activity activity, String version, HashSet accountUuids, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { try { - IStorageExporter storageExporter = StorageVersioning.createExporter(version); + IStorageExporter storageExporter = StorageFormat.createExporter(version); if (storageExporter == null) { throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, version), null); } diff --git a/src/com/fsck/k9/preferences/StorageVersioning.java b/src/com/fsck/k9/preferences/StorageFormat.java similarity index 65% rename from src/com/fsck/k9/preferences/StorageVersioning.java rename to src/com/fsck/k9/preferences/StorageFormat.java index 95080c1d2..6d63254cf 100644 --- a/src/com/fsck/k9/preferences/StorageVersioning.java +++ b/src/com/fsck/k9/preferences/StorageFormat.java @@ -4,17 +4,17 @@ import java.util.HashMap; import java.util.Map; -public class StorageVersioning { +public class StorageFormat { // Never, ever re-use these numbers! public static final String ENCRYPTED_XML_FILE = "1"; - public static Map versionMap = new HashMap(); + public static Map versionMap = new HashMap(); static { - versionMap.put(ENCRYPTED_XML_FILE, new StorageVersioning(StorageImporterVersion1.class, StorageExporterVersion1.class, true)); + versionMap.put(ENCRYPTED_XML_FILE, new StorageFormat(StorageImporterVersion1.class, StorageExporterVersion1.class, true)); } public static IStorageImporter createImporter(String version) throws InstantiationException, IllegalAccessException { - StorageVersioning storageVersion = versionMap.get(version); + StorageFormat storageVersion = versionMap.get(version); if (storageVersion == null) { return null; } @@ -22,7 +22,7 @@ public class StorageVersioning { } public static IStorageExporter createExporter(String version) throws InstantiationException, IllegalAccessException { - StorageVersioning storageVersion = versionMap.get(version); + StorageFormat storageVersion = versionMap.get(version); if (storageVersion == null) { return null; } @@ -30,7 +30,7 @@ public class StorageVersioning { } public static Boolean needsKey(String version) { - StorageVersioning storageVersion = versionMap.get(version); + StorageFormat storageVersion = versionMap.get(version); if (storageVersion == null) { return null; } @@ -42,7 +42,7 @@ public class StorageVersioning { private final Class exporterClass; private final boolean needsKey; - private StorageVersioning(Class imclass, Class exclass, boolean nk) { + private StorageFormat(Class imclass, Class exclass, boolean nk) { importerClass = imclass; exporterClass = exclass; needsKey = nk; diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index 57038af80..e6917156e 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -42,7 +42,7 @@ public class StorageImporter { String version = dataset.attributes.get("version"); Log.i(K9.LOG_TAG, "Got settings file version " + version); - IStorageImporter storageImporter = StorageVersioning.createImporter(version); + IStorageImporter storageImporter = StorageFormat.createImporter(version); if (storageImporter == null) { throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, version)); } From 087feb478bc013013d078201ee90607d914319cd Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 26 Mar 2011 07:19:20 +0100 Subject: [PATCH 028/116] First step at replacing the term "version" with "storageFormat" --- src/com/fsck/k9/activity/AsyncUIProcessor.java | 4 ++-- src/com/fsck/k9/activity/ExportHelper.java | 6 +++--- src/com/fsck/k9/preferences/StorageExporter.java | 14 +++++++------- ...on1.java => StorageExporterEncryptedXml.java} | 2 +- src/com/fsck/k9/preferences/StorageFormat.java | 16 ++++++++-------- src/com/fsck/k9/preferences/StorageImporter.java | 8 ++++---- ...on1.java => StorageImporterEncryptedXml.java} | 2 +- 7 files changed, 26 insertions(+), 26 deletions(-) rename src/com/fsck/k9/preferences/{StorageExporterVersion1.java => StorageExporterEncryptedXml.java} (97%) rename src/com/fsck/k9/preferences/{StorageImporterVersion1.java => StorageImporterEncryptedXml.java} (98%) diff --git a/src/com/fsck/k9/activity/AsyncUIProcessor.java b/src/com/fsck/k9/activity/AsyncUIProcessor.java index 52d047296..7c9baa55c 100644 --- a/src/com/fsck/k9/activity/AsyncUIProcessor.java +++ b/src/com/fsck/k9/activity/AsyncUIProcessor.java @@ -41,7 +41,7 @@ public class AsyncUIProcessor { public void execute(Runnable runnable) { threadPool.execute(runnable); } - public void exportSettings(final Activity activity, final String version, final HashSet accountUuids, final ExportListener listener) { + public void exportSettings(final Activity activity, final String storageFormat, final HashSet accountUuids, final ExportListener listener) { threadPool.execute(new Runnable() { @Override @@ -54,7 +54,7 @@ public class AsyncUIProcessor { dir.mkdirs(); File file = Utility.createUniqueFile(dir, "settings.k9s"); String fileName = file.getAbsolutePath(); - StorageExporter.exportPreferences(activity, version, accountUuids, fileName, null, listener); + StorageExporter.exportPreferences(activity, storageFormat, accountUuids, fileName, null, listener); } catch (Exception e) { Log.w(K9.LOG_TAG, "Exception during export", e); listener.failure(e.getLocalizedMessage(), e); diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java index c3414c667..385908001 100644 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ b/src/com/fsck/k9/activity/ExportHelper.java @@ -11,9 +11,9 @@ import com.fsck.k9.preferences.StorageFormat; public class ExportHelper { public static void exportSettings(final Activity activity, final HashSet accountUuids, final ExportListener listener) { - // Once there are more versions, build a UI to select which one to use. For now, use the encrypted/encoded version: - String version = StorageFormat.ENCRYPTED_XML_FILE; - AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, version, accountUuids, new ExportListener() { + // Once there are more file formats, build a UI to select which one to use. For now, use the encrypted/encoded format: + String storageFormat = StorageFormat.ENCRYPTED_XML_FILE; + AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, storageFormat, accountUuids, new ExportListener() { @Override public void canceled() { diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 01c606df7..4f5ea6359 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -15,11 +15,11 @@ import com.fsck.k9.activity.ExportListener; import com.fsck.k9.activity.PasswordEntryDialog; public class StorageExporter { - private static void exportPreferences(Activity activity, String version, HashSet accountUuids, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { + private static void exportPreferences(Activity activity, String storageFormat, HashSet accountUuids, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { try { - IStorageExporter storageExporter = StorageFormat.createExporter(version); + IStorageExporter storageExporter = StorageFormat.createExporter(storageFormat); if (storageExporter == null) { - throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, version), null); + throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, storageFormat), null); } if (storageExporter.needsKey() && encryptionKey == null) { gatherPassword(activity, storageExporter, accountUuids, fileName, os, listener); @@ -35,12 +35,12 @@ public class StorageExporter { } } - public static void exportPreferences(Activity activity, String version, HashSet accountUuids, String fileName, String encryptionKey, final ExportListener listener) throws StorageImportExportException { - exportPreferences(activity, version, accountUuids, fileName, null, encryptionKey, listener); + public static void exportPreferences(Activity activity, String storageFormat, HashSet accountUuids, String fileName, String encryptionKey, final ExportListener listener) throws StorageImportExportException { + exportPreferences(activity, storageFormat, accountUuids, fileName, null, encryptionKey, listener); } - public static void exportPrefererences(Activity activity, String version, HashSet accountUuids, OutputStream os, String encryptionKey, final ExportListener listener) throws StorageImportExportException { - exportPreferences(activity, version, accountUuids, null, os, encryptionKey, listener); + public static void exportPrefererences(Activity activity, String storageFormat, HashSet accountUuids, OutputStream os, String encryptionKey, final ExportListener listener) throws StorageImportExportException { + exportPreferences(activity, storageFormat, accountUuids, null, os, encryptionKey, listener); } private static void gatherPassword(final Activity activity, final IStorageExporter storageExporter, final HashSet accountUuids, final String fileName, final OutputStream os, final ExportListener listener) { diff --git a/src/com/fsck/k9/preferences/StorageExporterVersion1.java b/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java similarity index 97% rename from src/com/fsck/k9/preferences/StorageExporterVersion1.java rename to src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java index 742b7896b..ca08c09cc 100644 --- a/src/com/fsck/k9/preferences/StorageExporterVersion1.java +++ b/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java @@ -14,7 +14,7 @@ import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.Preferences; -public class StorageExporterVersion1 implements IStorageExporter { +public class StorageExporterEncryptedXml implements IStorageExporter { public void exportPreferences(Context context, HashSet accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException { try { Log.i(K9.LOG_TAG, "Exporting preferences"); diff --git a/src/com/fsck/k9/preferences/StorageFormat.java b/src/com/fsck/k9/preferences/StorageFormat.java index 6d63254cf..4c472edcb 100644 --- a/src/com/fsck/k9/preferences/StorageFormat.java +++ b/src/com/fsck/k9/preferences/StorageFormat.java @@ -8,29 +8,29 @@ public class StorageFormat { // Never, ever re-use these numbers! public static final String ENCRYPTED_XML_FILE = "1"; - public static Map versionMap = new HashMap(); + public static Map storageFormatMap = new HashMap(); static { - versionMap.put(ENCRYPTED_XML_FILE, new StorageFormat(StorageImporterVersion1.class, StorageExporterVersion1.class, true)); + storageFormatMap.put(ENCRYPTED_XML_FILE, new StorageFormat(StorageImporterEncryptedXml.class, StorageExporterEncryptedXml.class, true)); } - public static IStorageImporter createImporter(String version) throws InstantiationException, IllegalAccessException { - StorageFormat storageVersion = versionMap.get(version); + public static IStorageImporter createImporter(String storageFormat) throws InstantiationException, IllegalAccessException { + StorageFormat storageVersion = storageFormatMap.get(storageFormat); if (storageVersion == null) { return null; } return storageVersion.importerClass.newInstance(); } - public static IStorageExporter createExporter(String version) throws InstantiationException, IllegalAccessException { - StorageFormat storageVersion = versionMap.get(version); + public static IStorageExporter createExporter(String storageFormat) throws InstantiationException, IllegalAccessException { + StorageFormat storageVersion = storageFormatMap.get(storageFormat); if (storageVersion == null) { return null; } return storageVersion.exporterClass.newInstance(); } - public static Boolean needsKey(String version) { - StorageFormat storageVersion = versionMap.get(version); + public static Boolean needsKey(String storageFormat) { + StorageFormat storageVersion = storageFormatMap.get(storageFormat); if (storageVersion == null) { return null; } diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index e6917156e..e40b7708c 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -39,12 +39,12 @@ public class StorageImporter { xr.parse(new InputSource(is)); ImportElement dataset = handler.getRootElement(); - String version = dataset.attributes.get("version"); - Log.i(K9.LOG_TAG, "Got settings file version " + version); + String storageFormat = dataset.attributes.get("version"); + Log.i(K9.LOG_TAG, "Got settings file version " + storageFormat); - IStorageImporter storageImporter = StorageFormat.createImporter(version); + IStorageImporter storageImporter = StorageFormat.createImporter(storageFormat); if (storageImporter == null) { - throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, version)); + throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, storageFormat)); } if (storageImporter.needsKey() && providedEncryptionKey == null) { gatherPassword(activity, storageImporter, dataset, listener); diff --git a/src/com/fsck/k9/preferences/StorageImporterVersion1.java b/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java similarity index 98% rename from src/com/fsck/k9/preferences/StorageImporterVersion1.java rename to src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java index 1a8c28a7f..f09836fc3 100644 --- a/src/com/fsck/k9/preferences/StorageImporterVersion1.java +++ b/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java @@ -16,7 +16,7 @@ import com.fsck.k9.K9; import com.fsck.k9.Preferences; import com.fsck.k9.preferences.StorageImporter.ImportElement; -public class StorageImporterVersion1 implements IStorageImporter { +public class StorageImporterEncryptedXml implements IStorageImporter { public int importPreferences(Preferences preferences, SharedPreferences.Editor editor, ImportElement dataset, String encryptionKey) throws StorageImportExportException { try { From 52825f409feba1175ec042237ab5ced9b60b60c0 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Sun, 27 Mar 2011 11:37:37 +0800 Subject: [PATCH 029/116] As suggested by cketti, HashSet -> Set in our method signature definition --- src/com/fsck/k9/activity/AsyncUIProcessor.java | 3 ++- src/com/fsck/k9/activity/ExportHelper.java | 4 +++- src/com/fsck/k9/preferences/IStorageExporter.java | 3 ++- src/com/fsck/k9/preferences/StorageExporter.java | 9 +++++---- .../fsck/k9/preferences/StorageExporterEncryptedXml.java | 3 ++- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/com/fsck/k9/activity/AsyncUIProcessor.java b/src/com/fsck/k9/activity/AsyncUIProcessor.java index 7c9baa55c..264e49dbc 100644 --- a/src/com/fsck/k9/activity/AsyncUIProcessor.java +++ b/src/com/fsck/k9/activity/AsyncUIProcessor.java @@ -2,6 +2,7 @@ package com.fsck.k9.activity; import java.io.File; import java.io.InputStream; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.HashSet; @@ -41,7 +42,7 @@ public class AsyncUIProcessor { public void execute(Runnable runnable) { threadPool.execute(runnable); } - public void exportSettings(final Activity activity, final String storageFormat, final HashSet accountUuids, final ExportListener listener) { + public void exportSettings(final Activity activity, final String storageFormat, final Set accountUuids, final ExportListener listener) { threadPool.execute(new Runnable() { @Override diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java index 385908001..1fd1fa575 100644 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ b/src/com/fsck/k9/activity/ExportHelper.java @@ -1,6 +1,8 @@ package com.fsck.k9.activity; import java.util.HashSet; +import java.util.Set; + import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; @@ -10,7 +12,7 @@ import com.fsck.k9.R; import com.fsck.k9.preferences.StorageFormat; public class ExportHelper { - public static void exportSettings(final Activity activity, final HashSet accountUuids, final ExportListener listener) { + public static void exportSettings(final Activity activity, final Set accountUuids, final ExportListener listener) { // Once there are more file formats, build a UI to select which one to use. For now, use the encrypted/encoded format: String storageFormat = StorageFormat.ENCRYPTED_XML_FILE; AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, storageFormat, accountUuids, new ExportListener() { diff --git a/src/com/fsck/k9/preferences/IStorageExporter.java b/src/com/fsck/k9/preferences/IStorageExporter.java index 5cf94afad..63cbd00f6 100644 --- a/src/com/fsck/k9/preferences/IStorageExporter.java +++ b/src/com/fsck/k9/preferences/IStorageExporter.java @@ -2,10 +2,11 @@ package com.fsck.k9.preferences; import java.io.OutputStream; import java.util.HashSet; +import java.util.Set; import android.content.Context; public interface IStorageExporter { public boolean needsKey(); - public void exportPreferences(Context context, HashSet accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException; + public void exportPreferences(Context context, Set accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException; } diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 4f5ea6359..148d09c3b 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.HashSet; +import java.util.Set; import android.app.Activity; import android.util.Log; @@ -15,7 +16,7 @@ import com.fsck.k9.activity.ExportListener; import com.fsck.k9.activity.PasswordEntryDialog; public class StorageExporter { - private static void exportPreferences(Activity activity, String storageFormat, HashSet accountUuids, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { + private static void exportPreferences(Activity activity, String storageFormat, Set accountUuids, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { try { IStorageExporter storageExporter = StorageFormat.createExporter(storageFormat); if (storageExporter == null) { @@ -35,7 +36,7 @@ public class StorageExporter { } } - public static void exportPreferences(Activity activity, String storageFormat, HashSet accountUuids, String fileName, String encryptionKey, final ExportListener listener) throws StorageImportExportException { + public static void exportPreferences(Activity activity, String storageFormat, Set accountUuids, String fileName, String encryptionKey, final ExportListener listener) throws StorageImportExportException { exportPreferences(activity, storageFormat, accountUuids, fileName, null, encryptionKey, listener); } @@ -43,7 +44,7 @@ public class StorageExporter { exportPreferences(activity, storageFormat, accountUuids, null, os, encryptionKey, listener); } - private static void gatherPassword(final Activity activity, final IStorageExporter storageExporter, final HashSet accountUuids, final String fileName, final OutputStream os, final ExportListener listener) { + private static void gatherPassword(final Activity activity, final IStorageExporter storageExporter, final Set accountUuids, final String fileName, final OutputStream os, final ExportListener listener) { activity.runOnUiThread(new Runnable() { @Override @@ -81,7 +82,7 @@ public class StorageExporter { } - private static void finishExport(Activity activity, IStorageExporter storageExporter, HashSet accountUuids, String fileName, OutputStream os, String encryptionKey, ExportListener listener) throws StorageImportExportException { + private static void finishExport(Activity activity, IStorageExporter storageExporter, Set accountUuids, String fileName, OutputStream os, String encryptionKey, ExportListener listener) throws StorageImportExportException { boolean needToClose = false; if (listener != null) { listener.started(); diff --git a/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java b/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java index ca08c09cc..eb2ec1e6d 100644 --- a/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java +++ b/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java @@ -5,6 +5,7 @@ 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; @@ -15,7 +16,7 @@ import com.fsck.k9.K9; import com.fsck.k9.Preferences; public class StorageExporterEncryptedXml implements IStorageExporter { - public void exportPreferences(Context context, HashSet accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException { + public void exportPreferences(Context context, Set accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException { try { Log.i(K9.LOG_TAG, "Exporting preferences"); K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.ENCRYPT); From 9deeaf9c11e5560f7413e7cab96da916394425e3 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Sun, 27 Mar 2011 12:03:23 +0800 Subject: [PATCH 030/116] No longer export global settings when exporting one account --- src/com/fsck/k9/activity/Accounts.java | 8 +++---- .../fsck/k9/activity/AsyncUIProcessor.java | 4 ++-- src/com/fsck/k9/activity/ExportHelper.java | 4 ++-- .../fsck/k9/preferences/IStorageExporter.java | 2 +- .../fsck/k9/preferences/StorageExporter.java | 22 +++++++++---------- .../StorageExporterEncryptedXml.java | 5 ++++- 6 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 0fdab7d86..8de469b09 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -659,7 +659,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC onRecreate(realAccount); break; case R.id.export: - onExport(realAccount); + onExport(false, realAccount); break; } return true; @@ -708,7 +708,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC onSearchRequested(); break; case R.id.export_all: - onExport(null); + onExport(true, null); break; case R.id.import_settings: onImport(); @@ -1110,7 +1110,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } - public void onExport(final Account account) { + public void onExport(final boolean includeGlobals, final Account account) { // TODO, prompt to allow a user to choose which accounts to export HashSet accountUuids; @@ -1119,7 +1119,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC accountUuids.add(account.getUuid()); } - ExportHelper.exportSettings(this, accountUuids, new ExportListener() { + ExportHelper.exportSettings(this, includeGlobals, accountUuids, new ExportListener() { @Override public void canceled() { diff --git a/src/com/fsck/k9/activity/AsyncUIProcessor.java b/src/com/fsck/k9/activity/AsyncUIProcessor.java index 264e49dbc..0ab2971e7 100644 --- a/src/com/fsck/k9/activity/AsyncUIProcessor.java +++ b/src/com/fsck/k9/activity/AsyncUIProcessor.java @@ -42,7 +42,7 @@ public class AsyncUIProcessor { public void execute(Runnable runnable) { threadPool.execute(runnable); } - public void exportSettings(final Activity activity, final String storageFormat, final Set accountUuids, final ExportListener listener) { + public void exportSettings(final Activity activity, final String storageFormat, final boolean includeGlobals, final Set accountUuids, final ExportListener listener) { threadPool.execute(new Runnable() { @Override @@ -55,7 +55,7 @@ public class AsyncUIProcessor { dir.mkdirs(); File file = Utility.createUniqueFile(dir, "settings.k9s"); String fileName = file.getAbsolutePath(); - StorageExporter.exportPreferences(activity, storageFormat, accountUuids, fileName, null, listener); + StorageExporter.exportPreferences(activity, storageFormat, includeGlobals, accountUuids, fileName, null, listener); } catch (Exception e) { Log.w(K9.LOG_TAG, "Exception during export", e); listener.failure(e.getLocalizedMessage(), e); diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java index 1fd1fa575..6ab57932b 100644 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ b/src/com/fsck/k9/activity/ExportHelper.java @@ -12,10 +12,10 @@ import com.fsck.k9.R; import com.fsck.k9.preferences.StorageFormat; public class ExportHelper { - public static void exportSettings(final Activity activity, final Set accountUuids, final ExportListener listener) { + public static void exportSettings(final Activity activity, final boolean includeGlobals, final Set accountUuids, final ExportListener listener) { // Once there are more file formats, build a UI to select which one to use. For now, use the encrypted/encoded format: String storageFormat = StorageFormat.ENCRYPTED_XML_FILE; - AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, storageFormat, accountUuids, new ExportListener() { + AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, storageFormat, includeGlobals, accountUuids, new ExportListener() { @Override public void canceled() { diff --git a/src/com/fsck/k9/preferences/IStorageExporter.java b/src/com/fsck/k9/preferences/IStorageExporter.java index 63cbd00f6..095db3f36 100644 --- a/src/com/fsck/k9/preferences/IStorageExporter.java +++ b/src/com/fsck/k9/preferences/IStorageExporter.java @@ -8,5 +8,5 @@ import android.content.Context; public interface IStorageExporter { public boolean needsKey(); - public void exportPreferences(Context context, Set accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException; + public void exportPreferences(Context context, boolean includeGlobals, Set accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException; } diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 148d09c3b..890d2b2b4 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -16,16 +16,16 @@ import com.fsck.k9.activity.ExportListener; import com.fsck.k9.activity.PasswordEntryDialog; public class StorageExporter { - private static void exportPreferences(Activity activity, String storageFormat, Set accountUuids, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { + private static void exportPreferences(Activity activity, String storageFormat, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { try { IStorageExporter storageExporter = StorageFormat.createExporter(storageFormat); if (storageExporter == null) { throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, storageFormat), null); } if (storageExporter.needsKey() && encryptionKey == null) { - gatherPassword(activity, storageExporter, accountUuids, fileName, os, listener); + gatherPassword(activity, storageExporter, includeGlobals, accountUuids, fileName, os, listener); } else { - finishExport(activity, storageExporter, accountUuids, fileName, os, encryptionKey, listener); + finishExport(activity, storageExporter, includeGlobals, accountUuids, fileName, os, encryptionKey, listener); } } @@ -36,15 +36,15 @@ public class StorageExporter { } } - public static void exportPreferences(Activity activity, String storageFormat, Set accountUuids, String fileName, String encryptionKey, final ExportListener listener) throws StorageImportExportException { - exportPreferences(activity, storageFormat, accountUuids, fileName, null, encryptionKey, listener); + public static void exportPreferences(Activity activity, String storageFormat, boolean includeGlobals, Set accountUuids, String fileName, String encryptionKey, final ExportListener listener) throws StorageImportExportException { + exportPreferences(activity, storageFormat, includeGlobals, accountUuids, fileName, null, encryptionKey, listener); } - public static void exportPrefererences(Activity activity, String storageFormat, HashSet accountUuids, OutputStream os, String encryptionKey, final ExportListener listener) throws StorageImportExportException { - exportPreferences(activity, storageFormat, accountUuids, null, os, encryptionKey, listener); + public static void exportPrefererences(Activity activity, String storageFormat, boolean includeGlobals, HashSet accountUuids, OutputStream os, String encryptionKey, final ExportListener listener) throws StorageImportExportException { + exportPreferences(activity, storageFormat, includeGlobals, accountUuids, null, os, encryptionKey, listener); } - private static void gatherPassword(final Activity activity, final IStorageExporter storageExporter, final Set accountUuids, final String fileName, final OutputStream os, final ExportListener listener) { + private static void gatherPassword(final Activity activity, final IStorageExporter storageExporter, final boolean includeGlobals, final Set accountUuids, final String fileName, final OutputStream os, final ExportListener listener) { activity.runOnUiThread(new Runnable() { @Override @@ -58,7 +58,7 @@ public class StorageExporter { @Override public void run() { try { - finishExport(activity, storageExporter, accountUuids, fileName, os, chosenPassword, listener); + finishExport(activity, storageExporter, includeGlobals, accountUuids, fileName, os, chosenPassword, listener); } catch (Exception e) { Log.w(K9.LOG_TAG, "Exception while finishing export", e); if (listener != null) { @@ -82,7 +82,7 @@ public class StorageExporter { } - private static void finishExport(Activity activity, IStorageExporter storageExporter, Set accountUuids, String fileName, OutputStream os, String encryptionKey, ExportListener listener) throws StorageImportExportException { + private static void finishExport(Activity activity, IStorageExporter storageExporter, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey, ExportListener listener) throws StorageImportExportException { boolean needToClose = false; if (listener != null) { listener.started(); @@ -96,7 +96,7 @@ public class StorageExporter { os = new FileOutputStream(outFile); } if (os != null) { - storageExporter.exportPreferences(activity, accountUuids, os, encryptionKey); + storageExporter.exportPreferences(activity, includeGlobals, accountUuids, os, encryptionKey); if (listener != null) { if (fileName != null) { listener.success(fileName); diff --git a/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java b/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java index eb2ec1e6d..b406834fa 100644 --- a/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java +++ b/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java @@ -16,7 +16,7 @@ import com.fsck.k9.K9; import com.fsck.k9.Preferences; public class StorageExporterEncryptedXml implements IStorageExporter { - public void exportPreferences(Context context, Set accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException { + public void exportPreferences(Context context, boolean includeGlobals, Set accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException { try { Log.i(K9.LOG_TAG, "Exporting preferences"); K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.ENCRYPT); @@ -53,6 +53,9 @@ public class StorageExporterEncryptedXml implements IStorageExporter { //Log.i(K9.LOG_TAG, "Skipping key " + key + " which is not for any current account"); continue; } + } else if (!includeGlobals) { + // Skip global config entries if the user didn't request them + continue; } String keyEnc = krypto.encrypt(key); String valueEnc = krypto.encrypt(value); From b3b8302c52c428847a1b6c388b6f7e26875da277 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Sun, 27 Mar 2011 12:40:00 +0800 Subject: [PATCH 031/116] Remove some vestiges of HashSet --- src/com/fsck/k9/activity/ExportHelper.java | 1 - src/com/fsck/k9/preferences/StorageExporter.java | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java index 6ab57932b..350419505 100644 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ b/src/com/fsck/k9/activity/ExportHelper.java @@ -1,6 +1,5 @@ package com.fsck.k9.activity; -import java.util.HashSet; import java.util.Set; import android.app.Activity; diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 890d2b2b4..0419cc862 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -3,7 +3,6 @@ package com.fsck.k9.preferences; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; -import java.util.HashSet; import java.util.Set; import android.app.Activity; @@ -40,7 +39,7 @@ public class StorageExporter { exportPreferences(activity, storageFormat, includeGlobals, accountUuids, fileName, null, encryptionKey, listener); } - public static void exportPrefererences(Activity activity, String storageFormat, boolean includeGlobals, HashSet accountUuids, OutputStream os, String encryptionKey, final ExportListener listener) throws StorageImportExportException { + public static void exportPrefererences(Activity activity, String storageFormat, boolean includeGlobals, Set accountUuids, OutputStream os, String encryptionKey, final ExportListener listener) throws StorageImportExportException { exportPreferences(activity, storageFormat, includeGlobals, accountUuids, null, os, encryptionKey, listener); } From 0bded12843d8e30632891cae4e4349276b8c9356 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Sun, 27 Mar 2011 13:09:26 +0800 Subject: [PATCH 032/116] Kill off ExportHelper. It had become clutter that was only used once --- src/com/fsck/k9/activity/Accounts.java | 21 +++++- src/com/fsck/k9/activity/ExportHelper.java | 86 ---------------------- 2 files changed, 20 insertions(+), 87 deletions(-) delete mode 100644 src/com/fsck/k9/activity/ExportHelper.java diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 8de469b09..714afbd26 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -61,6 +61,8 @@ import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.view.ColorChip; +import com.fsck.k9.preferences.StorageFormat; + public class Accounts extends K9ListActivity implements OnItemClickListener, OnClickListener { @@ -1119,7 +1121,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC accountUuids.add(account.getUuid()); } - ExportHelper.exportSettings(this, includeGlobals, accountUuids, new ExportListener() { + // Once there are more file formats, build a UI to select which one to use. For now, use the encrypted/encoded format: + String storageFormat = StorageFormat.ENCRYPTED_XML_FILE; + AsyncUIProcessor.getInstance(this.getApplication()).exportSettings(this, storageFormat, includeGlobals, accountUuids, new ExportListener() { @Override public void canceled() { @@ -1129,23 +1133,38 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override public void failure(String message, Exception e) { setProgress(false); + showDialog(Accounts.this, R.string.settings_export_failed_header, Accounts.this.getString(R.string.settings_export_failure, message)); } @Override public void started() { setProgress(true); + runOnUiThread(new Runnable() { + + @Override + public void run() { + String toastText = Accounts.this.getString(R.string.settings_exporting); + Toast toast = Toast.makeText(Accounts.this, toastText, Toast.LENGTH_SHORT); + toast.show(); + } + }); } @Override public void success(String fileName) { setProgress(false); + showDialog(Accounts.this, R.string.settings_export_success_header, Accounts.this.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 setProgress(false); } }); + + + } } diff --git a/src/com/fsck/k9/activity/ExportHelper.java b/src/com/fsck/k9/activity/ExportHelper.java deleted file mode 100644 index 350419505..000000000 --- a/src/com/fsck/k9/activity/ExportHelper.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.fsck.k9.activity; - -import java.util.Set; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.widget.Toast; - -import com.fsck.k9.R; -import com.fsck.k9.preferences.StorageFormat; - -public class ExportHelper { - public static void exportSettings(final Activity activity, final boolean includeGlobals, final Set accountUuids, final ExportListener listener) { - // Once there are more file formats, build a UI to select which one to use. For now, use the encrypted/encoded format: - String storageFormat = StorageFormat.ENCRYPTED_XML_FILE; - AsyncUIProcessor.getInstance(activity.getApplication()).exportSettings(activity, storageFormat, includeGlobals, accountUuids, new ExportListener() { - - @Override - public void canceled() { - if (listener != null) { - 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(); - } - }); - } - - @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 - } - }); - } - - private static void showDialog(final Activity activity, final int headerRes, final String message) { - activity.runOnUiThread(new Runnable() { - - @Override - public void run() { - 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(); - } - }); - - } -} From e5ef068c1535712b32c5c05cf67d1677537cfd18 Mon Sep 17 00:00:00 2001 From: danapple Date: Sun, 27 Mar 2011 11:37:43 -0500 Subject: [PATCH 033/116] Move the generation of the XML envelope to a common place since it would be used in all storageFormats. StorageImporter uses the header to figure out which specific import implementation to use. --- .../fsck/k9/preferences/IStorageExporter.java | 2 +- .../fsck/k9/preferences/StorageExporter.java | 23 +++++++++++++++---- .../StorageExporterEncryptedXml.java | 7 +----- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/com/fsck/k9/preferences/IStorageExporter.java b/src/com/fsck/k9/preferences/IStorageExporter.java index 095db3f36..76bdce369 100644 --- a/src/com/fsck/k9/preferences/IStorageExporter.java +++ b/src/com/fsck/k9/preferences/IStorageExporter.java @@ -1,12 +1,12 @@ package com.fsck.k9.preferences; import java.io.OutputStream; -import java.util.HashSet; import java.util.Set; import android.content.Context; public interface IStorageExporter { public boolean needsKey(); + // exportPreferences must be sure to flush all data to the OutputStream before returning public void exportPreferences(Context context, boolean includeGlobals, Set accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException; } diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 0419cc862..50a918e68 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -3,6 +3,8 @@ package com.fsck.k9.preferences; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.util.Set; import android.app.Activity; @@ -22,9 +24,9 @@ public class StorageExporter { throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, storageFormat), null); } if (storageExporter.needsKey() && encryptionKey == null) { - gatherPassword(activity, storageExporter, includeGlobals, accountUuids, fileName, os, listener); + gatherPassword(activity, storageFormat, storageExporter, includeGlobals, accountUuids, fileName, os, listener); } else { - finishExport(activity, storageExporter, includeGlobals, accountUuids, fileName, os, encryptionKey, listener); + finishExport(activity, storageFormat, storageExporter, includeGlobals, accountUuids, fileName, os, encryptionKey, listener); } } @@ -43,7 +45,7 @@ public class StorageExporter { exportPreferences(activity, storageFormat, includeGlobals, accountUuids, null, os, encryptionKey, listener); } - private static void gatherPassword(final Activity activity, final IStorageExporter storageExporter, final boolean includeGlobals, final Set accountUuids, final String fileName, final OutputStream os, final ExportListener listener) { + private static void gatherPassword(final Activity activity, final String storageFormat, final IStorageExporter storageExporter, final boolean includeGlobals, final Set accountUuids, final String fileName, final OutputStream os, final ExportListener listener) { activity.runOnUiThread(new Runnable() { @Override @@ -57,7 +59,7 @@ public class StorageExporter { @Override public void run() { try { - finishExport(activity, storageExporter, includeGlobals, accountUuids, fileName, os, chosenPassword, listener); + finishExport(activity, storageFormat, storageExporter, includeGlobals, accountUuids, fileName, os, chosenPassword, listener); } catch (Exception e) { Log.w(K9.LOG_TAG, "Exception while finishing export", e); if (listener != null) { @@ -81,7 +83,7 @@ public class StorageExporter { } - private static void finishExport(Activity activity, IStorageExporter storageExporter, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey, ExportListener listener) throws StorageImportExportException { + private static void finishExport(Activity activity, String storageFormat, IStorageExporter storageExporter, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey, ExportListener listener) throws StorageImportExportException { boolean needToClose = false; if (listener != null) { listener.started(); @@ -95,7 +97,18 @@ public class StorageExporter { os = new FileOutputStream(outFile); } if (os != null) { + + OutputStreamWriter sw = new OutputStreamWriter(os); + PrintWriter pf = new PrintWriter(sw); + pf.println(""); + + pf.println(""); + pf.flush(); + storageExporter.exportPreferences(activity, includeGlobals, accountUuids, os, encryptionKey); + + pf.println(""); + pf.flush(); if (listener != null) { if (fileName != null) { listener.success(fileName); diff --git a/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java b/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java index b406834fa..8aa48da86 100644 --- a/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java +++ b/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java @@ -24,11 +24,7 @@ public class StorageExporterEncryptedXml implements IStorageExporter { PrintWriter pf = new PrintWriter(sw); long keysEvaluated = 0; long keysExported = 0; - pf.println(""); - - pf.print(""); - + Preferences preferences = Preferences.getPreferences(context); SharedPreferences storage = preferences.getPreferences(); @@ -66,7 +62,6 @@ public class StorageExporterEncryptedXml implements IStorageExporter { } - pf.println(""); pf.flush(); Log.i(K9.LOG_TAG, "Exported " + keysExported + " of " + keysEvaluated + " settings."); From 90e88c251e43a77e875be55e99d649517e38661a Mon Sep 17 00:00:00 2001 From: danapple Date: Sun, 27 Mar 2011 13:14:36 -0500 Subject: [PATCH 034/116] Explanation of the reasons for mapping imported UUIDs into new local UUIDs. --- .../fsck/k9/preferences/StorageImporterEncryptedXml.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java b/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java index f09836fc3..af7977e0e 100644 --- a/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java +++ b/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java @@ -23,6 +23,15 @@ public class StorageImporterEncryptedXml implements IStorageImporter { String data = dataset.data.toString(); List accountNumbers = Account.getExistingAccountNumbers(preferences); Log.i(K9.LOG_TAG, "Existing accountNumbers = " + accountNumbers); + /** + * We translate UUIDs in the import file into new UUIDs in the local instance for the following reasons: + * 1) Accidentally importing the same file twice cannot damage settings in an existing account. + * (Say, an account that was imported two months ago and has since had significant settings changes.) + * 2) All UUIDs used in the local instance will be generated with the same generator. + * 3) Importing a single file multiple times allows for creating multiple accounts from the same template. + * 4) Exporting an account and importing back into the same instance is a poor-man's account copy (until a real + * copy function is created, if ever) + */ Map uuidMapping = new HashMap(); String accountUuids = preferences.getPreferences().getString("accountUuids", null); From ad4c7f707158e758bf3bcbf955ec95b6bd8588f0 Mon Sep 17 00:00:00 2001 From: danapple Date: Sun, 27 Mar 2011 15:27:14 -0500 Subject: [PATCH 035/116] UUIDs could have been generated by different generators within the same instance due to local upgrades of K-9 Mail. --- src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java b/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java index af7977e0e..0707fa133 100644 --- a/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java +++ b/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java @@ -27,9 +27,8 @@ public class StorageImporterEncryptedXml implements IStorageImporter { * We translate UUIDs in the import file into new UUIDs in the local instance for the following reasons: * 1) Accidentally importing the same file twice cannot damage settings in an existing account. * (Say, an account that was imported two months ago and has since had significant settings changes.) - * 2) All UUIDs used in the local instance will be generated with the same generator. - * 3) Importing a single file multiple times allows for creating multiple accounts from the same template. - * 4) Exporting an account and importing back into the same instance is a poor-man's account copy (until a real + * 2) Importing a single file multiple times allows for creating multiple accounts from the same template. + * 3) Exporting an account and importing back into the same instance is a poor-man's account copy (until a real * copy function is created, if ever) */ Map uuidMapping = new HashMap(); From a559a3524951ad1fa2207d296d40fb2f9c982c25 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 28 Mar 2011 01:18:36 +0200 Subject: [PATCH 036/116] Use AsyncTask instead of AsyncUIProcessor for export --- src/com/fsck/k9/activity/Accounts.java | 95 +++++++++++-------- .../fsck/k9/activity/AsyncUIProcessor.java | 29 ------ 2 files changed, 57 insertions(+), 67 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 714afbd26..8737c6c3f 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1,6 +1,7 @@ package com.fsck.k9.activity; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -18,7 +19,9 @@ import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; +import android.os.Environment; import android.os.Handler; import android.util.Log; import android.util.TypedValue; @@ -57,10 +60,12 @@ import com.fsck.k9.activity.setup.Prefs; import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.helper.SizeFormatter; +import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.view.ColorChip; +import com.fsck.k9.preferences.StorageExporter; import com.fsck.k9.preferences.StorageFormat; @@ -1123,48 +1128,62 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC // Once there are more file formats, build a UI to select which one to use. For now, use the encrypted/encoded format: String storageFormat = StorageFormat.ENCRYPTED_XML_FILE; - AsyncUIProcessor.getInstance(this.getApplication()).exportSettings(this, storageFormat, includeGlobals, accountUuids, new ExportListener() { + new ExportAsyncTask(storageFormat, includeGlobals, accountUuids).execute(); + } - @Override - public void canceled() { - setProgress(false); + private class ExportAsyncTask extends AsyncTask { + private boolean mIncludeGlobals; + private Set mAccountUuids; + private String mStorageFormat; + private String mFileName; + + private ExportAsyncTask(String storageFormat, boolean includeGlobals, + Set accountUuids) { + mStorageFormat = storageFormat; + mIncludeGlobals = includeGlobals; + mAccountUuids = accountUuids; + } + + @Override + protected void onPreExecute() { + //TODO: show progress bar instead of displaying toast + String toastText = Accounts.this.getString(R.string.settings_exporting); + Toast toast = Toast.makeText(Accounts.this, toastText, Toast.LENGTH_SHORT); + toast.show(); + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + //FIXME: this code doesn't belong here! + // Do not store with application files. Settings exports should *not* be + // deleted when the application is uninstalled. + File dir = new File(Environment.getExternalStorageDirectory() + File.separator + + Accounts.this.getApplication().getPackageName()); + dir.mkdirs(); + File file = Utility.createUniqueFile(dir, "settings.k9s"); + mFileName = file.getAbsolutePath(); + StorageExporter.exportPreferences(Accounts.this, mStorageFormat, mIncludeGlobals, + mAccountUuids, mFileName, null, null); + } catch (Exception e) { + Log.w(K9.LOG_TAG, "Exception during export", e); + return false; } + return true; + } - @Override - public void failure(String message, Exception e) { - setProgress(false); - showDialog(Accounts.this, R.string.settings_export_failed_header, Accounts.this.getString(R.string.settings_export_failure, message)); + @Override + protected void onPostExecute(Boolean success) { + setProgress(false); + if (success) { + showDialog(Accounts.this, R.string.settings_export_success_header, + Accounts.this.getString(R.string.settings_export_success, mFileName)); + } else { + //TODO: make the exporter return an error code; translate that error code to a localized string here + showDialog(Accounts.this, R.string.settings_export_failed_header, + Accounts.this.getString(R.string.settings_export_failure, "Something went wrong")); } - - @Override - public void started() { - setProgress(true); - runOnUiThread(new Runnable() { - - @Override - public void run() { - String toastText = Accounts.this.getString(R.string.settings_exporting); - Toast toast = Toast.makeText(Accounts.this, toastText, Toast.LENGTH_SHORT); - toast.show(); - } - }); - } - - @Override - public void success(String fileName) { - setProgress(false); - showDialog(Accounts.this, R.string.settings_export_success_header, Accounts.this.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 - setProgress(false); - } - }); - - - + } } } diff --git a/src/com/fsck/k9/activity/AsyncUIProcessor.java b/src/com/fsck/k9/activity/AsyncUIProcessor.java index 0ab2971e7..a9108dcb5 100644 --- a/src/com/fsck/k9/activity/AsyncUIProcessor.java +++ b/src/com/fsck/k9/activity/AsyncUIProcessor.java @@ -1,22 +1,16 @@ package com.fsck.k9.activity; -import java.io.File; import java.io.InputStream; -import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.HashSet; import android.app.Activity; import android.app.Application; import android.content.ContentResolver; import android.net.Uri; -import android.os.Environment; import android.util.Log; import com.fsck.k9.K9; -import com.fsck.k9.helper.Utility; -import com.fsck.k9.preferences.StorageExporter; import com.fsck.k9.preferences.StorageImporter; /** @@ -42,29 +36,6 @@ public class AsyncUIProcessor { public void execute(Runnable runnable) { threadPool.execute(runnable); } - public void exportSettings(final Activity activity, final String storageFormat, final boolean includeGlobals, final Set accountUuids, final ExportListener listener) { - threadPool.execute(new Runnable() { - - @Override - public void run() { - try { - // Do not store with application files. Settings exports should *not* be - // deleted when the application is uninstalled - File dir = new File(Environment.getExternalStorageDirectory() + File.separator - + mApplication.getPackageName()); - dir.mkdirs(); - File file = Utility.createUniqueFile(dir, "settings.k9s"); - String fileName = file.getAbsolutePath(); - StorageExporter.exportPreferences(activity, storageFormat, includeGlobals, accountUuids, fileName, null, listener); - } catch (Exception e) { - Log.w(K9.LOG_TAG, "Exception during export", e); - listener.failure(e.getLocalizedMessage(), e); - } - } - } - ); - - } public void importSettings(final Activity activity, final Uri uri, final ImportListener listener) { threadPool.execute(new Runnable() { From 1d146278b041f2c22d5b120c7863bf095f74f8ce Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 28 Mar 2011 01:55:46 +0200 Subject: [PATCH 037/116] Moved password input dialog for export to Accounts activity. --- src/com/fsck/k9/activity/Accounts.java | 33 ++++++++----- .../fsck/k9/preferences/StorageExporter.java | 48 +------------------ 2 files changed, 23 insertions(+), 58 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 8737c6c3f..44caa4b2a 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1120,28 +1120,38 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC public void onExport(final boolean includeGlobals, final Account account) { // TODO, prompt to allow a user to choose which accounts to export - HashSet accountUuids; - accountUuids = new HashSet(); + final Set accountUuids = new HashSet(); if (account != null) { accountUuids.add(account.getUuid()); } - // Once there are more file formats, build a UI to select which one to use. For now, use the encrypted/encoded format: - String storageFormat = StorageFormat.ENCRYPTED_XML_FILE; - new ExportAsyncTask(storageFormat, includeGlobals, accountUuids).execute(); + // Prompt the user for a password + new PasswordEntryDialog(this, + getString(R.string.settings_export_encryption_password_prompt), + new PasswordEntryDialog.PasswordEntryListener() { + public void passwordChosen(final String chosenPassword) { + // Got the password. Now run export task in the background. + new ExportAsyncTask(includeGlobals, accountUuids, chosenPassword).execute(); + } + + public void cancel() { + // User cancelled the export. Nothing more to do. + } + }) + .show(); } private class ExportAsyncTask extends AsyncTask { private boolean mIncludeGlobals; private Set mAccountUuids; - private String mStorageFormat; + private String mEncryptionKey; private String mFileName; - private ExportAsyncTask(String storageFormat, boolean includeGlobals, - Set accountUuids) { - mStorageFormat = storageFormat; + private ExportAsyncTask(boolean includeGlobals, Set accountUuids, + String encryptionKey) { mIncludeGlobals = includeGlobals; mAccountUuids = accountUuids; + mEncryptionKey = encryptionKey; } @Override @@ -1163,8 +1173,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC dir.mkdirs(); File file = Utility.createUniqueFile(dir, "settings.k9s"); mFileName = file.getAbsolutePath(); - StorageExporter.exportPreferences(Accounts.this, mStorageFormat, mIncludeGlobals, - mAccountUuids, mFileName, null, null); + StorageExporter.exportPreferences(Accounts.this, StorageFormat.ENCRYPTED_XML_FILE, + mIncludeGlobals, mAccountUuids, mFileName, mEncryptionKey, null); } catch (Exception e) { Log.w(K9.LOG_TAG, "Exception during export", e); return false; @@ -1185,5 +1195,4 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } } } - } diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 50a918e68..c7a879628 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -12,9 +12,7 @@ import android.util.Log; import com.fsck.k9.K9; 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 { private static void exportPreferences(Activity activity, String storageFormat, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { @@ -24,7 +22,7 @@ public class StorageExporter { throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, storageFormat), null); } if (storageExporter.needsKey() && encryptionKey == null) { - gatherPassword(activity, storageFormat, storageExporter, includeGlobals, accountUuids, fileName, os, listener); + throw new StorageImportExportException("Encryption key required, but none supplied"); } else { finishExport(activity, storageFormat, storageExporter, includeGlobals, accountUuids, fileName, os, encryptionKey, listener); } @@ -41,48 +39,6 @@ public class StorageExporter { exportPreferences(activity, storageFormat, includeGlobals, accountUuids, fileName, null, encryptionKey, listener); } - public static void exportPrefererences(Activity activity, String storageFormat, boolean includeGlobals, Set accountUuids, OutputStream os, String encryptionKey, final ExportListener listener) throws StorageImportExportException { - exportPreferences(activity, storageFormat, includeGlobals, accountUuids, null, os, encryptionKey, listener); - } - - private static void gatherPassword(final Activity activity, final String storageFormat, final IStorageExporter storageExporter, final boolean includeGlobals, final Set accountUuids, 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_export_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, storageFormat, storageExporter, includeGlobals, accountUuids, 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, String storageFormat, IStorageExporter storageExporter, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey, ExportListener listener) throws StorageImportExportException { boolean needToClose = false; if (listener != null) { @@ -101,7 +57,7 @@ public class StorageExporter { OutputStreamWriter sw = new OutputStreamWriter(os); PrintWriter pf = new PrintWriter(sw); pf.println(""); - + pf.println(""); pf.flush(); From 97f493c8810a10893cb9fe1f32368b3d4fdc9b20 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 28 Mar 2011 07:18:47 +0200 Subject: [PATCH 038/116] Get rid of ExportListener --- src/com/fsck/k9/activity/Accounts.java | 2 +- src/com/fsck/k9/activity/ExportListener.java | 13 --------- .../fsck/k9/preferences/StorageExporter.java | 27 +++++-------------- 3 files changed, 8 insertions(+), 34 deletions(-) delete mode 100644 src/com/fsck/k9/activity/ExportListener.java diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 44caa4b2a..1f31be48f 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1174,7 +1174,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC File file = Utility.createUniqueFile(dir, "settings.k9s"); mFileName = file.getAbsolutePath(); StorageExporter.exportPreferences(Accounts.this, StorageFormat.ENCRYPTED_XML_FILE, - mIncludeGlobals, mAccountUuids, mFileName, mEncryptionKey, null); + mIncludeGlobals, mAccountUuids, mFileName, mEncryptionKey); } catch (Exception e) { Log.w(K9.LOG_TAG, "Exception during export", e); return false; diff --git a/src/com/fsck/k9/activity/ExportListener.java b/src/com/fsck/k9/activity/ExportListener.java deleted file mode 100644 index 2ec062545..000000000 --- a/src/com/fsck/k9/activity/ExportListener.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.fsck.k9.activity; - -public interface ExportListener { - public void success(String fileName); - public void success(); - - public void failure(String message, Exception e); - - public void canceled(); - - public void started(); - -} diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index c7a879628..9535b990a 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -12,10 +12,9 @@ import android.util.Log; import com.fsck.k9.K9; import com.fsck.k9.R; -import com.fsck.k9.activity.ExportListener; public class StorageExporter { - private static void exportPreferences(Activity activity, String storageFormat, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey, final ExportListener listener) { + private static void exportPreferences(Activity activity, String storageFormat, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey) throws StorageImportExportException { try { IStorageExporter storageExporter = StorageFormat.createExporter(storageFormat); if (storageExporter == null) { @@ -24,26 +23,21 @@ public class StorageExporter { if (storageExporter.needsKey() && encryptionKey == null) { throw new StorageImportExportException("Encryption key required, but none supplied"); } else { - finishExport(activity, storageFormat, storageExporter, includeGlobals, accountUuids, fileName, os, encryptionKey, listener); + finishExport(activity, storageFormat, storageExporter, includeGlobals, accountUuids, fileName, os, encryptionKey); } } - catch (Exception e) { - if (listener != null) { - listener.failure(e.getLocalizedMessage(), e); - } + //FIXME: get this right + throw new StorageImportExportException(); } } - public static void exportPreferences(Activity activity, String storageFormat, boolean includeGlobals, Set accountUuids, String fileName, String encryptionKey, final ExportListener listener) throws StorageImportExportException { - exportPreferences(activity, storageFormat, includeGlobals, accountUuids, fileName, null, encryptionKey, listener); + public static void exportPreferences(Activity activity, String storageFormat, boolean includeGlobals, Set accountUuids, String fileName, String encryptionKey) throws StorageImportExportException { + exportPreferences(activity, storageFormat, includeGlobals, accountUuids, fileName, null, encryptionKey); } - private static void finishExport(Activity activity, String storageFormat, IStorageExporter storageExporter, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey, ExportListener listener) throws StorageImportExportException { + private static void finishExport(Activity activity, String storageFormat, IStorageExporter storageExporter, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey) throws StorageImportExportException { boolean needToClose = false; - if (listener != null) { - listener.started(); - } try { // This needs to be after the password prompt. If the user cancels the password, we do not want // to create the file needlessly @@ -65,13 +59,6 @@ public class StorageExporter { pf.println(""); pf.flush(); - if (listener != null) { - if (fileName != null) { - listener.success(fileName); - } else { - listener.success(); - } - } } else { throw new StorageImportExportException("Internal error; no fileName or OutputStream", null); } From f5c153b405f0e47f630040eb1fa4e36b272a6497 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 28 Mar 2011 07:25:07 +0200 Subject: [PATCH 039/116] Get rid of StorageFormat --- src/com/fsck/k9/activity/Accounts.java | 5 +- .../fsck/k9/preferences/StorageExporter.java | 18 +++---- .../fsck/k9/preferences/StorageFormat.java | 50 ------------------- .../fsck/k9/preferences/StorageImporter.java | 5 +- 4 files changed, 10 insertions(+), 68 deletions(-) delete mode 100644 src/com/fsck/k9/preferences/StorageFormat.java diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 1f31be48f..5c3b70a13 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -66,7 +66,6 @@ import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.view.ColorChip; import com.fsck.k9.preferences.StorageExporter; -import com.fsck.k9.preferences.StorageFormat; public class Accounts extends K9ListActivity implements OnItemClickListener, OnClickListener { @@ -1173,8 +1172,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC dir.mkdirs(); File file = Utility.createUniqueFile(dir, "settings.k9s"); mFileName = file.getAbsolutePath(); - StorageExporter.exportPreferences(Accounts.this, StorageFormat.ENCRYPTED_XML_FILE, - mIncludeGlobals, mAccountUuids, mFileName, mEncryptionKey); + StorageExporter.exportPreferences(Accounts.this, mIncludeGlobals, + mAccountUuids, mFileName, mEncryptionKey); } catch (Exception e) { Log.w(K9.LOG_TAG, "Exception during export", e); return false; diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 9535b990a..4d52d9f28 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -11,19 +11,15 @@ import android.app.Activity; import android.util.Log; import com.fsck.k9.K9; -import com.fsck.k9.R; public class StorageExporter { - private static void exportPreferences(Activity activity, String storageFormat, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey) throws StorageImportExportException { + private static void exportPreferences(Activity activity, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey) throws StorageImportExportException { try { - IStorageExporter storageExporter = StorageFormat.createExporter(storageFormat); - if (storageExporter == null) { - throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, storageFormat), null); - } + IStorageExporter storageExporter = new StorageExporterEncryptedXml(); if (storageExporter.needsKey() && encryptionKey == null) { throw new StorageImportExportException("Encryption key required, but none supplied"); } else { - finishExport(activity, storageFormat, storageExporter, includeGlobals, accountUuids, fileName, os, encryptionKey); + finishExport(activity, storageExporter, includeGlobals, accountUuids, fileName, os, encryptionKey); } } catch (Exception e) { @@ -32,11 +28,11 @@ public class StorageExporter { } } - public static void exportPreferences(Activity activity, String storageFormat, boolean includeGlobals, Set accountUuids, String fileName, String encryptionKey) throws StorageImportExportException { - exportPreferences(activity, storageFormat, includeGlobals, accountUuids, fileName, null, encryptionKey); + public static void exportPreferences(Activity activity, boolean includeGlobals, Set accountUuids, String fileName, String encryptionKey) throws StorageImportExportException { + exportPreferences(activity, includeGlobals, accountUuids, fileName, null, encryptionKey); } - private static void finishExport(Activity activity, String storageFormat, IStorageExporter storageExporter, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey) throws StorageImportExportException { + private static void finishExport(Activity activity, IStorageExporter storageExporter, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey) throws StorageImportExportException { boolean needToClose = false; try { // This needs to be after the password prompt. If the user cancels the password, we do not want @@ -52,7 +48,7 @@ public class StorageExporter { PrintWriter pf = new PrintWriter(sw); pf.println(""); - pf.println(""); + pf.println(""); pf.flush(); storageExporter.exportPreferences(activity, includeGlobals, accountUuids, os, encryptionKey); diff --git a/src/com/fsck/k9/preferences/StorageFormat.java b/src/com/fsck/k9/preferences/StorageFormat.java deleted file mode 100644 index 4c472edcb..000000000 --- a/src/com/fsck/k9/preferences/StorageFormat.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.fsck.k9.preferences; - -import java.util.HashMap; -import java.util.Map; - - -public class StorageFormat { - // Never, ever re-use these numbers! - public static final String ENCRYPTED_XML_FILE = "1"; - - public static Map storageFormatMap = new HashMap(); - static { - storageFormatMap.put(ENCRYPTED_XML_FILE, new StorageFormat(StorageImporterEncryptedXml.class, StorageExporterEncryptedXml.class, true)); - } - - public static IStorageImporter createImporter(String storageFormat) throws InstantiationException, IllegalAccessException { - StorageFormat storageVersion = storageFormatMap.get(storageFormat); - if (storageVersion == null) { - return null; - } - return storageVersion.importerClass.newInstance(); - } - - public static IStorageExporter createExporter(String storageFormat) throws InstantiationException, IllegalAccessException { - StorageFormat storageVersion = storageFormatMap.get(storageFormat); - if (storageVersion == null) { - return null; - } - return storageVersion.exporterClass.newInstance(); - } - - public static Boolean needsKey(String storageFormat) { - StorageFormat storageVersion = storageFormatMap.get(storageFormat); - if (storageVersion == null) { - return null; - } - return storageVersion.needsKey; - } - - - private final Class importerClass; - private final Class exporterClass; - private final boolean needsKey; - - private StorageFormat(Class imclass, Class exclass, boolean nk) { - importerClass = imclass; - exporterClass = exclass; - needsKey = nk; - } -} diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index e40b7708c..e1142e19f 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -42,10 +42,7 @@ public class StorageImporter { String storageFormat = dataset.attributes.get("version"); Log.i(K9.LOG_TAG, "Got settings file version " + storageFormat); - IStorageImporter storageImporter = StorageFormat.createImporter(storageFormat); - if (storageImporter == null) { - throw new StorageImportExportException(activity.getString(R.string.settings_unknown_version, storageFormat)); - } + IStorageImporter storageImporter = new StorageImporterEncryptedXml(); if (storageImporter.needsKey() && providedEncryptionKey == null) { gatherPassword(activity, storageImporter, dataset, listener); } else { From 6510919eeef510c26eb821f5f3b1f60ea00cf0b5 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 28 Mar 2011 08:04:43 +0200 Subject: [PATCH 040/116] Moved export to file code from Accounts activity into StorageExporter --- src/com/fsck/k9/activity/Accounts.java | 18 +--- .../fsck/k9/preferences/StorageExporter.java | 98 +++++++++---------- 2 files changed, 52 insertions(+), 64 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 5c3b70a13..5b0d47721 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1,7 +1,6 @@ package com.fsck.k9.activity; -import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -21,7 +20,6 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.os.Environment; import android.os.Handler; import android.util.Log; import android.util.TypedValue; @@ -60,12 +58,12 @@ import com.fsck.k9.activity.setup.Prefs; import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.helper.SizeFormatter; -import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.view.ColorChip; import com.fsck.k9.preferences.StorageExporter; +import com.fsck.k9.preferences.StorageImportExportException; public class Accounts extends K9ListActivity implements OnItemClickListener, OnClickListener { @@ -1164,17 +1162,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override protected Boolean doInBackground(Void... params) { try { - //FIXME: this code doesn't belong here! - // Do not store with application files. Settings exports should *not* be - // deleted when the application is uninstalled. - File dir = new File(Environment.getExternalStorageDirectory() + File.separator - + Accounts.this.getApplication().getPackageName()); - dir.mkdirs(); - File file = Utility.createUniqueFile(dir, "settings.k9s"); - mFileName = file.getAbsolutePath(); - StorageExporter.exportPreferences(Accounts.this, mIncludeGlobals, - mAccountUuids, mFileName, mEncryptionKey); - } catch (Exception e) { + mFileName = StorageExporter.exportToFile(Accounts.this, mIncludeGlobals, + mAccountUuids, mEncryptionKey); + } catch (StorageImportExportException e) { Log.w(K9.LOG_TAG, "Exception during export", e); return false; } diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 4d52d9f28..20bc0bc5f 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -2,74 +2,72 @@ package com.fsck.k9.preferences; import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.Set; -import android.app.Activity; -import android.util.Log; +import android.content.Context; +import android.os.Environment; + +import com.fsck.k9.helper.Utility; -import com.fsck.k9.K9; public class StorageExporter { - private static void exportPreferences(Activity activity, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey) throws StorageImportExportException { - try { - IStorageExporter storageExporter = new StorageExporterEncryptedXml(); - if (storageExporter.needsKey() && encryptionKey == null) { - throw new StorageImportExportException("Encryption key required, but none supplied"); - } else { - finishExport(activity, storageExporter, includeGlobals, accountUuids, fileName, os, encryptionKey); - } - } - catch (Exception e) { - //FIXME: get this right - throw new StorageImportExportException(); - } - } + private static final String EXPORT_FILENAME = "settings.k9s"; - public static void exportPreferences(Activity activity, boolean includeGlobals, Set accountUuids, String fileName, String encryptionKey) throws StorageImportExportException { - exportPreferences(activity, includeGlobals, accountUuids, fileName, null, encryptionKey); - } - private static void finishExport(Activity activity, IStorageExporter storageExporter, boolean includeGlobals, Set accountUuids, String fileName, OutputStream os, String encryptionKey) throws StorageImportExportException { - boolean needToClose = false; - try { - // This needs to be after the password prompt. If the user cancels the password, we do not want - // to create the file needlessly - if (os == null && fileName != null) { - needToClose = true; - File outFile = new File(fileName); - os = new FileOutputStream(outFile); - } - if (os != null) { + public static String exportToFile(Context context, boolean includeGlobals, + Set accountUuids, String encryptionKey) + throws StorageImportExportException { - OutputStreamWriter sw = new OutputStreamWriter(os); - PrintWriter pf = new PrintWriter(sw); - pf.println(""); + OutputStream os = null; + try + { + File dir = new File(Environment.getExternalStorageDirectory() + File.separator + + context.getPackageName()); + dir.mkdirs(); + File file = Utility.createUniqueFile(dir, EXPORT_FILENAME); + String fileName = file.getAbsolutePath(); + os = new FileOutputStream(fileName); + exportPreferences(context, os, includeGlobals, accountUuids, encryptionKey); - pf.println(""); - pf.flush(); - - storageExporter.exportPreferences(activity, includeGlobals, accountUuids, os, encryptionKey); - - pf.println(""); - pf.flush(); - } else { - throw new StorageImportExportException("Internal error; no fileName or OutputStream", null); - } + // If all went well, we return the name of the file just written. + return fileName; } catch (Exception e) { - throw new StorageImportExportException(e.getLocalizedMessage(), e); + throw new StorageImportExportException(); } finally { - if (needToClose && os != null) { + if (os != null) { try { os.close(); - } catch (Exception e) { - Log.w(K9.LOG_TAG, "Unable to close OutputStream", e); - } + } catch (IOException ioe) {} } } - } + public static void exportPreferences(Context context, OutputStream os, boolean includeGlobals, + Set accountUuids, String encryptionKey) throws StorageImportExportException { + + IStorageExporter storageExporter = new StorageExporterEncryptedXml(); + if (storageExporter.needsKey() && encryptionKey == null) { + throw new StorageImportExportException("Encryption key required, but none supplied"); + } + + try { + OutputStreamWriter sw = new OutputStreamWriter(os); + PrintWriter pf = new PrintWriter(sw); + pf.println(""); + + pf.println(""); + pf.flush(); + + storageExporter.exportPreferences(context, includeGlobals, accountUuids, os, encryptionKey); + + pf.println(""); + pf.flush(); + } catch (Exception e) { + throw new StorageImportExportException(e.getLocalizedMessage(), e); + } + } } From 84f433176697b15e13d9e5d337badfe6fc806194 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 28 Mar 2011 08:12:12 +0200 Subject: [PATCH 041/116] Disable import for now --- src/com/fsck/k9/activity/Accounts.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 5b0d47721..94723a8da 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -854,6 +854,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } private void onImport(Uri uri) { + Toast.makeText(this, "Import is disabled for now", Toast.LENGTH_SHORT).show(); + /* Log.i(K9.LOG_TAG, "onImport importing from URI " + uri.getPath()); final String fileName = uri.getPath(); @@ -897,6 +899,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } }); + */ } private void showDialog(final Context context, final int headerRes, final String message) { this.runOnUiThread(new Runnable() { From 9a78145e2263151921f64cd58f45cd221637cb39 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 29 Mar 2011 04:27:41 +0200 Subject: [PATCH 042/116] Merge StorageExporterEncryptedXml into StorageExporter --- .../fsck/k9/preferences/IStorageExporter.java | 12 --- .../fsck/k9/preferences/StorageExporter.java | 56 +++++++++++++- .../StorageExporterEncryptedXml.java | 77 ------------------- 3 files changed, 53 insertions(+), 92 deletions(-) delete mode 100644 src/com/fsck/k9/preferences/IStorageExporter.java delete mode 100644 src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java diff --git a/src/com/fsck/k9/preferences/IStorageExporter.java b/src/com/fsck/k9/preferences/IStorageExporter.java deleted file mode 100644 index 76bdce369..000000000 --- a/src/com/fsck/k9/preferences/IStorageExporter.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.fsck.k9.preferences; - -import java.io.OutputStream; -import java.util.Set; - -import android.content.Context; - -public interface IStorageExporter { - public boolean needsKey(); - // exportPreferences must be sure to flush all data to the OutputStream before returning - public void exportPreferences(Context context, boolean includeGlobals, Set accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException; -} diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 20bc0bc5f..49d5d2c17 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -6,11 +6,18 @@ import java.io.IOException; 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.os.Environment; +import android.util.Log; +import com.fsck.k9.Account; +import com.fsck.k9.K9; +import com.fsck.k9.Preferences; import com.fsck.k9.helper.Utility; @@ -49,8 +56,7 @@ public class StorageExporter { public static void exportPreferences(Context context, OutputStream os, boolean includeGlobals, Set accountUuids, String encryptionKey) throws StorageImportExportException { - IStorageExporter storageExporter = new StorageExporterEncryptedXml(); - if (storageExporter.needsKey() && encryptionKey == null) { + if (encryptionKey == null) { throw new StorageImportExportException("Encryption key required, but none supplied"); } @@ -62,7 +68,51 @@ public class StorageExporter { pf.println(""); pf.flush(); - storageExporter.exportPreferences(context, includeGlobals, accountUuids, os, encryptionKey); + Log.i(K9.LOG_TAG, "Exporting preferences"); + K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.ENCRYPT); + long keysEvaluated = 0; + long keysExported = 0; + + Preferences preferences = Preferences.getPreferences(context); + SharedPreferences storage = preferences.getPreferences(); + + if (accountUuids == null) { + Account[] accounts = preferences.getAccounts(); + accountUuids = new HashSet(); + 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++; + 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; + } + } else if (!includeGlobals) { + // Skip global config entries if the user didn't request them + 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.flush(); + + Log.i(K9.LOG_TAG, "Exported " + keysExported + " of " + keysEvaluated + " settings."); pf.println(""); pf.flush(); diff --git a/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java b/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java deleted file mode 100644 index 8aa48da86..000000000 --- a/src/com/fsck/k9/preferences/StorageExporterEncryptedXml.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.fsck.k9.preferences; - -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 StorageExporterEncryptedXml implements IStorageExporter { - public void exportPreferences(Context context, boolean includeGlobals, Set accountUuids, OutputStream os, String encryptionKey) throws StorageImportExportException { - try { - Log.i(K9.LOG_TAG, "Exporting preferences"); - K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.ENCRYPT); - OutputStreamWriter sw = new OutputStreamWriter(os); - PrintWriter pf = new PrintWriter(sw); - long keysEvaluated = 0; - long keysExported = 0; - - Preferences preferences = Preferences.getPreferences(context); - SharedPreferences storage = preferences.getPreferences(); - - if (accountUuids == null) { - Account[] accounts = preferences.getAccounts(); - accountUuids = new HashSet(); - 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++; - 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; - } - } else if (!includeGlobals) { - // Skip global config entries if the user didn't request them - 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.flush(); - - Log.i(K9.LOG_TAG, "Exported " + keysExported + " of " + keysEvaluated + " settings."); - } catch (Exception e) { - throw new StorageImportExportException("Unable to encrypt settings", e); - } - } - - @Override - public boolean needsKey() { - return true; - } -} From c36182f58680f265cb0a478ec202845dd493c11f Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 29 Mar 2011 05:27:18 +0200 Subject: [PATCH 043/116] Use XmlSerializer instead of writing XML tags manually --- .../fsck/k9/preferences/StorageExporter.java | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 49d5d2c17..498c04c93 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -4,16 +4,16 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; 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 org.xmlpull.v1.XmlSerializer; import android.content.Context; import android.content.SharedPreferences; import android.os.Environment; import android.util.Log; +import android.util.Xml; import com.fsck.k9.Account; import com.fsck.k9.K9; @@ -61,12 +61,17 @@ public class StorageExporter { } try { - OutputStreamWriter sw = new OutputStreamWriter(os); - PrintWriter pf = new PrintWriter(sw); - pf.println(""); + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(os, "UTF-8"); - pf.println(""); - pf.flush(); + serializer.startDocument(null, Boolean.valueOf(true)); + + // Output with indentation + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + // Root tag + serializer.startTag(null, "k9settings"); + serializer.attribute(null, "version", "1"); Log.i(K9.LOG_TAG, "Exporting preferences"); K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.ENCRYPT); @@ -105,17 +110,17 @@ public class StorageExporter { String valueEnc = krypto.encrypt(value); String output = keyEnc + ":" + valueEnc; //Log.i(K9.LOG_TAG, "For key " + key + ", output is " + output); - pf.println(output); + serializer.text(output + "\n"); keysExported++; } - pf.flush(); - Log.i(K9.LOG_TAG, "Exported " + keysExported + " of " + keysEvaluated + " settings."); - pf.println(""); - pf.flush(); + serializer.endTag(null, "k9settings"); + serializer.endDocument(); + serializer.flush(); + } catch (Exception e) { throw new StorageImportExportException(e.getLocalizedMessage(), e); } From 0ac406d3cdd8874e34a07b4bcbfff301512f9f76 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 29 Mar 2011 06:15:58 +0200 Subject: [PATCH 044/116] Removed per key/value encryption on export --- src/com/fsck/k9/activity/Accounts.java | 3 +++ .../fsck/k9/preferences/StorageExporter.java | 19 +++++++------------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 94723a8da..b68efa82e 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1125,6 +1125,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC accountUuids.add(account.getUuid()); } + /* Disabled for now // Prompt the user for a password new PasswordEntryDialog(this, getString(R.string.settings_export_encryption_password_prompt), @@ -1139,6 +1140,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } }) .show(); + */ + new ExportAsyncTask(includeGlobals, accountUuids, null).execute(); } private class ExportAsyncTask extends AsyncTask { diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 498c04c93..e952d9775 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -38,7 +38,7 @@ public class StorageExporter { File file = Utility.createUniqueFile(dir, EXPORT_FILENAME); String fileName = file.getAbsolutePath(); os = new FileOutputStream(fileName); - exportPreferences(context, os, includeGlobals, accountUuids, encryptionKey); + exportPreferences(context, os, includeGlobals, accountUuids); // If all went well, we return the name of the file just written. return fileName; @@ -54,11 +54,7 @@ public class StorageExporter { } public static void exportPreferences(Context context, OutputStream os, boolean includeGlobals, - Set accountUuids, String encryptionKey) throws StorageImportExportException { - - if (encryptionKey == null) { - throw new StorageImportExportException("Encryption key required, but none supplied"); - } + Set accountUuids) throws StorageImportExportException { try { XmlSerializer serializer = Xml.newSerializer(); @@ -71,10 +67,9 @@ public class StorageExporter { // Root tag serializer.startTag(null, "k9settings"); - serializer.attribute(null, "version", "1"); + serializer.attribute(null, "version", "x"); Log.i(K9.LOG_TAG, "Exporting preferences"); - K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.ENCRYPT); long keysEvaluated = 0; long keysExported = 0; @@ -106,11 +101,11 @@ public class StorageExporter { // Skip global config entries if the user didn't request them continue; } - String keyEnc = krypto.encrypt(key); - String valueEnc = krypto.encrypt(value); - String output = keyEnc + ":" + valueEnc; + serializer.startTag(null, "value"); + serializer.attribute(null, "key", key); + serializer.text(value); + serializer.endTag(null, "value"); //Log.i(K9.LOG_TAG, "For key " + key + ", output is " + output); - serializer.text(output + "\n"); keysExported++; } From 81931967a915d49ced5950781b3e4f7acad7af72 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 29 Mar 2011 06:59:02 +0200 Subject: [PATCH 045/116] Fix code so accounts are exported when exporting everything --- src/com/fsck/k9/activity/Accounts.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index b68efa82e..e40dadb0a 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1120,8 +1120,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC public void onExport(final boolean includeGlobals, final Account account) { // TODO, prompt to allow a user to choose which accounts to export - final Set accountUuids = new HashSet(); + Set accountUuids = null; if (account != null) { + accountUuids = new HashSet(); accountUuids.add(account.getUuid()); } From fbe1b26f245b192aedc28ba6ad1cb053348bf427 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 29 Mar 2011 07:08:29 +0200 Subject: [PATCH 046/116] Reflect some of the settings structure in exported XML --- .../fsck/k9/preferences/StorageExporter.java | 83 +++++++++++++------ 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index e952d9775..a7ad7b6b2 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -70,8 +70,6 @@ public class StorageExporter { serializer.attribute(null, "version", "x"); Log.i(K9.LOG_TAG, "Exporting preferences"); - long keysEvaluated = 0; - long keysExported = 0; Preferences preferences = Preferences.getPreferences(context); SharedPreferences storage = preferences.getPreferences(); @@ -84,33 +82,19 @@ public class StorageExporter { } } - 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++; - 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; - } - } else if (!includeGlobals) { - // Skip global config entries if the user didn't request them - continue; - } - serializer.startTag(null, "value"); - serializer.attribute(null, "key", key); - serializer.text(value); - serializer.endTag(null, "value"); - //Log.i(K9.LOG_TAG, "For key " + key + ", output is " + output); - keysExported++; + Map prefs = storage.getAll(); + if (includeGlobals) { + serializer.startTag(null, "settings"); + writeSettings(serializer, prefs); + serializer.endTag(null, "settings"); } - Log.i(K9.LOG_TAG, "Exported " + keysExported + " of " + keysEvaluated + " settings."); + serializer.startTag(null, "accounts"); + for (String accountUuid : accountUuids) { + writeAccount(serializer, accountUuid, prefs); + } + serializer.endTag(null, "accounts"); serializer.endTag(null, "k9settings"); serializer.endDocument(); @@ -120,4 +104,51 @@ public class StorageExporter { throw new StorageImportExportException(e.getLocalizedMessage(), e); } } + + private static void writeSettings(XmlSerializer serializer, + Map prefs) throws IOException { + + for (Map.Entry entry : prefs.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue().toString(); + if (key.indexOf('.') != -1) { + // Skip account entries + continue; + } + serializer.startTag(null, "value"); + serializer.attribute(null, "key", key); + serializer.text(value); + serializer.endTag(null, "value"); + } + } + + private static void writeAccount(XmlSerializer serializer, String accountUuid, + Map prefs) throws IOException { + + serializer.startTag(null, "account"); + serializer.attribute(null, "uuid", accountUuid); + for (Map.Entry entry : prefs.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue().toString(); + String[] comps = key.split("\\."); + if (comps.length > 1) { + String keyUuid = comps[0]; + if (!keyUuid.equals(accountUuid)) { + continue; + } + } else { + // Skip global config entries + continue; + } + + // Strip account UUID from key + String keyPart = key.substring(comps[0].length() + 1); + + serializer.startTag(null, "value"); + serializer.attribute(null, "key", keyPart); + serializer.text(value); + serializer.endTag(null, "value"); + } + serializer.endTag(null, "account"); + } } From 77f7303aa26315f9aa8800cd23feb60f3352f8ed Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 30 Mar 2011 00:21:15 +0200 Subject: [PATCH 047/116] Use constants for element names and attributes in XML export code --- .../fsck/k9/preferences/StorageExporter.java | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index a7ad7b6b2..f7396ec04 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -24,6 +24,15 @@ import com.fsck.k9.helper.Utility; public class StorageExporter { private static final String EXPORT_FILENAME = "settings.k9s"; + private static final String ROOT_ELEMENT = "k9settings"; + private static final String VERSION_ATTRIBUTE = "version"; + private static final String SETTINGS_ELEMENT = "settings"; + private static final String ACCOUNTS_ELEMENT = "accounts"; + private static final String ACCOUNT_ELEMENT = "account"; + private static final String UUID_ATTRIBUTE = "uuid"; + private static final String VALUE_ELEMENT = "value"; + private static final String KEY_ATTRIBUTE = "key"; + public static String exportToFile(Context context, boolean includeGlobals, Set accountUuids, String encryptionKey) @@ -65,9 +74,8 @@ public class StorageExporter { // Output with indentation serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - // Root tag - serializer.startTag(null, "k9settings"); - serializer.attribute(null, "version", "x"); + serializer.startTag(null, ROOT_ELEMENT); + serializer.attribute(null, VERSION_ATTRIBUTE, "x"); Log.i(K9.LOG_TAG, "Exporting preferences"); @@ -85,18 +93,18 @@ public class StorageExporter { Map prefs = storage.getAll(); if (includeGlobals) { - serializer.startTag(null, "settings"); + serializer.startTag(null, SETTINGS_ELEMENT); writeSettings(serializer, prefs); - serializer.endTag(null, "settings"); + serializer.endTag(null, SETTINGS_ELEMENT); } - serializer.startTag(null, "accounts"); + serializer.startTag(null, ACCOUNTS_ELEMENT); for (String accountUuid : accountUuids) { writeAccount(serializer, accountUuid, prefs); } - serializer.endTag(null, "accounts"); + serializer.endTag(null, ACCOUNTS_ELEMENT); - serializer.endTag(null, "k9settings"); + serializer.endTag(null, ROOT_ELEMENT); serializer.endDocument(); serializer.flush(); @@ -115,18 +123,18 @@ public class StorageExporter { // Skip account entries continue; } - serializer.startTag(null, "value"); - serializer.attribute(null, "key", key); + serializer.startTag(null, VALUE_ELEMENT); + serializer.attribute(null, KEY_ATTRIBUTE, key); serializer.text(value); - serializer.endTag(null, "value"); + serializer.endTag(null, VALUE_ELEMENT); } } private static void writeAccount(XmlSerializer serializer, String accountUuid, Map prefs) throws IOException { - serializer.startTag(null, "account"); - serializer.attribute(null, "uuid", accountUuid); + serializer.startTag(null, ACCOUNT_ELEMENT); + serializer.attribute(null, UUID_ATTRIBUTE, accountUuid); for (Map.Entry entry : prefs.entrySet()) { String key = entry.getKey(); String value = entry.getValue().toString(); @@ -144,11 +152,11 @@ public class StorageExporter { // Strip account UUID from key String keyPart = key.substring(comps[0].length() + 1); - serializer.startTag(null, "value"); - serializer.attribute(null, "key", keyPart); + serializer.startTag(null, VALUE_ELEMENT); + serializer.attribute(null, KEY_ATTRIBUTE, keyPart); serializer.text(value); - serializer.endTag(null, "value"); + serializer.endTag(null, VALUE_ELEMENT); } - serializer.endTag(null, "account"); + serializer.endTag(null, ACCOUNT_ELEMENT); } } From 24785bab66f2460355d4cb272049df1ae2b1d591 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 30 Mar 2011 03:55:14 +0200 Subject: [PATCH 048/116] More structure for the XML export (identity) --- src/com/fsck/k9/Account.java | 13 +++- .../fsck/k9/preferences/StorageExporter.java | 60 ++++++++++++++++++- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java index f868e1a88..1bc83e8ed 100644 --- a/src/com/fsck/k9/Account.java +++ b/src/com/fsck/k9/Account.java @@ -22,10 +22,12 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -53,6 +55,16 @@ public class Account implements BaseAccount { private static final String DEFAULT_QUOTE_PREFIX = ">"; private static final boolean DEFAULT_REPLY_AFTER_QUOTE = false; + public static final Set IDENTITY_KEYS = new HashSet(); + static { + IDENTITY_KEYS.add("name"); + IDENTITY_KEYS.add("email"); + IDENTITY_KEYS.add("description"); + IDENTITY_KEYS.add("signatureUse"); + IDENTITY_KEYS.add("signature"); + IDENTITY_KEYS.add("replyTo"); + } + /** *
      * 0 - Never (DELETE_POLICY_NEVER)
@@ -1018,7 +1030,6 @@ public class Account implements BaseAccount {
         return mUuid.hashCode();
     }
 
-
     private synchronized List loadIdentities(SharedPreferences prefs) {
         List newIdentities = new ArrayList();
         int ident = 0;
diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index f7396ec04..01a2625a1 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -29,6 +29,8 @@ public class StorageExporter {
     private static final String SETTINGS_ELEMENT = "settings";
     private static final String ACCOUNTS_ELEMENT = "accounts";
     private static final String ACCOUNT_ELEMENT = "account";
+    private static final String IDENTITIES_ELEMENT = "identities";
+    private static final String IDENTITY_ELEMENT = "identity";
     private static final String UUID_ATTRIBUTE = "uuid";
     private static final String VALUE_ELEMENT = "value";
     private static final String KEY_ATTRIBUTE = "key";
@@ -133,19 +135,34 @@ public class StorageExporter {
     private static void writeAccount(XmlSerializer serializer, String accountUuid,
             Map prefs) throws IOException {
 
+        Set identities = new HashSet();
+
         serializer.startTag(null, ACCOUNT_ELEMENT);
         serializer.attribute(null, UUID_ATTRIBUTE, accountUuid);
+
+        serializer.startTag(null, SETTINGS_ELEMENT);
         for (Map.Entry entry : prefs.entrySet()) {
             String key = entry.getKey();
             String value = entry.getValue().toString();
             String[] comps = key.split("\\.");
-            if (comps.length > 1) {
+            if (comps.length >= 2) {
                 String keyUuid = comps[0];
                 if (!keyUuid.equals(accountUuid)) {
                     continue;
                 }
+                if (comps.length == 3) {
+                    String identityKey = comps[1];
+                    String identityIndex = comps[2];
+
+                    if (Account.IDENTITY_KEYS.contains(identityKey)) {
+                        // This is an identity key. Save identity index for later...
+                        identities.add(identityIndex);
+                        // ... but don't write it now.
+                        continue;
+                    }
+                }
             } else {
-                // Skip global config entries
+                // Skip global config entries and identity entries
                 continue;
             }
 
@@ -157,6 +174,45 @@ public class StorageExporter {
             serializer.text(value);
             serializer.endTag(null, VALUE_ELEMENT);
         }
+        serializer.endTag(null, SETTINGS_ELEMENT);
+
+        if (identities.size() > 0) {
+            serializer.startTag(null, IDENTITIES_ELEMENT);
+            for (String identityIndex : identities) {
+                writeIdentity(serializer, accountUuid, identityIndex, prefs);
+            }
+            serializer.endTag(null, IDENTITIES_ELEMENT);
+        }
+
         serializer.endTag(null, ACCOUNT_ELEMENT);
     }
+
+    private static void writeIdentity(XmlSerializer serializer, String accountUuid,
+            String identity, Map prefs) throws IOException {
+
+        serializer.startTag(null, IDENTITY_ELEMENT);
+        for (Map.Entry entry : prefs.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue().toString();
+            String[] comps = key.split("\\.");
+            if (comps.length >= 3) {
+                String keyUuid = comps[0];
+                String identityKey = comps[1];
+                String identityIndex = comps[2];
+                if (!keyUuid.equals(accountUuid) || !identityIndex.equals(identity)
+                        || !Account.IDENTITY_KEYS.contains(identityKey)) {
+                    continue;
+                }
+            } else {
+                // Skip non-identity config entries
+                continue;
+            }
+
+            serializer.startTag(null, VALUE_ELEMENT);
+            serializer.attribute(null, KEY_ATTRIBUTE, comps[1]);
+            serializer.text(value);
+            serializer.endTag(null, VALUE_ELEMENT);
+        }
+        serializer.endTag(null, IDENTITY_ELEMENT);
+    }
 }

From 83b2972a13a5351619f8f2fc35837dd692a9bf33 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Wed, 30 Mar 2011 04:23:54 +0200
Subject: [PATCH 049/116] Rename "settings" tag to "global" for global settings
 (export)

---
 src/com/fsck/k9/preferences/StorageExporter.java | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index 01a2625a1..b3208b981 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -26,6 +26,7 @@ public class StorageExporter {
 
     private static final String ROOT_ELEMENT = "k9settings";
     private static final String VERSION_ATTRIBUTE = "version";
+    private static final String GLOBAL_ELEMENT = "global";
     private static final String SETTINGS_ELEMENT = "settings";
     private static final String ACCOUNTS_ELEMENT = "accounts";
     private static final String ACCOUNT_ELEMENT = "account";
@@ -95,9 +96,9 @@ public class StorageExporter {
             Map prefs = storage.getAll();
 
             if (includeGlobals) {
-                serializer.startTag(null, SETTINGS_ELEMENT);
+                serializer.startTag(null, GLOBAL_ELEMENT);
                 writeSettings(serializer, prefs);
-                serializer.endTag(null, SETTINGS_ELEMENT);
+                serializer.endTag(null, GLOBAL_ELEMENT);
             }
 
             serializer.startTag(null, ACCOUNTS_ELEMENT);

From 29c42eb1a347df853b61b0dc5236e35b0f88c4ae Mon Sep 17 00:00:00 2001
From: cketti 
Date: Wed, 30 Mar 2011 04:57:05 +0200
Subject: [PATCH 050/116] More structure for the XML export (folder settings)

---
 src/com/fsck/k9/mail/store/LocalStore.java    | 10 ++++
 .../fsck/k9/preferences/StorageExporter.java  | 60 +++++++++++++++++--
 2 files changed, 65 insertions(+), 5 deletions(-)

diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index e90e5a64e..3b0137a00 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -87,6 +87,16 @@ public class LocalStore extends Store implements Serializable {
         HEADERS_TO_SAVE.add("Content-Disposition");
         HEADERS_TO_SAVE.add("User-Agent");
     }
+
+    public static final Set FOLDER_SETTINGS_KEYS = new HashSet();
+    static {
+        FOLDER_SETTINGS_KEYS.add("displayMode");
+        FOLDER_SETTINGS_KEYS.add("syncMode");
+        FOLDER_SETTINGS_KEYS.add("pushMode");
+        FOLDER_SETTINGS_KEYS.add("inTopGroup");
+        FOLDER_SETTINGS_KEYS.add("integrate");
+    }
+
     /*
      * a String containing the columns getMessages expects to work with
      * in the correct order.
diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index b3208b981..08f1d6e6c 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -19,6 +19,7 @@ import com.fsck.k9.Account;
 import com.fsck.k9.K9;
 import com.fsck.k9.Preferences;
 import com.fsck.k9.helper.Utility;
+import com.fsck.k9.mail.store.LocalStore;
 
 
 public class StorageExporter {
@@ -30,9 +31,12 @@ public class StorageExporter {
     private static final String SETTINGS_ELEMENT = "settings";
     private static final String ACCOUNTS_ELEMENT = "accounts";
     private static final String ACCOUNT_ELEMENT = "account";
+    private static final String UUID_ATTRIBUTE = "uuid";
     private static final String IDENTITIES_ELEMENT = "identities";
     private static final String IDENTITY_ELEMENT = "identity";
-    private static final String UUID_ATTRIBUTE = "uuid";
+    private static final String FOLDERS_ELEMENT = "folders";
+    private static final String FOLDER_ELEMENT = "folder";
+    private static final String NAME_ATTRIBUTE = "name";
     private static final String VALUE_ELEMENT = "value";
     private static final String KEY_ATTRIBUTE = "key";
 
@@ -137,6 +141,7 @@ public class StorageExporter {
             Map prefs) throws IOException {
 
         Set identities = new HashSet();
+        Set folders = new HashSet();
 
         serializer.startTag(null, ACCOUNT_ELEMENT);
         serializer.attribute(null, UUID_ATTRIBUTE, accountUuid);
@@ -152,12 +157,19 @@ public class StorageExporter {
                     continue;
                 }
                 if (comps.length == 3) {
-                    String identityKey = comps[1];
-                    String identityIndex = comps[2];
+                    String secondPart = comps[1];
+                    String thirdPart = comps[2];
 
-                    if (Account.IDENTITY_KEYS.contains(identityKey)) {
+                    if (Account.IDENTITY_KEYS.contains(secondPart)) {
                         // This is an identity key. Save identity index for later...
-                        identities.add(identityIndex);
+                        identities.add(thirdPart);
+                        // ... but don't write it now.
+                        continue;
+                    }
+
+                    if (LocalStore.FOLDER_SETTINGS_KEYS.contains(thirdPart)) {
+                        // This is a folder key. Save folder name for later...
+                        folders.add(secondPart);
                         // ... but don't write it now.
                         continue;
                     }
@@ -185,6 +197,14 @@ public class StorageExporter {
             serializer.endTag(null, IDENTITIES_ELEMENT);
         }
 
+        if (folders.size() > 0) {
+            serializer.startTag(null, FOLDERS_ELEMENT);
+            for (String folder : folders) {
+                writeFolder(serializer, accountUuid, folder, prefs);
+            }
+            serializer.endTag(null, FOLDERS_ELEMENT);
+        }
+
         serializer.endTag(null, ACCOUNT_ELEMENT);
     }
 
@@ -216,4 +236,34 @@ public class StorageExporter {
         }
         serializer.endTag(null, IDENTITY_ELEMENT);
     }
+
+    private static void writeFolder(XmlSerializer serializer, String accountUuid,
+            String folder, Map prefs) throws IOException {
+
+        serializer.startTag(null, FOLDER_ELEMENT);
+        serializer.attribute(null, NAME_ATTRIBUTE, folder);
+        for (Map.Entry entry : prefs.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue().toString();
+            String[] comps = key.split("\\.");
+            if (comps.length >= 3) {
+                String keyUuid = comps[0];
+                String folderName = comps[1];
+                String folderKey = comps[2];
+                if (!keyUuid.equals(accountUuid) || !folderName.equals(folder)
+                        || !LocalStore.FOLDER_SETTINGS_KEYS.contains(folderKey)) {
+                    continue;
+                }
+            } else {
+                // Skip non-folder config entries
+                continue;
+            }
+
+            serializer.startTag(null, VALUE_ELEMENT);
+            serializer.attribute(null, KEY_ATTRIBUTE, comps[2]);
+            serializer.text(value);
+            serializer.endTag(null, VALUE_ELEMENT);
+        }
+        serializer.endTag(null, FOLDER_ELEMENT);
+    }
 }

From 6258118ed64bc8ea28df401713c68320c91deeca Mon Sep 17 00:00:00 2001
From: cketti 
Date: Wed, 30 Mar 2011 05:08:49 +0200
Subject: [PATCH 051/116] Write identity elements in correct order (export)

---
 .../fsck/k9/preferences/StorageExporter.java   | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index 08f1d6e6c..e424ddcab 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -4,7 +4,10 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.xmlpull.v1.XmlSerializer;
@@ -140,7 +143,7 @@ public class StorageExporter {
     private static void writeAccount(XmlSerializer serializer, String accountUuid,
             Map prefs) throws IOException {
 
-        Set identities = new HashSet();
+        Set identities = new HashSet();
         Set folders = new HashSet();
 
         serializer.startTag(null, ACCOUNT_ELEMENT);
@@ -162,7 +165,9 @@ public class StorageExporter {
 
                     if (Account.IDENTITY_KEYS.contains(secondPart)) {
                         // This is an identity key. Save identity index for later...
-                        identities.add(thirdPart);
+                        try {
+                            identities.add(Integer.parseInt(thirdPart));
+                        } catch (NumberFormatException e) { /* ignore */ }
                         // ... but don't write it now.
                         continue;
                     }
@@ -191,8 +196,13 @@ public class StorageExporter {
 
         if (identities.size() > 0) {
             serializer.startTag(null, IDENTITIES_ELEMENT);
-            for (String identityIndex : identities) {
-                writeIdentity(serializer, accountUuid, identityIndex, prefs);
+
+            // Sort identity indices (that's why we store them as Integers)
+            List sortedIdentities = new ArrayList(identities);
+            Collections.sort(sortedIdentities);
+
+            for (Integer identityIndex : sortedIdentities) {
+                writeIdentity(serializer, accountUuid, identityIndex.toString(), prefs);
             }
             serializer.endTag(null, IDENTITIES_ELEMENT);
         }

From 8850915987b868695c20ac07c376effb644fb2b7 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Wed, 30 Mar 2011 05:31:48 +0200
Subject: [PATCH 052/116] Add method to export an encrypted settings file

---
 .../fsck/k9/preferences/StorageExporter.java  | 22 ++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index e424ddcab..4019a4cdb 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -10,6 +10,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import javax.crypto.CipherOutputStream;
 import org.xmlpull.v1.XmlSerializer;
 
 import android.content.Context;
@@ -57,7 +58,13 @@ public class StorageExporter {
             File file = Utility.createUniqueFile(dir, EXPORT_FILENAME);
             String fileName = file.getAbsolutePath();
             os = new FileOutputStream(fileName);
-            exportPreferences(context, os, includeGlobals, accountUuids);
+
+            if (encryptionKey == null) {
+                exportPreferences(context, os, includeGlobals, accountUuids);
+            } else {
+                exportPreferencesEncrypted(context, os, includeGlobals, accountUuids,
+                        encryptionKey);
+            }
 
             // If all went well, we return the name of the file just written.
             return fileName;
@@ -72,6 +79,19 @@ public class StorageExporter {
         }
     }
 
+    public static void exportPreferencesEncrypted(Context context, OutputStream os, boolean includeGlobals,
+            Set accountUuids, String encryptionKey) throws StorageImportExportException  {
+
+        try {
+            K9Krypto k = new K9Krypto(encryptionKey, K9Krypto.MODE.ENCRYPT);
+            CipherOutputStream cos = new CipherOutputStream(os, k.mCipher);
+
+            exportPreferences(context, cos, includeGlobals, accountUuids);
+        } catch (Exception e) {
+            throw new StorageImportExportException();
+        }
+    }
+
     public static void exportPreferences(Context context, OutputStream os, boolean includeGlobals,
             Set accountUuids) throws StorageImportExportException  {
 

From 70f9a7b852336f88eec3f6e1f9d6f2f8516a0f31 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Wed, 30 Mar 2011 06:37:10 +0200
Subject: [PATCH 053/116] Granted some account/identity keys their own XML
 element (export)

---
 src/com/fsck/k9/Account.java                  | 32 +++++++------
 .../fsck/k9/preferences/StorageExporter.java  | 45 +++++++++++++++++--
 2 files changed, 61 insertions(+), 16 deletions(-)

diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java
index 1bc83e8ed..aab3e4924 100644
--- a/src/com/fsck/k9/Account.java
+++ b/src/com/fsck/k9/Account.java
@@ -55,11 +55,17 @@ public class Account implements BaseAccount {
     private static final String DEFAULT_QUOTE_PREFIX = ">";
     private static final boolean DEFAULT_REPLY_AFTER_QUOTE = false;
 
+    public static final String ACCOUNT_DESCRIPTION_KEY = "description";
+
+    public static final String IDENTITY_NAME_KEY = "name";
+    public static final String IDENTITY_EMAIL_KEY = "email";
+    public static final String IDENTITY_DESCRIPTION_KEY = "description";
+
     public static final Set IDENTITY_KEYS = new HashSet();
     static {
-        IDENTITY_KEYS.add("name");
-        IDENTITY_KEYS.add("email");
-        IDENTITY_KEYS.add("description");
+        IDENTITY_KEYS.add(IDENTITY_NAME_KEY);
+        IDENTITY_KEYS.add(IDENTITY_EMAIL_KEY);
+        IDENTITY_KEYS.add(IDENTITY_DESCRIPTION_KEY);
         IDENTITY_KEYS.add("signatureUse");
         IDENTITY_KEYS.add("signature");
         IDENTITY_KEYS.add("replyTo");
@@ -1036,11 +1042,11 @@ public class Account implements BaseAccount {
         boolean gotOne = false;
         do {
             gotOne = false;
-            String name = prefs.getString(mUuid + ".name." + ident, null);
-            String email = prefs.getString(mUuid + ".email." + ident, null);
+            String name = prefs.getString(mUuid + "." + IDENTITY_NAME_KEY + "." + ident, null);
+            String email = prefs.getString(mUuid + "." + IDENTITY_EMAIL_KEY + "." + ident, null);
             boolean signatureUse = prefs.getBoolean(mUuid  + ".signatureUse." + ident, true);
             String signature = prefs.getString(mUuid + ".signature." + ident, null);
-            String description = prefs.getString(mUuid + ".description." + ident, null);
+            String description = prefs.getString(mUuid + "." + IDENTITY_DESCRIPTION_KEY + "." + ident, null);
             final String replyTo = prefs.getString(mUuid + ".replyTo." + ident, null);
             if (email != null) {
                 Identity identity = new Identity();
@@ -1078,13 +1084,13 @@ public class Account implements BaseAccount {
         boolean gotOne = false;
         do {
             gotOne = false;
-            String email = prefs.getString(mUuid + ".email." + ident, null);
+            String email = prefs.getString(mUuid + "." + IDENTITY_EMAIL_KEY + "." + ident, null);
             if (email != null) {
-                editor.remove(mUuid + ".name." + ident);
-                editor.remove(mUuid + ".email." + ident);
+                editor.remove(mUuid + "." + IDENTITY_NAME_KEY + "." + ident);
+                editor.remove(mUuid + "." + IDENTITY_EMAIL_KEY + "." + ident);
                 editor.remove(mUuid + ".signatureUse." + ident);
                 editor.remove(mUuid + ".signature." + ident);
-                editor.remove(mUuid + ".description." + ident);
+                editor.remove(mUuid + "." + IDENTITY_DESCRIPTION_KEY + "." + ident);
                 editor.remove(mUuid + ".replyTo." + ident);
                 gotOne = true;
             }
@@ -1097,11 +1103,11 @@ public class Account implements BaseAccount {
         int ident = 0;
 
         for (Identity identity : identities) {
-            editor.putString(mUuid + ".name." + ident, identity.getName());
-            editor.putString(mUuid + ".email." + ident, identity.getEmail());
+            editor.putString(mUuid + "." + IDENTITY_NAME_KEY + "." + ident, identity.getName());
+            editor.putString(mUuid + "." + IDENTITY_EMAIL_KEY + "." + ident, identity.getEmail());
             editor.putBoolean(mUuid + ".signatureUse." + ident, identity.getSignatureUse());
             editor.putString(mUuid + ".signature." + ident, identity.getSignature());
-            editor.putString(mUuid + ".description." + ident, identity.getDescription());
+            editor.putString(mUuid + "." + IDENTITY_DESCRIPTION_KEY + "." + ident, identity.getDescription());
             editor.putString(mUuid + ".replyTo." + ident, identity.getReplyTo());
             ident++;
         }
diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index 4019a4cdb..b34fff1cb 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -43,6 +43,9 @@ public class StorageExporter {
     private static final String NAME_ATTRIBUTE = "name";
     private static final String VALUE_ELEMENT = "value";
     private static final String KEY_ATTRIBUTE = "key";
+    private static final String NAME_ELEMENT = "name";
+    private static final String EMAIL_ELEMENT = "email";
+    private static final String DESCRIPTION_ELEMENT = "description";
 
 
     public static String exportToFile(Context context, boolean includeGlobals,
@@ -169,6 +172,13 @@ public class StorageExporter {
         serializer.startTag(null, ACCOUNT_ELEMENT);
         serializer.attribute(null, UUID_ATTRIBUTE, accountUuid);
 
+        String name = (String) prefs.get(accountUuid + "." + Account.ACCOUNT_DESCRIPTION_KEY);
+        if (name != null) {
+            serializer.startTag(null, NAME_ELEMENT);
+            serializer.text(name);
+            serializer.endTag(null, NAME_ELEMENT);
+        }
+
         serializer.startTag(null, SETTINGS_ELEMENT);
         for (Map.Entry entry : prefs.entrySet()) {
             String key = entry.getKey();
@@ -176,11 +186,13 @@ public class StorageExporter {
             String[] comps = key.split("\\.");
             if (comps.length >= 2) {
                 String keyUuid = comps[0];
-                if (!keyUuid.equals(accountUuid)) {
+                String secondPart = comps[1];
+
+                if (!keyUuid.equals(accountUuid)
+                        || Account.ACCOUNT_DESCRIPTION_KEY.equals(secondPart)) {
                     continue;
                 }
                 if (comps.length == 3) {
-                    String secondPart = comps[1];
                     String thirdPart = comps[2];
 
                     if (Account.IDENTITY_KEYS.contains(secondPart)) {
@@ -242,6 +254,28 @@ public class StorageExporter {
             String identity, Map prefs) throws IOException {
 
         serializer.startTag(null, IDENTITY_ELEMENT);
+
+        String name = (String) prefs.get(accountUuid + "." + Account.IDENTITY_NAME_KEY +
+                "." + identity);
+        serializer.startTag(null, NAME_ELEMENT);
+        serializer.text(name);
+        serializer.endTag(null, NAME_ELEMENT);
+
+        String email = (String) prefs.get(accountUuid + "." + Account.IDENTITY_EMAIL_KEY +
+                "." + identity);
+        serializer.startTag(null, EMAIL_ELEMENT);
+        serializer.text(email);
+        serializer.endTag(null, EMAIL_ELEMENT);
+
+        String description = (String) prefs.get(accountUuid + "." +
+                Account.IDENTITY_DESCRIPTION_KEY + "." + identity);
+        if (description != null) {
+            serializer.startTag(null, DESCRIPTION_ELEMENT);
+            serializer.text(description);
+            serializer.endTag(null, DESCRIPTION_ELEMENT);
+        }
+
+        serializer.startTag(null, SETTINGS_ELEMENT);
         for (Map.Entry entry : prefs.entrySet()) {
             String key = entry.getKey();
             String value = entry.getValue().toString();
@@ -251,7 +285,10 @@ public class StorageExporter {
                 String identityKey = comps[1];
                 String identityIndex = comps[2];
                 if (!keyUuid.equals(accountUuid) || !identityIndex.equals(identity)
-                        || !Account.IDENTITY_KEYS.contains(identityKey)) {
+                        || !Account.IDENTITY_KEYS.contains(identityKey)
+                        || Account.IDENTITY_NAME_KEY.equals(identityKey)
+                        || Account.IDENTITY_EMAIL_KEY.equals(identityKey)
+                        || Account.IDENTITY_DESCRIPTION_KEY.equals(identityKey)) {
                     continue;
                 }
             } else {
@@ -264,6 +301,8 @@ public class StorageExporter {
             serializer.text(value);
             serializer.endTag(null, VALUE_ELEMENT);
         }
+        serializer.endTag(null, SETTINGS_ELEMENT);
+
         serializer.endTag(null, IDENTITY_ELEMENT);
     }
 

From 49e30b1be465adb57c86c15d5a12e9eb00f8a833 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Wed, 30 Mar 2011 06:44:16 +0200
Subject: [PATCH 054/116] Export settings sorted by key

---
 .../fsck/k9/preferences/StorageExporter.java  | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index b34fff1cb..a0f81d58b 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -10,6 +10,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 import javax.crypto.CipherOutputStream;
 import org.xmlpull.v1.XmlSerializer;
 
@@ -123,7 +124,7 @@ public class StorageExporter {
                 }
             }
 
-            Map prefs = storage.getAll();
+            Map prefs = new TreeMap(storage.getAll());
 
             if (includeGlobals) {
                 serializer.startTag(null, GLOBAL_ELEMENT);
@@ -147,9 +148,9 @@ public class StorageExporter {
     }
 
     private static void writeSettings(XmlSerializer serializer,
-            Map prefs) throws IOException {
+            Map prefs) throws IOException {
 
-        for (Map.Entry entry : prefs.entrySet()) {
+        for (Map.Entry entry : prefs.entrySet()) {
             String key = entry.getKey();
             String value = entry.getValue().toString();
             if (key.indexOf('.') != -1) {
@@ -164,7 +165,7 @@ public class StorageExporter {
     }
 
     private static void writeAccount(XmlSerializer serializer, String accountUuid,
-            Map prefs) throws IOException {
+            Map prefs) throws IOException {
 
         Set identities = new HashSet();
         Set folders = new HashSet();
@@ -180,7 +181,7 @@ public class StorageExporter {
         }
 
         serializer.startTag(null, SETTINGS_ELEMENT);
-        for (Map.Entry entry : prefs.entrySet()) {
+        for (Map.Entry entry : prefs.entrySet()) {
             String key = entry.getKey();
             String value = entry.getValue().toString();
             String[] comps = key.split("\\.");
@@ -251,7 +252,7 @@ public class StorageExporter {
     }
 
     private static void writeIdentity(XmlSerializer serializer, String accountUuid,
-            String identity, Map prefs) throws IOException {
+            String identity, Map prefs) throws IOException {
 
         serializer.startTag(null, IDENTITY_ELEMENT);
 
@@ -276,7 +277,7 @@ public class StorageExporter {
         }
 
         serializer.startTag(null, SETTINGS_ELEMENT);
-        for (Map.Entry entry : prefs.entrySet()) {
+        for (Map.Entry entry : prefs.entrySet()) {
             String key = entry.getKey();
             String value = entry.getValue().toString();
             String[] comps = key.split("\\.");
@@ -307,11 +308,11 @@ public class StorageExporter {
     }
 
     private static void writeFolder(XmlSerializer serializer, String accountUuid,
-            String folder, Map prefs) throws IOException {
+            String folder, Map prefs) throws IOException {
 
         serializer.startTag(null, FOLDER_ELEMENT);
         serializer.attribute(null, NAME_ATTRIBUTE, folder);
-        for (Map.Entry entry : prefs.entrySet()) {
+        for (Map.Entry entry : prefs.entrySet()) {
             String key = entry.getKey();
             String value = entry.getValue().toString();
             String[] comps = key.split("\\.");

From 45afa3a747f59143260a853b3161c57f3eeb996e Mon Sep 17 00:00:00 2001
From: cketti 
Date: Wed, 30 Mar 2011 20:56:26 +0200
Subject: [PATCH 055/116] Make XML element/attribute names for export public

---
 .../fsck/k9/preferences/StorageExporter.java  | 34 +++++++++----------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index a0f81d58b..d23e0dbdd 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -30,23 +30,23 @@ import com.fsck.k9.mail.store.LocalStore;
 public class StorageExporter {
     private static final String EXPORT_FILENAME = "settings.k9s";
 
-    private static final String ROOT_ELEMENT = "k9settings";
-    private static final String VERSION_ATTRIBUTE = "version";
-    private static final String GLOBAL_ELEMENT = "global";
-    private static final String SETTINGS_ELEMENT = "settings";
-    private static final String ACCOUNTS_ELEMENT = "accounts";
-    private static final String ACCOUNT_ELEMENT = "account";
-    private static final String UUID_ATTRIBUTE = "uuid";
-    private static final String IDENTITIES_ELEMENT = "identities";
-    private static final String IDENTITY_ELEMENT = "identity";
-    private static final String FOLDERS_ELEMENT = "folders";
-    private static final String FOLDER_ELEMENT = "folder";
-    private static final String NAME_ATTRIBUTE = "name";
-    private static final String VALUE_ELEMENT = "value";
-    private static final String KEY_ATTRIBUTE = "key";
-    private static final String NAME_ELEMENT = "name";
-    private static final String EMAIL_ELEMENT = "email";
-    private static final String DESCRIPTION_ELEMENT = "description";
+    public static final String ROOT_ELEMENT = "k9settings";
+    public static final String VERSION_ATTRIBUTE = "version";
+    public static final String GLOBAL_ELEMENT = "global";
+    public static final String SETTINGS_ELEMENT = "settings";
+    public static final String ACCOUNTS_ELEMENT = "accounts";
+    public static final String ACCOUNT_ELEMENT = "account";
+    public static final String UUID_ATTRIBUTE = "uuid";
+    public static final String IDENTITIES_ELEMENT = "identities";
+    public static final String IDENTITY_ELEMENT = "identity";
+    public static final String FOLDERS_ELEMENT = "folders";
+    public static final String FOLDER_ELEMENT = "folder";
+    public static final String NAME_ATTRIBUTE = "name";
+    public static final String VALUE_ELEMENT = "value";
+    public static final String KEY_ATTRIBUTE = "key";
+    public static final String NAME_ELEMENT = "name";
+    public static final String EMAIL_ELEMENT = "email";
+    public static final String DESCRIPTION_ELEMENT = "description";
 
 
     public static String exportToFile(Context context, boolean includeGlobals,

From d5197fdc56ca118879bd1e4e7cc8e754c736b905 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Wed, 30 Mar 2011 21:00:34 +0200
Subject: [PATCH 056/116] Put all import code in StorageImporter

Get rid of StorageImporterEncryptedXml and IStorageImporter. Also
AsyncUIProcessor is now obsolete.
---
 .../fsck/k9/activity/AsyncUIProcessor.java    | 104 ------------
 .../fsck/k9/preferences/IStorageImporter.java |  11 --
 .../fsck/k9/preferences/StorageImporter.java  | 152 ++++++++++--------
 .../StorageImporterEncryptedXml.java          | 102 ------------
 4 files changed, 85 insertions(+), 284 deletions(-)
 delete mode 100644 src/com/fsck/k9/activity/AsyncUIProcessor.java
 delete mode 100644 src/com/fsck/k9/preferences/IStorageImporter.java
 delete mode 100644 src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java

diff --git a/src/com/fsck/k9/activity/AsyncUIProcessor.java b/src/com/fsck/k9/activity/AsyncUIProcessor.java
deleted file mode 100644
index a9108dcb5..000000000
--- a/src/com/fsck/k9/activity/AsyncUIProcessor.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package com.fsck.k9.activity;
-
-import java.io.InputStream;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import android.app.Activity;
-import android.app.Application;
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.util.Log;
-
-import com.fsck.k9.K9;
-import com.fsck.k9.preferences.StorageImporter;
-
-/**
- * The class should be used to run long-running processes invoked from the UI that
- * do not affect the Stores.  There are probably pieces of MessagingController
- * that can be moved here.
- *
- */
-public class AsyncUIProcessor {
-
-    private final ExecutorService threadPool = Executors.newCachedThreadPool();
-    private Application mApplication;
-    private static AsyncUIProcessor inst = null;
-    private AsyncUIProcessor(Application application) {
-        mApplication = application;
-    }
-    public synchronized static AsyncUIProcessor getInstance(Application application) {
-        if (inst == null) {
-            inst = new AsyncUIProcessor(application);
-        }
-        return inst;
-    }
-    public void execute(Runnable runnable) {
-        threadPool.execute(runnable);
-    }
-
-    public void importSettings(final Activity activity, final Uri uri, final ImportListener listener) {
-        threadPool.execute(new Runnable() {
-            @Override
-            public void run() {
-                InputStream is = null;
-                try {
-                    ContentResolver resolver = mApplication.getContentResolver();
-                    is = resolver.openInputStream(uri);
-                } 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();
-                        }
-                    }
-                });
-            }
-        }
-                          );
-    }
-
-    private void quietClose(InputStream is) {
-        if (is != null) {
-            try {
-                is.close();
-            } catch (Exception e) {
-                Log.w(K9.LOG_TAG, "Unable to close inputStream", e);
-            }
-        }
-    }
-
-
-}
diff --git a/src/com/fsck/k9/preferences/IStorageImporter.java b/src/com/fsck/k9/preferences/IStorageImporter.java
deleted file mode 100644
index 415ad9bdc..000000000
--- a/src/com/fsck/k9/preferences/IStorageImporter.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.fsck.k9.preferences;
-
-import com.fsck.k9.Preferences;
-import com.fsck.k9.preferences.StorageImporter.ImportElement;
-
-import android.content.SharedPreferences;
-
-public interface IStorageImporter {
-    public boolean needsKey();
-    public abstract int importPreferences(Preferences preferences, SharedPreferences.Editor context, ImportElement dataset, String encryptionKey)  throws StorageImportExportException;
-}
\ No newline at end of file
diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index e1142e19f..f87183e20 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -1,9 +1,13 @@
 package com.fsck.k9.preferences;
 
+import java.io.BufferedReader;
 import java.io.InputStream;
+import java.io.StringReader;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Stack;
+import java.util.UUID;
 
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
@@ -14,21 +18,21 @@ import org.xml.sax.SAXException;
 import org.xml.sax.XMLReader;
 import org.xml.sax.helpers.DefaultHandler;
 
-import android.app.Activity;
+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;
-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;
 
 public class StorageImporter {
 
-    public static void importPreferences(Activity activity, InputStream is, String providedEncryptionKey, ImportListener listener) {
+    public static void importPreferences(Context context, InputStream is, String encryptionKey,
+            boolean globalSettings, String[] importAccountUuids, boolean overwrite)
+    throws StorageImportExportException {
+
         try {
             SAXParserFactory spf = SAXParserFactory.newInstance();
             SAXParser sp = spf.newSAXParser();
@@ -42,74 +46,88 @@ public class StorageImporter {
             String storageFormat = dataset.attributes.get("version");
             Log.i(K9.LOG_TAG, "Got settings file version " + storageFormat);
 
-            IStorageImporter storageImporter = new StorageImporterEncryptedXml();
-            if (storageImporter.needsKey() && providedEncryptionKey == null) {
-                gatherPassword(activity, storageImporter, dataset, listener);
-            } else {
-                finishImport(activity, storageImporter, dataset, providedEncryptionKey, listener);
-            }
-        } catch (Exception e) {
-            if (listener != null) {
-                listener.failure(e.getLocalizedMessage(), e);
-            }
-        }
-    }
+            Preferences preferences = Preferences.getPreferences(context);
+            SharedPreferences storage = preferences.getPreferences();
+            SharedPreferences.Editor editor = storage.edit();
 
-    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);
-        }
-    }
+            String data = dataset.data.toString();
+            List accountNumbers = Account.getExistingAccountNumbers(preferences);
+            Log.i(K9.LOG_TAG, "Existing accountNumbers = " + accountNumbers);
+            /**
+             *  We translate UUIDs in the import file into new UUIDs in the local instance for the following reasons:
+             *  1) Accidentally importing the same file twice cannot damage settings in an existing account.
+             *     (Say, an account that was imported two months ago and has since had significant settings changes.)
+             *  2) Importing a single file multiple times allows for creating multiple accounts from the same template.
+             *  3) Exporting an account and importing back into the same instance is a poor-man's account copy (until a real
+             *     copy function is created, if ever)
+             */
+            Map uuidMapping = new HashMap();
+            String accountUuids = preferences.getPreferences().getString("accountUuids", null);
 
-    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_import_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);
-                                    }
-                                }
+            StringReader sr = new StringReader(data);
+            BufferedReader br = new BufferedReader(sr);
+            String line = null;
+            int settingsImported = 0;
+            int numAccounts = 0;
+            K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.DECRYPT);
+            do {
+                line = br.readLine();
+                if (line != null) {
+                    //Log.i(K9.LOG_TAG, "Got line " + line);
+                    String[] comps = line.split(":");
+                    if (comps.length > 1) {
+                        String keyEnc = comps[0];
+                        String valueEnc = comps[1];
+                        String key = krypto.decrypt(keyEnc);
+                        String value = krypto.decrypt(valueEnc);
+                        String[] keyParts = key.split("\\.");
+                        if (keyParts.length > 1) {
+                            String oldUuid = keyParts[0];
+                            String newUuid = uuidMapping.get(oldUuid);
+                            if (newUuid == null) {
+                                newUuid = UUID.randomUUID().toString();
+                                uuidMapping.put(oldUuid, newUuid);
+
+                                Log.i(K9.LOG_TAG, "Mapping oldUuid " + oldUuid + " to newUuid " + newUuid);
                             }
-                        });
-                    }
-
-                    public void cancel() {
-                        if (listener != null) {
-                            listener.canceled();
+                            keyParts[0] = newUuid;
+                            if ("accountNumber".equals(keyParts[1])) {
+                                int accountNumber = Account.findNewAccountNumber(accountNumbers);
+                                accountNumbers.add(accountNumber);
+                                value = Integer.toString(accountNumber);
+                                accountUuids += (accountUuids.length() != 0 ? "," : "") + newUuid;
+                                numAccounts++;
+                            }
+                            StringBuilder builder = new StringBuilder();
+                            for (String part : keyParts) {
+                                if (builder.length() > 0) {
+                                    builder.append(".");
+                                }
+                                builder.append(part);
+                            }
+                            key = builder.toString();
                         }
+                        //Log.i(K9.LOG_TAG, "Setting " + key + " = " + value);
+                        settingsImported++;
+                        editor.putString(key, value);
                     }
-                });
+                }
 
-                dialog.show();
-            }
-        });
+            } while (line != null);
 
-    };
+            editor.putString("accountUuids", accountUuids);
+            Log.i(K9.LOG_TAG, "Imported " + settingsImported + " settings and " + numAccounts + " accounts");
+
+            editor.commit();
+            Preferences.getPreferences(context).refreshAccounts();
+            DateFormatter.clearChosenFormat();
+            K9.loadPrefs(Preferences.getPreferences(context));
+            K9.setServicesEnabled(context);
+
+        } catch (Exception e) {
+            throw new StorageImportExportException();
+        }
+    }
 
 
     public static class ImportElement {
diff --git a/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java b/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java
deleted file mode 100644
index 0707fa133..000000000
--- a/src/com/fsck/k9/preferences/StorageImporterEncryptedXml.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.fsck.k9.preferences;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import android.content.SharedPreferences;
-import android.util.Log;
-
-import com.fsck.k9.Account;
-import com.fsck.k9.K9;
-import com.fsck.k9.Preferences;
-import com.fsck.k9.preferences.StorageImporter.ImportElement;
-
-public class StorageImporterEncryptedXml implements IStorageImporter {
-    public int importPreferences(Preferences preferences, SharedPreferences.Editor editor, ImportElement dataset, String encryptionKey) throws StorageImportExportException {
-        try {
-
-            String data = dataset.data.toString();
-            List accountNumbers = Account.getExistingAccountNumbers(preferences);
-            Log.i(K9.LOG_TAG, "Existing accountNumbers = " + accountNumbers);
-            /**
-             *  We translate UUIDs in the import file into new UUIDs in the local instance for the following reasons:
-             *  1) Accidentally importing the same file twice cannot damage settings in an existing account.
-             *     (Say, an account that was imported two months ago and has since had significant settings changes.)
-             *  2) Importing a single file multiple times allows for creating multiple accounts from the same template.
-             *  3) Exporting an account and importing back into the same instance is a poor-man's account copy (until a real
-             *     copy function is created, if ever)
-             */
-            Map uuidMapping = new HashMap();
-            String accountUuids = preferences.getPreferences().getString("accountUuids", null);
-
-            StringReader sr = new StringReader(data);
-            BufferedReader br = new BufferedReader(sr);
-            String line = null;
-            int settingsImported = 0;
-            int numAccounts = 0;
-            K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.DECRYPT);
-            do {
-                line = br.readLine();
-                if (line != null) {
-                    //Log.i(K9.LOG_TAG, "Got line " + line);
-                    String[] comps = line.split(":");
-                    if (comps.length > 1) {
-                        String keyEnc = comps[0];
-                        String valueEnc = comps[1];
-                        String key = krypto.decrypt(keyEnc);
-                        String value = krypto.decrypt(valueEnc);
-                        String[] keyParts = key.split("\\.");
-                        if (keyParts.length > 1) {
-                            String oldUuid = keyParts[0];
-                            String newUuid = uuidMapping.get(oldUuid);
-                            if (newUuid == null) {
-                                newUuid = UUID.randomUUID().toString();
-                                uuidMapping.put(oldUuid, newUuid);
-
-                                Log.i(K9.LOG_TAG, "Mapping oldUuid " + oldUuid + " to newUuid " + newUuid);
-                            }
-                            keyParts[0] = newUuid;
-                            if ("accountNumber".equals(keyParts[1])) {
-                                int accountNumber = Account.findNewAccountNumber(accountNumbers);
-                                accountNumbers.add(accountNumber);
-                                value = Integer.toString(accountNumber);
-                                accountUuids += (accountUuids.length() != 0 ? "," : "") + newUuid;
-                                numAccounts++;
-                            }
-                            StringBuilder builder = new StringBuilder();
-                            for (String part : keyParts) {
-                                if (builder.length() > 0) {
-                                    builder.append(".");
-                                }
-                                builder.append(part);
-                            }
-                            key = builder.toString();
-                        }
-                        //Log.i(K9.LOG_TAG, "Setting " + key + " = " + value);
-                        settingsImported++;
-                        editor.putString(key, value);
-                    }
-                }
-
-            } while (line != null);
-
-            editor.putString("accountUuids", accountUuids);
-            Log.i(K9.LOG_TAG, "Imported " + settingsImported + " settings and " + numAccounts + " accounts");
-            return numAccounts;
-        } catch (IOException ie) {
-            throw new StorageImportExportException("Unable to import settings", ie);
-        } catch (Exception e) {
-            throw new StorageImportExportException("Unable to decrypt settings", e);
-        }
-    }
-
-    @Override
-    public boolean needsKey() {
-        return true;
-    }
-}

From 12d87854ac6f72e692ae0c9a1f0eb8c629d7a619 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Wed, 13 Apr 2011 03:37:44 +0200
Subject: [PATCH 057/116] First version of the import code that reads the new
 file format

---
 src/com/fsck/k9/activity/Accounts.java        | 220 ++++-
 .../fsck/k9/preferences/StorageImporter.java  | 796 +++++++++++++++---
 2 files changed, 836 insertions(+), 180 deletions(-)

diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java
index e40dadb0a..0e11b11a6 100644
--- a/src/com/fsck/k9/activity/Accounts.java
+++ b/src/com/fsck/k9/activity/Accounts.java
@@ -1,6 +1,8 @@
 
 package com.fsck.k9.activity;
 
+import java.io.FileNotFoundException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -22,6 +24,7 @@ import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.Log;
+import android.util.SparseBooleanArray;
 import android.util.TypedValue;
 import android.view.ContextMenu;
 import android.view.Menu;
@@ -34,14 +37,17 @@ import android.view.View.OnClickListener;
 import android.webkit.WebView;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
+import android.widget.CheckedTextView;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
+import android.widget.ListAdapter;
 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.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
 
 import com.fsck.k9.Account;
 import com.fsck.k9.AccountStats;
@@ -64,6 +70,9 @@ import com.fsck.k9.mail.store.StorageManager;
 import com.fsck.k9.view.ColorChip;
 import com.fsck.k9.preferences.StorageExporter;
 import com.fsck.k9.preferences.StorageImportExportException;
+import com.fsck.k9.preferences.StorageImporter;
+import com.fsck.k9.preferences.StorageImporter.AccountDescription;
+import com.fsck.k9.preferences.StorageImporter.ImportContents;
 
 
 public class Accounts extends K9ListActivity implements OnItemClickListener, OnClickListener {
@@ -854,53 +863,13 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
     }
 
     private void onImport(Uri uri) {
-        Toast.makeText(this, "Import is disabled for now", Toast.LENGTH_SHORT).show();
-        /*
-        Log.i(K9.LOG_TAG, "onImport importing from URI " + uri.getPath());
+        //Toast.makeText(this, "Import is disabled for now", Toast.LENGTH_SHORT).show();
 
-        final String fileName = uri.getPath();
-        AsyncUIProcessor.getInstance(Accounts.this.getApplication()).importSettings(this, uri, new ImportListener() {
-            @Override
-            public void success(int numAccounts) {
-                mHandler.progress(false);
-                String accountQtyText = getResources().getQuantityString(R.plurals.settings_import_success, numAccounts, numAccounts);
-                String messageText = getString(R.string.settings_import_success, accountQtyText, fileName);
-                showDialog(Accounts.this, R.string.settings_import_success_header, messageText);
-                runOnUiThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        refresh();
-                    }
-                });
-            }
+        Log.i(K9.LOG_TAG, "onImport importing from URI " + uri.toString());
 
-            @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();
-                    }
-                });
-
-            }
-        });
-        */
+        new ListImportContentsAsyncTask(uri, null).execute();
     }
+
     private void showDialog(final Context context, final int headerRes, final String message) {
         this.runOnUiThread(new Runnable() {
             @Override
@@ -1180,7 +1149,6 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
 
         @Override
         protected void onPostExecute(Boolean success) {
-            setProgress(false);
             if (success) {
                 showDialog(Accounts.this, R.string.settings_export_success_header,
                         Accounts.this.getString(R.string.settings_export_success, mFileName));
@@ -1191,4 +1159,166 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
             }
         }
     }
+
+    private class ImportAsyncTask extends AsyncTask {
+        private boolean mIncludeGlobals;
+        private Set mAccountUuids;
+        private boolean mOverwrite;
+        private String mEncryptionKey;
+        private InputStream mInputStream;
+
+        private ImportAsyncTask(boolean includeGlobals, Set accountUuids,
+                boolean overwrite, String encryptionKey, InputStream is) {
+            mIncludeGlobals = includeGlobals;
+            mAccountUuids = accountUuids;
+            mOverwrite = overwrite;
+            mEncryptionKey = encryptionKey;
+            mInputStream = is;
+        }
+
+        @Override
+        protected void onPreExecute() {
+            //TODO: show progress bar instead of displaying toast
+            String toastText = Accounts.this.getString(R.string.settings_importing);
+            Toast toast = Toast.makeText(Accounts.this, toastText, Toast.LENGTH_SHORT);
+            toast.show();
+        }
+
+        @Override
+        protected Boolean doInBackground(Void... params) {
+            try {
+                StorageImporter.importSettings(Accounts.this, mInputStream, mEncryptionKey,
+                        mIncludeGlobals, mAccountUuids, mOverwrite);
+            } catch (StorageImportExportException e) {
+                Log.w(K9.LOG_TAG, "Exception during export", e);
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        protected void onPostExecute(Boolean success) {
+            if (success) {
+                showDialog(Accounts.this, R.string.settings_import_success_header,
+                        //FIXME: use correct number of imported accounts
+                        Accounts.this.getString(R.string.settings_import_success, 3, "unknown"));
+                refresh();
+            } else {
+                //TODO: make the importer return an error code; translate that error code to a localized string here
+                showDialog(Accounts.this, R.string.settings_import_failed_header,
+                        Accounts.this.getString(R.string.settings_import_failure, "unknown", "Something went wrong"));
+            }
+        }
+    }
+
+    ImportContents mImportContents;
+    private class ListImportContentsAsyncTask extends AsyncTask {
+        private Uri mUri;
+        private String mEncryptionKey;
+        private InputStream mInputStream;
+
+        private ListImportContentsAsyncTask(Uri uri, String encryptionKey) {
+            mUri = uri;
+            mEncryptionKey = encryptionKey;
+        }
+
+        @Override
+        protected void onPreExecute() {
+            //TODO: show progress bar
+        }
+
+        @Override
+        protected Boolean doInBackground(Void... params) {
+            try {
+
+                InputStream is = getContentResolver().openInputStream(mUri);
+                mImportContents = StorageImporter.getImportStreamContents(
+                        Accounts.this, is, mEncryptionKey);
+
+                // Open another InputStream in the background. This is used later by ImportAsyncTask
+                mInputStream = getContentResolver().openInputStream(mUri);
+
+            } catch (StorageImportExportException e) {
+                Log.w(K9.LOG_TAG, "Exception during export", e);
+                return false;
+            }
+            catch (FileNotFoundException e) {
+                Log.w(K9.LOG_TAG, "Couldn't read content from URI " + mUri);
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        protected void onPostExecute(Boolean success) {
+            if (success) {
+                final ListView importSelectionView = new ListView(Accounts.this);
+                List contents = new ArrayList();
+                if (mImportContents.globalSettings) {
+                    contents.add("Global settings");
+                }
+                for (AccountDescription account : mImportContents.accounts) {
+                    contents.add(account.name);
+                }
+                importSelectionView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+                importSelectionView.setAdapter(new ArrayAdapter(Accounts.this, android.R.layout.simple_list_item_checked, contents));
+                importSelectionView.setOnItemSelectedListener(new OnItemSelectedListener() {
+                    @Override
+                    public void onItemSelected(AdapterView parent, View view, int pos, long id) {
+                        CheckedTextView ctv = (CheckedTextView)view;
+                        ctv.setChecked(!ctv.isChecked());
+                    }
+
+                    @Override
+                    public void onNothingSelected(AdapterView arg0) {}
+                });
+
+                //TODO: listview header: "Please select the settings you wish to import"
+                //TODO: listview footer: "Select all" / "Select none" buttons?
+                //TODO: listview footer: "Overwrite existing accounts?" checkbox
+
+                final AlertDialog.Builder builder = new AlertDialog.Builder(Accounts.this);
+                builder.setTitle("Import selection");
+                builder.setView(importSelectionView);
+                builder.setInverseBackgroundForced(true);
+                builder.setPositiveButton(R.string.okay_action,
+                    new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            ListAdapter adapter = importSelectionView.getAdapter();
+                            int count = adapter.getCount();
+                            SparseBooleanArray pos = importSelectionView.getCheckedItemPositions();
+
+                            boolean includeGlobals = mImportContents.globalSettings ? pos.get(0) : false;
+                            Set accountUuids = new HashSet();
+                            for (int i = 1; i < count; i++) {
+                                if (pos.get(i)) {
+                                    accountUuids.add(mImportContents.accounts.get(i-1).uuid);
+                                }
+                            }
+
+                            boolean overwrite = false; //TODO: get value from dialog
+
+                            dialog.dismiss();
+                            new ImportAsyncTask(includeGlobals, accountUuids, overwrite, mEncryptionKey, mInputStream).execute();
+                        }
+                    });
+                builder.setNegativeButton(R.string.cancel_action,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                dialog.dismiss();
+                                try {
+                                    mInputStream.close();
+                                } catch (Exception e) { /* Ignore */ }
+                            }
+                        });
+                builder.show();
+            } else {
+                //TODO: make the importer return an error code; translate that error code to a localized string here
+                showDialog(Accounts.this, R.string.settings_import_failed_header,
+                        Accounts.this.getString(R.string.settings_import_failure, "unknown", "Something went wrong"));
+            }
+        }
+    }
 }
diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index f87183e20..47f15fa08 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -1,190 +1,716 @@
 package com.fsck.k9.preferences;
 
-import java.io.BufferedReader;
+import java.io.IOException;
 import java.io.InputStream;
-import java.io.StringReader;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Stack;
+import java.util.Set;
 import java.util.UUID;
 
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.helpers.DefaultHandler;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
 
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.util.Log;
 
 import com.fsck.k9.Account;
+import com.fsck.k9.Identity;
 import com.fsck.k9.K9;
 import com.fsck.k9.Preferences;
 import com.fsck.k9.helper.DateFormatter;
+import com.fsck.k9.helper.Utility;
 
 public class StorageImporter {
 
-    public static void importPreferences(Context context, InputStream is, String encryptionKey,
-            boolean globalSettings, String[] importAccountUuids, boolean overwrite)
-    throws StorageImportExportException {
+    /**
+     * Class to list the contents of an import file/stream.
+     *
+     * @see StorageImporter#getImportStreamContents(Context,InputStream,String)
+     */
+    public static class ImportContents {
+        /**
+         * True, if the import file contains global settings.
+         */
+        public final boolean globalSettings;
+
+        /**
+         * The list of accounts found in the import file. Never {@code null}.
+         */
+        public final List accounts;
+
+        private ImportContents(boolean globalSettings, List accounts) {
+            this.globalSettings = globalSettings;
+            this.accounts = accounts;
+        }
+    }
+
+    /**
+     * Class to describe an account (name, UUID).
+     *
+     * @see ImportContents
+     */
+    public static class AccountDescription {
+        /**
+         * The name of the account.
+         */
+        public final String name;
+
+        /**
+         * The UUID of the account.
+         */
+        public final String uuid;
+
+        private AccountDescription(String name, String uuid) {
+            this.name = name;
+            this.uuid = uuid;
+        }
+    }
+
+    public static boolean isImportStreamEncrypted(Context context, InputStream inputStream) {
+        return false;
+    }
+
+    /**
+     * Parses an import {@link InputStream} and returns information on whether it contains global
+     * settings and/or account settings. For all account configurations found, the name of the
+     * account along with the account UUID is returned.
+     *
+     * @param context
+     * @param inputStream
+     * @param encryptionKey
+     * @return
+     * @throws StorageImportExportException
+     */
+    public static ImportContents getImportStreamContents(Context context, InputStream inputStream,
+            String encryptionKey) throws StorageImportExportException {
 
         try {
-            SAXParserFactory spf = SAXParserFactory.newInstance();
-            SAXParser sp = spf.newSAXParser();
-            XMLReader xr = sp.getXMLReader();
-            StorageImporterHandler handler = new StorageImporterHandler();
-            xr.setContentHandler(handler);
+            // Parse the import stream but don't save individual settings (overview=true)
+            Imported imported = parseSettings(inputStream, false, null, false, true);
 
-            xr.parse(new InputSource(is));
+            // If the stream contains global settings the "globalSettings" member will not be null
+            boolean globalSettings = (imported.globalSettings != null);
 
-            ImportElement dataset = handler.getRootElement();
-            String storageFormat = dataset.attributes.get("version");
-            Log.i(K9.LOG_TAG, "Got settings file version " + storageFormat);
+            final List accounts = new ArrayList();
+            // If the stream contains at least one account configuration the "accounts" member
+            // will not be null.
+            if (imported.accounts != null) {
+                for (ImportedAccount account : imported.accounts.values()) {
+                    accounts.add(new AccountDescription(account.name, account.uuid));
+                }
+            }
+
+            return new ImportContents(globalSettings, accounts);
+
+        } catch (StorageImportExportException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new StorageImportExportException(e);
+        }
+    }
+
+    /**
+     * Reads an import {@link InputStream} and imports the global settings and/or account
+     * configurations specified by the arguments.
+     *
+     * @param context
+     * @param inputStream
+     * @param encryptionKey
+     * @param globalSettings
+     * @param accountUuids
+     * @param overwrite
+     * @throws StorageImportExportException
+     */
+    public static void importSettings(Context context, InputStream inputStream, String encryptionKey,
+            boolean globalSettings, Set accountUuids, boolean overwrite)
+    throws StorageImportExportException {
+
+        try
+        {
+            Imported imported = parseSettings(inputStream, globalSettings, accountUuids, overwrite, false);
 
             Preferences preferences = Preferences.getPreferences(context);
             SharedPreferences storage = preferences.getPreferences();
             SharedPreferences.Editor editor = storage.edit();
 
-            String data = dataset.data.toString();
-            List accountNumbers = Account.getExistingAccountNumbers(preferences);
-            Log.i(K9.LOG_TAG, "Existing accountNumbers = " + accountNumbers);
-            /**
-             *  We translate UUIDs in the import file into new UUIDs in the local instance for the following reasons:
-             *  1) Accidentally importing the same file twice cannot damage settings in an existing account.
-             *     (Say, an account that was imported two months ago and has since had significant settings changes.)
-             *  2) Importing a single file multiple times allows for creating multiple accounts from the same template.
-             *  3) Exporting an account and importing back into the same instance is a poor-man's account copy (until a real
-             *     copy function is created, if ever)
-             */
-            Map uuidMapping = new HashMap();
-            String accountUuids = preferences.getPreferences().getString("accountUuids", null);
-
-            StringReader sr = new StringReader(data);
-            BufferedReader br = new BufferedReader(sr);
-            String line = null;
-            int settingsImported = 0;
-            int numAccounts = 0;
-            K9Krypto krypto = new K9Krypto(encryptionKey, K9Krypto.MODE.DECRYPT);
-            do {
-                line = br.readLine();
-                if (line != null) {
-                    //Log.i(K9.LOG_TAG, "Got line " + line);
-                    String[] comps = line.split(":");
-                    if (comps.length > 1) {
-                        String keyEnc = comps[0];
-                        String valueEnc = comps[1];
-                        String key = krypto.decrypt(keyEnc);
-                        String value = krypto.decrypt(valueEnc);
-                        String[] keyParts = key.split("\\.");
-                        if (keyParts.length > 1) {
-                            String oldUuid = keyParts[0];
-                            String newUuid = uuidMapping.get(oldUuid);
-                            if (newUuid == null) {
-                                newUuid = UUID.randomUUID().toString();
-                                uuidMapping.put(oldUuid, newUuid);
-
-                                Log.i(K9.LOG_TAG, "Mapping oldUuid " + oldUuid + " to newUuid " + newUuid);
-                            }
-                            keyParts[0] = newUuid;
-                            if ("accountNumber".equals(keyParts[1])) {
-                                int accountNumber = Account.findNewAccountNumber(accountNumbers);
-                                accountNumbers.add(accountNumber);
-                                value = Integer.toString(accountNumber);
-                                accountUuids += (accountUuids.length() != 0 ? "," : "") + newUuid;
-                                numAccounts++;
-                            }
-                            StringBuilder builder = new StringBuilder();
-                            for (String part : keyParts) {
-                                if (builder.length() > 0) {
-                                    builder.append(".");
-                                }
-                                builder.append(part);
-                            }
-                            key = builder.toString();
-                        }
-                        //Log.i(K9.LOG_TAG, "Setting " + key + " = " + value);
-                        settingsImported++;
-                        editor.putString(key, value);
-                    }
+            if (globalSettings) {
+                if (imported.globalSettings != null) {
+                    importGlobalSettings(editor, imported.globalSettings);
+                } else {
+                    Log.w(K9.LOG_TAG, "Was asked to import global settings but none found.");
                 }
+            }
 
-            } while (line != null);
+            if (accountUuids != null && accountUuids.size() > 0) {
+                if (imported.accounts != null) {
+                    List newUuids = new ArrayList();
+                    for (String accountUuid : accountUuids) {
+                        if (imported.accounts.containsKey(accountUuid)) {
+                            String newUuid = importAccount(context, editor, imported.accounts.get(accountUuid), overwrite);
+                            if (newUuid != null) {
+                                newUuids.add(newUuid);
+                            }
+                        } else {
+                            Log.w(K9.LOG_TAG, "Was asked to import account with UUID " +
+                                    accountUuid + ". But this account wasn't found.");
+                        }
+                    }
+                    if (newUuids.size() > 0) {
+                        String oldAccountUuids = storage.getString("accountUuids", "");
+                        String appendUuids = Utility.combine(newUuids.toArray(new String[0]), ',');
+                        String prefix = "";
+                        if (oldAccountUuids.length() > 0) {
+                            prefix = oldAccountUuids + ",";
+                        }
+                        editor.putString("accountUuids", prefix + appendUuids);
+                    }
+                } else {
+                    Log.w(K9.LOG_TAG, "Was asked to import at least one account but none found.");
+                }
+            }
 
-            editor.putString("accountUuids", accountUuids);
-            Log.i(K9.LOG_TAG, "Imported " + settingsImported + " settings and " + numAccounts + " accounts");
+            if (!editor.commit()) {
+                throw new StorageImportExportException("Couldn't save imported settings");
+            }
 
-            editor.commit();
-            Preferences.getPreferences(context).refreshAccounts();
+            preferences.refreshAccounts();
             DateFormatter.clearChosenFormat();
-            K9.loadPrefs(Preferences.getPreferences(context));
+            K9.loadPrefs(preferences);
             K9.setServicesEnabled(context);
 
+        } catch (StorageImportExportException e) {
+            throw e;
         } catch (Exception e) {
-            throw new StorageImportExportException();
+            throw new StorageImportExportException(e);
         }
     }
 
+    private static void importGlobalSettings(SharedPreferences.Editor editor,
+            ImportedSettings settings) {
+        //TODO: input validation
 
-    public static class ImportElement {
-        String name;
-        Map attributes = new HashMap();
-        Map subElements = new HashMap();
-        StringBuilder data = new StringBuilder();
+        for (Map.Entry setting : settings.settings.entrySet()) {
+            String key = setting.getKey();
+            String value = setting.getValue();
+            //FIXME: drop this key during input validation. then remove this check
+            if ("accountUuids".equals(key)) {
+                continue;
+            }
+            editor.putString(key, value);
+        }
     }
 
-    private static class StorageImporterHandler extends DefaultHandler {
-        private ImportElement rootElement = new ImportElement();
-        private Stack mOpenTags = new Stack();
+    private static String importAccount(Context context, SharedPreferences.Editor editor,
+            ImportedAccount account, boolean overwrite) {
 
-        public ImportElement getRootElement() {
-            return this.rootElement;
+        //TODO: input validation
+        //TODO: remove latestOldMessageSeenTime?
+
+        Preferences prefs = Preferences.getPreferences(context);
+        Account[] accounts = prefs.getAccounts();
+
+        String uuid = account.uuid;
+        Account existingAccount = prefs.getAccount(uuid);
+        if (!overwrite && existingAccount != null) {
+            // An account with this UUID already exists, but we're not allowed to overwrite it.
+            // So generate a new UUID.
+            uuid = UUID.randomUUID().toString();
         }
 
-        @Override
-        public void startDocument() throws SAXException {
-        }
-
-        @Override
-        public void endDocument() throws SAXException {
-            /* Do nothing */
-        }
-
-        @Override
-        public void startElement(String namespaceURI, String localName,
-                                 String qName, Attributes attributes) throws SAXException {
-            Log.i(K9.LOG_TAG, "Starting element " + localName);
-            ImportElement element = new ImportElement();
-            element.name = localName;
-            mOpenTags.push(element);
-            for (int i = 0; i < attributes.getLength(); i++) {
-                String key = attributes.getLocalName(i);
-                String value = attributes.getValue(i);
-                Log.i(K9.LOG_TAG, "Got attribute " + key + " = " + value);
-                element.attributes.put(key, value);
+        String accountName = account.name;
+        if (isAccountNameUsed(accountName, accounts)) {
+            // Account name is already in use. So generate a new one by appending " (x)", where x
+            // is the first number >= 1 that results in an unused account name.
+            for (int i = 1; i <= accounts.length; i++) {
+                accountName = account.name + " (" + i + ")";
+                if (!isAccountNameUsed(accountName, accounts)) {
+                    break;
+                }
             }
         }
 
-        @Override
-        public void endElement(String namespaceURI, String localName, String qName) {
-            Log.i(K9.LOG_TAG, "Ending element " + localName);
-            ImportElement element = mOpenTags.pop();
-            ImportElement superElement = mOpenTags.empty() ? null : mOpenTags.peek();
-            if (superElement != null) {
-                superElement.subElements.put(element.name, element);
-            } else {
-                rootElement = element;
+        String accountKeyPrefix = uuid + ".";
+        editor.putString(accountKeyPrefix + Account.ACCOUNT_DESCRIPTION_KEY, accountName);
+
+        // Write account settings
+        for (Map.Entry setting : account.settings.settings.entrySet()) {
+            //FIXME: drop this key during input validation. then remove this check
+            if ("accountNumber".equals(setting.getKey())) {
+                continue;
+            }
+
+            String key = accountKeyPrefix + setting.getKey();
+            String value = setting.getValue();
+            editor.putString(key, value);
+        }
+
+        // If it's a new account generate and write a new "accountNumber"
+        if (existingAccount == null || !uuid.equals(account.uuid)) {
+            int newAccountNumber = Account.generateAccountNumber(prefs);
+            editor.putString(accountKeyPrefix + "accountNumber", Integer.toString(newAccountNumber));
+        }
+
+        if (account.identities != null) {
+            importIdentities(editor, uuid, account, overwrite, existingAccount);
+        }
+
+        // Write folder settings
+        if (account.folders != null) {
+            for (ImportedFolder folder : account.folders) {
+                String folderKeyPrefix = uuid + "." + folder.name + ".";
+                for (Map.Entry setting : folder.settings.settings.entrySet()) {
+                    String key = folderKeyPrefix + setting.getKey();
+                    String value = setting.getValue();
+                    editor.putString(key, value);
+                }
             }
         }
 
-        @Override
-        public void characters(char ch[], int start, int length) {
-            String value = new String(ch, start, length);
-            mOpenTags.peek().data.append(value);
+        //TODO: sync folder settings with localstore?
+
+        return (overwrite && existingAccount != null) ? null : uuid;
+    }
+
+    private static void importIdentities(SharedPreferences.Editor editor, String uuid,
+            ImportedAccount account, boolean overwrite, Account existingAccount) {
+
+        String accountKeyPrefix = uuid + ".";
+
+        // Gather information about existing identities for this account (if any)
+        int nextIdentityIndex = 0;
+        final List existingIdentities;
+        if (overwrite && existingAccount != null) {
+            existingIdentities = existingAccount.getIdentities();
+            nextIdentityIndex = existingIdentities.size();
+        } else {
+            existingIdentities = new ArrayList();
         }
+
+        // Write identities
+        for (ImportedIdentity identity : account.identities) {
+            int writeIdentityIndex = nextIdentityIndex;
+            if (existingIdentities.size() > 0) {
+                int identityIndex = findIdentity(identity, existingIdentities);
+                if (overwrite && identityIndex != -1) {
+                    writeIdentityIndex = identityIndex;
+                }
+            }
+            if (writeIdentityIndex == nextIdentityIndex) {
+                nextIdentityIndex++;
+            }
+
+            String identityDescription = identity.description;
+            if (isIdentityDescriptionUsed(identityDescription, existingIdentities)) {
+                // Identity description is already in use. So generate a new one by appending
+                // " (x)", where x is the first number >= 1 that results in an unused identity
+                // description.
+                for (int i = 1; i <= existingIdentities.size(); i++) {
+                    identityDescription = identity.description + " (" + i + ")";
+                    if (!isIdentityDescriptionUsed(identityDescription, existingIdentities)) {
+                        break;
+                    }
+                }
+            }
+
+            editor.putString(accountKeyPrefix + Account.IDENTITY_NAME_KEY + "." +
+                    writeIdentityIndex, identity.name);
+            editor.putString(accountKeyPrefix + Account.IDENTITY_EMAIL_KEY + "." +
+                    writeIdentityIndex, identity.email);
+            editor.putString(accountKeyPrefix + Account.IDENTITY_DESCRIPTION_KEY + "." +
+                    writeIdentityIndex, identityDescription);
+
+            // Write identity settings
+            for (Map.Entry setting : identity.settings.settings.entrySet()) {
+                String key = setting.getKey();
+                String value = setting.getValue();
+                editor.putString(accountKeyPrefix + key + "." + writeIdentityIndex, value);
+            }
+        }
+    }
+
+    private static boolean isAccountNameUsed(String name, Account[] accounts) {
+        for (Account account : accounts) {
+            if (account.getDescription().equals(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean isIdentityDescriptionUsed(String description, List identities) {
+        for (Identity identitiy : identities) {
+            if (identitiy.getDescription().equals(description)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static int findIdentity(ImportedIdentity identity,
+            List identities) {
+        for (int i = 0; i < identities.size(); i++) {
+            Identity existingIdentity = identities.get(i);
+            if (existingIdentity.getName().equals(identity.name) &&
+                    existingIdentity.getEmail().equals(identity.email)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private static Imported parseSettings(InputStream inputStream, boolean globalSettings,
+            Set accountUuids, boolean overwrite, boolean overview)
+    throws StorageImportExportException {
+
+        if (!overview && accountUuids == null) {
+            throw new IllegalArgumentException("Argument 'accountUuids' must not be null.");
+        }
+
+        try {
+            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+            //factory.setNamespaceAware(true);
+            XmlPullParser xpp = factory.newPullParser();
+
+            InputStreamReader reader = new InputStreamReader(inputStream);
+            xpp.setInput(reader);
+
+            Imported imported = null;
+            int eventType = xpp.getEventType();
+            while (eventType != XmlPullParser.END_DOCUMENT) {
+                if(eventType == XmlPullParser.START_TAG) {
+                    if (StorageExporter.ROOT_ELEMENT.equals(xpp.getName())) {
+                        imported = parseRoot(xpp, globalSettings, accountUuids, overview);
+                    } else {
+                        Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
+                    }
+                }
+                eventType = xpp.next();
+            }
+
+            if (imported == null || (overview && imported.globalSettings == null &&
+                    imported.accounts == null)) {
+                throw new StorageImportExportException("Invalid import data");
+            }
+
+            return imported;
+        } catch (Exception e) {
+            throw new StorageImportExportException(e);
+        } finally {
+            try {
+                inputStream.close();
+            } catch (Exception e) { /* Ignore */ }
+        }
+    }
+
+    private static void skipToEndTag(XmlPullParser xpp, String endTag)
+    throws XmlPullParserException, IOException {
+
+        int eventType = xpp.next();
+        while (!(eventType == XmlPullParser.END_TAG && endTag.equals(xpp.getName()))) {
+            eventType = xpp.next();
+        }
+    }
+
+    private static String getText(XmlPullParser xpp)
+    throws XmlPullParserException, IOException {
+
+        int eventType = xpp.next();
+        if (eventType != XmlPullParser.TEXT) {
+            return null;
+        }
+        return xpp.getText();
+    }
+
+    private static Imported parseRoot(XmlPullParser xpp, boolean globalSettings,
+            Set accountUuids, boolean overview)
+    throws XmlPullParserException, IOException {
+
+        Imported result = new Imported();
+
+        //TODO: check version attribute
+
+        int eventType = xpp.next();
+        while (!(eventType == XmlPullParser.END_TAG &&
+                 StorageExporter.ROOT_ELEMENT.equals(xpp.getName()))) {
+
+            if(eventType == XmlPullParser.START_TAG) {
+                String element = xpp.getName();
+                if (StorageExporter.GLOBAL_ELEMENT.equals(element)) {
+                    if (overview || globalSettings) {
+                        if (result.globalSettings == null) {
+                            if (overview) {
+                                result.globalSettings = new ImportedSettings();
+                                skipToEndTag(xpp, StorageExporter.GLOBAL_ELEMENT);
+                            } else {
+                                result.globalSettings = parseSettings(xpp, StorageExporter.GLOBAL_ELEMENT);
+                            }
+                        } else {
+                            skipToEndTag(xpp, StorageExporter.GLOBAL_ELEMENT);
+                            Log.w(K9.LOG_TAG, "More than one global settings element. Only using the first one!");
+                        }
+                    } else {
+                        skipToEndTag(xpp, StorageExporter.GLOBAL_ELEMENT);
+                        Log.i(K9.LOG_TAG, "Skipping global settings");
+                    }
+                } else if (StorageExporter.ACCOUNTS_ELEMENT.equals(element)) {
+                    if (result.accounts == null) {
+                        result.accounts = parseAccounts(xpp, accountUuids, overview);
+                    } else {
+                        Log.w(K9.LOG_TAG, "More than one accounts element. Only using the first one!");
+                    }
+                } else {
+                    Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
+                }
+            }
+            eventType = xpp.next();
+        }
+
+        return result;
+    }
+
+    private static ImportedSettings parseSettings(XmlPullParser xpp, String endTag)
+    throws XmlPullParserException, IOException {
+
+        ImportedSettings result = null;
+
+        int eventType = xpp.next();
+        while (!(eventType == XmlPullParser.END_TAG && endTag.equals(xpp.getName()))) {
+
+            if(eventType == XmlPullParser.START_TAG) {
+                String element = xpp.getName();
+                if (StorageExporter.VALUE_ELEMENT.equals(element)) {
+                    String key = xpp.getAttributeValue(null, StorageExporter.KEY_ATTRIBUTE);
+                    String value = getText(xpp);
+
+                    if (result == null) {
+                        result = new ImportedSettings();
+                    }
+
+                    if (result.settings.containsKey(key)) {
+                        Log.w(K9.LOG_TAG, "Already read key \"" + key + "\". Ignoring value \"" + value + "\"");
+                    } else {
+                        result.settings.put(key, value);
+                    }
+                } else {
+                    Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
+                }
+            }
+            eventType = xpp.next();
+        }
+
+        return result;
+    }
+
+    private static Map parseAccounts(XmlPullParser xpp,
+            Set accountUuids, boolean overview)
+    throws XmlPullParserException, IOException {
+
+        Map accounts = null;
+
+        int eventType = xpp.next();
+        while (!(eventType == XmlPullParser.END_TAG &&
+                 StorageExporter.ACCOUNTS_ELEMENT.equals(xpp.getName()))) {
+
+            if(eventType == XmlPullParser.START_TAG) {
+                String element = xpp.getName();
+                if (StorageExporter.ACCOUNT_ELEMENT.equals(element)) {
+                    if (accounts == null) {
+                        accounts = new HashMap();
+                    }
+
+                    ImportedAccount account = parseAccount(xpp, accountUuids, overview);
+
+                    if (!accounts.containsKey(account.uuid)) {
+                        accounts.put(account.uuid, account);
+                    } else {
+                        Log.w(K9.LOG_TAG, "Duplicate account entries with UUID " + account.uuid +
+                                ". Ignoring!");
+                    }
+                } else {
+                    Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
+                }
+            }
+            eventType = xpp.next();
+        }
+
+        return accounts;
+    }
+
+    private static ImportedAccount parseAccount(XmlPullParser xpp, Set accountUuids,
+            boolean overview)
+    throws XmlPullParserException, IOException {
+
+        ImportedAccount account = new ImportedAccount();
+
+        String uuid = xpp.getAttributeValue(null, StorageExporter.UUID_ATTRIBUTE);
+        account.uuid = uuid;
+
+        if (overview || accountUuids.contains(uuid)) {
+            int eventType = xpp.next();
+            while (!(eventType == XmlPullParser.END_TAG &&
+                     StorageExporter.ACCOUNT_ELEMENT.equals(xpp.getName()))) {
+
+                if(eventType == XmlPullParser.START_TAG) {
+                    String element = xpp.getName();
+                    if (StorageExporter.NAME_ELEMENT.equals(element)) {
+                        account.name = getText(xpp);
+                    } else if (StorageExporter.SETTINGS_ELEMENT.equals(element)) {
+                        if (overview) {
+                            skipToEndTag(xpp, StorageExporter.SETTINGS_ELEMENT);
+                        } else {
+                            account.settings = parseSettings(xpp, StorageExporter.SETTINGS_ELEMENT);
+                        }
+                    } else if (StorageExporter.IDENTITIES_ELEMENT.equals(element)) {
+                        if (overview) {
+                            skipToEndTag(xpp, StorageExporter.IDENTITIES_ELEMENT);
+                        } else {
+                            account.identities = parseIdentities(xpp);
+                        }
+                    } else if (StorageExporter.FOLDERS_ELEMENT.equals(element)) {
+                        if (overview) {
+                            skipToEndTag(xpp, StorageExporter.FOLDERS_ELEMENT);
+                        } else {
+                            account.folders = parseFolders(xpp);
+                        }
+                    } else {
+                        Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
+                    }
+                }
+                eventType = xpp.next();
+            }
+        } else {
+            skipToEndTag(xpp, StorageExporter.ACCOUNT_ELEMENT);
+            Log.i(K9.LOG_TAG, "Skipping account with UUID " + uuid);
+        }
+
+        return account;
+    }
+
+    private static List parseIdentities(XmlPullParser xpp)
+    throws XmlPullParserException, IOException {
+        List identities = null;
+
+        int eventType = xpp.next();
+        while (!(eventType == XmlPullParser.END_TAG &&
+                 StorageExporter.IDENTITIES_ELEMENT.equals(xpp.getName()))) {
+
+            if(eventType == XmlPullParser.START_TAG) {
+                String element = xpp.getName();
+                if (StorageExporter.IDENTITY_ELEMENT.equals(element)) {
+                    if (identities == null) {
+                        identities = new ArrayList();
+                    }
+
+                    ImportedIdentity identity = parseIdentity(xpp);
+                    identities.add(identity);
+                } else {
+                    Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
+                }
+            }
+            eventType = xpp.next();
+        }
+
+        return identities;
+    }
+
+    private static ImportedIdentity parseIdentity(XmlPullParser xpp)
+    throws XmlPullParserException, IOException {
+        ImportedIdentity identity = new ImportedIdentity();
+
+        int eventType = xpp.next();
+        while (!(eventType == XmlPullParser.END_TAG &&
+                 StorageExporter.IDENTITY_ELEMENT.equals(xpp.getName()))) {
+
+            if(eventType == XmlPullParser.START_TAG) {
+                String element = xpp.getName();
+                if (StorageExporter.NAME_ELEMENT.equals(element)) {
+                    identity.name = getText(xpp);
+                } else if (StorageExporter.EMAIL_ELEMENT.equals(element)) {
+                    identity.email = getText(xpp);
+                } else if (StorageExporter.DESCRIPTION_ELEMENT.equals(element)) {
+                    identity.description = getText(xpp);
+                } else if (StorageExporter.SETTINGS_ELEMENT.equals(element)) {
+                    identity.settings = parseSettings(xpp, StorageExporter.SETTINGS_ELEMENT);
+                } else {
+                    Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
+                }
+            }
+            eventType = xpp.next();
+        }
+
+        return identity;
+    }
+
+    private static List parseFolders(XmlPullParser xpp)
+    throws XmlPullParserException, IOException {
+        List folders = null;
+
+        int eventType = xpp.next();
+        while (!(eventType == XmlPullParser.END_TAG &&
+                 StorageExporter.FOLDERS_ELEMENT.equals(xpp.getName()))) {
+
+            if(eventType == XmlPullParser.START_TAG) {
+                String element = xpp.getName();
+                if (StorageExporter.FOLDER_ELEMENT.equals(element)) {
+                    if (folders == null) {
+                        folders = new ArrayList();
+                    }
+
+                    ImportedFolder folder = parseFolder(xpp);
+                    folders.add(folder);
+                } else {
+                    Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
+                }
+            }
+            eventType = xpp.next();
+        }
+
+        return folders;
+    }
+
+    private static ImportedFolder parseFolder(XmlPullParser xpp)
+    throws XmlPullParserException, IOException {
+        ImportedFolder folder = new ImportedFolder();
+
+        String name = xpp.getAttributeValue(null, StorageExporter.NAME_ATTRIBUTE);
+        folder.name = name;
+
+        folder.settings = parseSettings(xpp, StorageExporter.FOLDER_ELEMENT);
+
+        return folder;
+    }
+
+    private static class Imported {
+        public ImportedSettings globalSettings;
+        public Map accounts;
+    }
+
+    private static class ImportedSettings {
+        public Map settings = new HashMap();
+    }
+
+    private static class ImportedAccount {
+        public String uuid;
+        public String name;
+        public ImportedSettings settings;
+        public List identities;
+        public List folders;
+    }
+
+    private static class ImportedIdentity {
+        public String name;
+        public String email;
+        public String description;
+        public ImportedSettings settings;
+    }
+
+    private static class ImportedFolder {
+        public String name;
+        public ImportedSettings settings;
     }
 }

From 8240d7ff1040f317ee419d9173636139084e7645 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Wed, 13 Apr 2011 03:44:43 +0200
Subject: [PATCH 058/116] Always reload all accounts after importing one or
 more accounts

---
 src/com/fsck/k9/Preferences.java              | 23 ++++---------------
 .../fsck/k9/preferences/StorageImporter.java  |  2 +-
 2 files changed, 6 insertions(+), 19 deletions(-)

diff --git a/src/com/fsck/k9/Preferences.java b/src/com/fsck/k9/Preferences.java
index 6ee07f538..462d37e63 100644
--- a/src/com/fsck/k9/Preferences.java
+++ b/src/com/fsck/k9/Preferences.java
@@ -49,36 +49,23 @@ public class Preferences {
         }
     }
 
-    private synchronized void loadAccounts() {
+    public synchronized void loadAccounts() {
         accounts = new HashMap();
-        refreshAccounts();
-    }
-
-    public synchronized void refreshAccounts() {
-        Map newAccountMap = new HashMap();
         accountsInOrder = new LinkedList();
         String accountUuids = getPreferences().getString("accountUuids", null);
         if ((accountUuids != null) && (accountUuids.length() != 0)) {
             String[] uuids = accountUuids.split(",");
             for (String uuid : uuids) {
-                Account account = accounts.get(uuid);
-                if (account != null) {
-                    newAccountMap.put(uuid, account);
-                    accountsInOrder.add(account);
-                } else {
-                    Account newAccount = new Account(this, uuid);
-                    newAccountMap.put(uuid, newAccount);
-                    accountsInOrder.add(newAccount);
-                }
+                Account newAccount = new Account(this, uuid);
+                accounts.put(uuid, newAccount);
+                accountsInOrder.add(newAccount);
             }
         }
         if ((newAccount != null) && newAccount.getAccountNumber() != -1) {
-            newAccountMap.put(newAccount.getUuid(), newAccount);
+            accounts.put(newAccount.getUuid(), newAccount);
             accountsInOrder.add(newAccount);
             newAccount = null;
         }
-
-        accounts = newAccountMap;
     }
 
     /**
diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index 47f15fa08..8f7412cc1 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -178,7 +178,7 @@ public class StorageImporter {
                 throw new StorageImportExportException("Couldn't save imported settings");
             }
 
-            preferences.refreshAccounts();
+            preferences.loadAccounts();
             DateFormatter.clearChosenFormat();
             K9.loadPrefs(preferences);
             K9.setServicesEnabled(context);

From 237302cc2b8e7b461a2323e057a24b4d640b3a30 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Tue, 19 Apr 2011 23:04:43 +0200
Subject: [PATCH 059/116] Changed import code to set "defaultAccountUuid" if
 necessary

---
 src/com/fsck/k9/activity/Accounts.java        |  6 +++---
 .../fsck/k9/preferences/StorageImporter.java  | 20 +++++++++++++------
 2 files changed, 17 insertions(+), 9 deletions(-)

diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java
index 0e11b11a6..690ec748e 100644
--- a/src/com/fsck/k9/activity/Accounts.java
+++ b/src/com/fsck/k9/activity/Accounts.java
@@ -1162,12 +1162,12 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
 
     private class ImportAsyncTask extends AsyncTask {
         private boolean mIncludeGlobals;
-        private Set mAccountUuids;
+        private List mAccountUuids;
         private boolean mOverwrite;
         private String mEncryptionKey;
         private InputStream mInputStream;
 
-        private ImportAsyncTask(boolean includeGlobals, Set accountUuids,
+        private ImportAsyncTask(boolean includeGlobals, List accountUuids,
                 boolean overwrite, String encryptionKey, InputStream is) {
             mIncludeGlobals = includeGlobals;
             mAccountUuids = accountUuids;
@@ -1290,7 +1290,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
                             SparseBooleanArray pos = importSelectionView.getCheckedItemPositions();
 
                             boolean includeGlobals = mImportContents.globalSettings ? pos.get(0) : false;
-                            Set accountUuids = new HashSet();
+                            List accountUuids = new ArrayList();
                             for (int i = 1; i < count; i++) {
                                 if (pos.get(i)) {
                                     accountUuids.add(mImportContents.accounts.get(i-1).uuid);
diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index 8f7412cc1..bd9e37d36 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -7,7 +7,6 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.UUID;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -105,6 +104,8 @@ public class StorageImporter {
                 }
             }
 
+            //TODO: throw exception if neither global settings nor account settings could be found
+
             return new ImportContents(globalSettings, accounts);
 
         } catch (StorageImportExportException e) {
@@ -127,7 +128,7 @@ public class StorageImporter {
      * @throws StorageImportExportException
      */
     public static void importSettings(Context context, InputStream inputStream, String encryptionKey,
-            boolean globalSettings, Set accountUuids, boolean overwrite)
+            boolean globalSettings, List accountUuids, boolean overwrite)
     throws StorageImportExportException {
 
         try
@@ -160,6 +161,7 @@ public class StorageImporter {
                                     accountUuid + ". But this account wasn't found.");
                         }
                     }
+
                     if (newUuids.size() > 0) {
                         String oldAccountUuids = storage.getString("accountUuids", "");
                         String appendUuids = Utility.combine(newUuids.toArray(new String[0]), ',');
@@ -169,6 +171,12 @@ public class StorageImporter {
                         }
                         editor.putString("accountUuids", prefix + appendUuids);
                     }
+
+                    String defaultAccountUuid = storage.getString("defaultAccountUuid", null);
+                    if (defaultAccountUuid == null) {
+                        editor.putString("defaultAccountUuid", accountUuids.get(0));
+                    }
+
                 } else {
                     Log.w(K9.LOG_TAG, "Was asked to import at least one account but none found.");
                 }
@@ -364,7 +372,7 @@ public class StorageImporter {
     }
 
     private static Imported parseSettings(InputStream inputStream, boolean globalSettings,
-            Set accountUuids, boolean overwrite, boolean overview)
+            List accountUuids, boolean overwrite, boolean overview)
     throws StorageImportExportException {
 
         if (!overview && accountUuids == null) {
@@ -427,7 +435,7 @@ public class StorageImporter {
     }
 
     private static Imported parseRoot(XmlPullParser xpp, boolean globalSettings,
-            Set accountUuids, boolean overview)
+            List accountUuids, boolean overview)
     throws XmlPullParserException, IOException {
 
         Imported result = new Imported();
@@ -507,7 +515,7 @@ public class StorageImporter {
     }
 
     private static Map parseAccounts(XmlPullParser xpp,
-            Set accountUuids, boolean overview)
+            List accountUuids, boolean overview)
     throws XmlPullParserException, IOException {
 
         Map accounts = null;
@@ -541,7 +549,7 @@ public class StorageImporter {
         return accounts;
     }
 
-    private static ImportedAccount parseAccount(XmlPullParser xpp, Set accountUuids,
+    private static ImportedAccount parseAccount(XmlPullParser xpp, List accountUuids,
             boolean overview)
     throws XmlPullParserException, IOException {
 

From 71f423d02935395fd254cefbbd0641889dc18aea Mon Sep 17 00:00:00 2001
From: cketti 
Date: Wed, 20 Apr 2011 21:37:48 +0200
Subject: [PATCH 060/116] Added first version of import file validation

---
 src/com/fsck/k9/Account.java                  |   8 +-
 .../fsck/k9/preferences/AccountSettings.java  | 258 ++++++++++++++++++
 .../fsck/k9/preferences/GlobalSettings.java   | 195 +++++++++++++
 src/com/fsck/k9/preferences/Settings.java     | 193 +++++++++++++
 .../fsck/k9/preferences/StorageImporter.java  |  29 +-
 .../k9/preferences/TimePickerPreference.java  |   2 +-
 6 files changed, 665 insertions(+), 20 deletions(-)
 create mode 100644 src/com/fsck/k9/preferences/AccountSettings.java
 create mode 100644 src/com/fsck/k9/preferences/GlobalSettings.java
 create mode 100644 src/com/fsck/k9/preferences/Settings.java

diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java
index aab3e4924..a9596570a 100644
--- a/src/com/fsck/k9/Account.java
+++ b/src/com/fsck/k9/Account.java
@@ -50,10 +50,10 @@ public class Account implements BaseAccount {
     public static final String TYPE_OTHER = "OTHER";
     private static final String[] networkTypes = { TYPE_WIFI, TYPE_MOBILE, TYPE_OTHER };
 
-    private static final MessageFormat DEFAULT_MESSAGE_FORMAT = MessageFormat.HTML;
-    private static final QuoteStyle DEFAULT_QUOTE_STYLE = QuoteStyle.PREFIX;
-    private static final String DEFAULT_QUOTE_PREFIX = ">";
-    private static final boolean DEFAULT_REPLY_AFTER_QUOTE = false;
+    public static final MessageFormat DEFAULT_MESSAGE_FORMAT = MessageFormat.HTML;
+    public static final QuoteStyle DEFAULT_QUOTE_STYLE = QuoteStyle.PREFIX;
+    public static final String DEFAULT_QUOTE_PREFIX = ">";
+    public static final boolean DEFAULT_REPLY_AFTER_QUOTE = false;
 
     public static final String ACCOUNT_DESCRIPTION_KEY = "description";
 
diff --git a/src/com/fsck/k9/preferences/AccountSettings.java b/src/com/fsck/k9/preferences/AccountSettings.java
new file mode 100644
index 000000000..8e691eaf6
--- /dev/null
+++ b/src/com/fsck/k9/preferences/AccountSettings.java
@@ -0,0 +1,258 @@
+package com.fsck.k9.preferences;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import android.net.Uri;
+import android.util.Log;
+import com.fsck.k9.Account;
+import com.fsck.k9.K9;
+import com.fsck.k9.R;
+import com.fsck.k9.Account.FolderMode;
+import com.fsck.k9.Account.ScrollButtons;
+import com.fsck.k9.crypto.Apg;
+import com.fsck.k9.helper.Utility;
+import com.fsck.k9.mail.store.StorageManager;
+import com.fsck.k9.preferences.Settings.*;
+
+public class AccountSettings {
+    public static final Map SETTINGS;
+
+    static {
+        SETTINGS = new LinkedHashMap();
+
+        // mandatory
+        SETTINGS.put("storeUri",
+                SD(SettingType.STRING, Settings.EXCEPTION_DEFAULT_VALUE, new StoreUriValidator()));
+        SETTINGS.put("transportUri",
+                SD(SettingType.STRING, Settings.EXCEPTION_DEFAULT_VALUE,
+                        new TransportUriValidator()));
+
+        SETTINGS.put("archiveFolderName",
+                SD(SettingType.STRING, "Archive", null));
+        SETTINGS.put("autoExpandFolderName",
+                SD(SettingType.STRING, "INBOX", null));
+        SETTINGS.put("automaticCheckIntervalMinutes",
+                SD(SettingType.INTEGER, -1, new ResourceArrayValidator(
+                        R.array.account_settings_check_frequency_values)));
+        SETTINGS.put("chipColor",
+                SD(SettingType.INTEGER, 0xff0000ff, Settings.SOLID_COLOR_VALIDATOR));
+        SETTINGS.put("cryptoApp",
+                SD(SettingType.STRING, Apg.NAME, null));
+        SETTINGS.put("cryptoAutoSignature",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("deletePolicy",
+                SD(SettingType.STRING, 0, new ResourceArrayValidator(
+                        R.array.account_setup_delete_policy_values)));
+        SETTINGS.put("displayCount",
+                SD(SettingType.STRING, K9.DEFAULT_VISIBLE_LIMIT, new ResourceArrayValidator(
+                        R.array.account_settings_display_count_values)));
+        SETTINGS.put("draftsFolderName",
+                SD(SettingType.STRING, "Drafts", null));
+        SETTINGS.put("enableMoveButtons",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("expungePolicy",
+                SD(SettingType.STRING, Account.EXPUNGE_IMMEDIATELY, new ResourceArrayValidator(
+                        R.array.account_setup_expunge_policy_values)));
+        SETTINGS.put("folderDisplayMode",
+                SD(SettingType.ENUM, FolderMode.NOT_SECOND_CLASS, new ResourceArrayValidator(
+                        R.array.account_settings_folder_display_mode_values)));
+        SETTINGS.put("folderPushMode",
+                SD(SettingType.ENUM, FolderMode.FIRST_CLASS, new ResourceArrayValidator(
+                        R.array.account_settings_folder_push_mode_values)));
+        SETTINGS.put("folderSyncMode",
+                SD(SettingType.ENUM, FolderMode.FIRST_CLASS, new ResourceArrayValidator(
+                        R.array.folder_settings_folder_sync_mode_values)));
+        SETTINGS.put("folderTargetMode",
+                SD(SettingType.ENUM, FolderMode.NOT_SECOND_CLASS, new ResourceArrayValidator(
+                        R.array.account_settings_folder_target_mode_values)));
+        SETTINGS.put("goToUnreadMessageSearch",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("hideButtonsEnum",
+                SD(SettingType.ENUM, ScrollButtons.NEVER, new ResourceArrayValidator(
+                        R.array.account_settings_hide_buttons_values)));
+        SETTINGS.put("hideMoveButtonsEnum",
+                SD(SettingType.ENUM, ScrollButtons.NEVER, new ResourceArrayValidator(
+                        R.array.account_settings_hide_move_buttons_values)));
+        SETTINGS.put("idleRefreshMinutes",
+                SD(SettingType.INTEGER, 24, new ResourceArrayValidator(
+                        R.array.idle_refresh_period_values)));
+        SETTINGS.put("led",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("ledColor",
+                SD(SettingType.INTEGER, 0xff0000ff, Settings.SOLID_COLOR_VALIDATOR));
+        SETTINGS.put("localStorageProvider",
+                SD(SettingType.STRING, new StorageProviderDefaultValue(),
+                        new StorageProviderValidator()));
+        SETTINGS.put("maxPushFolders",
+                SD(SettingType.INTEGER, 10, Settings.POSITIVE_INTEGER_VALIDATOR));
+        SETTINGS.put("maximumAutoDownloadMessageSize",
+                SD(SettingType.ENUM, 32768, new ResourceArrayValidator(
+                        R.array.account_settings_autodownload_message_size_values)));
+        SETTINGS.put("maximumPolledMessageAge",
+                SD(SettingType.ENUM, -1, new ResourceArrayValidator(
+                        R.array.account_settings_message_age_values)));
+        SETTINGS.put("messageFormat",
+                SD(SettingType.ENUM, Account.DEFAULT_MESSAGE_FORMAT, new ResourceArrayValidator(
+                        R.array.account_settings_message_format_values)));
+        SETTINGS.put("notificationUnreadCount",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("notifyMailCheck",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("notifyNewMail",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("notifySelfNewMail",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("pushPollOnConnect",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("quotePrefix",
+                SD(SettingType.STRING, Account.DEFAULT_QUOTE_PREFIX, null));
+        SETTINGS.put("quoteStyle",
+                SD(SettingType.ENUM, Account.DEFAULT_QUOTE_STYLE, new ResourceArrayValidator(
+                        R.array.account_settings_quote_style_values)));
+        SETTINGS.put("replyAfterQuote",
+                SD(SettingType.BOOLEAN, Account.DEFAULT_REPLY_AFTER_QUOTE, null));
+        SETTINGS.put("ring",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("ringtone",
+                SD(SettingType.STRING, "content://settings/system/notification_sound",
+                        new RingtoneValidator()));
+        SETTINGS.put("saveAllHeaders",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("searchableFolders",
+                SD(SettingType.ENUM, Account.Searchable.ALL, new ResourceArrayValidator(
+                        R.array.account_settings_searchable_values)));
+        SETTINGS.put("sentFolderName",
+                SD(SettingType.STRING, "Sent", null));
+        SETTINGS.put("showPicturesEnum",
+                SD(SettingType.ENUM, Account.ShowPictures.NEVER, new ResourceArrayValidator(
+                        R.array.account_settings_show_pictures_values)));
+        SETTINGS.put("signatureBeforeQuotedText",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("spamFolderName",
+                SD(SettingType.STRING, "Spam", null));
+        SETTINGS.put("subscribedFoldersOnly",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("syncRemoteDeletions",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("trashFolderName",
+                SD(SettingType.STRING, "Trash", null));
+        SETTINGS.put("useCompression.MOBILE",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("useCompression.OTHER",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("useCompression.WIFI",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("vibrate",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("vibratePattern",
+                SD(SettingType.INTEGER, 0, new ResourceArrayValidator(
+                        R.array.account_settings_vibrate_pattern_values)));
+        SETTINGS.put("vibrateTimes",
+                SD(SettingType.INTEGER, 5, new ResourceArrayValidator(
+                        R.array.account_settings_vibrate_times_label)));
+    }
+
+    // Just to have shorter lines in SETTINGS initialization
+    private static SettingsDescription SD(SettingType type,
+            Object defaultValue, ISettingValidator validator) {
+        return new SettingsDescription(type, defaultValue, validator);
+    }
+
+    public static Map validate(Map importedSettings) {
+        return Settings.validate(SETTINGS, importedSettings);
+    }
+
+    public static class StorageProviderDefaultValue implements IDefaultValue {
+        @Override
+        public Object computeDefaultValue(String key, Map validatedSettings) {
+            return StorageManager.getInstance(K9.app).getDefaultProviderId();
+        }
+
+    }
+
+    public static class StorageProviderValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            Map providers = StorageManager.getInstance(K9.app).getAvailableProviders();
+            for (String storageProvider : providers.keySet()) {
+                if (storageProvider.equals(value)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public static class RingtoneValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            // TODO implement
+            return true;
+        }
+    }
+
+    public static class StoreUriValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            try {
+                String uriString = Utility.base64Decode(value);
+                if (!uriString.startsWith("imap") && !uriString.startsWith("pop3") &&
+                        !uriString.startsWith("webdav")) {
+                    return false;
+                }
+
+                //TODO: check complete scheme (imap+ssl etc.)
+
+                Uri uri = Uri.parse(uriString);
+                String[] userInfoParts = uri.getUserInfo().split(":");
+                if (userInfoParts.length < 2) {
+                    return false;
+                }
+                //TODO: check if username and password are urlencoded
+
+                String host = uri.getHost();
+                if (host == null || host.length() == 0) {
+                    return false;
+                }
+
+                //TODO: check store specifics
+
+                return true;
+            } catch (Exception e) { Log.e(K9.LOG_TAG, "oops", e); /* Ignore */ }
+
+            return false;
+        }
+    }
+
+    public static class TransportUriValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            try {
+                String uriString = Utility.base64Decode(value);
+                if (!uriString.startsWith("smtp") && !uriString.startsWith("webdav")) {
+                    return false;
+                }
+
+                //TODO: check complete scheme (smtp+ssl etc.)
+
+                Uri uri = Uri.parse(uriString);
+                String[] userInfoParts = uri.getUserInfo().split(":");
+                if (userInfoParts.length < 2) {
+                    return false;
+                }
+                //TODO: check if username and password are urlencoded
+
+                String host = uri.getHost();
+                if (host == null || host.length() == 0) {
+                    return false;
+                }
+
+                //TODO: check store specifics
+
+                return true;
+            } catch (Exception e) { Log.e(K9.LOG_TAG, "oops", e); /* Ignore */ }
+
+            return false;
+        }
+    }
+}
diff --git a/src/com/fsck/k9/preferences/GlobalSettings.java b/src/com/fsck/k9/preferences/GlobalSettings.java
new file mode 100644
index 000000000..ba12440dc
--- /dev/null
+++ b/src/com/fsck/k9/preferences/GlobalSettings.java
@@ -0,0 +1,195 @@
+package com.fsck.k9.preferences;
+
+import java.text.SimpleDateFormat;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import com.fsck.k9.FontSizes;
+import com.fsck.k9.K9;
+import com.fsck.k9.R;
+import com.fsck.k9.helper.DateFormatter;
+import com.fsck.k9.preferences.Settings.*;
+
+public class GlobalSettings {
+    public static final ISettingValidator FONT_SIZE_VALIDATOR = new DipFontSizeValidator();
+    public static final ISettingValidator TIME_VALIDATOR = new TimeValidator();
+
+    public static final Map SETTINGS;
+
+    static {
+        SETTINGS = new LinkedHashMap();
+
+        SETTINGS.put("animations",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("backgroundOperations",
+                SD(SettingType.ENUM, K9.BACKGROUND_OPS.WHEN_CHECKED, new ResourceArrayValidator(
+                        R.array.background_ops_values)));
+        SETTINGS.put("changeRegisteredNameColor",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("compactLayouts",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("confirmDelete",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("countSearchMessages",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("dateFormat",
+                SD(SettingType.ENUM, DateFormatter.DEFAULT_FORMAT, new DateFormatValidator()));
+        SETTINGS.put("enableDebugLogging",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("enableSensitiveLogging",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("fontSizeAccountDescription",
+                SD(SettingType.INTEGER, 14, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeAccountName",
+                SD(SettingType.INTEGER, 18, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeFolderName",
+                SD(SettingType.INTEGER, 22, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeFolderStatus",
+                SD(SettingType.INTEGER, 14, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeMessageListDate",
+                SD(SettingType.INTEGER, 14, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeMessageListPreview",
+                SD(SettingType.INTEGER, 14, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeMessageListSender",
+                SD(SettingType.INTEGER, 14, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeMessageListSubject",
+                SD(SettingType.INTEGER, 16, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeMessageViewAdditionalHeaders",
+                SD(SettingType.INTEGER, 12, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeMessageViewCC",
+                SD(SettingType.INTEGER, 12, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeMessageViewContent",
+                SD(SettingType.INTEGER, 3, new WebViewFontSizeValidator()));
+        SETTINGS.put("fontSizeMessageViewDate",
+                SD(SettingType.INTEGER, 10, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeMessageViewSender",
+                SD(SettingType.INTEGER, 14, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeMessageViewSubject",
+                SD(SettingType.INTEGER, 12, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeMessageViewTime",
+                SD(SettingType.INTEGER, 10, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("fontSizeMessageViewTo",
+                SD(SettingType.INTEGER, 12, FONT_SIZE_VALIDATOR));
+        SETTINGS.put("gesturesEnabled",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("hideSpecialAccounts",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("keyguardPrivacy",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("language",
+                SD(SettingType.STRING, "", new ResourceArrayValidator(
+                        R.array.settings_language_values)));
+        SETTINGS.put("manageBack",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("measureAccounts",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("messageListCheckboxes",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("messageListPreviewLines",
+                SD(SettingType.INTEGER, 2, Settings.POSITIVE_INTEGER_VALIDATOR));
+        SETTINGS.put("messageListStars",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("messageListTouchable",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("messageViewFixedWidthFont",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("messageViewReturnToList",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("mobileOptimizedLayout",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("quietTimeEnabled",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("quietTimeEnds",
+                SD(SettingType.STRING, "7:00", TIME_VALIDATOR));
+        SETTINGS.put("quietTimeStarts",
+                SD(SettingType.STRING, "21:00", TIME_VALIDATOR));
+        SETTINGS.put("registeredNameColor",
+                SD(SettingType.INTEGER, 0xFF00008F, Settings.SOLID_COLOR_VALIDATOR));
+        SETTINGS.put("showContactName",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("showCorrespondentNames",
+                SD(SettingType.BOOLEAN, true, null));
+        SETTINGS.put("startIntegratedInbox",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("theme",
+                SD(SettingType.INTEGER, android.R.style.Theme_Light, new ThemeValidator()));
+        SETTINGS.put("useGalleryBugWorkaround",
+                SD(SettingType.BOOLEAN, new GalleryBugWorkaroundDefaultValue(), null));
+        SETTINGS.put("useVolumeKeysForListNavigation",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("useVolumeKeysForNavigation",
+                SD(SettingType.BOOLEAN, false, null));
+        SETTINGS.put("zoomControlsEnabled",
+                SD(SettingType.BOOLEAN, false, null));
+    }
+
+    // Just to have shorter lines in SETTINGS initialization
+    private static SettingsDescription SD(SettingType type,
+            Object defaultValue, ISettingValidator validator) {
+        return new SettingsDescription(type, defaultValue, validator);
+    }
+
+    public static Map validate(Map importedSettings) {
+        return Settings.validate(SETTINGS, importedSettings);
+    }
+
+    public static class GalleryBugWorkaroundDefaultValue implements IDefaultValue {
+        @Override
+        public Object computeDefaultValue(String key, Map validatedSettings) {
+            return K9.isGalleryBuggy();
+        }
+    }
+
+    public static class DipFontSizeValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            int val = Integer.parseInt(value);
+            switch (val) {
+                case FontSizes.FONT_10DIP:
+                case FontSizes.FONT_12DIP:
+                case FontSizes.SMALL:
+                case FontSizes.FONT_16DIP:
+                case FontSizes.MEDIUM:
+                case FontSizes.FONT_20DIP:
+                case FontSizes.LARGE:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+    }
+
+    public static class WebViewFontSizeValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            int val = Integer.parseInt(value);
+            return (val >= 1 && val <= 5);
+        }
+    }
+
+    public static class TimeValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            return value.matches(TimePickerPreference.VALIDATION_EXPRESSION);
+        }
+    }
+
+    public static class DateFormatValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            try {
+                new SimpleDateFormat(value);
+                return true;
+            } catch (Exception e) {
+                return false;
+            }
+        }
+    }
+
+    public static class ThemeValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            int val = Integer.parseInt(value);
+            return (val == android.R.style.Theme_Light || val == android.R.style.Theme);
+        }
+    }
+}
diff --git a/src/com/fsck/k9/preferences/Settings.java b/src/com/fsck/k9/preferences/Settings.java
new file mode 100644
index 000000000..29b0068b8
--- /dev/null
+++ b/src/com/fsck/k9/preferences/Settings.java
@@ -0,0 +1,193 @@
+package com.fsck.k9.preferences;
+
+import java.util.HashMap;
+import java.util.Map;
+import android.util.Log;
+import com.fsck.k9.K9;
+
+/*
+ * TODO:
+ * - add support for different settings versions (validate old version and upgrade to new format)
+ * - use the default values defined in GlobalSettings and AccountSettings when creating new
+ *   accounts
+ * - use the settings description to decide which keys to export
+ * - convert internal representation to a "pretty" format when exporting (e.g. we want to export
+ *   the value "light" rather than the integer constant for android.R.style.Theme_Light); revert
+ *   that conversion when importing
+ * - think of a better way to validate enums than to use the resource arrays (i.e. get rid of
+ *   ResourceArrayValidator); maybe even use the settings description for the settings UI
+ * - add unit test that validates the default values are actually valid according to the validator
+ */
+
+public class Settings {
+    public static final IDefaultValue EXCEPTION_DEFAULT_VALUE = new ExceptionDefaultValue();
+
+    public static final ISettingValidator BOOLEAN_VALIDATOR = new BooleanValidator();
+    public static final ISettingValidator INTEGER_VALIDATOR = new IntegerValidator();
+    public static final ISettingValidator POSITIVE_INTEGER_VALIDATOR = new PositiveIntegerValidator();
+    public static final ISettingValidator SOLID_COLOR_VALIDATOR = new SolidColorValidator();
+
+    public static Map validate(Map settings,
+            Map importedSettings) {
+
+        Map validatedSettings = new HashMap();
+        for (Map.Entry setting : settings.entrySet()) {
+            String key = setting.getKey();
+            SettingsDescription desc = setting.getValue();
+
+            boolean useDefaultValue;
+            if (!importedSettings.containsKey(key)) {
+                Log.v(K9.LOG_TAG, "Key \"" + key + "\" wasn't found in the imported file. Using default value.");
+                useDefaultValue = true;
+            } else {
+                String importedValue = importedSettings.get(key);
+                if (Settings.isValid(desc, key, importedValue, validatedSettings)) {
+                    validatedSettings.put(key, importedValue);
+                    useDefaultValue = false;
+                } else {
+                    Log.v(K9.LOG_TAG, "Key \"" + key + "\" has invalid value \"" + importedValue + "\" in " +
+                            "imported file. Using default value.");
+                    useDefaultValue = true;
+                }
+            }
+
+            if (useDefaultValue) {
+                Object defaultValue;
+                if (desc.defaultValue instanceof IDefaultValue) {
+                    defaultValue = ((IDefaultValue)desc.defaultValue).computeDefaultValue(key, validatedSettings);
+                } else {
+                    defaultValue = desc.defaultValue;
+                }
+
+                validatedSettings.put(key, defaultValue.toString());
+            }
+        }
+
+        return validatedSettings;
+    }
+
+    public static boolean isValid(SettingsDescription desc, String key, String value,
+            Map validatedSettings) {
+        try {
+            switch (desc.type) {
+                case BOOLEAN:
+                    if (!Settings.BOOLEAN_VALIDATOR.isValid(key, value, validatedSettings)) {
+                        return false;
+                    }
+                    break;
+                case INTEGER:
+                    if (!Settings.INTEGER_VALIDATOR.isValid(key, value, validatedSettings)) {
+                        return false;
+                    }
+                    break;
+                default:
+                    break;
+            }
+
+            if (desc.validator != null) {
+                return desc.validator.isValid(key, value, validatedSettings);
+            }
+
+            return true;
+        } catch (Exception e) {
+            Log.e(K9.LOG_TAG, "Exception while running validator for value \"" + value + "\"", e);
+            return false;
+        }
+    }
+
+    public enum SettingType {
+        BOOLEAN,
+        INTEGER,
+        STRING,
+        ENUM
+    }
+
+    public static class SettingsDescription {
+        public final SettingType type;
+        public final Object defaultValue;
+        public final ISettingValidator validator;
+
+        protected SettingsDescription(SettingType type,
+                Object defaultValue, ISettingValidator validator) {
+            this.type = type;
+            this.defaultValue = defaultValue;
+            this.validator = validator;
+        }
+    }
+
+    public interface IDefaultValue {
+        Object computeDefaultValue(String key, Map validatedSettings);
+    }
+
+    public static class ExceptionDefaultValue implements IDefaultValue {
+        @Override
+        public Object computeDefaultValue(String key, Map validatedSettings) {
+            throw new RuntimeException("There is no default value for key \"" + key + "\".");
+        }
+
+    }
+
+    public interface ISettingValidator {
+        boolean isValid(String key, String value, Map validatedSettings);
+    }
+
+    public static class BooleanValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            return Boolean.TRUE.toString().equals(value) || Boolean.FALSE.toString().equals(value);
+        }
+    }
+
+    public static class IntegerValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            try {
+                Integer.parseInt(value);
+                return true;
+            } catch (NumberFormatException e) {
+                return false;
+            }
+        }
+    }
+
+    public static class PositiveIntegerValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            return (Integer.parseInt(value) >= 0);
+        }
+    }
+
+    public static class ResourceArrayValidator implements ISettingValidator {
+        private final int mResource;
+
+        public ResourceArrayValidator(int res) {
+            mResource = res;
+        }
+
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            try {
+                String[] values = K9.app.getResources().getStringArray(mResource);
+
+                for (String validValue : values) {
+                    if (validValue.equals(value)) {
+                        return true;
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(K9.LOG_TAG, "Something went wrong during validation of key " + key, e);
+            }
+
+            return false;
+        }
+    }
+
+    public static class SolidColorValidator implements ISettingValidator {
+        @Override
+        public boolean isValid(String key, String value, Map validatedSettings) {
+            int color = Integer.parseInt(value);
+            return ((color & 0xFF000000) == 0xFF000000);
+        }
+
+    }
+}
diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index bd9e37d36..23c5e7193 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -200,15 +200,13 @@ public class StorageImporter {
 
     private static void importGlobalSettings(SharedPreferences.Editor editor,
             ImportedSettings settings) {
-        //TODO: input validation
 
-        for (Map.Entry setting : settings.settings.entrySet()) {
+        Map writeSettings = GlobalSettings.validate(settings.settings);
+
+        for (Map.Entry setting : writeSettings.entrySet()) {
             String key = setting.getKey();
             String value = setting.getValue();
-            //FIXME: drop this key during input validation. then remove this check
-            if ("accountUuids".equals(key)) {
-                continue;
-            }
+            Log.v(K9.LOG_TAG, "Write " + key + "=" + value);
             editor.putString(key, value);
         }
     }
@@ -216,8 +214,14 @@ public class StorageImporter {
     private static String importAccount(Context context, SharedPreferences.Editor editor,
             ImportedAccount account, boolean overwrite) {
 
-        //TODO: input validation
-        //TODO: remove latestOldMessageSeenTime?
+        // Validate input and ignore malformed values when possible
+        Map validatedSettings =
+            AccountSettings.validate(account.settings.settings);
+
+        //TODO: validate account name
+        //TODO: validate identity settings
+        //TODO: validate folder settings
+
 
         Preferences prefs = Preferences.getPreferences(context);
         Account[] accounts = prefs.getAccounts();
@@ -246,12 +250,7 @@ public class StorageImporter {
         editor.putString(accountKeyPrefix + Account.ACCOUNT_DESCRIPTION_KEY, accountName);
 
         // Write account settings
-        for (Map.Entry setting : account.settings.settings.entrySet()) {
-            //FIXME: drop this key during input validation. then remove this check
-            if ("accountNumber".equals(setting.getKey())) {
-                continue;
-            }
-
+        for (Map.Entry setting : validatedSettings.entrySet()) {
             String key = accountKeyPrefix + setting.getKey();
             String value = setting.getValue();
             editor.putString(key, value);
@@ -429,7 +428,7 @@ public class StorageImporter {
 
         int eventType = xpp.next();
         if (eventType != XmlPullParser.TEXT) {
-            return null;
+            return "";
         }
         return xpp.getText();
     }
diff --git a/src/com/fsck/k9/preferences/TimePickerPreference.java b/src/com/fsck/k9/preferences/TimePickerPreference.java
index b8824bac1..d706e76e7 100644
--- a/src/com/fsck/k9/preferences/TimePickerPreference.java
+++ b/src/com/fsck/k9/preferences/TimePickerPreference.java
@@ -19,7 +19,7 @@ public class TimePickerPreference extends DialogPreference implements
     /**
      * The validation expression for this preference
      */
-    private static final String VALIDATION_EXPRESSION = "[0-2]*[0-9]:[0-5]*[0-9]";
+    public static final String VALIDATION_EXPRESSION = "[0-2]*[0-9]:[0-5]*[0-9]";
 
     /**
      * The default value for this preference

From 7e24377bb8c76f3821fb026b57d267cd4000f515 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Fri, 29 Apr 2011 04:29:16 +0200
Subject: [PATCH 061/116] Return detailed information on imported accounts

---
 src/com/fsck/k9/activity/Accounts.java        | 14 ++-
 .../fsck/k9/preferences/StorageImporter.java  | 88 +++++++++++++++----
 2 files changed, 82 insertions(+), 20 deletions(-)

diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java
index 690ec748e..af9bf241d 100644
--- a/src/com/fsck/k9/activity/Accounts.java
+++ b/src/com/fsck/k9/activity/Accounts.java
@@ -73,6 +73,7 @@ import com.fsck.k9.preferences.StorageImportExportException;
 import com.fsck.k9.preferences.StorageImporter;
 import com.fsck.k9.preferences.StorageImporter.AccountDescription;
 import com.fsck.k9.preferences.StorageImporter.ImportContents;
+import com.fsck.k9.preferences.StorageImporter.ImportResults;
 
 
 public class Accounts extends K9ListActivity implements OnItemClickListener, OnClickListener {
@@ -1166,6 +1167,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
         private boolean mOverwrite;
         private String mEncryptionKey;
         private InputStream mInputStream;
+        private ImportResults mImportResults;
 
         private ImportAsyncTask(boolean includeGlobals, List accountUuids,
                 boolean overwrite, String encryptionKey, InputStream is) {
@@ -1187,8 +1189,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
         @Override
         protected Boolean doInBackground(Void... params) {
             try {
-                StorageImporter.importSettings(Accounts.this, mInputStream, mEncryptionKey,
-                        mIncludeGlobals, mAccountUuids, mOverwrite);
+                mImportResults = StorageImporter.importSettings(Accounts.this, mInputStream,
+                        mEncryptionKey, mIncludeGlobals, mAccountUuids, mOverwrite);
             } catch (StorageImportExportException e) {
                 Log.w(K9.LOG_TAG, "Exception during export", e);
                 return false;
@@ -1199,9 +1201,13 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
         @Override
         protected void onPostExecute(Boolean success) {
             if (success) {
+                int imported = mImportResults.importedAccounts.size();
+
+                //TODO: display names of imported accounts (name from file *and* possibly new name)
+
                 showDialog(Accounts.this, R.string.settings_import_success_header,
-                        //FIXME: use correct number of imported accounts
-                        Accounts.this.getString(R.string.settings_import_success, 3, "unknown"));
+                        //FIXME: use correct file name
+                        Accounts.this.getString(R.string.settings_import_success, imported, "filename"));
                 refresh();
             } else {
                 //TODO: make the importer return an error code; translate that error code to a localized string here
diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index 23c5e7193..3768a06bb 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -70,6 +70,30 @@ public class StorageImporter {
         }
     }
 
+    public static class AccountDescriptionPair {
+        public final AccountDescription original;
+        public final AccountDescription imported;
+
+        private AccountDescriptionPair(AccountDescription original, AccountDescription imported) {
+            this.original = original;
+            this.imported = imported;
+        }
+    }
+
+    public static class ImportResults {
+        public final boolean globalSettings;
+        public final List importedAccounts;
+        public final List errorneousAccounts;
+
+        private ImportResults(boolean globalSettings,
+                List importedAccounts,
+                List errorneousAccounts) {
+           this.globalSettings = globalSettings;
+           this.importedAccounts = importedAccounts;
+           this.errorneousAccounts = errorneousAccounts;
+        }
+    }
+
     public static boolean isImportStreamEncrypted(Context context, InputStream inputStream) {
         return false;
     }
@@ -127,23 +151,34 @@ public class StorageImporter {
      * @param overwrite
      * @throws StorageImportExportException
      */
-    public static void importSettings(Context context, InputStream inputStream, String encryptionKey,
+    public static ImportResults importSettings(Context context, InputStream inputStream, String encryptionKey,
             boolean globalSettings, List accountUuids, boolean overwrite)
     throws StorageImportExportException {
 
         try
         {
+            boolean globalSettingsImported = false;
+            List importedAccounts = new ArrayList();
+            List errorneousAccounts = new ArrayList();
+
             Imported imported = parseSettings(inputStream, globalSettings, accountUuids, overwrite, false);
 
             Preferences preferences = Preferences.getPreferences(context);
             SharedPreferences storage = preferences.getPreferences();
-            SharedPreferences.Editor editor = storage.edit();
 
             if (globalSettings) {
-                if (imported.globalSettings != null) {
-                    importGlobalSettings(editor, imported.globalSettings);
-                } else {
-                    Log.w(K9.LOG_TAG, "Was asked to import global settings but none found.");
+                try {
+                    SharedPreferences.Editor editor = storage.edit();
+                    if (imported.globalSettings != null) {
+                        importGlobalSettings(editor, imported.globalSettings);
+                    } else {
+                        Log.w(K9.LOG_TAG, "Was asked to import global settings but none found.");
+                    }
+                    if (editor.commit()) {
+                        globalSettingsImported = true;
+                    }
+                } catch (Exception e) {
+                    Log.e(K9.LOG_TAG, "Exception while importing global settings", e);
                 }
             }
 
@@ -152,9 +187,24 @@ public class StorageImporter {
                     List newUuids = new ArrayList();
                     for (String accountUuid : accountUuids) {
                         if (imported.accounts.containsKey(accountUuid)) {
-                            String newUuid = importAccount(context, editor, imported.accounts.get(accountUuid), overwrite);
-                            if (newUuid != null) {
-                                newUuids.add(newUuid);
+                            ImportedAccount account = imported.accounts.get(accountUuid);
+                            try {
+                                SharedPreferences.Editor editor = storage.edit();
+
+                                AccountDescriptionPair importResult = importAccount(context,
+                                        editor, account, overwrite);
+
+                                String newUuid = importResult.imported.uuid;
+                                if (!newUuid.equals(importResult.original.uuid)) {
+                                    newUuids.add(newUuid);
+                                }
+                                if (editor.commit()) {
+                                    importedAccounts.add(importResult);
+                                } else {
+                                    errorneousAccounts.add(importResult.original);
+                                }
+                            } catch (Exception e) {
+                                errorneousAccounts.add(new AccountDescription(account.name, account.uuid));
                             }
                         } else {
                             Log.w(K9.LOG_TAG, "Was asked to import account with UUID " +
@@ -162,6 +212,8 @@ public class StorageImporter {
                         }
                     }
 
+                    SharedPreferences.Editor editor = storage.edit();
+
                     if (newUuids.size() > 0) {
                         String oldAccountUuids = storage.getString("accountUuids", "");
                         String appendUuids = Utility.combine(newUuids.toArray(new String[0]), ',');
@@ -177,20 +229,21 @@ public class StorageImporter {
                         editor.putString("defaultAccountUuid", accountUuids.get(0));
                     }
 
+                    if (!editor.commit()) {
+                        throw new StorageImportExportException("Failed to set default account");
+                    }
                 } else {
                     Log.w(K9.LOG_TAG, "Was asked to import at least one account but none found.");
                 }
             }
 
-            if (!editor.commit()) {
-                throw new StorageImportExportException("Couldn't save imported settings");
-            }
-
             preferences.loadAccounts();
             DateFormatter.clearChosenFormat();
             K9.loadPrefs(preferences);
             K9.setServicesEnabled(context);
 
+            return new ImportResults(globalSettingsImported, importedAccounts, errorneousAccounts);
+
         } catch (StorageImportExportException e) {
             throw e;
         } catch (Exception e) {
@@ -211,8 +264,10 @@ public class StorageImporter {
         }
     }
 
-    private static String importAccount(Context context, SharedPreferences.Editor editor,
-            ImportedAccount account, boolean overwrite) {
+    private static AccountDescriptionPair importAccount(Context context,
+            SharedPreferences.Editor editor, ImportedAccount account, boolean overwrite) {
+
+        AccountDescription original = new AccountDescription(account.name, account.uuid);
 
         // Validate input and ignore malformed values when possible
         Map validatedSettings =
@@ -280,7 +335,8 @@ public class StorageImporter {
 
         //TODO: sync folder settings with localstore?
 
-        return (overwrite && existingAccount != null) ? null : uuid;
+        AccountDescription imported = new AccountDescription(accountName, uuid);
+        return new AccountDescriptionPair(original, imported);
     }
 
     private static void importIdentities(SharedPreferences.Editor editor, String uuid,

From 42987cee512c4d52fb72f2bdc05bf7f5ea89f186 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Mon, 2 May 2011 04:06:22 +0200
Subject: [PATCH 062/116] Use current values for missing or malformed global
 settings in the import file

---
 .../fsck/k9/preferences/AccountSettings.java   |  5 +++--
 .../fsck/k9/preferences/GlobalSettings.java    | 16 +++++++++++++++-
 src/com/fsck/k9/preferences/Settings.java      | 14 ++++++++------
 .../fsck/k9/preferences/StorageImporter.java   | 18 ++++++++++++------
 4 files changed, 38 insertions(+), 15 deletions(-)

diff --git a/src/com/fsck/k9/preferences/AccountSettings.java b/src/com/fsck/k9/preferences/AccountSettings.java
index 8e691eaf6..89ab53905 100644
--- a/src/com/fsck/k9/preferences/AccountSettings.java
+++ b/src/com/fsck/k9/preferences/AccountSettings.java
@@ -158,8 +158,9 @@ public class AccountSettings {
         return new SettingsDescription(type, defaultValue, validator);
     }
 
-    public static Map validate(Map importedSettings) {
-        return Settings.validate(SETTINGS, importedSettings);
+    public static Map validate(Map importedSettings,
+            boolean useDefaultValues) {
+        return Settings.validate(SETTINGS, importedSettings, useDefaultValues);
     }
 
     public static class StorageProviderDefaultValue implements IDefaultValue {
diff --git a/src/com/fsck/k9/preferences/GlobalSettings.java b/src/com/fsck/k9/preferences/GlobalSettings.java
index ba12440dc..8064c67ef 100644
--- a/src/com/fsck/k9/preferences/GlobalSettings.java
+++ b/src/com/fsck/k9/preferences/GlobalSettings.java
@@ -1,8 +1,10 @@
 package com.fsck.k9.preferences;
 
 import java.text.SimpleDateFormat;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import android.content.SharedPreferences;
 import com.fsck.k9.FontSizes;
 import com.fsck.k9.K9;
 import com.fsck.k9.R;
@@ -129,9 +131,21 @@ public class GlobalSettings {
     }
 
     public static Map validate(Map importedSettings) {
-        return Settings.validate(SETTINGS, importedSettings);
+        return Settings.validate(SETTINGS, importedSettings, false);
     }
 
+    public static Map getGlobalSettings(SharedPreferences storage) {
+        Map result = new HashMap();
+        for (String key : SETTINGS.keySet()) {
+            String value = storage.getString(key, null);
+            if (value != null) {
+                result.put(key, value);
+            }
+        }
+        return result;
+    }
+
+
     public static class GalleryBugWorkaroundDefaultValue implements IDefaultValue {
         @Override
         public Object computeDefaultValue(String key, Map validatedSettings) {
diff --git a/src/com/fsck/k9/preferences/Settings.java b/src/com/fsck/k9/preferences/Settings.java
index 29b0068b8..c1b763c5f 100644
--- a/src/com/fsck/k9/preferences/Settings.java
+++ b/src/com/fsck/k9/preferences/Settings.java
@@ -28,7 +28,7 @@ public class Settings {
     public static final ISettingValidator SOLID_COLOR_VALIDATOR = new SolidColorValidator();
 
     public static Map validate(Map settings,
-            Map importedSettings) {
+            Map importedSettings, boolean useDefaultValues) {
 
         Map validatedSettings = new HashMap();
         for (Map.Entry setting : settings.entrySet()) {
@@ -37,17 +37,19 @@ public class Settings {
 
             boolean useDefaultValue;
             if (!importedSettings.containsKey(key)) {
-                Log.v(K9.LOG_TAG, "Key \"" + key + "\" wasn't found in the imported file. Using default value.");
-                useDefaultValue = true;
+                Log.v(K9.LOG_TAG, "Key \"" + key + "\" wasn't found in the imported file." +
+                        ((useDefaultValues) ? " Using default value." : ""));
+                useDefaultValue = useDefaultValues;
             } else {
                 String importedValue = importedSettings.get(key);
                 if (Settings.isValid(desc, key, importedValue, validatedSettings)) {
                     validatedSettings.put(key, importedValue);
                     useDefaultValue = false;
                 } else {
-                    Log.v(K9.LOG_TAG, "Key \"" + key + "\" has invalid value \"" + importedValue + "\" in " +
-                            "imported file. Using default value.");
-                    useDefaultValue = true;
+                    Log.v(K9.LOG_TAG, "Key \"" + key + "\" has invalid value \"" + importedValue +
+                            "\" in imported file. " +
+                            ((useDefaultValues) ? "Using default value." : "Skipping."));
+                    useDefaultValue = useDefaultValues;
                 }
             }
 
diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index 3768a06bb..de97d2da3 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -170,7 +170,7 @@ public class StorageImporter {
                 try {
                     SharedPreferences.Editor editor = storage.edit();
                     if (imported.globalSettings != null) {
-                        importGlobalSettings(editor, imported.globalSettings);
+                        importGlobalSettings(storage, editor, imported.globalSettings);
                     } else {
                         Log.w(K9.LOG_TAG, "Was asked to import global settings but none found.");
                     }
@@ -251,12 +251,18 @@ public class StorageImporter {
         }
     }
 
-    private static void importGlobalSettings(SharedPreferences.Editor editor,
-            ImportedSettings settings) {
+    private static void importGlobalSettings(SharedPreferences storage,
+            SharedPreferences.Editor editor, ImportedSettings settings) {
 
-        Map writeSettings = GlobalSettings.validate(settings.settings);
+        Map validatedSettings = GlobalSettings.validate(settings.settings);
 
-        for (Map.Entry setting : writeSettings.entrySet()) {
+        // Use current global settings as base and overwrite with validated settings read from the
+        // import file.
+        Map mergedSettings =
+            new HashMap(GlobalSettings.getGlobalSettings(storage));
+        mergedSettings.putAll(validatedSettings);
+
+        for (Map.Entry setting : mergedSettings.entrySet()) {
             String key = setting.getKey();
             String value = setting.getValue();
             Log.v(K9.LOG_TAG, "Write " + key + "=" + value);
@@ -271,7 +277,7 @@ public class StorageImporter {
 
         // Validate input and ignore malformed values when possible
         Map validatedSettings =
-            AccountSettings.validate(account.settings.settings);
+            AccountSettings.validate(account.settings.settings, true);
 
         //TODO: validate account name
         //TODO: validate identity settings

From a57e605496ccd9f1f92edf9ca336ef3c871ac351 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Mon, 6 Jun 2011 19:44:01 +0200
Subject: [PATCH 063/116] Added first version of input validation for account
 settings

---
 .../fsck/k9/preferences/AccountSettings.java  | 15 ++++++++
 .../fsck/k9/preferences/StorageImporter.java  | 35 ++++++++++++-------
 2 files changed, 38 insertions(+), 12 deletions(-)

diff --git a/src/com/fsck/k9/preferences/AccountSettings.java b/src/com/fsck/k9/preferences/AccountSettings.java
index 89ab53905..a2497454a 100644
--- a/src/com/fsck/k9/preferences/AccountSettings.java
+++ b/src/com/fsck/k9/preferences/AccountSettings.java
@@ -1,7 +1,9 @@
 package com.fsck.k9.preferences;
 
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import android.content.SharedPreferences;
 import android.net.Uri;
 import android.util.Log;
 import com.fsck.k9.Account;
@@ -163,6 +165,19 @@ public class AccountSettings {
         return Settings.validate(SETTINGS, importedSettings, useDefaultValues);
     }
 
+    public static Map getAccountSettings(SharedPreferences storage, String uuid) {
+        Map result = new HashMap();
+        String prefix = uuid + ".";
+        for (String key : SETTINGS.keySet()) {
+            String value = storage.getString(prefix + key, null);
+            if (value != null) {
+                result.put(key, value);
+            }
+        }
+        return result;
+    }
+
+
     public static class StorageProviderDefaultValue implements IDefaultValue {
         @Override
         public Object computeDefaultValue(String key, Map validatedSettings) {
diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index de97d2da3..2d543dda6 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -70,7 +70,7 @@ public class StorageImporter {
         }
     }
 
-    public static class AccountDescriptionPair {
+    private static class AccountDescriptionPair {
         public final AccountDescription original;
         public final AccountDescription imported;
 
@@ -275,26 +275,37 @@ public class StorageImporter {
 
         AccountDescription original = new AccountDescription(account.name, account.uuid);
 
-        // Validate input and ignore malformed values when possible
-        Map validatedSettings =
-            AccountSettings.validate(account.settings.settings, true);
-
-        //TODO: validate account name
-        //TODO: validate identity settings
-        //TODO: validate folder settings
-
-
         Preferences prefs = Preferences.getPreferences(context);
         Account[] accounts = prefs.getAccounts();
 
         String uuid = account.uuid;
         Account existingAccount = prefs.getAccount(uuid);
+        boolean mergeImportedAccount = (overwrite && existingAccount != null);
+
         if (!overwrite && existingAccount != null) {
             // An account with this UUID already exists, but we're not allowed to overwrite it.
             // So generate a new UUID.
             uuid = UUID.randomUUID().toString();
         }
 
+        Map validatedSettings =
+            AccountSettings.validate(account.settings.settings, !mergeImportedAccount);
+
+        Map writeSettings;
+        if (mergeImportedAccount) {
+            writeSettings = new HashMap(
+                    AccountSettings.getAccountSettings(prefs.getPreferences(), uuid));
+            writeSettings.putAll(validatedSettings);
+        } else {
+            writeSettings = new HashMap(validatedSettings);
+        }
+
+
+        //TODO: validate account name
+        //TODO: validate identity settings
+        //TODO: validate folder settings
+
+
         String accountName = account.name;
         if (isAccountNameUsed(accountName, accounts)) {
             // Account name is already in use. So generate a new one by appending " (x)", where x
@@ -311,14 +322,14 @@ public class StorageImporter {
         editor.putString(accountKeyPrefix + Account.ACCOUNT_DESCRIPTION_KEY, accountName);
 
         // Write account settings
-        for (Map.Entry setting : validatedSettings.entrySet()) {
+        for (Map.Entry setting : writeSettings.entrySet()) {
             String key = accountKeyPrefix + setting.getKey();
             String value = setting.getValue();
             editor.putString(key, value);
         }
 
         // If it's a new account generate and write a new "accountNumber"
-        if (existingAccount == null || !uuid.equals(account.uuid)) {
+        if (!mergeImportedAccount) {
             int newAccountNumber = Account.generateAccountNumber(prefs);
             editor.putString(accountKeyPrefix + "accountNumber", Integer.toString(newAccountNumber));
         }

From 9a3ce9e03eea119a67af69c7313576bb9ba097fc Mon Sep 17 00:00:00 2001
From: cketti 
Date: Mon, 6 Jun 2011 19:54:54 +0200
Subject: [PATCH 064/116] Added file format version attribute to export file

---
 src/com/fsck/k9/preferences/StorageExporter.java | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index d23e0dbdd..41325ef30 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -32,6 +32,7 @@ public class StorageExporter {
 
     public static final String ROOT_ELEMENT = "k9settings";
     public static final String VERSION_ATTRIBUTE = "version";
+    public static final String FILE_FORMAT_ATTRIBUTE = "format";
     public static final String GLOBAL_ELEMENT = "global";
     public static final String SETTINGS_ELEMENT = "settings";
     public static final String ACCOUNTS_ELEMENT = "accounts";
@@ -109,7 +110,10 @@ public class StorageExporter {
             serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
 
             serializer.startTag(null, ROOT_ELEMENT);
+            //TODO: write content version number here
             serializer.attribute(null, VERSION_ATTRIBUTE, "x");
+            //TODO: set file format version to "1" once the feature is stable and about to be merged into master
+            serializer.attribute(null, FILE_FORMAT_ATTRIBUTE, "y");
 
             Log.i(K9.LOG_TAG, "Exporting preferences");
 

From 0e2afc38eff5cef036b4db7f7588015c78250c9b Mon Sep 17 00:00:00 2001
From: cketti 
Date: Mon, 6 Jun 2011 21:10:14 +0200
Subject: [PATCH 065/116] Cosmetic changes

- Grouped static fields and functions
- Renamed static fields to match code standard
---
 src/com/fsck/k9/mail/Store.java | 31 +++++++++++++++++++------------
 1 file changed, 19 insertions(+), 12 deletions(-)

diff --git a/src/com/fsck/k9/mail/Store.java b/src/com/fsck/k9/mail/Store.java
index 904a16baa..e112b0de7 100644
--- a/src/com/fsck/k9/mail/Store.java
+++ b/src/com/fsck/k9/mail/Store.java
@@ -29,17 +29,13 @@ public abstract class Store {
     /**
      * Remote stores indexed by Uri.
      */
-    private static HashMap mStores = new HashMap();
+    private static HashMap sStores = new HashMap();
+
     /**
      * Local stores indexed by UUid because the Uri may change due to migration to/from SD-card.
      */
-    private static HashMap mLocalStores = new HashMap();
+    private static HashMap sLocalStores = new HashMap();
 
-    protected final Account mAccount;
-
-    protected Store(Account account) {
-        mAccount = account;
-    }
 
     /**
      * Get an instance of a remote mail store.
@@ -51,7 +47,7 @@ public abstract class Store {
             throw new RuntimeException("Asked to get non-local Store object but given LocalStore URI");
         }
 
-        Store store = mStores.get(uri);
+        Store store = sStores.get(uri);
         if (store == null) {
             if (uri.startsWith("imap")) {
                 store = new ImapStore(account);
@@ -62,7 +58,7 @@ public abstract class Store {
             }
 
             if (store != null) {
-                mStores.put(uri, store);
+                sStores.put(uri, store);
             }
         }
 
@@ -78,15 +74,23 @@ public abstract class Store {
      * @throws UnavailableStorageException if not {@link StorageProvider#isReady(Context)}
      */
     public synchronized static LocalStore getLocalInstance(Account account, Application application) throws MessagingException {
-        Store store = mLocalStores.get(account.getUuid());
+        Store store = sLocalStores.get(account.getUuid());
         if (store == null) {
             store = new LocalStore(account, application);
-            mLocalStores.put(account.getUuid(), store);
+            sLocalStores.put(account.getUuid(), store);
         }
 
         return (LocalStore) store;
     }
 
+
+    protected final Account mAccount;
+
+
+    protected Store(Account account) {
+        mAccount = account;
+    }
+
     public abstract Folder getFolder(String name);
 
     public abstract List  getPersonalNamespaces(boolean forceListAll) throws MessagingException;
@@ -96,20 +100,23 @@ public abstract class Store {
     public boolean isCopyCapable() {
         return false;
     }
+
     public boolean isMoveCapable() {
         return false;
     }
+
     public boolean isPushCapable() {
         return false;
     }
+
     public boolean isSendCapable() {
         return false;
     }
+
     public boolean isExpungeCapable() {
         return false;
     }
 
-
     public void sendMessages(Message[] messages) throws MessagingException {
     }
 

From 29738993d99880632c1da2970306688c8be970b1 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Tue, 7 Jun 2011 00:08:43 +0200
Subject: [PATCH 066/116] Added function to decode store URIs into a container
 object

This will later be used by the export code to make exporting the
password optional (and the XML output "pretty").
It's also the first step to get away from store URIs towards something
more easily extensible, like Store.StoreSettings.
---
 src/com/fsck/k9/mail/ConnectionSecurity.java |  18 ++
 src/com/fsck/k9/mail/Store.java              | 112 +++++++++
 src/com/fsck/k9/mail/store/ImapStore.java    | 190 ++++++++++-----
 src/com/fsck/k9/mail/store/Pop3Store.java    | 134 +++++++----
 src/com/fsck/k9/mail/store/WebDavStore.java  | 235 +++++++++++++------
 5 files changed, 524 insertions(+), 165 deletions(-)
 create mode 100644 src/com/fsck/k9/mail/ConnectionSecurity.java

diff --git a/src/com/fsck/k9/mail/ConnectionSecurity.java b/src/com/fsck/k9/mail/ConnectionSecurity.java
new file mode 100644
index 000000000..bc6f2c8c2
--- /dev/null
+++ b/src/com/fsck/k9/mail/ConnectionSecurity.java
@@ -0,0 +1,18 @@
+package com.fsck.k9.mail;
+
+/**
+ * The currently available connection security types.
+ *
+ * 

+ * Right now this enum is only used by {@link Store.StoreSettings} and converted to store-specific + * constants in the different Store implementations. In the future we probably want to change this + * and use {@code ConnectionSecurity} exclusively. + *

+ */ +public enum ConnectionSecurity { + NONE, + STARTTLS_OPTIONAL, + STARTTLS_REQUIRED, + SSL_TLS_OPTIONAL, + SSL_TLS_REQUIRED +} diff --git a/src/com/fsck/k9/mail/Store.java b/src/com/fsck/k9/mail/Store.java index e112b0de7..11ed2d8ef 100644 --- a/src/com/fsck/k9/mail/Store.java +++ b/src/com/fsck/k9/mail/Store.java @@ -13,6 +13,7 @@ import com.fsck.k9.mail.store.StorageManager.StorageProvider; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Store is the access point for an email message store. It's location can be @@ -83,6 +84,117 @@ public abstract class Store { return (LocalStore) store; } + /** + * Decodes the contents of store-specific URIs and puts them into a {@link StoreSettings} + * object. + * + * @param uri + * the store-specific URI to decode + * + * @return A {@link StoreSettings} object holding the settings contained in the URI. + * + * @see ImapStore#decodeUri(String) + * @see Pop3Store#decodeUri(String) + * @see WebDavStore#decodeUri(String) + */ + public static StoreSettings decodeStoreUri(String uri) { + if (uri.startsWith("imap")) { + return ImapStore.decodeUri(uri); + } else if (uri.startsWith("pop3")) { + return Pop3Store.decodeUri(uri); + } else if (uri.startsWith("webdav")) { + return WebDavStore.decodeUri(uri); + } else { + throw new IllegalArgumentException("Not a valid store URI"); + } + } + + /** + * This is an abstraction to get rid of the store-specific URIs. + * + *

+ * Right now it's only used for settings import/export. But the goal is to get rid of + * store URIs altogether. + *

+ * + * @see Account#getStoreUri() + */ + public static class StoreSettings { + /** + * The host name of the incoming server. + */ + public final String host; + + /** + * The port number of the incoming server. + */ + public final int port; + + /** + * The type of connection security to be used when connecting to the incoming server. + * + * {@link ConnectionSecurity#NONE} if not applicable for the store. + */ + public final ConnectionSecurity connectionSecurity; + + /** + * The authentication method to use when connecting to the incoming server. + * + * {@code null} if not applicable for the store. + */ + public final String authenticationType; + + /** + * The username part of the credentials needed to authenticate to the incoming server. + * + * {@code null} if unused or not applicable for the store. + */ + public final String username; + + /** + * The password part of the credentials needed to authenticate to the incoming server. + * + * {@code null} if unused or not applicable for the store. + */ + public final String password; + + + /** + * Creates a new {@code StoreSettings} object. + * + * @param host + * see {@link StoreSettings#host} + * @param port + * see {@link StoreSettings#port} + * @param connectionSecurity + * see {@link StoreSettings#connectionSecurity} + * @param authenticationType + * see {@link StoreSettings#authenticationType} + * @param username + * see {@link StoreSettings#username} + * @param password + * see {@link StoreSettings#password} + */ + public StoreSettings(String host, int port, ConnectionSecurity connectionSecurity, + String authenticationType, String username, String password) { + this.host = host; + this.port = port; + this.connectionSecurity = connectionSecurity; + this.authenticationType = authenticationType; + this.username = username; + this.password = password; + } + + /** + * Returns store-specific settings as key/value pair. + * + *

Classes that inherit from this one are expected to override this method.

+ */ + public Map getExtra() { + return null; + } + } + protected final Account mAccount; diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index ec77a1dd2..052c2b427 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -67,6 +67,7 @@ import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock; import com.fsck.k9.mail.AuthenticationFailedException; import com.fsck.k9.mail.Body; import com.fsck.k9.mail.CertificateValidationException; +import com.fsck.k9.mail.ConnectionSecurity; import com.fsck.k9.mail.FetchProfile; import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Folder; @@ -132,6 +133,115 @@ public class ImapStore extends Store { private static final String[] EMPTY_STRING_ARRAY = new String[0]; + /** + * Decodes an ImapStore URI. + * + *

Possible forms:

+ *
+     * imap://auth:user:password@server:port CONNECTION_SECURITY_NONE
+     * imap+tls://auth:user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
+     * imap+tls+://auth:user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
+     * imap+ssl+://auth:user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
+     * imap+ssl://auth:user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
+     * 
+ */ + public static ImapStoreSettings decodeUri(String uri) { + String host; + int port; + ConnectionSecurity connectionSecurity; + String authenticationType = null; + String username = null; + String password = null; + String pathPrefix = null; + + URI imapUri; + try { + imapUri = new URI(uri); + } catch (URISyntaxException use) { + throw new IllegalArgumentException("Invalid ImapStore URI", use); + } + + String scheme = imapUri.getScheme(); + if (scheme.equals("imap")) { + connectionSecurity = ConnectionSecurity.NONE; + port = 143; + } else if (scheme.equals("imap+tls")) { + connectionSecurity = ConnectionSecurity.STARTTLS_OPTIONAL; + port = 143; + } else if (scheme.equals("imap+tls+")) { + connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED; + port = 143; + } else if (scheme.equals("imap+ssl+")) { + connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED; + port = 993; + } else if (scheme.equals("imap+ssl")) { + connectionSecurity = ConnectionSecurity.SSL_TLS_OPTIONAL; + port = 993; + } else { + throw new IllegalArgumentException("Unsupported protocol (" + scheme + ")"); + } + + host = imapUri.getHost(); + + if (imapUri.getPort() != -1) { + port = imapUri.getPort(); + } + + if (imapUri.getUserInfo() != null) { + try { + String[] userInfoParts = imapUri.getUserInfo().split(":"); + if (userInfoParts.length == 2) { + authenticationType = AuthType.PLAIN.name(); + username = URLDecoder.decode(userInfoParts[0], "UTF-8"); + password = URLDecoder.decode(userInfoParts[1], "UTF-8"); + } else { + authenticationType = AuthType.valueOf(userInfoParts[0]).name(); + username = URLDecoder.decode(userInfoParts[1], "UTF-8"); + password = URLDecoder.decode(userInfoParts[2], "UTF-8"); + } + } catch (UnsupportedEncodingException enc) { + // This shouldn't happen since the encoding is hardcoded to UTF-8 + throw new IllegalArgumentException("Couldn't urldecode username or password.", enc); + } + } + + String path = imapUri.getPath(); + if (path != null && path.length() > 0) { + pathPrefix = path.substring(1); + if (pathPrefix != null && pathPrefix.trim().length() == 0) { + pathPrefix = null; + } + } + + return new ImapStoreSettings(host, port, connectionSecurity, authenticationType, username, + password, pathPrefix); + } + + /** + * This class is used to store the decoded contents of an ImapStore URI. + * + * @see ImapStore#decodeUri(String) + */ + private static class ImapStoreSettings extends StoreSettings { + private static final String PATH_PREFIX_KEY = "path_prefix"; + + public final String pathPrefix; + + protected ImapStoreSettings(String host, int port, ConnectionSecurity connectionSecurity, + String authenticationType, String username, String password, String pathPrefix) { + super(host, port, connectionSecurity, authenticationType, username, password); + this.pathPrefix = pathPrefix; + } + + @Override + public Map getExtra() { + Map extra = new HashMap(); + extra.put(PATH_PREFIX_KEY, pathPrefix); + return extra; + } + } + + private String mHost; private int mPort; private String mUsername; @@ -229,74 +339,42 @@ public class ImapStore extends Store { */ private HashMap mFolderCache = new HashMap(); - /** - * imap://auth:user:password@server:port CONNECTION_SECURITY_NONE - * imap+tls://auth:user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL - * imap+tls+://auth:user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED - * imap+ssl+://auth:user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED - * imap+ssl://auth:user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL - * - * @param _uri - */ public ImapStore(Account account) throws MessagingException { super(account); - URI uri; + + ImapStoreSettings settings; try { - uri = new URI(mAccount.getStoreUri()); - } catch (URISyntaxException use) { - throw new MessagingException("Invalid ImapStore URI", use); + settings = decodeUri(mAccount.getStoreUri()); + } catch (IllegalArgumentException e) { + throw new MessagingException("Error while decoding store URI", e); } - String scheme = uri.getScheme(); - if (scheme.equals("imap")) { + mHost = settings.host; + mPort = settings.port; + + switch (settings.connectionSecurity) { + case NONE: mConnectionSecurity = CONNECTION_SECURITY_NONE; - mPort = 143; - } else if (scheme.equals("imap+tls")) { + break; + case STARTTLS_OPTIONAL: mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL; - mPort = 143; - } else if (scheme.equals("imap+tls+")) { + break; + case STARTTLS_REQUIRED: mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED; - mPort = 143; - } else if (scheme.equals("imap+ssl+")) { - mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED; - mPort = 993; - } else if (scheme.equals("imap+ssl")) { + break; + case SSL_TLS_OPTIONAL: mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL; - mPort = 993; - } else { - throw new MessagingException("Unsupported protocol"); + break; + case SSL_TLS_REQUIRED: + mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED; + break; } - mHost = uri.getHost(); + mAuthType = AuthType.valueOf(settings.authenticationType); + mUsername = settings.username; + mPassword = settings.password; - if (uri.getPort() != -1) { - mPort = uri.getPort(); - } - - if (uri.getUserInfo() != null) { - try { - String[] userInfoParts = uri.getUserInfo().split(":"); - if (userInfoParts.length == 2) { - mAuthType = AuthType.PLAIN; - mUsername = URLDecoder.decode(userInfoParts[0], "UTF-8"); - mPassword = URLDecoder.decode(userInfoParts[1], "UTF-8"); - } else { - mAuthType = AuthType.valueOf(userInfoParts[0]); - mUsername = URLDecoder.decode(userInfoParts[1], "UTF-8"); - mPassword = URLDecoder.decode(userInfoParts[2], "UTF-8"); - } - } catch (UnsupportedEncodingException enc) { - // This shouldn't happen since the encoding is hardcoded to UTF-8 - Log.e(K9.LOG_TAG, "Couldn't urldecode username or password.", enc); - } - } - - if ((uri.getPath() != null) && (uri.getPath().length() > 0)) { - mPathPrefix = uri.getPath().substring(1); - if (mPathPrefix != null && mPathPrefix.trim().length() == 0) { - mPathPrefix = null; - } - } + mPathPrefix = settings.pathPrefix; mModifiedUtf7Charset = new CharsetProvider().charsetForName("X-RFC-3501"); } diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java index 64f5709f2..f6b7c86cc 100644 --- a/src/com/fsck/k9/mail/store/Pop3Store.java +++ b/src/com/fsck/k9/mail/store/Pop3Store.java @@ -34,6 +34,75 @@ public class Pop3Store extends Store { private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED }; + /** + * Decodes a Pop3Store URI. + * + *

Possible forms:

+ *
+     * pop3://user:password@server:port CONNECTION_SECURITY_NONE
+     * pop3+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
+     * pop3+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
+     * pop3+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
+     * pop3+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
+     * 
+ */ + public static StoreSettings decodeUri(String uri) { + String host; + int port; + ConnectionSecurity connectionSecurity; + String username = null; + String password = null; + + URI pop3Uri; + try { + pop3Uri = new URI(uri); + } catch (URISyntaxException use) { + throw new IllegalArgumentException("Invalid Pop3Store URI", use); + } + + String scheme = pop3Uri.getScheme(); + if (scheme.equals("pop3")) { + connectionSecurity = ConnectionSecurity.NONE; + port = 110; + } else if (scheme.equals("pop3+tls")) { + connectionSecurity = ConnectionSecurity.STARTTLS_OPTIONAL; + port = 110; + } else if (scheme.equals("pop3+tls+")) { + connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED; + port = 110; + } else if (scheme.equals("pop3+ssl+")) { + connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED; + port = 995; + } else if (scheme.equals("pop3+ssl")) { + connectionSecurity = ConnectionSecurity.SSL_TLS_OPTIONAL; + port = 995; + } else { + throw new IllegalArgumentException("Unsupported protocol (" + scheme + ")"); + } + + host = pop3Uri.getHost(); + + if (pop3Uri.getPort() != -1) { + port = pop3Uri.getPort(); + } + + if (pop3Uri.getUserInfo() != null) { + try { + String[] userInfoParts = pop3Uri.getUserInfo().split(":"); + username = URLDecoder.decode(userInfoParts[0], "UTF-8"); + if (userInfoParts.length > 1) { + password = URLDecoder.decode(userInfoParts[1], "UTF-8"); + } + } catch (UnsupportedEncodingException enc) { + // This shouldn't happen since the encoding is hardcoded to UTF-8 + throw new IllegalArgumentException("Couldn't urldecode username or password.", enc); + } + } + + return new StoreSettings(host, port, connectionSecurity, null, username, password); + } + + private String mHost; private int mPort; private String mUsername; @@ -42,61 +111,40 @@ public class Pop3Store extends Store { private HashMap mFolders = new HashMap(); private Pop3Capabilities mCapabilities; - /** - * pop3://user:password@server:port CONNECTION_SECURITY_NONE - * pop3+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL - * pop3+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED - * pop3+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED - * pop3+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL - */ + public Pop3Store(Account account) throws MessagingException { super(account); - URI uri; + StoreSettings settings; try { - uri = new URI(mAccount.getStoreUri()); - } catch (URISyntaxException use) { - throw new MessagingException("Invalid Pop3Store URI", use); + settings = decodeUri(mAccount.getStoreUri()); + } catch (IllegalArgumentException e) { + throw new MessagingException("Error while decoding store URI", e); } - String scheme = uri.getScheme(); - if (scheme.equals("pop3")) { + mHost = settings.host; + mPort = settings.port; + + switch (settings.connectionSecurity) { + case NONE: mConnectionSecurity = CONNECTION_SECURITY_NONE; - mPort = 110; - } else if (scheme.equals("pop3+tls")) { + break; + case STARTTLS_OPTIONAL: mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL; - mPort = 110; - } else if (scheme.equals("pop3+tls+")) { + break; + case STARTTLS_REQUIRED: mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED; - mPort = 110; - } else if (scheme.equals("pop3+ssl+")) { - mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED; - mPort = 995; - } else if (scheme.equals("pop3+ssl")) { + break; + case SSL_TLS_OPTIONAL: mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL; - mPort = 995; - } else { - throw new MessagingException("Unsupported protocol"); + break; + case SSL_TLS_REQUIRED: + mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED; + break; } - mHost = uri.getHost(); - - if (uri.getPort() != -1) { - mPort = uri.getPort(); - } - - if (uri.getUserInfo() != null) { - try { - String[] userInfoParts = uri.getUserInfo().split(":"); - mUsername = URLDecoder.decode(userInfoParts[0], "UTF-8"); - if (userInfoParts.length > 1) { - mPassword = URLDecoder.decode(userInfoParts[1], "UTF-8"); - } - } catch (UnsupportedEncodingException enc) { - // This shouldn't happen since the encoding is hardcoded to UTF-8 - Log.e(K9.LOG_TAG, "Couldn't urldecode username or password.", enc); - } - } + mUsername = settings.username; + mPassword = settings.password; } @Override diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java index efedebf7b..a9026d29b 100644 --- a/src/com/fsck/k9/mail/store/WebDavStore.java +++ b/src/com/fsck/k9/mail/store/WebDavStore.java @@ -51,6 +51,7 @@ import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Stack; import java.util.zip.GZIPInputStream; @@ -82,6 +83,148 @@ public class WebDavStore extends Store { private static final String DAV_MAIL_SEND_FOLDER = "##DavMailSubmissionURI##"; private static final String DAV_MAIL_TMP_FOLDER = "drafts"; + + /** + * Decodes a WebDavStore URI. + * + *

Possible forms:

+ *
+     * webdav://user:password@server:port CONNECTION_SECURITY_NONE
+     * webdav+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
+     * webdav+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
+     * webdav+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
+     * webdav+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
+     * 
+ */ + public static WebDavStoreSettings decodeUri(String uri) { + String host; + int port; + ConnectionSecurity connectionSecurity; + String username = null; + String password = null; + String alias = null; + String path = null; + String authPath = null; + String mailboxPath = null; + + + URI webDavUri; + try { + webDavUri = new URI(uri); + } catch (URISyntaxException use) { + throw new IllegalArgumentException("Invalid WebDavStore URI", use); + } + + String scheme = webDavUri.getScheme(); + if (scheme.equals("webdav")) { + connectionSecurity = ConnectionSecurity.NONE; + } else if (scheme.equals("webdav+ssl")) { + connectionSecurity = ConnectionSecurity.SSL_TLS_OPTIONAL; + } else if (scheme.equals("webdav+ssl+")) { + connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED; + } else if (scheme.equals("webdav+tls")) { + connectionSecurity = ConnectionSecurity.STARTTLS_OPTIONAL; + } else if (scheme.equals("webdav+tls+")) { + connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED; + } else { + throw new IllegalArgumentException("Unsupported protocol (" + scheme + ")"); + } + + host = webDavUri.getHost(); + if (host.startsWith("http")) { + String[] hostParts = host.split("://", 2); + if (hostParts.length > 1) { + host = hostParts[1]; + } + } + + port = webDavUri.getPort(); + + String userInfo = webDavUri.getUserInfo(); + if (userInfo != null) { + try { + String[] userInfoParts = userInfo.split(":"); + username = URLDecoder.decode(userInfoParts[0], "UTF-8"); + String userParts[] = username.split("\\\\", 2); + + if (userParts.length > 1) { + alias = userParts[1]; + } else { + alias = username; + } + if (userInfoParts.length > 1) { + password = URLDecoder.decode(userInfoParts[1], "UTF-8"); + } + } catch (UnsupportedEncodingException enc) { + // This shouldn't happen since the encoding is hardcoded to UTF-8 + throw new IllegalArgumentException("Couldn't urldecode username or password.", enc); + } + } + + String[] pathParts = webDavUri.getPath().split("\\|"); + for (int i = 0, count = pathParts.length; i < count; i++) { + if (i == 0) { + if (pathParts[0] != null && + pathParts[0].length() > 1) { + path = pathParts[0]; + } + } else if (i == 1) { + if (pathParts[1] != null && + pathParts[1].length() > 1) { + authPath = pathParts[1]; + } + } else if (i == 2) { + if (pathParts[2] != null && + pathParts[2].length() > 1) { + mailboxPath = pathParts[2]; + } + } + } + + return new WebDavStoreSettings(host, port, connectionSecurity, null, + username, password, alias, path, authPath, mailboxPath, webDavUri); + } + + /** + * This class is used to store the decoded contents of an WebDavStore URI. + * + * @see WebDavStore#decodeUri(String) + */ + private static class WebDavStoreSettings extends StoreSettings { + private static final String ALIAS_KEY = "alias"; + private static final String PATH_KEY = "path"; + private static final String AUTH_PATH_KEY = "authPath"; + private static final String MAILBOX_PATH_KEY = "mailboxPath"; + + public final String alias; + public final String path; + public final String authPath; + public final String mailboxPath; + public final URI uri; + + protected WebDavStoreSettings(String host, int port, ConnectionSecurity connectionSecurity, + String authenticationType, String username, String password, String alias, + String path, String authPath, String mailboxPath, URI uri) { + super(host, port, connectionSecurity, authenticationType, username, password); + this.alias = alias; + this.path = path; + this.authPath = authPath; + this.mailboxPath = mailboxPath; + this.uri = uri; + } + + @Override + public Map getExtra() { + Map extra = new HashMap(); + extra.put(ALIAS_KEY, alias); + extra.put(PATH_KEY, path); + extra.put(AUTH_PATH_KEY, authPath); + extra.put(MAILBOX_PATH_KEY, mailboxPath); + return extra; + } + } + + private short mConnectionSecurity; private String mUsername; /* Stores the username for authentications */ private String mAlias; /* Stores the alias for the user's mailbox */ @@ -103,85 +246,45 @@ public class WebDavStore extends Store { private HashMap mFolderList = new HashMap(); - /** - * webdav://user:password@server:port CONNECTION_SECURITY_NONE - * webdav+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL - * webdav+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED - * webdav+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED - * webdav+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL - */ public WebDavStore(Account account) throws MessagingException { super(account); + WebDavStoreSettings settings; try { - mUri = new URI(mAccount.getStoreUri()); - } catch (URISyntaxException use) { - throw new MessagingException("Invalid WebDavStore URI", use); + settings = decodeUri(mAccount.getStoreUri()); + } catch (IllegalArgumentException e) { + throw new MessagingException("Error while decoding store URI", e); } - String scheme = mUri.getScheme(); - if (scheme.equals("webdav")) { + mHost = settings.host; + mUri = settings.uri; + + switch (settings.connectionSecurity) { + case NONE: mConnectionSecurity = CONNECTION_SECURITY_NONE; - } else if (scheme.equals("webdav+ssl")) { - mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL; - } else if (scheme.equals("webdav+ssl+")) { - mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED; - } else if (scheme.equals("webdav+tls")) { + break; + case STARTTLS_OPTIONAL: mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL; - } else if (scheme.equals("webdav+tls+")) { + break; + case STARTTLS_REQUIRED: mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED; - } else { - throw new MessagingException("Unsupported protocol"); + break; + case SSL_TLS_OPTIONAL: + mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL; + break; + case SSL_TLS_REQUIRED: + mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED; + break; } - mHost = mUri.getHost(); - if (mHost.startsWith("http")) { - String[] hostParts = mHost.split("://", 2); - if (hostParts.length > 1) { - mHost = hostParts[1]; - } - } + mUsername = settings.username; + mPassword = settings.password; + mAlias = settings.alias; - if (mUri.getUserInfo() != null) { - try { - String[] userInfoParts = mUri.getUserInfo().split(":"); - mUsername = URLDecoder.decode(userInfoParts[0], "UTF-8"); - String userParts[] = mUsername.split("\\\\", 2); + mPath = settings.path; + mAuthPath = settings.authPath; + mMailboxPath = settings.mailboxPath; - if (userParts.length > 1) { - mAlias = userParts[1]; - } else { - mAlias = mUsername; - } - if (userInfoParts.length > 1) { - mPassword = URLDecoder.decode(userInfoParts[1], "UTF-8"); - } - } catch (UnsupportedEncodingException enc) { - // This shouldn't happen since the encoding is hardcoded to UTF-8 - Log.e(K9.LOG_TAG, "Couldn't urldecode username or password.", enc); - } - } - - String[] pathParts = mUri.getPath().split("\\|"); - - for (int i = 0, count = pathParts.length; i < count; i++) { - if (i == 0) { - if (pathParts[0] != null && - pathParts[0].length() > 1) { - mPath = pathParts[0]; - } - } else if (i == 1) { - if (pathParts[1] != null && - pathParts[1].length() > 1) { - mAuthPath = pathParts[1]; - } - } else if (i == 2) { - if (pathParts[2] != null && - pathParts[2].length() > 1) { - mMailboxPath = pathParts[2]; - } - } - } if (mPath == null || mPath.equals("")) { mPath = "/Exchange"; From 25c1a565e78e97f04077f0ea6a41d5222b663bd3 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 7 Jun 2011 03:01:02 +0200 Subject: [PATCH 067/116] WebDavStore cleanup Store port number so we can throw away the store URI once decoded --- src/com/fsck/k9/mail/store/WebDavStore.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java index a9026d29b..69a460cba 100644 --- a/src/com/fsck/k9/mail/store/WebDavStore.java +++ b/src/com/fsck/k9/mail/store/WebDavStore.java @@ -181,8 +181,8 @@ public class WebDavStore extends Store { } } - return new WebDavStoreSettings(host, port, connectionSecurity, null, - username, password, alias, path, authPath, mailboxPath, webDavUri); + return new WebDavStoreSettings(host, port, connectionSecurity, null, username, password, + alias, path, authPath, mailboxPath); } /** @@ -200,17 +200,15 @@ public class WebDavStore extends Store { public final String path; public final String authPath; public final String mailboxPath; - public final URI uri; protected WebDavStoreSettings(String host, int port, ConnectionSecurity connectionSecurity, String authenticationType, String username, String password, String alias, - String path, String authPath, String mailboxPath, URI uri) { + String path, String authPath, String mailboxPath) { super(host, port, connectionSecurity, authenticationType, username, password); this.alias = alias; this.path = path; this.authPath = authPath; this.mailboxPath = mailboxPath; - this.uri = uri; } @Override @@ -231,10 +229,10 @@ public class WebDavStore extends Store { private String mPassword; /* Stores the password for authentications */ private String mUrl; /* Stores the base URL for the server */ private String mHost; /* Stores the host name for the server */ + private int mPort; private String mPath; /* Stores the path for the server */ private String mAuthPath; /* Stores the path off of the server to post data to for form based authentication */ private String mMailboxPath; /* Stores the user specified path to the mailbox */ - private URI mUri; /* Stores the Uniform Resource Indicator with all connection info */ private boolean mSecure; private WebDavHttpClient mHttpClient = null; @@ -257,7 +255,7 @@ public class WebDavStore extends Store { } mHost = settings.host; - mUri = settings.uri; + mPort = settings.port; switch (settings.connectionSecurity) { case NONE: @@ -322,7 +320,7 @@ public class WebDavStore extends Store { } else { root = "http"; } - root += "://" + mHost + ":" + mUri.getPort(); + root += "://" + mHost + ":" + mPort; return root; } From f6de6f8e424b919a2439a783fe78245721b14be7 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 7 Jun 2011 04:07:50 +0200 Subject: [PATCH 068/116] Save name of the store type in StoreSettings --- src/com/fsck/k9/mail/Store.java | 19 +++++++++++++++++-- src/com/fsck/k9/mail/store/ImapStore.java | 8 +++++--- src/com/fsck/k9/mail/store/Pop3Store.java | 5 ++++- src/com/fsck/k9/mail/store/WebDavStore.java | 12 +++++++----- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/com/fsck/k9/mail/Store.java b/src/com/fsck/k9/mail/Store.java index 11ed2d8ef..33fd4e5dd 100644 --- a/src/com/fsck/k9/mail/Store.java +++ b/src/com/fsck/k9/mail/Store.java @@ -120,6 +120,11 @@ public abstract class Store { * @see Account#getStoreUri() */ public static class StoreSettings { + /** + * Name of the store type (e.g. "IMAP"). + */ + public final String type; + /** * The host name of the incoming server. */ @@ -162,6 +167,8 @@ public abstract class Store { /** * Creates a new {@code StoreSettings} object. * + * @param type + * see {@link StoreSettings#type} * @param host * see {@link StoreSettings#host} * @param port @@ -175,8 +182,10 @@ public abstract class Store { * @param password * see {@link StoreSettings#password} */ - public StoreSettings(String host, int port, ConnectionSecurity connectionSecurity, - String authenticationType, String username, String password) { + public StoreSettings(String type, String host, int port, + ConnectionSecurity connectionSecurity, String authenticationType, String username, + String password) { + this.type = type; this.host = host; this.port = port; this.connectionSecurity = connectionSecurity; @@ -193,6 +202,12 @@ public abstract class Store { public Map getExtra() { return null; } + + protected void putIfNotNull(Map map, String key, String value) { + if (value != null) { + map.put(key, value); + } + } } diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 052c2b427..8db0d1fb1 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -223,20 +223,22 @@ public class ImapStore extends Store { * @see ImapStore#decodeUri(String) */ private static class ImapStoreSettings extends StoreSettings { - private static final String PATH_PREFIX_KEY = "path_prefix"; + private static final String STORE_TYPE = "IMAP"; + private static final String PATH_PREFIX_KEY = "pathPrefix"; public final String pathPrefix; protected ImapStoreSettings(String host, int port, ConnectionSecurity connectionSecurity, String authenticationType, String username, String password, String pathPrefix) { - super(host, port, connectionSecurity, authenticationType, username, password); + super(STORE_TYPE, host, port, connectionSecurity, authenticationType, username, + password); this.pathPrefix = pathPrefix; } @Override public Map getExtra() { Map extra = new HashMap(); - extra.put(PATH_PREFIX_KEY, pathPrefix); + putIfNotNull(extra, PATH_PREFIX_KEY, pathPrefix); return extra; } } diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java index f6b7c86cc..c6a275c9f 100644 --- a/src/com/fsck/k9/mail/store/Pop3Store.java +++ b/src/com/fsck/k9/mail/store/Pop3Store.java @@ -26,6 +26,8 @@ import java.util.HashSet; import java.util.List; public class Pop3Store extends Store { + private static final String STORE_TYPE = "POP3"; + public static final int CONNECTION_SECURITY_NONE = 0; public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1; public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2; @@ -99,7 +101,8 @@ public class Pop3Store extends Store { } } - return new StoreSettings(host, port, connectionSecurity, null, username, password); + return new StoreSettings(STORE_TYPE, host, port, connectionSecurity, null, username, + password); } diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java index 69a460cba..94d446eff 100644 --- a/src/com/fsck/k9/mail/store/WebDavStore.java +++ b/src/com/fsck/k9/mail/store/WebDavStore.java @@ -191,6 +191,7 @@ public class WebDavStore extends Store { * @see WebDavStore#decodeUri(String) */ private static class WebDavStoreSettings extends StoreSettings { + private static final String STORE_TYPE = "WebDAV"; private static final String ALIAS_KEY = "alias"; private static final String PATH_KEY = "path"; private static final String AUTH_PATH_KEY = "authPath"; @@ -204,7 +205,8 @@ public class WebDavStore extends Store { protected WebDavStoreSettings(String host, int port, ConnectionSecurity connectionSecurity, String authenticationType, String username, String password, String alias, String path, String authPath, String mailboxPath) { - super(host, port, connectionSecurity, authenticationType, username, password); + super(STORE_TYPE, host, port, connectionSecurity, authenticationType, username, + password); this.alias = alias; this.path = path; this.authPath = authPath; @@ -214,10 +216,10 @@ public class WebDavStore extends Store { @Override public Map getExtra() { Map extra = new HashMap(); - extra.put(ALIAS_KEY, alias); - extra.put(PATH_KEY, path); - extra.put(AUTH_PATH_KEY, authPath); - extra.put(MAILBOX_PATH_KEY, mailboxPath); + putIfNotNull(extra, ALIAS_KEY, alias); + putIfNotNull(extra, PATH_KEY, path); + putIfNotNull(extra, AUTH_PATH_KEY, authPath); + putIfNotNull(extra, MAILBOX_PATH_KEY, mailboxPath); return extra; } } From 6abb5b5850e332523e7dc5f6d4b3465ac9a8cb9b Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 7 Jun 2011 04:25:17 +0200 Subject: [PATCH 069/116] Write incoming-server element instead of storeUri key to export file --- .../fsck/k9/preferences/StorageExporter.java | 92 +++++++++++++++---- 1 file changed, 72 insertions(+), 20 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 41325ef30..0debd0287 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.Map.Entry; import javax.crypto.CipherOutputStream; import org.xmlpull.v1.XmlSerializer; @@ -24,6 +25,8 @@ import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.Preferences; import com.fsck.k9.helper.Utility; +import com.fsck.k9.mail.Store; +import com.fsck.k9.mail.Store.StoreSettings; import com.fsck.k9.mail.store.LocalStore; @@ -38,6 +41,15 @@ public class StorageExporter { public static final String ACCOUNTS_ELEMENT = "accounts"; public static final String ACCOUNT_ELEMENT = "account"; public static final String UUID_ATTRIBUTE = "uuid"; + public static final String INCOMING_SERVER_ELEMENT = "incoming-server"; + public static final String TYPE_ATTRIBUTE = "type"; + public static final String HOST_ELEMENT = "host"; + public static final String PORT_ELEMENT = "port"; + public static final String CONNECTION_SECURITY_ELEMENT = "connection-security"; + public static final String AUTHENTICATION_TYPE_ELEMENT = "authentication-type"; + public static final String USERNAME_ELEMENT = "username"; + public static final String PASSWORD_ELEMENT = "password"; + public static final String EXTRA_ELEMENT = "extra"; public static final String IDENTITIES_ELEMENT = "identities"; public static final String IDENTITY_ELEMENT = "identity"; public static final String FOLDERS_ELEMENT = "folders"; @@ -74,7 +86,7 @@ public class StorageExporter { // If all went well, we return the name of the file just written. return fileName; } catch (Exception e) { - throw new StorageImportExportException(); + throw new StorageImportExportException(e); } finally { if (os != null) { try { @@ -138,7 +150,8 @@ public class StorageExporter { serializer.startTag(null, ACCOUNTS_ELEMENT); for (String accountUuid : accountUuids) { - writeAccount(serializer, accountUuid, prefs); + Account account = preferences.getAccount(accountUuid); + writeAccount(serializer, account, prefs); } serializer.endTag(null, ACCOUNTS_ELEMENT); @@ -161,18 +174,16 @@ public class StorageExporter { // Skip account entries continue; } - serializer.startTag(null, VALUE_ELEMENT); - serializer.attribute(null, KEY_ATTRIBUTE, key); - serializer.text(value); - serializer.endTag(null, VALUE_ELEMENT); + writeKeyValue(serializer, key, value); } } - private static void writeAccount(XmlSerializer serializer, String accountUuid, + private static void writeAccount(XmlSerializer serializer, Account account, Map prefs) throws IOException { Set identities = new HashSet(); Set folders = new HashSet(); + String accountUuid = account.getUuid(); serializer.startTag(null, ACCOUNT_ELEMENT); serializer.attribute(null, UUID_ATTRIBUTE, accountUuid); @@ -184,6 +195,36 @@ public class StorageExporter { serializer.endTag(null, NAME_ELEMENT); } + + // Write incoming server settings + StoreSettings incoming = Store.decodeStoreUri(account.getStoreUri()); + serializer.startTag(null, INCOMING_SERVER_ELEMENT); + serializer.attribute(null, TYPE_ATTRIBUTE, incoming.type); + + writeElement(serializer, HOST_ELEMENT, incoming.host); + writeElement(serializer, PORT_ELEMENT, Integer.toString(incoming.port)); + writeElement(serializer, CONNECTION_SECURITY_ELEMENT, incoming.connectionSecurity.name()); + writeElement(serializer, AUTHENTICATION_TYPE_ELEMENT, incoming.authenticationType); + writeElement(serializer, USERNAME_ELEMENT, incoming.username); + //TODO: make saving the password optional + writeElement(serializer, PASSWORD_ELEMENT, incoming.password); + + Map extras = incoming.getExtra(); + if (extras != null && extras.size() > 0) { + serializer.startTag(null, EXTRA_ELEMENT); + for (Entry extra : extras.entrySet()) { + writeKeyValue(serializer, extra.getKey(), extra.getValue()); + } + serializer.endTag(null, EXTRA_ELEMENT); + } + + serializer.endTag(null, INCOMING_SERVER_ELEMENT); + + + //TODO: write outgoing server settings + + + // Write account settings serializer.startTag(null, SETTINGS_ELEMENT); for (Map.Entry entry : prefs.entrySet()) { String key = entry.getKey(); @@ -194,7 +235,8 @@ public class StorageExporter { String secondPart = comps[1]; if (!keyUuid.equals(accountUuid) - || Account.ACCOUNT_DESCRIPTION_KEY.equals(secondPart)) { + || Account.ACCOUNT_DESCRIPTION_KEY.equals(secondPart) + || "storeUri".equals(secondPart)) { continue; } if (comps.length == 3) { @@ -224,10 +266,7 @@ public class StorageExporter { // Strip account UUID from key String keyPart = key.substring(comps[0].length() + 1); - serializer.startTag(null, VALUE_ELEMENT); - serializer.attribute(null, KEY_ATTRIBUTE, keyPart); - serializer.text(value); - serializer.endTag(null, VALUE_ELEMENT); + writeKeyValue(serializer, keyPart, value); } serializer.endTag(null, SETTINGS_ELEMENT); @@ -301,10 +340,7 @@ public class StorageExporter { continue; } - serializer.startTag(null, VALUE_ELEMENT); - serializer.attribute(null, KEY_ATTRIBUTE, comps[1]); - serializer.text(value); - serializer.endTag(null, VALUE_ELEMENT); + writeKeyValue(serializer, comps[1], value); } serializer.endTag(null, SETTINGS_ELEMENT); @@ -333,11 +369,27 @@ public class StorageExporter { continue; } - serializer.startTag(null, VALUE_ELEMENT); - serializer.attribute(null, KEY_ATTRIBUTE, comps[2]); - serializer.text(value); - serializer.endTag(null, VALUE_ELEMENT); + writeKeyValue(serializer, comps[2], value); } serializer.endTag(null, FOLDER_ELEMENT); } + + private static void writeElement(XmlSerializer serializer, String elementName, String value) + throws IllegalArgumentException, IllegalStateException, IOException { + if (value != null) { + serializer.startTag(null, elementName); + serializer.text(value); + serializer.endTag(null, elementName); + } + } + + private static void writeKeyValue(XmlSerializer serializer, String key, String value) + throws IllegalArgumentException, IllegalStateException, IOException { + serializer.startTag(null, VALUE_ELEMENT); + serializer.attribute(null, KEY_ATTRIBUTE, key); + if (value != null) { + serializer.text(value); + } + serializer.endTag(null, VALUE_ELEMENT); + } } From f4bcb4d923d35fe199ccd7dd174aa41d8c164eeb Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 7 Jun 2011 15:59:23 +0200 Subject: [PATCH 070/116] Extracted inner class Store.StoreSettings --- src/com/fsck/k9/mail/ConnectionSecurity.java | 2 +- src/com/fsck/k9/mail/Store.java | 103 ----------------- src/com/fsck/k9/mail/StoreSettings.java | 105 ++++++++++++++++++ src/com/fsck/k9/mail/store/ImapStore.java | 1 + .../fsck/k9/preferences/StorageExporter.java | 2 +- 5 files changed, 108 insertions(+), 105 deletions(-) create mode 100644 src/com/fsck/k9/mail/StoreSettings.java diff --git a/src/com/fsck/k9/mail/ConnectionSecurity.java b/src/com/fsck/k9/mail/ConnectionSecurity.java index bc6f2c8c2..2fae9ff50 100644 --- a/src/com/fsck/k9/mail/ConnectionSecurity.java +++ b/src/com/fsck/k9/mail/ConnectionSecurity.java @@ -4,7 +4,7 @@ package com.fsck.k9.mail; * The currently available connection security types. * *

- * Right now this enum is only used by {@link Store.StoreSettings} and converted to store-specific + * Right now this enum is only used by {@link StoreSettings} and converted to store-specific * constants in the different Store implementations. In the future we probably want to change this * and use {@code ConnectionSecurity} exclusively. *

diff --git a/src/com/fsck/k9/mail/Store.java b/src/com/fsck/k9/mail/Store.java index 33fd4e5dd..4ee71ad0b 100644 --- a/src/com/fsck/k9/mail/Store.java +++ b/src/com/fsck/k9/mail/Store.java @@ -13,7 +13,6 @@ import com.fsck.k9.mail.store.StorageManager.StorageProvider; import java.util.HashMap; import java.util.List; -import java.util.Map; /** * Store is the access point for an email message store. It's location can be @@ -109,108 +108,6 @@ public abstract class Store { } } - /** - * This is an abstraction to get rid of the store-specific URIs. - * - *

- * Right now it's only used for settings import/export. But the goal is to get rid of - * store URIs altogether. - *

- * - * @see Account#getStoreUri() - */ - public static class StoreSettings { - /** - * Name of the store type (e.g. "IMAP"). - */ - public final String type; - - /** - * The host name of the incoming server. - */ - public final String host; - - /** - * The port number of the incoming server. - */ - public final int port; - - /** - * The type of connection security to be used when connecting to the incoming server. - * - * {@link ConnectionSecurity#NONE} if not applicable for the store. - */ - public final ConnectionSecurity connectionSecurity; - - /** - * The authentication method to use when connecting to the incoming server. - * - * {@code null} if not applicable for the store. - */ - public final String authenticationType; - - /** - * The username part of the credentials needed to authenticate to the incoming server. - * - * {@code null} if unused or not applicable for the store. - */ - public final String username; - - /** - * The password part of the credentials needed to authenticate to the incoming server. - * - * {@code null} if unused or not applicable for the store. - */ - public final String password; - - - /** - * Creates a new {@code StoreSettings} object. - * - * @param type - * see {@link StoreSettings#type} - * @param host - * see {@link StoreSettings#host} - * @param port - * see {@link StoreSettings#port} - * @param connectionSecurity - * see {@link StoreSettings#connectionSecurity} - * @param authenticationType - * see {@link StoreSettings#authenticationType} - * @param username - * see {@link StoreSettings#username} - * @param password - * see {@link StoreSettings#password} - */ - public StoreSettings(String type, String host, int port, - ConnectionSecurity connectionSecurity, String authenticationType, String username, - String password) { - this.type = type; - this.host = host; - this.port = port; - this.connectionSecurity = connectionSecurity; - this.authenticationType = authenticationType; - this.username = username; - this.password = password; - } - - /** - * Returns store-specific settings as key/value pair. - * - *

Classes that inherit from this one are expected to override this method.

- */ - public Map getExtra() { - return null; - } - - protected void putIfNotNull(Map map, String key, String value) { - if (value != null) { - map.put(key, value); - } - } - } - - protected final Account mAccount; diff --git a/src/com/fsck/k9/mail/StoreSettings.java b/src/com/fsck/k9/mail/StoreSettings.java new file mode 100644 index 000000000..77bdc064a --- /dev/null +++ b/src/com/fsck/k9/mail/StoreSettings.java @@ -0,0 +1,105 @@ +package com.fsck.k9.mail; + +import java.util.Map; +import com.fsck.k9.Account; + +/** + * This is an abstraction to get rid of the store-specific URIs. + * + *

+ * Right now it's only used for settings import/export. But the goal is to get rid of + * store URIs altogether. + *

+ * + * @see Account#getStoreUri() + */ +public class StoreSettings { + /** + * Name of the store type (e.g. "IMAP"). + */ + public final String type; + + /** + * The host name of the incoming server. + */ + public final String host; + + /** + * The port number of the incoming server. + */ + public final int port; + + /** + * The type of connection security to be used when connecting to the incoming server. + * + * {@link ConnectionSecurity#NONE} if not applicable for the store. + */ + public final ConnectionSecurity connectionSecurity; + + /** + * The authentication method to use when connecting to the incoming server. + * + * {@code null} if not applicable for the store. + */ + public final String authenticationType; + + /** + * The username part of the credentials needed to authenticate to the incoming server. + * + * {@code null} if unused or not applicable for the store. + */ + public final String username; + + /** + * The password part of the credentials needed to authenticate to the incoming server. + * + * {@code null} if unused or not applicable for the store. + */ + public final String password; + + + /** + * Creates a new {@code StoreSettings} object. + * + * @param type + * see {@link StoreSettings#type} + * @param host + * see {@link StoreSettings#host} + * @param port + * see {@link StoreSettings#port} + * @param connectionSecurity + * see {@link StoreSettings#connectionSecurity} + * @param authenticationType + * see {@link StoreSettings#authenticationType} + * @param username + * see {@link StoreSettings#username} + * @param password + * see {@link StoreSettings#password} + */ + public StoreSettings(String type, String host, int port, + ConnectionSecurity connectionSecurity, String authenticationType, String username, + String password) { + this.type = type; + this.host = host; + this.port = port; + this.connectionSecurity = connectionSecurity; + this.authenticationType = authenticationType; + this.username = username; + this.password = password; + } + + /** + * Returns store-specific settings as key/value pair. + * + *

Classes that inherit from this one are expected to override this method.

+ */ + public Map getExtra() { + return null; + } + + protected void putIfNotNull(Map map, String key, String value) { + if (value != null) { + map.put(key, value); + } + } +} \ No newline at end of file diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 8db0d1fb1..aa883ee53 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -77,6 +77,7 @@ import com.fsck.k9.mail.Part; import com.fsck.k9.mail.PushReceiver; import com.fsck.k9.mail.Pusher; import com.fsck.k9.mail.Store; +import com.fsck.k9.mail.StoreSettings; import com.fsck.k9.mail.filter.CountingOutputStream; import com.fsck.k9.mail.filter.EOLConvertingOutputStream; import com.fsck.k9.mail.filter.FixedLengthInputStream; diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 0debd0287..54bb811fe 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -26,7 +26,7 @@ import com.fsck.k9.K9; import com.fsck.k9.Preferences; import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.Store; -import com.fsck.k9.mail.Store.StoreSettings; +import com.fsck.k9.mail.StoreSettings; import com.fsck.k9.mail.store.LocalStore; From bccf0b55467a0b940b85bccdcb70bf7e7cba3f32 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 7 Jun 2011 16:09:15 +0200 Subject: [PATCH 071/116] Renamed class StoreSettings to ServerSettings --- src/com/fsck/k9/mail/ConnectionSecurity.java | 7 +-- ...StoreSettings.java => ServerSettings.java} | 51 ++++++++++--------- src/com/fsck/k9/mail/Store.java | 6 +-- src/com/fsck/k9/mail/store/ImapStore.java | 4 +- src/com/fsck/k9/mail/store/Pop3Store.java | 6 +-- src/com/fsck/k9/mail/store/WebDavStore.java | 2 +- .../fsck/k9/preferences/StorageExporter.java | 4 +- 7 files changed, 42 insertions(+), 38 deletions(-) rename src/com/fsck/k9/mail/{StoreSettings.java => ServerSettings.java} (58%) diff --git a/src/com/fsck/k9/mail/ConnectionSecurity.java b/src/com/fsck/k9/mail/ConnectionSecurity.java index 2fae9ff50..98741303e 100644 --- a/src/com/fsck/k9/mail/ConnectionSecurity.java +++ b/src/com/fsck/k9/mail/ConnectionSecurity.java @@ -4,9 +4,10 @@ package com.fsck.k9.mail; * The currently available connection security types. * *

- * Right now this enum is only used by {@link StoreSettings} and converted to store-specific - * constants in the different Store implementations. In the future we probably want to change this - * and use {@code ConnectionSecurity} exclusively. + * Right now this enum is only used by {@link ServerSettings} and converted to store- or + * transport-specific constants in the different {@link Store} and {@link Transport} + * implementations. In the future we probably want to change this and use + * {@code ConnectionSecurity} exclusively. *

*/ public enum ConnectionSecurity { diff --git a/src/com/fsck/k9/mail/StoreSettings.java b/src/com/fsck/k9/mail/ServerSettings.java similarity index 58% rename from src/com/fsck/k9/mail/StoreSettings.java rename to src/com/fsck/k9/mail/ServerSettings.java index 77bdc064a..cb36459c6 100644 --- a/src/com/fsck/k9/mail/StoreSettings.java +++ b/src/com/fsck/k9/mail/ServerSettings.java @@ -4,79 +4,82 @@ import java.util.Map; import com.fsck.k9.Account; /** - * This is an abstraction to get rid of the store-specific URIs. + * This is an abstraction to get rid of the store- and transport-specific URIs. * *

* Right now it's only used for settings import/export. But the goal is to get rid of - * store URIs altogether. + * store/transport URIs altogether. *

* * @see Account#getStoreUri() + * @see Account#getTransportUri() */ -public class StoreSettings { +public class ServerSettings { /** - * Name of the store type (e.g. "IMAP"). + * Name of the store or transport type (e.g. "IMAP"). */ public final String type; /** - * The host name of the incoming server. + * The host name of the server. + * + * {@code null} if not applicable for the store or transport. */ public final String host; /** - * The port number of the incoming server. + * The port number of the server. */ public final int port; /** - * The type of connection security to be used when connecting to the incoming server. + * The type of connection security to be used when connecting to the server. * - * {@link ConnectionSecurity#NONE} if not applicable for the store. + * {@link ConnectionSecurity#NONE} if not applicable for the store or transport. */ public final ConnectionSecurity connectionSecurity; /** - * The authentication method to use when connecting to the incoming server. + * The authentication method to use when connecting to the server. * - * {@code null} if not applicable for the store. + * {@code null} if not applicable for the store or transport. */ public final String authenticationType; /** - * The username part of the credentials needed to authenticate to the incoming server. + * The username part of the credentials needed to authenticate to the server. * - * {@code null} if unused or not applicable for the store. + * {@code null} if not applicable for the store or transport. */ public final String username; /** - * The password part of the credentials needed to authenticate to the incoming server. + * The password part of the credentials needed to authenticate to the server. * - * {@code null} if unused or not applicable for the store. + * {@code null} if not applicable for the store or transport. */ public final String password; /** - * Creates a new {@code StoreSettings} object. + * Creates a new {@code ServerSettings} object. * * @param type - * see {@link StoreSettings#type} + * see {@link ServerSettings#type} * @param host - * see {@link StoreSettings#host} + * see {@link ServerSettings#host} * @param port - * see {@link StoreSettings#port} + * see {@link ServerSettings#port} * @param connectionSecurity - * see {@link StoreSettings#connectionSecurity} + * see {@link ServerSettings#connectionSecurity} * @param authenticationType - * see {@link StoreSettings#authenticationType} + * see {@link ServerSettings#authenticationType} * @param username - * see {@link StoreSettings#username} + * see {@link ServerSettings#username} * @param password - * see {@link StoreSettings#password} + * see {@link ServerSettings#password} */ - public StoreSettings(String type, String host, int port, + public ServerSettings(String type, String host, int port, ConnectionSecurity connectionSecurity, String authenticationType, String username, String password) { this.type = type; @@ -89,7 +92,7 @@ public class StoreSettings { } /** - * Returns store-specific settings as key/value pair. + * Returns store- or transport-specific settings as key/value pair. * *

Classes that inherit from this one are expected to override this method.

*/ diff --git a/src/com/fsck/k9/mail/Store.java b/src/com/fsck/k9/mail/Store.java index 4ee71ad0b..bb3d1e273 100644 --- a/src/com/fsck/k9/mail/Store.java +++ b/src/com/fsck/k9/mail/Store.java @@ -84,19 +84,19 @@ public abstract class Store { } /** - * Decodes the contents of store-specific URIs and puts them into a {@link StoreSettings} + * Decodes the contents of store-specific URIs and puts them into a {@link ServerSettings} * object. * * @param uri * the store-specific URI to decode * - * @return A {@link StoreSettings} object holding the settings contained in the URI. + * @return A {@link ServerSettings} object holding the settings contained in the URI. * * @see ImapStore#decodeUri(String) * @see Pop3Store#decodeUri(String) * @see WebDavStore#decodeUri(String) */ - public static StoreSettings decodeStoreUri(String uri) { + public static ServerSettings decodeStoreUri(String uri) { if (uri.startsWith("imap")) { return ImapStore.decodeUri(uri); } else if (uri.startsWith("pop3")) { diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index aa883ee53..d5de81dd7 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -77,7 +77,7 @@ import com.fsck.k9.mail.Part; import com.fsck.k9.mail.PushReceiver; import com.fsck.k9.mail.Pusher; import com.fsck.k9.mail.Store; -import com.fsck.k9.mail.StoreSettings; +import com.fsck.k9.mail.ServerSettings; import com.fsck.k9.mail.filter.CountingOutputStream; import com.fsck.k9.mail.filter.EOLConvertingOutputStream; import com.fsck.k9.mail.filter.FixedLengthInputStream; @@ -223,7 +223,7 @@ public class ImapStore extends Store { * * @see ImapStore#decodeUri(String) */ - private static class ImapStoreSettings extends StoreSettings { + private static class ImapStoreSettings extends ServerSettings { private static final String STORE_TYPE = "IMAP"; private static final String PATH_PREFIX_KEY = "pathPrefix"; diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java index c6a275c9f..c356073ba 100644 --- a/src/com/fsck/k9/mail/store/Pop3Store.java +++ b/src/com/fsck/k9/mail/store/Pop3Store.java @@ -48,7 +48,7 @@ public class Pop3Store extends Store { * pop3+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL *
*/ - public static StoreSettings decodeUri(String uri) { + public static ServerSettings decodeUri(String uri) { String host; int port; ConnectionSecurity connectionSecurity; @@ -101,7 +101,7 @@ public class Pop3Store extends Store { } } - return new StoreSettings(STORE_TYPE, host, port, connectionSecurity, null, username, + return new ServerSettings(STORE_TYPE, host, port, connectionSecurity, null, username, password); } @@ -118,7 +118,7 @@ public class Pop3Store extends Store { public Pop3Store(Account account) throws MessagingException { super(account); - StoreSettings settings; + ServerSettings settings; try { settings = decodeUri(mAccount.getStoreUri()); } catch (IllegalArgumentException e) { diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java index 94d446eff..1a092c503 100644 --- a/src/com/fsck/k9/mail/store/WebDavStore.java +++ b/src/com/fsck/k9/mail/store/WebDavStore.java @@ -190,7 +190,7 @@ public class WebDavStore extends Store { * * @see WebDavStore#decodeUri(String) */ - private static class WebDavStoreSettings extends StoreSettings { + private static class WebDavStoreSettings extends ServerSettings { private static final String STORE_TYPE = "WebDAV"; private static final String ALIAS_KEY = "alias"; private static final String PATH_KEY = "path"; diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 54bb811fe..9f995e308 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -26,7 +26,7 @@ import com.fsck.k9.K9; import com.fsck.k9.Preferences; import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.Store; -import com.fsck.k9.mail.StoreSettings; +import com.fsck.k9.mail.ServerSettings; import com.fsck.k9.mail.store.LocalStore; @@ -197,7 +197,7 @@ public class StorageExporter { // Write incoming server settings - StoreSettings incoming = Store.decodeStoreUri(account.getStoreUri()); + ServerSettings incoming = Store.decodeStoreUri(account.getStoreUri()); serializer.startTag(null, INCOMING_SERVER_ELEMENT); serializer.attribute(null, TYPE_ATTRIBUTE, incoming.type); From 4a807e33d943ff3e89a70c261d1af09ffc92a774 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 7 Jun 2011 16:39:41 +0200 Subject: [PATCH 072/116] Decode transport URIs into ServerSettings objects --- src/com/fsck/k9/mail/ServerSettings.java | 20 +++ src/com/fsck/k9/mail/Transport.java | 23 +++ .../fsck/k9/mail/transport/SmtpTransport.java | 141 +++++++++++------- .../k9/mail/transport/WebDavTransport.java | 16 ++ 4 files changed, 149 insertions(+), 51 deletions(-) diff --git a/src/com/fsck/k9/mail/ServerSettings.java b/src/com/fsck/k9/mail/ServerSettings.java index cb36459c6..ef237e0a4 100644 --- a/src/com/fsck/k9/mail/ServerSettings.java +++ b/src/com/fsck/k9/mail/ServerSettings.java @@ -29,6 +29,8 @@ public class ServerSettings { /** * The port number of the server. + * + * {@code -1} if not applicable for the store or transport. */ public final int port; @@ -91,6 +93,24 @@ public class ServerSettings { this.password = password; } + /** + * Creates an "empty" {@code ServerSettings} object. + * + * Everything but {@link ServerSettings#type} is unused. + * + * @param type + * see {@link ServerSettings#type} + */ + public ServerSettings(String type) { + this.type = type; + host = null; + port = -1; + connectionSecurity = ConnectionSecurity.NONE; + authenticationType = null; + username = null; + password = null; + } + /** * Returns store- or transport-specific settings as key/value pair. * diff --git a/src/com/fsck/k9/mail/Transport.java b/src/com/fsck/k9/mail/Transport.java index 04634f01e..cd99923e5 100644 --- a/src/com/fsck/k9/mail/Transport.java +++ b/src/com/fsck/k9/mail/Transport.java @@ -22,6 +22,29 @@ public abstract class Transport { } } + /** + * Decodes the contents of transport-specific URIs and puts them into a {@link ServerSettings} + * object. + * + * @param uri + * the transport-specific URI to decode + * + * @return A {@link ServerSettings} object holding the settings contained in the URI. + * + * @see SmtpTransport#decodeUri(String) + * @see WebDavTransport#decodeUri(String) + */ + public static ServerSettings decodeTransportUri(String uri) { + if (uri.startsWith("smtp")) { + return SmtpTransport.decodeUri(uri); + } else if (uri.startsWith("webdav")) { + return WebDavTransport.decodeUri(uri); + } else { + throw new IllegalArgumentException("Not a valid transport URI"); + } + } + + public abstract void open() throws MessagingException; public abstract void sendMessage(Message message) throws MessagingException; diff --git a/src/com/fsck/k9/mail/transport/SmtpTransport.java b/src/com/fsck/k9/mail/transport/SmtpTransport.java index 552c883fc..160abd2a3 100644 --- a/src/com/fsck/k9/mail/transport/SmtpTransport.java +++ b/src/com/fsck/k9/mail/transport/SmtpTransport.java @@ -31,95 +31,134 @@ import org.apache.commons.codec.binary.Hex; import java.util.*; public class SmtpTransport extends Transport { + private static final String TRANSPORT_TYPE = "SMTP"; + public static final int CONNECTION_SECURITY_NONE = 0; - public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1; - public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2; - public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3; - public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4; - String mHost; - - int mPort; - - String mUsername; - - String mPassword; - - String mAuthType; - - int mConnectionSecurity; - - boolean mSecure; - - Socket mSocket; - - PeekableInputStream mIn; - - OutputStream mOut; - private boolean m8bitEncodingAllowed; /** + * Decodes a SmtpTransport URI. + * + *

Possible forms:

+ *
      * smtp://user:password@server:port CONNECTION_SECURITY_NONE
      * smtp+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
      * smtp+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
      * smtp+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
      * smtp+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
-     *
-     * @param _uri
+     * 
*/ - public SmtpTransport(String _uri) throws MessagingException { - URI uri; + public static ServerSettings decodeUri(String uri) { + String host; + int port; + ConnectionSecurity connectionSecurity; + String authenticationType = null; + String username = null; + String password = null; + + URI smtpUri; try { - uri = new URI(_uri); + smtpUri = new URI(uri); } catch (URISyntaxException use) { - throw new MessagingException("Invalid SmtpTransport URI", use); + throw new IllegalArgumentException("Invalid SmtpTransport URI", use); } - String scheme = uri.getScheme(); + String scheme = smtpUri.getScheme(); if (scheme.equals("smtp")) { - mConnectionSecurity = CONNECTION_SECURITY_NONE; - mPort = 25; + connectionSecurity = ConnectionSecurity.NONE; + port = 25; } else if (scheme.equals("smtp+tls")) { - mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL; - mPort = 25; + connectionSecurity = ConnectionSecurity.STARTTLS_OPTIONAL; + port = 25; } else if (scheme.equals("smtp+tls+")) { - mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED; - mPort = 25; + connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED; + port = 25; } else if (scheme.equals("smtp+ssl+")) { - mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED; - mPort = 465; + connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED; + port = 465; } else if (scheme.equals("smtp+ssl")) { - mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL; - mPort = 465; + connectionSecurity = ConnectionSecurity.SSL_TLS_OPTIONAL; + port = 465; } else { - throw new MessagingException("Unsupported protocol"); + throw new IllegalArgumentException("Unsupported protocol (" + scheme + ")"); } - mHost = uri.getHost(); + host = smtpUri.getHost(); - if (uri.getPort() != -1) { - mPort = uri.getPort(); + if (smtpUri.getPort() != -1) { + port = smtpUri.getPort(); } - if (uri.getUserInfo() != null) { + if (smtpUri.getUserInfo() != null) { try { - String[] userInfoParts = uri.getUserInfo().split(":"); - mUsername = URLDecoder.decode(userInfoParts[0], "UTF-8"); + String[] userInfoParts = smtpUri.getUserInfo().split(":"); + username = URLDecoder.decode(userInfoParts[0], "UTF-8"); if (userInfoParts.length > 1) { - mPassword = URLDecoder.decode(userInfoParts[1], "UTF-8"); + password = URLDecoder.decode(userInfoParts[1], "UTF-8"); } if (userInfoParts.length > 2) { - mAuthType = userInfoParts[2]; + authenticationType = userInfoParts[2]; } } catch (UnsupportedEncodingException enc) { // This shouldn't happen since the encoding is hardcoded to UTF-8 - Log.e(K9.LOG_TAG, "Couldn't urldecode username or password.", enc); + throw new IllegalArgumentException("Couldn't urldecode username or password.", enc); } } + + return new ServerSettings(TRANSPORT_TYPE, host, port, connectionSecurity, + authenticationType, username, password); + } + + + String mHost; + int mPort; + String mUsername; + String mPassword; + String mAuthType; + int mConnectionSecurity; + boolean mSecure; + Socket mSocket; + PeekableInputStream mIn; + OutputStream mOut; + private boolean m8bitEncodingAllowed; + + + public SmtpTransport(String uri) throws MessagingException { + ServerSettings settings; + try { + settings = decodeUri(uri); + } catch (IllegalArgumentException e) { + throw new MessagingException("Error while decoding transport URI", e); + } + + mHost = settings.host; + mPort = settings.port; + + switch (settings.connectionSecurity) { + case NONE: + mConnectionSecurity = CONNECTION_SECURITY_NONE; + break; + case STARTTLS_OPTIONAL: + mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL; + break; + case STARTTLS_REQUIRED: + mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED; + break; + case SSL_TLS_OPTIONAL: + mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL; + break; + case SSL_TLS_REQUIRED: + mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED; + break; + } + + mAuthType = settings.authenticationType; + mUsername = settings.username; + mPassword = settings.password; } @Override diff --git a/src/com/fsck/k9/mail/transport/WebDavTransport.java b/src/com/fsck/k9/mail/transport/WebDavTransport.java index 72159a5e6..7fd151e70 100644 --- a/src/com/fsck/k9/mail/transport/WebDavTransport.java +++ b/src/com/fsck/k9/mail/transport/WebDavTransport.java @@ -7,10 +7,26 @@ import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.mail.Message; import com.fsck.k9.mail.MessagingException; +import com.fsck.k9.mail.ServerSettings; import com.fsck.k9.mail.Transport; import com.fsck.k9.mail.store.WebDavStore; public class WebDavTransport extends Transport { + private static final String TRANSPORT_TYPE = "WebDAV"; + + /** + * Decodes a WebDavTransport URI. + * + *

+ * Note: Right now there is nothing to decode. Everything related to sending messages + * via WebDAV is handled by {@link WebDavStore}. + *

+ */ + public static ServerSettings decodeUri(String uri) { + return new ServerSettings(TRANSPORT_TYPE); + } + + private WebDavStore store; public WebDavTransport(Account account) throws MessagingException { From 8d97287ffd2ba5060b766af94af46de3e989799e Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 7 Jun 2011 23:09:40 +0200 Subject: [PATCH 073/116] Write outgoing-server element instead of transportUri to export file --- .../fsck/k9/preferences/StorageExporter.java | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 9f995e308..64d8782b3 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -27,6 +27,7 @@ import com.fsck.k9.Preferences; import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.Store; import com.fsck.k9.mail.ServerSettings; +import com.fsck.k9.mail.Transport; import com.fsck.k9.mail.store.LocalStore; @@ -42,6 +43,7 @@ public class StorageExporter { public static final String ACCOUNT_ELEMENT = "account"; public static final String UUID_ATTRIBUTE = "uuid"; public static final String INCOMING_SERVER_ELEMENT = "incoming-server"; + public static final String OUTGOING_SERVER_ELEMENT = "outgoing-server"; public static final String TYPE_ATTRIBUTE = "type"; public static final String HOST_ELEMENT = "host"; public static final String PORT_ELEMENT = "port"; @@ -202,7 +204,9 @@ public class StorageExporter { serializer.attribute(null, TYPE_ATTRIBUTE, incoming.type); writeElement(serializer, HOST_ELEMENT, incoming.host); - writeElement(serializer, PORT_ELEMENT, Integer.toString(incoming.port)); + if (incoming.port != -1) { + writeElement(serializer, PORT_ELEMENT, Integer.toString(incoming.port)); + } writeElement(serializer, CONNECTION_SECURITY_ELEMENT, incoming.connectionSecurity.name()); writeElement(serializer, AUTHENTICATION_TYPE_ELEMENT, incoming.authenticationType); writeElement(serializer, USERNAME_ELEMENT, incoming.username); @@ -221,7 +225,31 @@ public class StorageExporter { serializer.endTag(null, INCOMING_SERVER_ELEMENT); - //TODO: write outgoing server settings + // Write outgoing server settings + ServerSettings outgoing = Transport.decodeTransportUri(account.getTransportUri()); + serializer.startTag(null, OUTGOING_SERVER_ELEMENT); + serializer.attribute(null, TYPE_ATTRIBUTE, outgoing.type); + + writeElement(serializer, HOST_ELEMENT, outgoing.host); + if (outgoing.port != -1) { + writeElement(serializer, PORT_ELEMENT, Integer.toString(outgoing.port)); + } + writeElement(serializer, CONNECTION_SECURITY_ELEMENT, outgoing.connectionSecurity.name()); + writeElement(serializer, AUTHENTICATION_TYPE_ELEMENT, outgoing.authenticationType); + writeElement(serializer, USERNAME_ELEMENT, outgoing.username); + //TODO: make saving the password optional + writeElement(serializer, PASSWORD_ELEMENT, outgoing.password); + + extras = outgoing.getExtra(); + if (extras != null && extras.size() > 0) { + serializer.startTag(null, EXTRA_ELEMENT); + for (Entry extra : extras.entrySet()) { + writeKeyValue(serializer, extra.getKey(), extra.getValue()); + } + serializer.endTag(null, EXTRA_ELEMENT); + } + + serializer.endTag(null, OUTGOING_SERVER_ELEMENT); // Write account settings @@ -236,7 +264,8 @@ public class StorageExporter { if (!keyUuid.equals(accountUuid) || Account.ACCOUNT_DESCRIPTION_KEY.equals(secondPart) - || "storeUri".equals(secondPart)) { + || "storeUri".equals(secondPart) + || "transportUri".equals(secondPart)) { continue; } if (comps.length == 3) { From 4d11c80f657b9963fbd5b5d509abf049cfc2dba6 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 8 Jun 2011 22:28:34 +0200 Subject: [PATCH 074/116] Fix account selection on import if file doesn't contain global settings --- src/com/fsck/k9/activity/Accounts.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index af9bf241d..00c96f054 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1297,13 +1297,19 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC boolean includeGlobals = mImportContents.globalSettings ? pos.get(0) : false; List accountUuids = new ArrayList(); - for (int i = 1; i < count; i++) { + int start = mImportContents.globalSettings ? 1 : 0; + for (int i = start; i < count; i++) { if (pos.get(i)) { - accountUuids.add(mImportContents.accounts.get(i-1).uuid); + accountUuids.add(mImportContents.accounts.get(i-start).uuid); } } - boolean overwrite = false; //TODO: get value from dialog + /* + * TODO: Think some more about this. Overwriting could change the store + * type. This requires some additional code in order to work smoothly + * while the app is running. + */ + boolean overwrite = false; dialog.dismiss(); new ImportAsyncTask(includeGlobals, accountUuids, overwrite, mEncryptionKey, mInputStream).execute(); From ded489daab3ec66f662bcadc898b29401b1b7c99 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 9 Jun 2011 05:50:43 +0200 Subject: [PATCH 075/116] Add support for incoming-server and outgoing-server elements on import --- src/com/fsck/k9/Account.java | 2 + src/com/fsck/k9/mail/Store.java | 25 ++++ src/com/fsck/k9/mail/Transport.java | 21 ++++ src/com/fsck/k9/mail/store/ImapStore.java | 58 +++++++++- src/com/fsck/k9/mail/store/Pop3Store.java | 53 ++++++++- src/com/fsck/k9/mail/store/WebDavStore.java | 63 +++++++++- .../fsck/k9/mail/transport/SmtpTransport.java | 54 ++++++++- .../k9/mail/transport/WebDavTransport.java | 20 +++- .../fsck/k9/preferences/AccountSettings.java | 2 + .../fsck/k9/preferences/StorageImporter.java | 109 ++++++++++++++++++ 10 files changed, 399 insertions(+), 8 deletions(-) diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java index a9596570a..cccabd157 100644 --- a/src/com/fsck/k9/Account.java +++ b/src/com/fsck/k9/Account.java @@ -56,6 +56,8 @@ public class Account implements BaseAccount { public static final boolean DEFAULT_REPLY_AFTER_QUOTE = false; public static final String ACCOUNT_DESCRIPTION_KEY = "description"; + public static final String STORE_URI_KEY = "storeUri"; + public static final String TRANSPORT_URI_KEY = "transportUri"; public static final String IDENTITY_NAME_KEY = "name"; public static final String IDENTITY_EMAIL_KEY = "email"; diff --git a/src/com/fsck/k9/mail/Store.java b/src/com/fsck/k9/mail/Store.java index bb3d1e273..5013a8f29 100644 --- a/src/com/fsck/k9/mail/Store.java +++ b/src/com/fsck/k9/mail/Store.java @@ -108,6 +108,31 @@ public abstract class Store { } } + /** + * Creates a store URI from the information supplied in the {@link ServerSettings} object. + * + * @param server + * The {@link ServerSettings} object that holds the server settings. + * + * @return A store URI that holds the same information as the {@code server} parameter. + * + * @see ImapStore#createUri(ServerSettings) + * @see Pop3Store#createUri(ServerSettings) + * @see WebDavStore#createUri(ServerSettings) + */ + public static String createStoreUri(ServerSettings server) { + if (ImapStore.STORE_TYPE.equals(server.type)) { + return ImapStore.createUri(server); + } else if (Pop3Store.STORE_TYPE.equals(server.type)) { + return Pop3Store.createUri(server); + } else if (WebDavStore.STORE_TYPE.equals(server.type)) { + return WebDavStore.createUri(server); + } else { + throw new IllegalArgumentException("Not a valid store URI"); + } + } + + protected final Account mAccount; diff --git a/src/com/fsck/k9/mail/Transport.java b/src/com/fsck/k9/mail/Transport.java index cd99923e5..a0450ffeb 100644 --- a/src/com/fsck/k9/mail/Transport.java +++ b/src/com/fsck/k9/mail/Transport.java @@ -44,6 +44,27 @@ public abstract class Transport { } } + /** + * Creates a transport URI from the information supplied in the {@link ServerSettings} object. + * + * @param server + * The {@link ServerSettings} object that holds the server settings. + * + * @return A transport URI that holds the same information as the {@code server} parameter. + * + * @see SmtpTransport#createUri(ServerSettings) + * @see WebDavTransport#createUri(ServerSettings) + */ + public static String createTransportUri(ServerSettings server) { + if (SmtpTransport.TRANSPORT_TYPE.equals(server.type)) { + return SmtpTransport.createUri(server); + } else if (WebDavTransport.TRANSPORT_TYPE.equals(server.type)) { + return WebDavTransport.createUri(server); + } else { + throw new IllegalArgumentException("Not a valid transport URI"); + } + } + public abstract void open() throws MessagingException; diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index d5de81dd7..382cf9268 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -16,6 +16,7 @@ import java.net.SocketException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; +import java.net.URLEncoder; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; @@ -102,6 +103,8 @@ import java.util.zip.InflaterInputStream; * */ public class ImapStore extends Store { + public static final String STORE_TYPE = "IMAP"; + public static final int CONNECTION_SECURITY_NONE = 0; public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1; public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2; @@ -218,13 +221,66 @@ public class ImapStore extends Store { password, pathPrefix); } + /** + * Creates an ImapStore URI with the supplied settings. + * + * @param server + * The {@link ServerSettings} object that holds the server settings. + * + * @return An ImapStore URI that holds the same information as the {@code server} parameter. + * + * @see Account#getStoreUri() + * @see ImapStore#decodeUri(String) + */ + public static String createUri(ServerSettings server) { + String userEnc; + String passwordEnc; + try { + userEnc = URLEncoder.encode(server.username, "UTF-8"); + passwordEnc = URLEncoder.encode(server.password, "UTF-8"); + } + catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Could not encode username or password", e); + } + + String scheme; + switch (server.connectionSecurity) { + case SSL_TLS_OPTIONAL: + scheme = "imap+ssl"; + break; + case SSL_TLS_REQUIRED: + scheme = "imap+ssl+"; + break; + case STARTTLS_OPTIONAL: + scheme = "imap+tls"; + break; + case STARTTLS_REQUIRED: + scheme = "imap+tls+"; + break; + default: + case NONE: + scheme = "imap"; + break; + } + + String userInfo = server.authenticationType + ":" + userEnc + ":" + passwordEnc; + try { + Map extra = server.getExtra(); + String prefix = (extra != null) ? extra.get(ImapStoreSettings.PATH_PREFIX_KEY) : null; + return new URI(scheme, userInfo, server.host, server.port, + prefix, + null, null).toString(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Can't create ImapStore URI", e); + } + } + /** * This class is used to store the decoded contents of an ImapStore URI. * * @see ImapStore#decodeUri(String) */ private static class ImapStoreSettings extends ServerSettings { - private static final String STORE_TYPE = "IMAP"; private static final String PATH_PREFIX_KEY = "pathPrefix"; public final String pathPrefix; diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java index c356073ba..a38c58823 100644 --- a/src/com/fsck/k9/mail/store/Pop3Store.java +++ b/src/com/fsck/k9/mail/store/Pop3Store.java @@ -26,7 +26,7 @@ import java.util.HashSet; import java.util.List; public class Pop3Store extends Store { - private static final String STORE_TYPE = "POP3"; + public static final String STORE_TYPE = "POP3"; public static final int CONNECTION_SECURITY_NONE = 0; public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1; @@ -105,6 +105,57 @@ public class Pop3Store extends Store { password); } + /** + * Creates a Pop3Store URI with the supplied settings. + * + * @param server + * The {@link ServerSettings} object that holds the server settings. + * + * @return A Pop3Store URI that holds the same information as the {@code server} parameter. + * + * @see Account#getStoreUri() + * @see Pop3Store#decodeUri(String) + */ + public static String createUri(ServerSettings server) { + String userEnc; + String passwordEnc; + try { + userEnc = URLEncoder.encode(server.username, "UTF-8"); + passwordEnc = URLEncoder.encode(server.password, "UTF-8"); + } + catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Could not encode username or password", e); + } + + String scheme; + switch (server.connectionSecurity) { + case SSL_TLS_OPTIONAL: + scheme = "pop3+ssl"; + break; + case SSL_TLS_REQUIRED: + scheme = "pop3+ssl+"; + break; + case STARTTLS_OPTIONAL: + scheme = "pop3+tls"; + break; + case STARTTLS_REQUIRED: + scheme = "pop3+tls+"; + break; + default: + case NONE: + scheme = "pop3"; + break; + } + + String userInfo = userEnc + ":" + passwordEnc; + try { + return new URI(scheme, userInfo, server.host, server.port, null, null, + null).toString(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Can't create Pop3Store URI", e); + } + } + private String mHost; private int mPort; diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java index 1a092c503..85d539e77 100644 --- a/src/com/fsck/k9/mail/store/WebDavStore.java +++ b/src/com/fsck/k9/mail/store/WebDavStore.java @@ -62,6 +62,8 @@ import java.util.zip.GZIPInputStream; * */ public class WebDavStore extends Store { + public static final String STORE_TYPE = "WebDAV"; + // Security options private static final short CONNECTION_SECURITY_NONE = 0; private static final short CONNECTION_SECURITY_TLS_OPTIONAL = 1; @@ -185,13 +187,72 @@ public class WebDavStore extends Store { alias, path, authPath, mailboxPath); } + /** + * Creates a WebDavStore URI with the supplied settings. + * + * @param server + * The {@link ServerSettings} object that holds the server settings. + * + * @return A WebDavStore URI that holds the same information as the {@code server} parameter. + * + * @see Account#getStoreUri() + * @see WebDavStore#decodeUri(String) + */ + public static String createUri(ServerSettings server) { + String userEnc; + String passwordEnc; + try { + userEnc = URLEncoder.encode(server.username, "UTF-8"); + passwordEnc = URLEncoder.encode(server.password, "UTF-8"); + } + catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Could not encode username or password", e); + } + + String scheme; + switch (server.connectionSecurity) { + case SSL_TLS_OPTIONAL: + scheme = "webdav+ssl"; + break; + case SSL_TLS_REQUIRED: + scheme = "webdav+ssl+"; + break; + case STARTTLS_OPTIONAL: + scheme = "webdav+tls"; + break; + case STARTTLS_REQUIRED: + scheme = "webdav+tls+"; + break; + default: + case NONE: + scheme = "webdav"; + break; + } + + Map extra = server.getExtra(); + String userInfo = userEnc + ":" + passwordEnc; + String path = extra.get(WebDavStoreSettings.PATH_KEY); + path = (path != null) ? path : ""; + String authPath = extra.get(WebDavStoreSettings.AUTH_PATH_KEY); + authPath = (authPath != null) ? authPath : ""; + String mailboxPath = extra.get(WebDavStoreSettings.MAILBOX_PATH_KEY); + mailboxPath = (mailboxPath != null) ? mailboxPath : ""; + String uriPath = path + "|" + authPath + "|" + mailboxPath; + try { + return new URI(scheme, userInfo, server.host, server.port, uriPath, + null, null).toString(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Can't create WebDavStore URI", e); + } + } + + /** * This class is used to store the decoded contents of an WebDavStore URI. * * @see WebDavStore#decodeUri(String) */ private static class WebDavStoreSettings extends ServerSettings { - private static final String STORE_TYPE = "WebDAV"; private static final String ALIAS_KEY = "alias"; private static final String PATH_KEY = "path"; private static final String AUTH_PATH_KEY = "authPath"; diff --git a/src/com/fsck/k9/mail/transport/SmtpTransport.java b/src/com/fsck/k9/mail/transport/SmtpTransport.java index 160abd2a3..b92b8236f 100644 --- a/src/com/fsck/k9/mail/transport/SmtpTransport.java +++ b/src/com/fsck/k9/mail/transport/SmtpTransport.java @@ -2,6 +2,7 @@ package com.fsck.k9.mail.transport; import android.util.Log; +import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.mail.*; import com.fsck.k9.mail.Message.RecipientType; @@ -31,7 +32,7 @@ import org.apache.commons.codec.binary.Hex; import java.util.*; public class SmtpTransport extends Transport { - private static final String TRANSPORT_TYPE = "SMTP"; + public static final String TRANSPORT_TYPE = "SMTP"; public static final int CONNECTION_SECURITY_NONE = 0; public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1; @@ -113,6 +114,57 @@ public class SmtpTransport extends Transport { authenticationType, username, password); } + /** + * Creates a SmtpTransport URI with the supplied settings. + * + * @param server + * The {@link ServerSettings} object that holds the server settings. + * + * @return A SmtpTransport URI that holds the same information as the {@code server} parameter. + * + * @see Account#getTransportUri() + * @see SmtpTransport#decodeUri(String) + */ + public static String createUri(ServerSettings server) { + String userEnc; + String passwordEnc; + try { + userEnc = URLEncoder.encode(server.username, "UTF-8"); + passwordEnc = URLEncoder.encode(server.password, "UTF-8"); + } + catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Could not encode username or password", e); + } + + String scheme; + switch (server.connectionSecurity) { + case SSL_TLS_OPTIONAL: + scheme = "smtp+ssl"; + break; + case SSL_TLS_REQUIRED: + scheme = "smtp+ssl+"; + break; + case STARTTLS_OPTIONAL: + scheme = "smtp+tls"; + break; + case STARTTLS_REQUIRED: + scheme = "smtp+tls+"; + break; + default: + case NONE: + scheme = "smtp"; + break; + } + + String userInfo = userEnc + ":" + passwordEnc + ":" + server.authenticationType; + try { + return new URI(scheme, userInfo, server.host, server.port, null, null, + null).toString(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Can't create SmtpTransport URI", e); + } + } + String mHost; int mPort; diff --git a/src/com/fsck/k9/mail/transport/WebDavTransport.java b/src/com/fsck/k9/mail/transport/WebDavTransport.java index 7fd151e70..6b5b61d60 100644 --- a/src/com/fsck/k9/mail/transport/WebDavTransport.java +++ b/src/com/fsck/k9/mail/transport/WebDavTransport.java @@ -12,18 +12,30 @@ import com.fsck.k9.mail.Transport; import com.fsck.k9.mail.store.WebDavStore; public class WebDavTransport extends Transport { - private static final String TRANSPORT_TYPE = "WebDAV"; + public static final String TRANSPORT_TYPE = WebDavStore.STORE_TYPE; /** * Decodes a WebDavTransport URI. * *

- * Note: Right now there is nothing to decode. Everything related to sending messages - * via WebDAV is handled by {@link WebDavStore}. + * Note: Everything related to sending messages via WebDAV is handled by + * {@link WebDavStore}. So the transport URI is the same as the store URI. *

*/ public static ServerSettings decodeUri(String uri) { - return new ServerSettings(TRANSPORT_TYPE); + return WebDavStore.decodeUri(uri); + } + + /** + * Creates a WebDavTransport URI. + * + *

+ * Note: Everything related to sending messages via WebDAV is handled by + * {@link WebDavStore}. So the transport URI is the same as the store URI. + *

+ */ + public static String createUri(ServerSettings server) { + return WebDavStore.createUri(server); } diff --git a/src/com/fsck/k9/preferences/AccountSettings.java b/src/com/fsck/k9/preferences/AccountSettings.java index a2497454a..7e7c2c5c6 100644 --- a/src/com/fsck/k9/preferences/AccountSettings.java +++ b/src/com/fsck/k9/preferences/AccountSettings.java @@ -23,11 +23,13 @@ public class AccountSettings { SETTINGS = new LinkedHashMap(); // mandatory + /* SETTINGS.put("storeUri", SD(SettingType.STRING, Settings.EXCEPTION_DEFAULT_VALUE, new StoreUriValidator())); SETTINGS.put("transportUri", SD(SettingType.STRING, Settings.EXCEPTION_DEFAULT_VALUE, new TransportUriValidator())); + */ SETTINGS.put("archiveFolderName", SD(SettingType.STRING, "Archive", null)); diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index 2d543dda6..d721aa20e 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -23,6 +24,10 @@ import com.fsck.k9.K9; import com.fsck.k9.Preferences; import com.fsck.k9.helper.DateFormatter; import com.fsck.k9.helper.Utility; +import com.fsck.k9.mail.ConnectionSecurity; +import com.fsck.k9.mail.ServerSettings; +import com.fsck.k9.mail.Store; +import com.fsck.k9.mail.Transport; public class StorageImporter { @@ -204,6 +209,7 @@ public class StorageImporter { errorneousAccounts.add(importResult.original); } } catch (Exception e) { + Log.e(K9.LOG_TAG, "Exception while importing account", e); //XXX errorneousAccounts.add(new AccountDescription(account.name, account.uuid)); } } else { @@ -302,6 +308,7 @@ public class StorageImporter { //TODO: validate account name + //TODO: validate server settings //TODO: validate identity settings //TODO: validate folder settings @@ -321,6 +328,16 @@ public class StorageImporter { String accountKeyPrefix = uuid + "."; editor.putString(accountKeyPrefix + Account.ACCOUNT_DESCRIPTION_KEY, accountName); + // Write incoming server settings (storeUri) + ServerSettings incoming = new ImportedServerSettings(account.incoming); + String storeUri = Store.createStoreUri(incoming); + editor.putString(accountKeyPrefix + Account.STORE_URI_KEY, Utility.base64Encode(storeUri)); + + // Write outgoing server settings (transportUri) + ServerSettings outgoing = new ImportedServerSettings(account.outgoing); + String transportUri = Transport.createTransportUri(outgoing); + editor.putString(accountKeyPrefix + Account.TRANSPORT_URI_KEY, Utility.base64Encode(transportUri)); + // Write account settings for (Map.Entry setting : writeSettings.entrySet()) { String key = accountKeyPrefix + setting.getKey(); @@ -639,6 +656,18 @@ public class StorageImporter { String element = xpp.getName(); if (StorageExporter.NAME_ELEMENT.equals(element)) { account.name = getText(xpp); + } else if (StorageExporter.INCOMING_SERVER_ELEMENT.equals(element)) { + if (overview) { + skipToEndTag(xpp, StorageExporter.INCOMING_SERVER_ELEMENT); + } else { + account.incoming = parseServerSettings(xpp, StorageExporter.INCOMING_SERVER_ELEMENT); + } + } else if (StorageExporter.OUTGOING_SERVER_ELEMENT.equals(element)) { + if (overview) { + skipToEndTag(xpp, StorageExporter.OUTGOING_SERVER_ELEMENT); + } else { + account.outgoing = parseServerSettings(xpp, StorageExporter.OUTGOING_SERVER_ELEMENT); + } } else if (StorageExporter.SETTINGS_ELEMENT.equals(element)) { if (overview) { skipToEndTag(xpp, StorageExporter.SETTINGS_ELEMENT); @@ -671,6 +700,40 @@ public class StorageImporter { return account; } + private static ImportedServer parseServerSettings(XmlPullParser xpp, String endTag) + throws XmlPullParserException, IOException { + ImportedServer server = new ImportedServer(); + + server.type = xpp.getAttributeValue(null, StorageExporter.TYPE_ATTRIBUTE); + + int eventType = xpp.next(); + while (!(eventType == XmlPullParser.END_TAG && endTag.equals(xpp.getName()))) { + if(eventType == XmlPullParser.START_TAG) { + String element = xpp.getName(); + if (StorageExporter.HOST_ELEMENT.equals(element)) { + server.host = getText(xpp); + } else if (StorageExporter.PORT_ELEMENT.equals(element)) { + server.port = getText(xpp); + } else if (StorageExporter.CONNECTION_SECURITY_ELEMENT.equals(element)) { + server.connectionSecurity = getText(xpp); + } else if (StorageExporter.AUTHENTICATION_TYPE_ELEMENT.equals(element)) { + server.authenticationType = getText(xpp); + } else if (StorageExporter.USERNAME_ELEMENT.equals(element)) { + server.username = getText(xpp); + } else if (StorageExporter.PASSWORD_ELEMENT.equals(element)) { + server.password = getText(xpp); + } else if (StorageExporter.EXTRA_ELEMENT.equals(element)) { + server.extras = parseSettings(xpp, StorageExporter.EXTRA_ELEMENT); + } else { + Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName()); + } + } + eventType = xpp.next(); + } + + return server; + } + private static List parseIdentities(XmlPullParser xpp) throws XmlPullParserException, IOException { List identities = null; @@ -765,6 +828,39 @@ public class StorageImporter { return folder; } + private static class ImportedServerSettings extends ServerSettings { + private final ImportedServer mImportedServer; + + public ImportedServerSettings(ImportedServer server) { + super(server.type, server.host, convertPort(server.port), + convertConnectionSecurity(server.connectionSecurity), + server.authenticationType, server.username, server.password); + mImportedServer = server; + } + + @Override + public Map getExtra() { + return (mImportedServer.extras != null) ? + Collections.unmodifiableMap(mImportedServer.extras.settings) : null; + } + + private static int convertPort(String port) { + try { + return Integer.parseInt(port); + } catch (NumberFormatException e) { + return -1; + } + } + + private static ConnectionSecurity convertConnectionSecurity(String connectionSecurity) { + try { + return ConnectionSecurity.valueOf(connectionSecurity); + } catch (Exception e) { + return ConnectionSecurity.NONE; + } + } + } + private static class Imported { public ImportedSettings globalSettings; public Map accounts; @@ -777,11 +873,24 @@ public class StorageImporter { private static class ImportedAccount { public String uuid; public String name; + public ImportedServer incoming; + public ImportedServer outgoing; public ImportedSettings settings; public List identities; public List folders; } + private static class ImportedServer { + public String type; + public String host; + public String port; + public String connectionSecurity; + public String authenticationType; + public String username; + public String password; + public ImportedSettings extras; + } + private static class ImportedIdentity { public String name; public String email; From c76bec971b3e47f4388873d0527c929c4f20ed3d Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 30 Sep 2011 14:58:13 +0200 Subject: [PATCH 076/116] Fixed compiler warning Use new variable instead of overwriting an argument. --- src/com/fsck/k9/preferences/StorageExporter.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 64d8782b3..cda3c5f42 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -134,12 +134,15 @@ public class StorageExporter { Preferences preferences = Preferences.getPreferences(context); SharedPreferences storage = preferences.getPreferences(); + Set exportAccounts; if (accountUuids == null) { Account[] accounts = preferences.getAccounts(); - accountUuids = new HashSet(); + exportAccounts = new HashSet(); for (Account account : accounts) { - accountUuids.add(account.getUuid()); + exportAccounts.add(account.getUuid()); } + } else { + exportAccounts = accountUuids; } Map prefs = new TreeMap(storage.getAll()); @@ -151,7 +154,7 @@ public class StorageExporter { } serializer.startTag(null, ACCOUNTS_ELEMENT); - for (String accountUuid : accountUuids) { + for (String accountUuid : exportAccounts) { Account account = preferences.getAccount(accountUuid); writeAccount(serializer, account, prefs); } From cd72e197ecf99bed92bf38acffe323e5ef3e8951 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 30 Sep 2011 16:47:59 +0200 Subject: [PATCH 077/116] Added code to skip certain settings on export --- .../fsck/k9/preferences/StorageExporter.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index cda3c5f42..5f5c490e9 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -63,6 +63,23 @@ public class StorageExporter { public static final String EMAIL_ELEMENT = "email"; public static final String DESCRIPTION_ELEMENT = "description"; + /** + * List of keys of global settings that don't need to or shouldn't be exported. + */ + private static final Set SKIP_GLOBAL_SETTINGS; + + static { + Set skip = new HashSet(); + + // No need to export the "accountUuids" field. It will be (re)created by the import code. + skip.add("accountUuids"); + + // "defaultAccountUuid" is also handled by the import code. + skip.add("defaultAccountUuid"); + + SKIP_GLOBAL_SETTINGS = skip; + } + public static String exportToFile(Context context, boolean includeGlobals, Set accountUuids, String encryptionKey) @@ -175,8 +192,8 @@ public class StorageExporter { for (Map.Entry entry : prefs.entrySet()) { String key = entry.getKey(); String value = entry.getValue().toString(); - if (key.indexOf('.') != -1) { - // Skip account entries + if (key.indexOf('.') != -1 || SKIP_GLOBAL_SETTINGS.contains(key)) { + // Skip account entries and keys we don't want/need to export continue; } writeKeyValue(serializer, key, value); From 6e1bf2965d094cecb702f5adee3982075651dc3e Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 30 Sep 2011 16:54:01 +0200 Subject: [PATCH 078/116] Fixed DateFormatValidator to handle placeholders SHORT and MEDIUM --- src/com/fsck/k9/preferences/GlobalSettings.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/com/fsck/k9/preferences/GlobalSettings.java b/src/com/fsck/k9/preferences/GlobalSettings.java index 8064c67ef..5c2291b24 100644 --- a/src/com/fsck/k9/preferences/GlobalSettings.java +++ b/src/com/fsck/k9/preferences/GlobalSettings.java @@ -191,6 +191,13 @@ public class GlobalSettings { @Override public boolean isValid(String key, String value, Map validatedSettings) { try { + // The placeholders "SHORT" and "MEDIUM" are fine. + if (DateFormatter.SHORT_FORMAT.equals(value) || + DateFormatter.MEDIUM_FORMAT.equals(value)) { + return true; + } + + // If the SimpleDateFormat constructor doesn't throw an exception, we're good. new SimpleDateFormat(value); return true; } catch (Exception e) { From dfa97cd878dceb6821a5e98c722d5bf3c5c5a02d Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 1 Oct 2011 20:11:14 +0200 Subject: [PATCH 079/116] Handle activity restarts due to configuration changes Close existing dialogs before the activity is destroyed. Recreate them and rewire AsyncTasks to the new activity instance after its creation. --- res/values/strings.xml | 9 +- src/com/fsck/k9/activity/Accounts.java | 397 ++++++++++++------ .../k9/activity/misc/ExtendedAsyncTask.java | 101 +++++ 3 files changed, 377 insertions(+), 130 deletions(-) create mode 100644 src/com/fsck/k9/activity/misc/ExtendedAsyncTask.java diff --git a/res/values/strings.xml b/res/values/strings.xml index d7e8f4902..09880400a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1034,9 +1034,12 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Please enter the password you used when exporting your settings: Export account settings Export settings and accounts + Import + Export Import settings Exporting settings... Importing settings... + Scanning file... Saved exported settings to %s Imported %s from %s @@ -1049,9 +1052,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Export failed Import succeeded Import failed - + Unable to handle file of version %s - + Account \"%s\" is unavailable; check storage - + diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 00c96f054..62c819ce7 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -13,6 +13,7 @@ import java.util.concurrent.ConcurrentHashMap; import android.app.AlertDialog; import android.app.Dialog; +import android.app.ProgressDialog; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; @@ -20,7 +21,6 @@ import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; -import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.util.Log; @@ -58,6 +58,7 @@ 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.misc.ExtendedAsyncTask; import com.fsck.k9.activity.setup.AccountSettings; import com.fsck.k9.activity.setup.AccountSetupBasics; import com.fsck.k9.activity.setup.Prefs; @@ -104,6 +105,30 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private SearchAccount integratedInboxAccount = null; private FontSizes mFontSizes = K9.getFontSizes(); + /** + * Contains a reference to a {@link ExtendedAsyncTask} while it is running. + */ + private ExtendedAsyncTask mAsyncTask; + + /** + * Contains information about the currently displayed dialog (if available). + * + *

+ * This object is returned from {@link #onRetainNonConfigurationInstance()} if a dialog is + * being displayed while the activity is being restarted. It is then used by the new activity + * instance to re-create that dialog. + *

+ */ + private DialogInfo mDialogInfo; + + /** + * Reference to the dialog currently being displayed (if available). + * + * @see #showDialog(int, String) + */ + private AlertDialog mDialog; + + private static final int ACTIVITY_REQUEST_PICK_SETTINGS_FILE = 1; class AccountsHandler extends Handler { @@ -310,34 +335,48 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC Intent intent = getIntent(); boolean startup = intent.getData() == null && intent.getBooleanExtra(EXTRA_STARTUP, true); onNewIntent(intent); + if (startup && K9.startIntegratedInbox()) { onOpenAccount(integratedInboxAccount); finish(); + return; } else if (startup && accounts.length == 1 && onOpenAccount(accounts[0])) { - // fall through to "else" if !onOpenAccount() finish(); - } else { - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - requestWindowFeature(Window.FEATURE_PROGRESS); - - setContentView(R.layout.accounts); - ListView listView = getListView(); - listView.setOnItemClickListener(this); - listView.setItemsCanFocus(false); - listView.setEmptyView(findViewById(R.id.empty)); - findViewById(R.id.next).setOnClickListener(this); - registerForContextMenu(listView); - - if (icicle != null && icicle.containsKey(SELECTED_CONTEXT_ACCOUNT)) { - String accountUuid = icicle.getString("selectedContextAccount"); - mSelectedContextAccount = Preferences.getPreferences(this).getAccount(accountUuid); - } - - restoreAccountStats(icicle); + return; } + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + requestWindowFeature(Window.FEATURE_PROGRESS); + setContentView(R.layout.accounts); + ListView listView = getListView(); + listView.setOnItemClickListener(this); + listView.setItemsCanFocus(false); + listView.setEmptyView(findViewById(R.id.empty)); + findViewById(R.id.next).setOnClickListener(this); + registerForContextMenu(listView); + if (icicle != null && icicle.containsKey(SELECTED_CONTEXT_ACCOUNT)) { + String accountUuid = icicle.getString("selectedContextAccount"); + mSelectedContextAccount = Preferences.getPreferences(this).getAccount(accountUuid); + } + + restoreAccountStats(icicle); + + // Handle activity restarts because of a configuration change (e.g. rotating the screen) + Object retained = getLastNonConfigurationInstance(); + if (retained != null) { + // If we displayed a dialog before the configuration change, re-create it here + if (retained instanceof DialogInfo) { + DialogInfo dialogInfo = (DialogInfo) retained; + showDialog(dialogInfo.headerRes, dialogInfo.message); + } + // If there's an ExtendedAsyncTask running, update it with the new Activity + else if (retained instanceof ExtendedAsyncTask) { + mAsyncTask = (ExtendedAsyncTask) retained; + mAsyncTask.attach(this); + } + } } @SuppressWarnings("unchecked") @@ -389,6 +428,22 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } + /** + * Save the reference to a currently displayed dialog or a running AsyncTask (if available). + */ + @Override + public Object onRetainNonConfigurationInstance() { + Object retain = null; + if (mDialogInfo != null) { + retain = mDialogInfo; + dismissDialog(); + } else if (mAsyncTask != null) { + retain = mAsyncTask; + mAsyncTask.detach(); + } + return retain; + } + private void refresh() { BaseAccount[] accounts = Preferences.getPreferences(this).getAccounts(); @@ -868,28 +923,73 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC Log.i(K9.LOG_TAG, "onImport importing from URI " + uri.toString()); - new ListImportContentsAsyncTask(uri, null).execute(); + mAsyncTask = new ListImportContentsAsyncTask(this, uri, null); + mAsyncTask.execute(); } - private void showDialog(final Context context, final int headerRes, final String message) { - this.runOnUiThread(new Runnable() { + private void asyncTaskFinished() { + mAsyncTask = null; + } + + /** + * Stores information about a dialog. + * + * @see Accounts#showDialog(int, String) + * @see Accounts#onCreate(Bundle) + */ + private static class DialogInfo { + public final int headerRes; + + //TODO: "message" is already localized. This is a problem if the activity is restarted when + // the system language was changed. We have to recreate the message string in that case. + public final String message; + + DialogInfo(int headerRes, String message) { + this.headerRes = headerRes; + this.message = message; + } + } + + /** + * Show a dialog. + * + * @param headerRes + * The resource ID of the string that is used as title for the dialog box. + * @param message + * The message to display. + */ + private void showDialog(final int headerRes, final String message) { + runOnUiThread(new Runnable() { @Override public void run() { - final AlertDialog.Builder builder = new AlertDialog.Builder(context); + // Store information about the dialog so it can be re-created when the activity is + // restarted due to a configuration change. + mDialogInfo = new DialogInfo(headerRes, message); + + final AlertDialog.Builder builder = new AlertDialog.Builder(Accounts.this); 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(); + dismissDialog(); } }); - builder.show(); + mDialog = builder.show(); } }); } + /** + * Dismiss the dialog that was created using {@link #showDialog(int, String)}. + */ + private void dismissDialog() { + mDialog.dismiss(); + mDialogInfo = null; + mDialog = null; + } + class AccountsAdapter extends ArrayAdapter { public AccountsAdapter(BaseAccount[] accounts) { super(Accounts.this, 0, accounts); @@ -1112,34 +1212,39 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC }) .show(); */ - new ExportAsyncTask(includeGlobals, accountUuids, null).execute(); + mAsyncTask = new ExportAsyncTask(this, includeGlobals, accountUuids, null); + mAsyncTask.execute(); } - private class ExportAsyncTask extends AsyncTask { + /** + * Handles exporting of global settings and/or accounts in a background thread. + */ + private static class ExportAsyncTask extends ExtendedAsyncTask { private boolean mIncludeGlobals; private Set mAccountUuids; private String mEncryptionKey; private String mFileName; - private ExportAsyncTask(boolean includeGlobals, Set accountUuids, - String encryptionKey) { + + private ExportAsyncTask(Accounts activity, boolean includeGlobals, + Set accountUuids, String encryptionKey) { + super(activity); mIncludeGlobals = includeGlobals; mAccountUuids = accountUuids; mEncryptionKey = encryptionKey; } @Override - protected void onPreExecute() { - //TODO: show progress bar instead of displaying toast - String toastText = Accounts.this.getString(R.string.settings_exporting); - Toast toast = Toast.makeText(Accounts.this, toastText, Toast.LENGTH_SHORT); - toast.show(); + protected void showProgressDialog() { + String title = mContext.getString(R.string.settings_export_dialog_title); + String message = mContext.getString(R.string.settings_exporting); + mProgressDialog = ProgressDialog.show(mActivity, title, message, true); } @Override protected Boolean doInBackground(Void... params) { try { - mFileName = StorageExporter.exportToFile(Accounts.this, mIncludeGlobals, + mFileName = StorageExporter.exportToFile(mContext, mIncludeGlobals, mAccountUuids, mEncryptionKey); } catch (StorageImportExportException e) { Log.w(K9.LOG_TAG, "Exception during export", e); @@ -1150,18 +1255,28 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override protected void onPostExecute(Boolean success) { + Accounts activity = (Accounts) mActivity; + + // Let the activity know that the background task is complete + activity.asyncTaskFinished(); + + removeProgressDialog(); + if (success) { - showDialog(Accounts.this, R.string.settings_export_success_header, - Accounts.this.getString(R.string.settings_export_success, mFileName)); + activity.showDialog(R.string.settings_export_success_header, + mContext.getString(R.string.settings_export_success, mFileName)); } else { //TODO: make the exporter return an error code; translate that error code to a localized string here - showDialog(Accounts.this, R.string.settings_export_failed_header, - Accounts.this.getString(R.string.settings_export_failure, "Something went wrong")); + activity.showDialog(R.string.settings_export_failed_header, + mContext.getString(R.string.settings_export_failure, "Something went wrong")); } } } - private class ImportAsyncTask extends AsyncTask { + /** + * Handles importing of global settings and/or accounts in a background thread. + */ + private static class ImportAsyncTask extends ExtendedAsyncTask { private boolean mIncludeGlobals; private List mAccountUuids; private boolean mOverwrite; @@ -1169,8 +1284,10 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private InputStream mInputStream; private ImportResults mImportResults; - private ImportAsyncTask(boolean includeGlobals, List accountUuids, - boolean overwrite, String encryptionKey, InputStream is) { + private ImportAsyncTask(Accounts activity, boolean includeGlobals, + List accountUuids, boolean overwrite, String encryptionKey, + InputStream is) { + super(activity); mIncludeGlobals = includeGlobals; mAccountUuids = accountUuids; mOverwrite = overwrite; @@ -1179,17 +1296,16 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } @Override - protected void onPreExecute() { - //TODO: show progress bar instead of displaying toast - String toastText = Accounts.this.getString(R.string.settings_importing); - Toast toast = Toast.makeText(Accounts.this, toastText, Toast.LENGTH_SHORT); - toast.show(); + protected void showProgressDialog() { + String title = mContext.getString(R.string.settings_import_dialog_title); + String message = mContext.getString(R.string.settings_importing); + mProgressDialog = ProgressDialog.show(mActivity, title, message, true); } @Override protected Boolean doInBackground(Void... params) { try { - mImportResults = StorageImporter.importSettings(Accounts.this, mInputStream, + mImportResults = StorageImporter.importSettings(mContext, mInputStream, mEncryptionKey, mIncludeGlobals, mAccountUuids, mOverwrite); } catch (StorageImportExportException e) { Log.w(K9.LOG_TAG, "Exception during export", e); @@ -1200,49 +1316,60 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override protected void onPostExecute(Boolean success) { + Accounts activity = (Accounts) mActivity; + + // Let the activity know that the background task is complete + activity.asyncTaskFinished(); + + removeProgressDialog(); + if (success) { int imported = mImportResults.importedAccounts.size(); //TODO: display names of imported accounts (name from file *and* possibly new name) - showDialog(Accounts.this, R.string.settings_import_success_header, + activity.showDialog(R.string.settings_import_success_header, //FIXME: use correct file name - Accounts.this.getString(R.string.settings_import_success, imported, "filename")); - refresh(); + mContext.getString(R.string.settings_import_success, imported, "filename")); + activity.refresh(); } else { //TODO: make the importer return an error code; translate that error code to a localized string here - showDialog(Accounts.this, R.string.settings_import_failed_header, - Accounts.this.getString(R.string.settings_import_failure, "unknown", "Something went wrong")); + activity.showDialog(R.string.settings_import_failed_header, + mContext.getString(R.string.settings_import_failure, "unknown", "Something went wrong")); } } } - ImportContents mImportContents; - private class ListImportContentsAsyncTask extends AsyncTask { + private static class ListImportContentsAsyncTask extends ExtendedAsyncTask { private Uri mUri; private String mEncryptionKey; private InputStream mInputStream; + private ImportContents mImportContents; + + private ListImportContentsAsyncTask(Accounts activity, Uri uri, String encryptionKey) { + super(activity); - private ListImportContentsAsyncTask(Uri uri, String encryptionKey) { mUri = uri; mEncryptionKey = encryptionKey; } @Override - protected void onPreExecute() { - //TODO: show progress bar + protected void showProgressDialog() { + String title = mContext.getString(R.string.settings_import_dialog_title); + String message = mContext.getString(R.string.settings_import_scanning_file); + mProgressDialog = ProgressDialog.show(mActivity, title, message, true); } @Override protected Boolean doInBackground(Void... params) { try { - - InputStream is = getContentResolver().openInputStream(mUri); - mImportContents = StorageImporter.getImportStreamContents( - Accounts.this, is, mEncryptionKey); + ContentResolver resolver = mContext.getContentResolver(); + InputStream is = resolver.openInputStream(mUri); + mImportContents = StorageImporter.getImportStreamContents(mContext, is, + mEncryptionKey); // Open another InputStream in the background. This is used later by ImportAsyncTask - mInputStream = getContentResolver().openInputStream(mUri); + mInputStream = resolver.openInputStream(mUri); } catch (StorageImportExportException e) { Log.w(K9.LOG_TAG, "Exception during export", e); @@ -1257,80 +1384,96 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override protected void onPostExecute(Boolean success) { + Accounts activity = (Accounts) mActivity; + + // Let the activity know that the background task is complete + activity.asyncTaskFinished(); + + removeProgressDialog(); + if (success) { - final ListView importSelectionView = new ListView(Accounts.this); - List contents = new ArrayList(); - if (mImportContents.globalSettings) { - contents.add("Global settings"); + showImportSelectionDialog(); + } else { + //TODO: make the importer return an error code; translate that error code to a localized string here + activity.showDialog(R.string.settings_import_failed_header, + mContext.getString(R.string.settings_import_failure, "unknown", "Something went wrong")); + } + } + + //TODO: we need to be able to re-create this dialog after a configuration change + private void showImportSelectionDialog() { + final ListView importSelectionView = new ListView(mActivity); + List contents = new ArrayList(); + if (mImportContents.globalSettings) { + contents.add("Global settings"); + } + for (AccountDescription account : mImportContents.accounts) { + contents.add(account.name); + } + importSelectionView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + importSelectionView.setAdapter(new ArrayAdapter(mActivity, android.R.layout.simple_list_item_checked, contents)); + importSelectionView.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int pos, long id) { + CheckedTextView ctv = (CheckedTextView)view; + ctv.setChecked(!ctv.isChecked()); } - for (AccountDescription account : mImportContents.accounts) { - contents.add(account.name); - } - importSelectionView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - importSelectionView.setAdapter(new ArrayAdapter(Accounts.this, android.R.layout.simple_list_item_checked, contents)); - importSelectionView.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onNothingSelected(AdapterView arg0) { /* Do nothing */ } + }); + + //TODO: listview header: "Please select the settings you wish to import" + //TODO: listview footer: "Select all" / "Select none" buttons? + //TODO: listview footer: "Overwrite existing accounts?" checkbox + + final AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); + builder.setTitle("Import selection"); + builder.setView(importSelectionView); + builder.setInverseBackgroundForced(true); + builder.setPositiveButton(R.string.okay_action, + new DialogInterface.OnClickListener() { + @Override - public void onItemSelected(AdapterView parent, View view, int pos, long id) { - CheckedTextView ctv = (CheckedTextView)view; - ctv.setChecked(!ctv.isChecked()); + public void onClick(DialogInterface dialog, int which) { + ListAdapter adapter = importSelectionView.getAdapter(); + int count = adapter.getCount(); + SparseBooleanArray pos = importSelectionView.getCheckedItemPositions(); + + boolean includeGlobals = mImportContents.globalSettings ? pos.get(0) : false; + List accountUuids = new ArrayList(); + int start = mImportContents.globalSettings ? 1 : 0; + for (int i = start; i < count; i++) { + if (pos.get(i)) { + accountUuids.add(mImportContents.accounts.get(i-start).uuid); + } + } + + /* + * TODO: Think some more about this. Overwriting could change the store + * type. This requires some additional code in order to work smoothly + * while the app is running. + */ + boolean overwrite = false; + + dialog.dismiss(); + Accounts activity = (Accounts) mActivity; + ImportAsyncTask importAsyncTask = new ImportAsyncTask(activity, includeGlobals, accountUuids, overwrite, mEncryptionKey, mInputStream); + activity.mAsyncTask = importAsyncTask; + importAsyncTask.execute(); } - - @Override - public void onNothingSelected(AdapterView arg0) {} }); - - //TODO: listview header: "Please select the settings you wish to import" - //TODO: listview footer: "Select all" / "Select none" buttons? - //TODO: listview footer: "Overwrite existing accounts?" checkbox - - final AlertDialog.Builder builder = new AlertDialog.Builder(Accounts.this); - builder.setTitle("Import selection"); - builder.setView(importSelectionView); - builder.setInverseBackgroundForced(true); - builder.setPositiveButton(R.string.okay_action, + builder.setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - ListAdapter adapter = importSelectionView.getAdapter(); - int count = adapter.getCount(); - SparseBooleanArray pos = importSelectionView.getCheckedItemPositions(); - - boolean includeGlobals = mImportContents.globalSettings ? pos.get(0) : false; - List accountUuids = new ArrayList(); - int start = mImportContents.globalSettings ? 1 : 0; - for (int i = start; i < count; i++) { - if (pos.get(i)) { - accountUuids.add(mImportContents.accounts.get(i-start).uuid); - } - } - - /* - * TODO: Think some more about this. Overwriting could change the store - * type. This requires some additional code in order to work smoothly - * while the app is running. - */ - boolean overwrite = false; - dialog.dismiss(); - new ImportAsyncTask(includeGlobals, accountUuids, overwrite, mEncryptionKey, mInputStream).execute(); + try { + mInputStream.close(); + } catch (Exception e) { /* Ignore */ } } }); - builder.setNegativeButton(R.string.cancel_action, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - try { - mInputStream.close(); - } catch (Exception e) { /* Ignore */ } - } - }); - builder.show(); - } else { - //TODO: make the importer return an error code; translate that error code to a localized string here - showDialog(Accounts.this, R.string.settings_import_failed_header, - Accounts.this.getString(R.string.settings_import_failure, "unknown", "Something went wrong")); - } + builder.show(); } } } diff --git a/src/com/fsck/k9/activity/misc/ExtendedAsyncTask.java b/src/com/fsck/k9/activity/misc/ExtendedAsyncTask.java new file mode 100644 index 000000000..f488bc929 --- /dev/null +++ b/src/com/fsck/k9/activity/misc/ExtendedAsyncTask.java @@ -0,0 +1,101 @@ +package com.fsck.k9.activity.misc; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.os.AsyncTask; + +/** + * Extends {@link AsyncTask} with methods to attach and detach an {@link Activity}. + * + *

+ * This is necessary to properly handle configuration changes that will restart an activity. + *

+ * Note: + * Implementing classes need to make sure they have no reference to the {@code Activity} instance + * that created the instance of that class. So if it's implemented as inner class, it needs to be + * {@code static}. + *

+ * + * @param + * see {@link AsyncTask} + * @param + * see {@link AsyncTask} + * @param + * see {@link AsyncTask} + * + * @see #attach(Activity) + * @see #detach() + */ +public abstract class ExtendedAsyncTask + extends AsyncTask { + protected Activity mActivity; + protected Context mContext; + protected ProgressDialog mProgressDialog; + + protected ExtendedAsyncTask(Activity activity) { + mActivity = activity; + mContext = activity.getApplicationContext(); + } + + /** + * Connect this {@link AsyncTask} to a new {@link Activity} instance after the activity + * was restarted due to a configuration change. + * + *

+ * This also creates a new progress dialog that is bound to the new activity. + *

+ * + * @param activity + * The new {@code Activity} instance. Never {@code null}. + */ + public void attach(Activity activity) { + mActivity = activity; + showProgressDialog(); + } + + /** + * Detach this {@link AsyncTask} from the {@link Activity} it was bound to. + * + *

+ * This needs to be called when the current activity is being destroyed during an activity + * restart due to a configuration change.
+ * We also have to destroy the progress dialog because it's bound to the activity that's + * being destroyed. + *

+ * + * @see Activity#onRetainNonConfigurationInstance() + */ + public void detach() { + removeProgressDialog(); + mActivity = null; + } + + /** + * Creates a {@link ProgressDialog} that is shown while the background thread is running. + * + *

+ * This needs to store a {@code ProgressDialog} instance in {@link #mProgressDialog} or + * override {@link #removeProgressDialog()}. + *

+ */ + protected abstract void showProgressDialog(); + + protected void removeProgressDialog() { + mProgressDialog.dismiss(); + mProgressDialog = null; + } + + /** + * This default implementation only creates a progress dialog. + * + *

+ * Important: + * Be sure to call {@link #removeProgressDialog()} in {@link AsyncTask#onPostExecute(Object)}. + *

+ */ + @Override + protected void onPreExecute() { + showProgressDialog(); + } +} From 33ae5ff7d06a413104883098251ec72a6f2a294b Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 3 Oct 2011 20:16:02 +0200 Subject: [PATCH 080/116] Added code to skip some account settings on export --- .../fsck/k9/preferences/StorageExporter.java | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 5f5c490e9..21b706f7d 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -68,16 +68,35 @@ public class StorageExporter { */ private static final Set SKIP_GLOBAL_SETTINGS; + /** + * List of keys of account settings that don't need to or shouldn't be exported. + */ + private static final Set SKIP_ACCOUNT_SETTINGS; + static { - Set skip = new HashSet(); + Set skipGlobal = new HashSet(); // No need to export the "accountUuids" field. It will be (re)created by the import code. - skip.add("accountUuids"); + skipGlobal.add("accountUuids"); // "defaultAccountUuid" is also handled by the import code. - skip.add("defaultAccountUuid"); + skipGlobal.add("defaultAccountUuid"); - SKIP_GLOBAL_SETTINGS = skip; + SKIP_GLOBAL_SETTINGS = skipGlobal; + + + Set skipAccount = new HashSet(); + + // No need to export the "accountNumber" field. It will be (re)created by the import code. + skipAccount.add("accountNumber"); + + // The values for the following keys get their own XML element in the export file and + // therefore don't need to be stored with the other account settings. + skipAccount.add(Account.ACCOUNT_DESCRIPTION_KEY); + skipAccount.add(Account.STORE_URI_KEY); + skipAccount.add(Account.TRANSPORT_URI_KEY); + + SKIP_ACCOUNT_SETTINGS = skipAccount; } @@ -282,10 +301,7 @@ public class StorageExporter { String keyUuid = comps[0]; String secondPart = comps[1]; - if (!keyUuid.equals(accountUuid) - || Account.ACCOUNT_DESCRIPTION_KEY.equals(secondPart) - || "storeUri".equals(secondPart) - || "transportUri".equals(secondPart)) { + if (!keyUuid.equals(accountUuid) || SKIP_ACCOUNT_SETTINGS.contains(secondPart)) { continue; } if (comps.length == 3) { From f2a37529303c23def48e2734711bc68b957f5ef9 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 4 Oct 2011 00:09:38 +0200 Subject: [PATCH 081/116] Added constants for (export) file format version and "settings version" --- src/com/fsck/k9/preferences/Settings.java | 13 +++++++++++++ .../fsck/k9/preferences/StorageExporter.java | 18 ++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/com/fsck/k9/preferences/Settings.java b/src/com/fsck/k9/preferences/Settings.java index c1b763c5f..98a97717d 100644 --- a/src/com/fsck/k9/preferences/Settings.java +++ b/src/com/fsck/k9/preferences/Settings.java @@ -20,6 +20,19 @@ import com.fsck.k9.K9; */ public class Settings { + /** + * Version number of global and account settings. + * + *

+ * This value is used as "version" attribute in the export file. It needs to be incremented + * when a global or account setting is added or removed, or when the format of a setting + * is changed (e.g. add a value to an enum). + *

+ * + * @see StorageExporter + */ + public static final int VERSION = 1; + public static final IDefaultValue EXCEPTION_DEFAULT_VALUE = new ExceptionDefaultValue(); public static final ISettingValidator BOOLEAN_VALIDATOR = new BooleanValidator(); diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 21b706f7d..3464febf9 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -34,6 +34,17 @@ import com.fsck.k9.mail.store.LocalStore; public class StorageExporter { private static final String EXPORT_FILENAME = "settings.k9s"; + /** + * File format version number. + * + *

+ * Increment this if you need to change the structure of the settings file. When you do this + * remember that we also have to be able to handle old file formats. So have fun adding support + * for that to {@link StorageImporter} :) + *

+ */ + public static final int FILE_FORMAT_VERSION = 1; + public static final String ROOT_ELEMENT = "k9settings"; public static final String VERSION_ATTRIBUTE = "version"; public static final String FILE_FORMAT_ATTRIBUTE = "format"; @@ -160,10 +171,9 @@ public class StorageExporter { serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); serializer.startTag(null, ROOT_ELEMENT); - //TODO: write content version number here - serializer.attribute(null, VERSION_ATTRIBUTE, "x"); - //TODO: set file format version to "1" once the feature is stable and about to be merged into master - serializer.attribute(null, FILE_FORMAT_ATTRIBUTE, "y"); + serializer.attribute(null, VERSION_ATTRIBUTE, Integer.toString(Settings.VERSION)); + serializer.attribute(null, FILE_FORMAT_ATTRIBUTE, + Integer.toString(FILE_FORMAT_VERSION)); Log.i(K9.LOG_TAG, "Exporting preferences"); From 644571cfe512a21e84764a4adcb34cf40da9a335 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 4 Oct 2011 00:48:43 +0200 Subject: [PATCH 082/116] Make sure the InputStream of the import file is always closed --- src/com/fsck/k9/activity/Accounts.java | 44 ++++++++++++------- .../fsck/k9/preferences/StorageImporter.java | 4 -- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 62c819ce7..646cd1712 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -2,6 +2,7 @@ package com.fsck.k9.activity; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; @@ -1281,18 +1282,18 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private List mAccountUuids; private boolean mOverwrite; private String mEncryptionKey; - private InputStream mInputStream; + private Uri mUri; private ImportResults mImportResults; private ImportAsyncTask(Accounts activity, boolean includeGlobals, List accountUuids, boolean overwrite, String encryptionKey, - InputStream is) { + Uri uri) { super(activity); mIncludeGlobals = includeGlobals; mAccountUuids = accountUuids; mOverwrite = overwrite; mEncryptionKey = encryptionKey; - mInputStream = is; + mUri = uri; } @Override @@ -1305,10 +1306,20 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override protected Boolean doInBackground(Void... params) { try { - mImportResults = StorageImporter.importSettings(mContext, mInputStream, - mEncryptionKey, mIncludeGlobals, mAccountUuids, mOverwrite); + InputStream is = mContext.getContentResolver().openInputStream(mUri); + try { + mImportResults = StorageImporter.importSettings(mContext, is, + mEncryptionKey, mIncludeGlobals, mAccountUuids, mOverwrite); + } finally { + try { + is.close(); + } catch (IOException e) { /* Ignore */ } + } } catch (StorageImportExportException e) { - Log.w(K9.LOG_TAG, "Exception during export", e); + Log.w(K9.LOG_TAG, "Exception during import", e); + return false; + } catch (FileNotFoundException e) { + Log.w(K9.LOG_TAG, "Couldn't open import file", e); return false; } return true; @@ -1343,7 +1354,6 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private static class ListImportContentsAsyncTask extends ExtendedAsyncTask { private Uri mUri; private String mEncryptionKey; - private InputStream mInputStream; private ImportContents mImportContents; private ListImportContentsAsyncTask(Accounts activity, Uri uri, String encryptionKey) { @@ -1365,12 +1375,14 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC try { ContentResolver resolver = mContext.getContentResolver(); InputStream is = resolver.openInputStream(mUri); - mImportContents = StorageImporter.getImportStreamContents(mContext, is, - mEncryptionKey); - - // Open another InputStream in the background. This is used later by ImportAsyncTask - mInputStream = resolver.openInputStream(mUri); - + try { + mImportContents = StorageImporter.getImportStreamContents(mContext, is, + mEncryptionKey); + } finally { + try { + is.close(); + } catch (IOException e) { /* Ignore */ } + } } catch (StorageImportExportException e) { Log.w(K9.LOG_TAG, "Exception during export", e); return false; @@ -1458,7 +1470,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC dialog.dismiss(); Accounts activity = (Accounts) mActivity; - ImportAsyncTask importAsyncTask = new ImportAsyncTask(activity, includeGlobals, accountUuids, overwrite, mEncryptionKey, mInputStream); + ImportAsyncTask importAsyncTask = new ImportAsyncTask(activity, + includeGlobals, accountUuids, overwrite, mEncryptionKey, mUri); activity.mAsyncTask = importAsyncTask; importAsyncTask.execute(); } @@ -1468,9 +1481,6 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); - try { - mInputStream.close(); - } catch (Exception e) { /* Ignore */ } } }); builder.show(); diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index d721aa20e..76ac954d2 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -497,10 +497,6 @@ public class StorageImporter { return imported; } catch (Exception e) { throw new StorageImportExportException(e); - } finally { - try { - inputStream.close(); - } catch (Exception e) { /* Ignore */ } } } From 83ee4253d5d0c8d4f6ee560aedb9ee02c12a7ea8 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 4 Oct 2011 05:13:17 +0200 Subject: [PATCH 083/116] Whitelist settings for export instead of blacklisting keys Use GlobalSettings.SETTINGS an AccountSettings.SETTINGS to decide which settings to export. --- .../fsck/k9/preferences/StorageExporter.java | 118 +++++++----------- 1 file changed, 45 insertions(+), 73 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index 3464febf9..ba98559cd 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -74,42 +74,6 @@ public class StorageExporter { public static final String EMAIL_ELEMENT = "email"; public static final String DESCRIPTION_ELEMENT = "description"; - /** - * List of keys of global settings that don't need to or shouldn't be exported. - */ - private static final Set SKIP_GLOBAL_SETTINGS; - - /** - * List of keys of account settings that don't need to or shouldn't be exported. - */ - private static final Set SKIP_ACCOUNT_SETTINGS; - - static { - Set skipGlobal = new HashSet(); - - // No need to export the "accountUuids" field. It will be (re)created by the import code. - skipGlobal.add("accountUuids"); - - // "defaultAccountUuid" is also handled by the import code. - skipGlobal.add("defaultAccountUuid"); - - SKIP_GLOBAL_SETTINGS = skipGlobal; - - - Set skipAccount = new HashSet(); - - // No need to export the "accountNumber" field. It will be (re)created by the import code. - skipAccount.add("accountNumber"); - - // The values for the following keys get their own XML element in the export file and - // therefore don't need to be stored with the other account settings. - skipAccount.add(Account.ACCOUNT_DESCRIPTION_KEY); - skipAccount.add(Account.STORE_URI_KEY); - skipAccount.add(Account.TRANSPORT_URI_KEY); - - SKIP_ACCOUNT_SETTINGS = skipAccount; - } - public static String exportToFile(Context context, boolean includeGlobals, Set accountUuids, String encryptionKey) @@ -218,14 +182,13 @@ public class StorageExporter { private static void writeSettings(XmlSerializer serializer, Map prefs) throws IOException { - for (Map.Entry entry : prefs.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue().toString(); - if (key.indexOf('.') != -1 || SKIP_GLOBAL_SETTINGS.contains(key)) { - // Skip account entries and keys we don't want/need to export - continue; + for (String key : GlobalSettings.SETTINGS.keySet()) { + Object value = prefs.get(key); + + if (value != null) { + String outputValue = value.toString(); + writeKeyValue(serializer, key, outputValue); } - writeKeyValue(serializer, key, value); } } @@ -307,41 +270,50 @@ public class StorageExporter { String key = entry.getKey(); String value = entry.getValue().toString(); String[] comps = key.split("\\."); - if (comps.length >= 2) { - String keyUuid = comps[0]; - String secondPart = comps[1]; - if (!keyUuid.equals(accountUuid) || SKIP_ACCOUNT_SETTINGS.contains(secondPart)) { - continue; - } - if (comps.length == 3) { - String thirdPart = comps[2]; - - if (Account.IDENTITY_KEYS.contains(secondPart)) { - // This is an identity key. Save identity index for later... - try { - identities.add(Integer.parseInt(thirdPart)); - } catch (NumberFormatException e) { /* ignore */ } - // ... but don't write it now. - continue; - } - - if (LocalStore.FOLDER_SETTINGS_KEYS.contains(thirdPart)) { - // This is a folder key. Save folder name for later... - folders.add(secondPart); - // ... but don't write it now. - continue; - } - } - } else { - // Skip global config entries and identity entries + if (comps.length < 2) { + // Skip global settings continue; } - // Strip account UUID from key - String keyPart = key.substring(comps[0].length() + 1); + String keyUuid = comps[0]; + String secondPart = comps[1]; - writeKeyValue(serializer, keyPart, value); + if (!keyUuid.equals(accountUuid)) { + // Setting doesn't belong to the account we're currently writing. + continue; + } + + String keyPart; + if (comps.length >= 3) { + String thirdPart = comps[2]; + + if (Account.IDENTITY_KEYS.contains(secondPart)) { + // This is an identity key. Save identity index for later... + try { + identities.add(Integer.parseInt(thirdPart)); + } catch (NumberFormatException e) { /* ignore */ } + // ... but don't write it now. + continue; + } + + if (LocalStore.FOLDER_SETTINGS_KEYS.contains(thirdPart)) { + // This is a folder key. Save folder name for later... + folders.add(secondPart); + // ... but don't write it now. + continue; + } + + // Strip account UUID from key + keyPart = key.substring(comps[0].length() + 1); + } else { + keyPart = secondPart; + } + + if (AccountSettings.SETTINGS.containsKey(keyPart)) { + // Only export account settings that can be found in AccountSettings.SETTINGS + writeKeyValue(serializer, keyPart, value); + } } serializer.endTag(null, SETTINGS_ELEMENT); From 0920b0c14d1f4f562a1fbd2b95d4adb4d5ccec09 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 7 Oct 2011 20:29:03 +0200 Subject: [PATCH 084/116] Changed the way SettingsDescription is used Added ability to rewrite the string representation used internally to something "pretty" on export. Now only settings that have entries is GlobalSettings and AccountSettings are exported. This prevents export of newer settings that are left in the preference storage when downgrading. --- .../fsck/k9/preferences/AccountSettings.java | 395 ++++++++-------- .../fsck/k9/preferences/GlobalSettings.java | 299 ++++++------ src/com/fsck/k9/preferences/Settings.java | 428 +++++++++++++----- .../fsck/k9/preferences/StorageExporter.java | 28 +- 4 files changed, 677 insertions(+), 473 deletions(-) diff --git a/src/com/fsck/k9/preferences/AccountSettings.java b/src/com/fsck/k9/preferences/AccountSettings.java index 7e7c2c5c6..4666f3afa 100644 --- a/src/com/fsck/k9/preferences/AccountSettings.java +++ b/src/com/fsck/k9/preferences/AccountSettings.java @@ -1,18 +1,16 @@ package com.fsck.k9.preferences; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import android.content.SharedPreferences; -import android.net.Uri; -import android.util.Log; import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.R; import com.fsck.k9.Account.FolderMode; import com.fsck.k9.Account.ScrollButtons; import com.fsck.k9.crypto.Apg; -import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.preferences.Settings.*; @@ -22,144 +20,73 @@ public class AccountSettings { static { SETTINGS = new LinkedHashMap(); - // mandatory - /* - SETTINGS.put("storeUri", - SD(SettingType.STRING, Settings.EXCEPTION_DEFAULT_VALUE, new StoreUriValidator())); - SETTINGS.put("transportUri", - SD(SettingType.STRING, Settings.EXCEPTION_DEFAULT_VALUE, - new TransportUriValidator())); - */ - - SETTINGS.put("archiveFolderName", - SD(SettingType.STRING, "Archive", null)); - SETTINGS.put("autoExpandFolderName", - SD(SettingType.STRING, "INBOX", null)); + SETTINGS.put("archiveFolderName", new StringSetting("Archive")); + SETTINGS.put("autoExpandFolderName", new StringSetting("INBOX")); SETTINGS.put("automaticCheckIntervalMinutes", - SD(SettingType.INTEGER, -1, new ResourceArrayValidator( - R.array.account_settings_check_frequency_values))); - SETTINGS.put("chipColor", - SD(SettingType.INTEGER, 0xff0000ff, Settings.SOLID_COLOR_VALIDATOR)); - SETTINGS.put("cryptoApp", - SD(SettingType.STRING, Apg.NAME, null)); - SETTINGS.put("cryptoAutoSignature", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("deletePolicy", - SD(SettingType.STRING, 0, new ResourceArrayValidator( - R.array.account_setup_delete_policy_values))); - SETTINGS.put("displayCount", - SD(SettingType.STRING, K9.DEFAULT_VISIBLE_LIMIT, new ResourceArrayValidator( - R.array.account_settings_display_count_values))); - SETTINGS.put("draftsFolderName", - SD(SettingType.STRING, "Drafts", null)); - SETTINGS.put("enableMoveButtons", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("expungePolicy", - SD(SettingType.STRING, Account.EXPUNGE_IMMEDIATELY, new ResourceArrayValidator( - R.array.account_setup_expunge_policy_values))); + new IntegerResourceSetting(-1, R.array.account_settings_check_frequency_values)); + SETTINGS.put("chipColor", new ColorSetting(0xFF0000FF)); + SETTINGS.put("cryptoApp", new StringSetting(Apg.NAME)); + SETTINGS.put("cryptoAutoSignature", new BooleanSetting(false)); + SETTINGS.put("deletePolicy", new DeletePolicySetting(Account.DELETE_POLICY_NEVER)); + SETTINGS.put("displayCount", new IntegerResourceSetting(K9.DEFAULT_VISIBLE_LIMIT, + R.array.account_settings_display_count_values)); + SETTINGS.put("draftsFolderName", new StringSetting("Drafts")); + SETTINGS.put("enableMoveButtons", new BooleanSetting(false)); + SETTINGS.put("expungePolicy", new StringResourceSetting(Account.EXPUNGE_IMMEDIATELY, + R.array.account_setup_expunge_policy_values)); SETTINGS.put("folderDisplayMode", - SD(SettingType.ENUM, FolderMode.NOT_SECOND_CLASS, new ResourceArrayValidator( - R.array.account_settings_folder_display_mode_values))); - SETTINGS.put("folderPushMode", - SD(SettingType.ENUM, FolderMode.FIRST_CLASS, new ResourceArrayValidator( - R.array.account_settings_folder_push_mode_values))); - SETTINGS.put("folderSyncMode", - SD(SettingType.ENUM, FolderMode.FIRST_CLASS, new ResourceArrayValidator( - R.array.folder_settings_folder_sync_mode_values))); + new EnumSetting(FolderMode.class, FolderMode.NOT_SECOND_CLASS)); + SETTINGS.put("folderPushMode", new EnumSetting(FolderMode.class, FolderMode.FIRST_CLASS)); + SETTINGS.put("folderSyncMode", new EnumSetting(FolderMode.class, FolderMode.FIRST_CLASS)); SETTINGS.put("folderTargetMode", - SD(SettingType.ENUM, FolderMode.NOT_SECOND_CLASS, new ResourceArrayValidator( - R.array.account_settings_folder_target_mode_values))); - SETTINGS.put("goToUnreadMessageSearch", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("hideButtonsEnum", - SD(SettingType.ENUM, ScrollButtons.NEVER, new ResourceArrayValidator( - R.array.account_settings_hide_buttons_values))); + new EnumSetting(FolderMode.class, FolderMode.NOT_SECOND_CLASS)); + SETTINGS.put("goToUnreadMessageSearch", new BooleanSetting(false)); + SETTINGS.put("hideButtonsEnum", new EnumSetting(ScrollButtons.class, ScrollButtons.NEVER)); SETTINGS.put("hideMoveButtonsEnum", - SD(SettingType.ENUM, ScrollButtons.NEVER, new ResourceArrayValidator( - R.array.account_settings_hide_move_buttons_values))); - SETTINGS.put("idleRefreshMinutes", - SD(SettingType.INTEGER, 24, new ResourceArrayValidator( - R.array.idle_refresh_period_values))); - SETTINGS.put("led", - SD(SettingType.BOOLEAN, true, null)); - SETTINGS.put("ledColor", - SD(SettingType.INTEGER, 0xff0000ff, Settings.SOLID_COLOR_VALIDATOR)); - SETTINGS.put("localStorageProvider", - SD(SettingType.STRING, new StorageProviderDefaultValue(), - new StorageProviderValidator())); - SETTINGS.put("maxPushFolders", - SD(SettingType.INTEGER, 10, Settings.POSITIVE_INTEGER_VALIDATOR)); - SETTINGS.put("maximumAutoDownloadMessageSize", - SD(SettingType.ENUM, 32768, new ResourceArrayValidator( - R.array.account_settings_autodownload_message_size_values))); - SETTINGS.put("maximumPolledMessageAge", - SD(SettingType.ENUM, -1, new ResourceArrayValidator( - R.array.account_settings_message_age_values))); + new EnumSetting(ScrollButtons.class, ScrollButtons.NEVER)); + SETTINGS.put("idleRefreshMinutes", new IntegerResourceSetting(24, + R.array.idle_refresh_period_values)); + SETTINGS.put("led", new BooleanSetting(true)); + SETTINGS.put("ledColor", new ColorSetting(0xFF0000FF)); + SETTINGS.put("localStorageProvider", new StorageProviderSetting()); + SETTINGS.put("maxPushFolders", new IntegerRangeSetting(0, 100, 10)); + SETTINGS.put("maximumAutoDownloadMessageSize", new IntegerResourceSetting(32768, + R.array.account_settings_autodownload_message_size_values)); + SETTINGS.put("maximumPolledMessageAge", new IntegerResourceSetting(-1, + R.array.account_settings_message_age_values)); SETTINGS.put("messageFormat", - SD(SettingType.ENUM, Account.DEFAULT_MESSAGE_FORMAT, new ResourceArrayValidator( - R.array.account_settings_message_format_values))); - SETTINGS.put("notificationUnreadCount", - SD(SettingType.BOOLEAN, true, null)); - SETTINGS.put("notifyMailCheck", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("notifyNewMail", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("notifySelfNewMail", - SD(SettingType.BOOLEAN, true, null)); - SETTINGS.put("pushPollOnConnect", - SD(SettingType.BOOLEAN, true, null)); - SETTINGS.put("quotePrefix", - SD(SettingType.STRING, Account.DEFAULT_QUOTE_PREFIX, null)); + new EnumSetting(Account.MessageFormat.class, Account.DEFAULT_MESSAGE_FORMAT)); + SETTINGS.put("notificationUnreadCount", new BooleanSetting(true)); + SETTINGS.put("notifyMailCheck", new BooleanSetting(false)); + SETTINGS.put("notifyNewMail", new BooleanSetting(false)); + SETTINGS.put("notifySelfNewMail", new BooleanSetting(true)); + SETTINGS.put("pushPollOnConnect", new BooleanSetting(true)); + SETTINGS.put("quotePrefix", new StringSetting(Account.DEFAULT_QUOTE_PREFIX)); SETTINGS.put("quoteStyle", - SD(SettingType.ENUM, Account.DEFAULT_QUOTE_STYLE, new ResourceArrayValidator( - R.array.account_settings_quote_style_values))); - SETTINGS.put("replyAfterQuote", - SD(SettingType.BOOLEAN, Account.DEFAULT_REPLY_AFTER_QUOTE, null)); - SETTINGS.put("ring", - SD(SettingType.BOOLEAN, true, null)); + new EnumSetting(Account.QuoteStyle.class, Account.DEFAULT_QUOTE_STYLE)); + SETTINGS.put("replyAfterQuote", new BooleanSetting(Account.DEFAULT_REPLY_AFTER_QUOTE)); + SETTINGS.put("ring", new BooleanSetting(true)); SETTINGS.put("ringtone", - SD(SettingType.STRING, "content://settings/system/notification_sound", - new RingtoneValidator())); - SETTINGS.put("saveAllHeaders", - SD(SettingType.BOOLEAN, true, null)); + new RingtoneSetting("content://settings/system/notification_sound")); + SETTINGS.put("saveAllHeaders", new BooleanSetting(true)); SETTINGS.put("searchableFolders", - SD(SettingType.ENUM, Account.Searchable.ALL, new ResourceArrayValidator( - R.array.account_settings_searchable_values))); - SETTINGS.put("sentFolderName", - SD(SettingType.STRING, "Sent", null)); + new EnumSetting(Account.Searchable.class, Account.Searchable.ALL)); + SETTINGS.put("sentFolderName", new StringSetting("Sent")); SETTINGS.put("showPicturesEnum", - SD(SettingType.ENUM, Account.ShowPictures.NEVER, new ResourceArrayValidator( - R.array.account_settings_show_pictures_values))); - SETTINGS.put("signatureBeforeQuotedText", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("spamFolderName", - SD(SettingType.STRING, "Spam", null)); - SETTINGS.put("subscribedFoldersOnly", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("syncRemoteDeletions", - SD(SettingType.BOOLEAN, true, null)); - SETTINGS.put("trashFolderName", - SD(SettingType.STRING, "Trash", null)); - SETTINGS.put("useCompression.MOBILE", - SD(SettingType.BOOLEAN, true, null)); - SETTINGS.put("useCompression.OTHER", - SD(SettingType.BOOLEAN, true, null)); - SETTINGS.put("useCompression.WIFI", - SD(SettingType.BOOLEAN, true, null)); - SETTINGS.put("vibrate", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("vibratePattern", - SD(SettingType.INTEGER, 0, new ResourceArrayValidator( - R.array.account_settings_vibrate_pattern_values))); - SETTINGS.put("vibrateTimes", - SD(SettingType.INTEGER, 5, new ResourceArrayValidator( - R.array.account_settings_vibrate_times_label))); - } - - // Just to have shorter lines in SETTINGS initialization - private static SettingsDescription SD(SettingType type, - Object defaultValue, ISettingValidator validator) { - return new SettingsDescription(type, defaultValue, validator); + new EnumSetting(Account.ShowPictures.class, Account.ShowPictures.NEVER)); + SETTINGS.put("signatureBeforeQuotedText", new BooleanSetting(false)); + SETTINGS.put("spamFolderName", new StringSetting("Spam")); + SETTINGS.put("subscribedFoldersOnly", new BooleanSetting(false)); + SETTINGS.put("syncRemoteDeletions", new BooleanSetting(true)); + SETTINGS.put("trashFolderName", new StringSetting("Trash")); + SETTINGS.put("useCompression.MOBILE", new BooleanSetting(true)); + SETTINGS.put("useCompression.OTHER", new BooleanSetting(true)); + SETTINGS.put("useCompression.WIFI", new BooleanSetting(true)); + SETTINGS.put("vibrate", new BooleanSetting(false)); + SETTINGS.put("vibratePattern", new IntegerResourceSetting(0, + R.array.account_settings_vibrate_pattern_values)); + SETTINGS.put("vibrateTimes", new IntegerResourceSetting(5, + R.array.account_settings_vibrate_times_label)); } public static Map validate(Map importedSettings, @@ -179,98 +106,148 @@ public class AccountSettings { return result; } + /** + * An integer resource setting. + * + *

+ * Basically a {@link PseudoEnumSetting} that is initialized from a resource array containing + * integer strings. + *

+ */ + public static class IntegerResourceSetting extends PseudoEnumSetting { + private final Map mMapping; + + public IntegerResourceSetting(int defaultValue, int resId) { + super(defaultValue); + + Map mapping = new HashMap(); + String[] values = K9.app.getResources().getStringArray(resId); + for (String value : values) { + int intValue = Integer.parseInt(value); + mapping.put(intValue, value); + } + mMapping = Collections.unmodifiableMap(mapping); + } - public static class StorageProviderDefaultValue implements IDefaultValue { @Override - public Object computeDefaultValue(String key, Map validatedSettings) { + protected Map getMapping() { + return mMapping; + } + + @Override + public Object fromString(String value) throws InvalidSettingValueException { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new InvalidSettingValueException(); + } + } + } + + /** + * A string resource setting. + * + *

+ * Basically a {@link PseudoEnumSetting} that is initialized from a resource array. + *

+ */ + public static class StringResourceSetting extends PseudoEnumSetting { + private final Map mMapping; + + public StringResourceSetting(String defaultValue, int resId) { + super(defaultValue); + + Map mapping = new HashMap(); + String[] values = K9.app.getResources().getStringArray(resId); + for (String value : values) { + mapping.put(value, value); + } + mMapping = Collections.unmodifiableMap(mapping); + } + + @Override + protected Map getMapping() { + return mMapping; + } + + @Override + public Object fromString(String value) throws InvalidSettingValueException { + if (!mMapping.containsKey(value)) { + throw new InvalidSettingValueException(); + } + return value; + } + } + + /** + * The notification ringtone setting. + */ + public static class RingtoneSetting extends SettingsDescription { + public RingtoneSetting(String defaultValue) { + super(defaultValue); + } + + @Override + public Object fromString(String value) { + //TODO: add validation + return value; + } + } + + /** + * The storage provider setting. + */ + public static class StorageProviderSetting extends SettingsDescription { + public StorageProviderSetting() { + super(null); + } + + @Override + public Object getDefaultValue() { return StorageManager.getInstance(K9.app).getDefaultProviderId(); } - } - - public static class StorageProviderValidator implements ISettingValidator { @Override - public boolean isValid(String key, String value, Map validatedSettings) { - Map providers = StorageManager.getInstance(K9.app).getAvailableProviders(); - for (String storageProvider : providers.keySet()) { - if (storageProvider.equals(value)) { - return true; - } + public Object fromString(String value) { + StorageManager storageManager = StorageManager.getInstance(K9.app); + Map providers = storageManager.getAvailableProviders(); + if (providers.containsKey(value)) { + return value; } - return false; + throw new RuntimeException("Validation failed"); } } - public static class RingtoneValidator implements ISettingValidator { - @Override - public boolean isValid(String key, String value, Map validatedSettings) { - // TODO implement - return true; - } - } + /** + * The delete policy setting. + */ + public static class DeletePolicySetting extends PseudoEnumSetting { + private Map mMapping; + + public DeletePolicySetting(int defaultValue) { + super(defaultValue); + Map mapping = new HashMap(); + mapping.put(Account.DELETE_POLICY_NEVER, "NEVER"); + mapping.put(Account.DELETE_POLICY_ON_DELETE, "DELETE"); + mapping.put(Account.DELETE_POLICY_MARK_AS_READ, "MARK_AS_READ"); + mMapping = Collections.unmodifiableMap(mapping); + } - public static class StoreUriValidator implements ISettingValidator { @Override - public boolean isValid(String key, String value, Map validatedSettings) { + protected Map getMapping() { + return mMapping; + } + + @Override + public Object fromString(String value) throws InvalidSettingValueException { try { - String uriString = Utility.base64Decode(value); - if (!uriString.startsWith("imap") && !uriString.startsWith("pop3") && - !uriString.startsWith("webdav")) { - return false; + Integer deletePolicy = Integer.parseInt(value); + if (mMapping.containsKey(deletePolicy)) { + return deletePolicy; } + } catch (NumberFormatException e) { /* do nothing */ } - //TODO: check complete scheme (imap+ssl etc.) - - Uri uri = Uri.parse(uriString); - String[] userInfoParts = uri.getUserInfo().split(":"); - if (userInfoParts.length < 2) { - return false; - } - //TODO: check if username and password are urlencoded - - String host = uri.getHost(); - if (host == null || host.length() == 0) { - return false; - } - - //TODO: check store specifics - - return true; - } catch (Exception e) { Log.e(K9.LOG_TAG, "oops", e); /* Ignore */ } - - return false; - } - } - - public static class TransportUriValidator implements ISettingValidator { - @Override - public boolean isValid(String key, String value, Map validatedSettings) { - try { - String uriString = Utility.base64Decode(value); - if (!uriString.startsWith("smtp") && !uriString.startsWith("webdav")) { - return false; - } - - //TODO: check complete scheme (smtp+ssl etc.) - - Uri uri = Uri.parse(uriString); - String[] userInfoParts = uri.getUserInfo().split(":"); - if (userInfoParts.length < 2) { - return false; - } - //TODO: check if username and password are urlencoded - - String host = uri.getHost(); - if (host == null || host.length() == 0) { - return false; - } - - //TODO: check store specifics - - return true; - } catch (Exception e) { Log.e(K9.LOG_TAG, "oops", e); /* Ignore */ } - - return false; + throw new InvalidSettingValueException(); } } } diff --git a/src/com/fsck/k9/preferences/GlobalSettings.java b/src/com/fsck/k9/preferences/GlobalSettings.java index 5c2291b24..e146bd450 100644 --- a/src/com/fsck/k9/preferences/GlobalSettings.java +++ b/src/com/fsck/k9/preferences/GlobalSettings.java @@ -1,6 +1,7 @@ package com.fsck.k9.preferences; import java.text.SimpleDateFormat; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -12,122 +13,63 @@ import com.fsck.k9.helper.DateFormatter; import com.fsck.k9.preferences.Settings.*; public class GlobalSettings { - public static final ISettingValidator FONT_SIZE_VALIDATOR = new DipFontSizeValidator(); - public static final ISettingValidator TIME_VALIDATOR = new TimeValidator(); - public static final Map SETTINGS; static { SETTINGS = new LinkedHashMap(); - SETTINGS.put("animations", - SD(SettingType.BOOLEAN, false, null)); + SETTINGS.put("animations", new BooleanSetting(false)); SETTINGS.put("backgroundOperations", - SD(SettingType.ENUM, K9.BACKGROUND_OPS.WHEN_CHECKED, new ResourceArrayValidator( - R.array.background_ops_values))); - SETTINGS.put("changeRegisteredNameColor", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("compactLayouts", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("confirmDelete", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("countSearchMessages", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("dateFormat", - SD(SettingType.ENUM, DateFormatter.DEFAULT_FORMAT, new DateFormatValidator())); - SETTINGS.put("enableDebugLogging", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("enableSensitiveLogging", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("fontSizeAccountDescription", - SD(SettingType.INTEGER, 14, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeAccountName", - SD(SettingType.INTEGER, 18, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeFolderName", - SD(SettingType.INTEGER, 22, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeFolderStatus", - SD(SettingType.INTEGER, 14, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeMessageListDate", - SD(SettingType.INTEGER, 14, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeMessageListPreview", - SD(SettingType.INTEGER, 14, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeMessageListSender", - SD(SettingType.INTEGER, 14, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeMessageListSubject", - SD(SettingType.INTEGER, 16, FONT_SIZE_VALIDATOR)); + new EnumSetting(K9.BACKGROUND_OPS.class, K9.BACKGROUND_OPS.WHEN_CHECKED)); + SETTINGS.put("changeRegisteredNameColor", new BooleanSetting(false)); + SETTINGS.put("compactLayouts", new BooleanSetting(false)); + SETTINGS.put("confirmDelete", new BooleanSetting(false)); + SETTINGS.put("countSearchMessages", new BooleanSetting(false)); + SETTINGS.put("dateFormat", new DateFormatSetting(DateFormatter.DEFAULT_FORMAT)); + SETTINGS.put("enableDebugLogging", new BooleanSetting(false)); + SETTINGS.put("enableSensitiveLogging", new BooleanSetting(false)); + SETTINGS.put("fontSizeAccountDescription", new FontSizeSetting(FontSizes.SMALL)); + SETTINGS.put("fontSizeAccountName", new FontSizeSetting(FontSizes.MEDIUM)); + SETTINGS.put("fontSizeFolderName", new FontSizeSetting(FontSizes.LARGE)); + SETTINGS.put("fontSizeFolderStatus", new FontSizeSetting(FontSizes.SMALL)); + SETTINGS.put("fontSizeMessageListDate", new FontSizeSetting(FontSizes.SMALL)); + SETTINGS.put("fontSizeMessageListPreview", new FontSizeSetting(FontSizes.SMALL)); + SETTINGS.put("fontSizeMessageListSender", new FontSizeSetting(FontSizes.SMALL)); + SETTINGS.put("fontSizeMessageListSubject", new FontSizeSetting(FontSizes.FONT_16DIP)); SETTINGS.put("fontSizeMessageViewAdditionalHeaders", - SD(SettingType.INTEGER, 12, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeMessageViewCC", - SD(SettingType.INTEGER, 12, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeMessageViewContent", - SD(SettingType.INTEGER, 3, new WebViewFontSizeValidator())); - SETTINGS.put("fontSizeMessageViewDate", - SD(SettingType.INTEGER, 10, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeMessageViewSender", - SD(SettingType.INTEGER, 14, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeMessageViewSubject", - SD(SettingType.INTEGER, 12, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeMessageViewTime", - SD(SettingType.INTEGER, 10, FONT_SIZE_VALIDATOR)); - SETTINGS.put("fontSizeMessageViewTo", - SD(SettingType.INTEGER, 12, FONT_SIZE_VALIDATOR)); - SETTINGS.put("gesturesEnabled", - SD(SettingType.BOOLEAN, true, null)); - SETTINGS.put("hideSpecialAccounts", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("keyguardPrivacy", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("language", - SD(SettingType.STRING, "", new ResourceArrayValidator( - R.array.settings_language_values))); - SETTINGS.put("manageBack", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("measureAccounts", - SD(SettingType.BOOLEAN, true, null)); - SETTINGS.put("messageListCheckboxes", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("messageListPreviewLines", - SD(SettingType.INTEGER, 2, Settings.POSITIVE_INTEGER_VALIDATOR)); - SETTINGS.put("messageListStars", - SD(SettingType.BOOLEAN, true, null)); - SETTINGS.put("messageListTouchable", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("messageViewFixedWidthFont", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("messageViewReturnToList", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("mobileOptimizedLayout", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("quietTimeEnabled", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("quietTimeEnds", - SD(SettingType.STRING, "7:00", TIME_VALIDATOR)); - SETTINGS.put("quietTimeStarts", - SD(SettingType.STRING, "21:00", TIME_VALIDATOR)); - SETTINGS.put("registeredNameColor", - SD(SettingType.INTEGER, 0xFF00008F, Settings.SOLID_COLOR_VALIDATOR)); - SETTINGS.put("showContactName", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("showCorrespondentNames", - SD(SettingType.BOOLEAN, true, null)); - SETTINGS.put("startIntegratedInbox", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("theme", - SD(SettingType.INTEGER, android.R.style.Theme_Light, new ThemeValidator())); - SETTINGS.put("useGalleryBugWorkaround", - SD(SettingType.BOOLEAN, new GalleryBugWorkaroundDefaultValue(), null)); - SETTINGS.put("useVolumeKeysForListNavigation", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("useVolumeKeysForNavigation", - SD(SettingType.BOOLEAN, false, null)); - SETTINGS.put("zoomControlsEnabled", - SD(SettingType.BOOLEAN, false, null)); - } - - // Just to have shorter lines in SETTINGS initialization - private static SettingsDescription SD(SettingType type, - Object defaultValue, ISettingValidator validator) { - return new SettingsDescription(type, defaultValue, validator); + new FontSizeSetting(FontSizes.FONT_12DIP)); + SETTINGS.put("fontSizeMessageViewCC", new FontSizeSetting(FontSizes.FONT_12DIP)); + SETTINGS.put("fontSizeMessageViewContent", new WebFontSizeSetting(3)); + SETTINGS.put("fontSizeMessageViewDate", new FontSizeSetting(FontSizes.FONT_10DIP)); + SETTINGS.put("fontSizeMessageViewSender", new FontSizeSetting(FontSizes.SMALL)); + SETTINGS.put("fontSizeMessageViewSubject", new FontSizeSetting(FontSizes.FONT_12DIP)); + SETTINGS.put("fontSizeMessageViewTime", new FontSizeSetting(FontSizes.FONT_10DIP)); + SETTINGS.put("fontSizeMessageViewTo", new FontSizeSetting(FontSizes.FONT_12DIP)); + SETTINGS.put("gesturesEnabled", new BooleanSetting(true)); + SETTINGS.put("hideSpecialAccounts", new BooleanSetting(false)); + SETTINGS.put("keyguardPrivacy", new BooleanSetting(false)); + SETTINGS.put("language", new LanguageSetting()); + SETTINGS.put("manageBack", new BooleanSetting(false)); + SETTINGS.put("measureAccounts", new BooleanSetting(true)); + SETTINGS.put("messageListCheckboxes", new BooleanSetting(false)); + SETTINGS.put("messageListPreviewLines", new IntegerRangeSetting(1, 100, 2)); + SETTINGS.put("messageListStars", new BooleanSetting(true)); + SETTINGS.put("messageListTouchable", new BooleanSetting(false)); + SETTINGS.put("messageViewFixedWidthFont", new BooleanSetting(false)); + SETTINGS.put("messageViewReturnToList", new BooleanSetting(false)); + SETTINGS.put("mobileOptimizedLayout", new BooleanSetting(false)); + SETTINGS.put("quietTimeEnabled", new BooleanSetting(false)); + SETTINGS.put("quietTimeEnds", new TimeSetting("7:00")); + SETTINGS.put("quietTimeStarts", new TimeSetting("21:00")); + SETTINGS.put("registeredNameColor", new ColorSetting(0xFF00008F)); + SETTINGS.put("showContactName", new BooleanSetting(false)); + SETTINGS.put("showCorrespondentNames", new BooleanSetting(true)); + SETTINGS.put("startIntegratedInbox", new BooleanSetting(false)); + SETTINGS.put("theme", new ThemeSetting(android.R.style.Theme_Light)); + SETTINGS.put("useGalleryBugWorkaround", new GalleryBugWorkaroundSetting()); + SETTINGS.put("useVolumeKeysForListNavigation", new BooleanSetting(false)); + SETTINGS.put("useVolumeKeysForNavigation", new BooleanSetting(false)); + SETTINGS.put("zoomControlsEnabled", new BooleanSetting(false)); } public static Map validate(Map importedSettings) { @@ -145,72 +87,141 @@ public class GlobalSettings { return result; } + /** + * The gallery bug work-around setting. + * + *

+ * The default value varies depending on whether you have a version of Gallery 3D installed + * that contains the bug we work around. + *

+ * + * @see K9#isGalleryBuggy() + */ + public static class GalleryBugWorkaroundSetting extends BooleanSetting { + public GalleryBugWorkaroundSetting() { + super(false); + } - public static class GalleryBugWorkaroundDefaultValue implements IDefaultValue { @Override - public Object computeDefaultValue(String key, Map validatedSettings) { + public Object getDefaultValue() { return K9.isGalleryBuggy(); } } - public static class DipFontSizeValidator implements ISettingValidator { - @Override - public boolean isValid(String key, String value, Map validatedSettings) { - int val = Integer.parseInt(value); - switch (val) { - case FontSizes.FONT_10DIP: - case FontSizes.FONT_12DIP: - case FontSizes.SMALL: - case FontSizes.FONT_16DIP: - case FontSizes.MEDIUM: - case FontSizes.FONT_20DIP: - case FontSizes.LARGE: - return true; - default: - return false; + /** + * The language setting. + * + *

+ * Valid values are read from {@code settings_language_values} in + * {@code res/values/arrays.xml}. + *

+ */ + public static class LanguageSetting extends PseudoEnumSetting { + private final Map mMapping; + + public LanguageSetting() { + super(""); + + Map mapping = new HashMap(); + String[] values = K9.app.getResources().getStringArray(R.array.settings_language_values); + for (String value : values) { + if (value.length() == 0) { + mapping.put("", "default"); + } else { + mapping.put(value, value); + } } + mMapping = Collections.unmodifiableMap(mapping); + } + + @Override + protected Map getMapping() { + return mMapping; + } + + @Override + public Object fromString(String value) throws InvalidSettingValueException { + if (mMapping.containsKey(value)) { + return value; + } + + throw new InvalidSettingValueException(); } } - public static class WebViewFontSizeValidator implements ISettingValidator { + /** + * The theme setting. + */ + public static class ThemeSetting extends PseudoEnumSetting { + private final Map mMapping; + + public ThemeSetting(int defaultValue) { + super(defaultValue); + + Map mapping = new HashMap(); + mapping.put(android.R.style.Theme_Light, "light"); + mapping.put(android.R.style.Theme, "dark"); + mMapping = Collections.unmodifiableMap(mapping); + } + @Override - public boolean isValid(String key, String value, Map validatedSettings) { - int val = Integer.parseInt(value); - return (val >= 1 && val <= 5); + protected Map getMapping() { + return mMapping; + } + + @Override + public Object fromString(String value) throws InvalidSettingValueException { + try { + Integer theme = Integer.parseInt(value); + if (mMapping.containsKey(theme)) { + return theme; + } + } catch (NumberFormatException e) { /* do nothing */ } + + throw new InvalidSettingValueException(); } } - public static class TimeValidator implements ISettingValidator { - @Override - public boolean isValid(String key, String value, Map validatedSettings) { - return value.matches(TimePickerPreference.VALIDATION_EXPRESSION); + /** + * A date format setting. + */ + public static class DateFormatSetting extends SettingsDescription { + public DateFormatSetting(String defaultValue) { + super(defaultValue); } - } - public static class DateFormatValidator implements ISettingValidator { @Override - public boolean isValid(String key, String value, Map validatedSettings) { + public Object fromString(String value) throws InvalidSettingValueException { try { // The placeholders "SHORT" and "MEDIUM" are fine. if (DateFormatter.SHORT_FORMAT.equals(value) || DateFormatter.MEDIUM_FORMAT.equals(value)) { - return true; + return value; } // If the SimpleDateFormat constructor doesn't throw an exception, we're good. new SimpleDateFormat(value); - return true; + return value; } catch (Exception e) { - return false; + throw new InvalidSettingValueException(); } } } - public static class ThemeValidator implements ISettingValidator { + /** + * A time setting. + */ + public static class TimeSetting extends SettingsDescription { + public TimeSetting(String defaultValue) { + super(defaultValue); + } + @Override - public boolean isValid(String key, String value, Map validatedSettings) { - int val = Integer.parseInt(value); - return (val == android.R.style.Theme_Light || val == android.R.style.Theme); + public Object fromString(String value) throws InvalidSettingValueException { + if (!value.matches(TimePickerPreference.VALIDATION_EXPRESSION)) { + throw new InvalidSettingValueException(); + } + return value; } } } diff --git a/src/com/fsck/k9/preferences/Settings.java b/src/com/fsck/k9/preferences/Settings.java index 98a97717d..fca782242 100644 --- a/src/com/fsck/k9/preferences/Settings.java +++ b/src/com/fsck/k9/preferences/Settings.java @@ -1,8 +1,13 @@ package com.fsck.k9.preferences; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; + import android.util.Log; + +import com.fsck.k9.FontSizes; import com.fsck.k9.K9; /* @@ -10,10 +15,6 @@ import com.fsck.k9.K9; * - add support for different settings versions (validate old version and upgrade to new format) * - use the default values defined in GlobalSettings and AccountSettings when creating new * accounts - * - use the settings description to decide which keys to export - * - convert internal representation to a "pretty" format when exporting (e.g. we want to export - * the value "light" rather than the integer constant for android.R.style.Theme_Light); revert - * that conversion when importing * - think of a better way to validate enums than to use the resource arrays (i.e. get rid of * ResourceArrayValidator); maybe even use the settings description for the settings UI * - add unit test that validates the default values are actually valid according to the validator @@ -33,13 +34,6 @@ public class Settings { */ public static final int VERSION = 1; - public static final IDefaultValue EXCEPTION_DEFAULT_VALUE = new ExceptionDefaultValue(); - - public static final ISettingValidator BOOLEAN_VALIDATOR = new BooleanValidator(); - public static final ISettingValidator INTEGER_VALIDATOR = new IntegerValidator(); - public static final ISettingValidator POSITIVE_INTEGER_VALIDATOR = new PositiveIntegerValidator(); - public static final ISettingValidator SOLID_COLOR_VALIDATOR = new SolidColorValidator(); - public static Map validate(Map settings, Map importedSettings, boolean useDefaultValues) { @@ -54,12 +48,14 @@ public class Settings { ((useDefaultValues) ? " Using default value." : "")); useDefaultValue = useDefaultValues; } else { - String importedValue = importedSettings.get(key); - if (Settings.isValid(desc, key, importedValue, validatedSettings)) { + String prettyValue = importedSettings.get(key); + try { + Object internalValue = desc.fromPrettyString(prettyValue); + String importedValue = desc.toString(internalValue); validatedSettings.put(key, importedValue); useDefaultValue = false; - } else { - Log.v(K9.LOG_TAG, "Key \"" + key + "\" has invalid value \"" + importedValue + + } catch (InvalidSettingValueException e) { + Log.v(K9.LOG_TAG, "Key \"" + key + "\" has invalid value \"" + prettyValue + "\" in imported file. " + ((useDefaultValues) ? "Using default value." : "Skipping.")); useDefaultValue = useDefaultValues; @@ -67,142 +63,346 @@ public class Settings { } if (useDefaultValue) { - Object defaultValue; - if (desc.defaultValue instanceof IDefaultValue) { - defaultValue = ((IDefaultValue)desc.defaultValue).computeDefaultValue(key, validatedSettings); - } else { - defaultValue = desc.defaultValue; - } - - validatedSettings.put(key, defaultValue.toString()); + Object defaultValue = desc.getDefaultValue(); + validatedSettings.put(key, desc.toString(defaultValue)); } } return validatedSettings; } - public static boolean isValid(SettingsDescription desc, String key, String value, - Map validatedSettings) { - try { - switch (desc.type) { - case BOOLEAN: - if (!Settings.BOOLEAN_VALIDATOR.isValid(key, value, validatedSettings)) { - return false; - } - break; - case INTEGER: - if (!Settings.INTEGER_VALIDATOR.isValid(key, value, validatedSettings)) { - return false; - } - break; - default: - break; - } - if (desc.validator != null) { - return desc.validator.isValid(key, value, validatedSettings); - } + /** + * Indicates an invalid setting value. + * + * @see SettingsDescription#fromString(String) + * @see SettingsDescription#fromPrettyString(String) + */ + public static class InvalidSettingValueException extends Exception { + private static final long serialVersionUID = 1L; + } - return true; - } catch (Exception e) { - Log.e(K9.LOG_TAG, "Exception while running validator for value \"" + value + "\"", e); - return false; + /** + * Describes a setting. + * + *

+ * Instances of this class are used to convert the string representations of setting values to + * an internal representation (e.g. an integer) and back. + *

+ * Currently we use two different string representations: + *

+ *
    + *
  1. + * The one that is used by the internal preference {@link Storage}. It is usually obtained by + * calling {@code toString()} on the internal representation of the setting value (see e.g. + * {@link K9#save(android.content.SharedPreferences.Editor)}). + *
  2. + *
  3. + * The "pretty" version that is used by the import/export settings file (e.g. colors are + * exported in #rrggbb format instead of a integer string like "-8734021"). + *
  4. + *
+ *

+ * Note: + * For the future we should aim to get rid of the "internal" string representation. The + * "pretty" version makes reading a database dump easier and the performance impact should be + * negligible. + *

+ */ + public static abstract class SettingsDescription { + /** + * The setting's default value (internal representation). + */ + protected Object mDefaultValue; + + public SettingsDescription(Object defaultValue) { + mDefaultValue = defaultValue; + } + + /** + * Get the default value. + * + * @return The internal representation of the default value. + */ + public Object getDefaultValue() { + return mDefaultValue; + } + + /** + * Convert a setting's value to the string representation. + * + * @param value + * The internal representation of a setting's value. + * + * @return The string representation of {@code value}. + */ + public String toString(Object value) { + return value.toString(); + } + + /** + * Parse the string representation of a setting's value . + * + * @param value + * The string representation of a setting's value. + * + * @return The internal representation of the setting's value. + * + * @throws InvalidSettingValueException + * If {@code value} contains an invalid value. + */ + public abstract Object fromString(String value) throws InvalidSettingValueException; + + /** + * Convert a setting value to the "pretty" string representation. + * + * @param value + * The setting's value. + * + * @return A pretty-printed version of the setting's value. + */ + public String toPrettyString(Object value) { + return toString(value); + } + + /** + * Convert the pretty-printed version of a setting's value to the internal representation. + * + * @param value + * The pretty-printed version of the setting's value. See + * {@link #toPrettyString(Object)}. + * + * @return The internal representation of the setting's value. + * + * @throws InvalidSettingValueException + * If {@code value} contains an invalid value. + */ + public Object fromPrettyString(String value) throws InvalidSettingValueException { + return fromString(value); } } - public enum SettingType { - BOOLEAN, - INTEGER, - STRING, - ENUM - } - - public static class SettingsDescription { - public final SettingType type; - public final Object defaultValue; - public final ISettingValidator validator; - - protected SettingsDescription(SettingType type, - Object defaultValue, ISettingValidator validator) { - this.type = type; - this.defaultValue = defaultValue; - this.validator = validator; + /** + * A string setting. + */ + public static class StringSetting extends SettingsDescription { + public StringSetting(String defaultValue) { + super(defaultValue); } - } - public interface IDefaultValue { - Object computeDefaultValue(String key, Map validatedSettings); - } - - public static class ExceptionDefaultValue implements IDefaultValue { @Override - public Object computeDefaultValue(String key, Map validatedSettings) { - throw new RuntimeException("There is no default value for key \"" + key + "\"."); - } - - } - - public interface ISettingValidator { - boolean isValid(String key, String value, Map validatedSettings); - } - - public static class BooleanValidator implements ISettingValidator { - @Override - public boolean isValid(String key, String value, Map validatedSettings) { - return Boolean.TRUE.toString().equals(value) || Boolean.FALSE.toString().equals(value); + public Object fromString(String value) { + return value; } } - public static class IntegerValidator implements ISettingValidator { + /** + * A boolean setting. + */ + public static class BooleanSetting extends SettingsDescription { + public BooleanSetting(boolean defaultValue) { + super(defaultValue); + } + @Override - public boolean isValid(String key, String value, Map validatedSettings) { - try { - Integer.parseInt(value); + public Object fromString(String value) throws InvalidSettingValueException { + if (Boolean.TRUE.toString().equals(value)) { return true; - } catch (NumberFormatException e) { + } else if (Boolean.FALSE.toString().equals(value)) { return false; } + throw new InvalidSettingValueException(); } } - public static class PositiveIntegerValidator implements ISettingValidator { - @Override - public boolean isValid(String key, String value, Map validatedSettings) { - return (Integer.parseInt(value) >= 0); - } - } - - public static class ResourceArrayValidator implements ISettingValidator { - private final int mResource; - - public ResourceArrayValidator(int res) { - mResource = res; + /** + * A color setting. + */ + public static class ColorSetting extends SettingsDescription { + public ColorSetting(int defaultValue) { + super(defaultValue); } @Override - public boolean isValid(String key, String value, Map validatedSettings) { + public Object fromString(String value) throws InvalidSettingValueException { try { - String[] values = K9.app.getResources().getStringArray(mResource); + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new InvalidSettingValueException(); + } + } - for (String validValue : values) { - if (validValue.equals(value)) { - return true; - } + @Override + public String toPrettyString(Object value) { + int color = ((Integer) value) & 0x00FFFFFF; + return String.format("#%06x", color); + } + + @Override + public Object fromPrettyString(String value) throws InvalidSettingValueException { + try { + if (value.length() == 7) { + return Integer.parseInt(value.substring(1), 16) | 0xFF000000; } + } catch (NumberFormatException e) { /* do nothing */ } + + throw new InvalidSettingValueException(); + } + } + + /** + * An {@code Enum} setting. + * + *

+ * {@link Enum#toString()} is used to obtain the "pretty" string representation. + *

+ */ + public static class EnumSetting extends SettingsDescription { + private Class> mEnumClass; + + public EnumSetting(Class> enumClass, Object defaultValue) { + super(defaultValue); + mEnumClass = enumClass; + } + + @SuppressWarnings("unchecked") + @Override + public Object fromString(String value) throws InvalidSettingValueException { + try { + return Enum.valueOf((Class)mEnumClass, value); } catch (Exception e) { - Log.e(K9.LOG_TAG, "Something went wrong during validation of key " + key, e); + throw new InvalidSettingValueException(); + } + } + } + + /** + * A setting that has multiple valid values but doesn't use an {@link Enum} internally. + * + * @param + * The type of the internal representation (e.g. {@code Integer}). + */ + public abstract static class PseudoEnumSetting extends SettingsDescription { + public PseudoEnumSetting(Object defaultValue) { + super(defaultValue); + } + + protected abstract Map getMapping(); + + @Override + public String toPrettyString(Object value) { + return getMapping().get(value); + } + + @Override + public Object fromPrettyString(String value) throws InvalidSettingValueException { + for (Entry entry : getMapping().entrySet()) { + if (entry.getValue().equals(value)) { + return entry.getKey(); + } } - return false; + throw new InvalidSettingValueException(); } } - public static class SolidColorValidator implements ISettingValidator { - @Override - public boolean isValid(String key, String value, Map validatedSettings) { - int color = Integer.parseInt(value); - return ((color & 0xFF000000) == 0xFF000000); + /** + * A font size setting. + */ + public static class FontSizeSetting extends PseudoEnumSetting { + private final Map mMapping; + + public FontSizeSetting(int defaultValue) { + super(defaultValue); + + Map mapping = new HashMap(); + mapping.put(FontSizes.FONT_10DIP, "tiniest"); + mapping.put(FontSizes.FONT_12DIP, "tiny"); + mapping.put(FontSizes.SMALL, "smaller"); + mapping.put(FontSizes.FONT_16DIP, "small"); + mapping.put(FontSizes.MEDIUM, "medium"); + mapping.put(FontSizes.FONT_20DIP, "large"); + mapping.put(FontSizes.LARGE, "larger"); + mMapping = Collections.unmodifiableMap(mapping); } + @Override + protected Map getMapping() { + return mMapping; + } + + @Override + public Object fromString(String value) throws InvalidSettingValueException { + try { + Integer fontSize = Integer.parseInt(value); + if (mMapping.containsKey(fontSize)) { + return fontSize; + } + } catch (NumberFormatException e) { /* do nothing */ } + + throw new InvalidSettingValueException(); + } + } + + /** + * A {@link android.webkit.WebView} font size setting. + */ + public static class WebFontSizeSetting extends PseudoEnumSetting { + private final Map mMapping; + + public WebFontSizeSetting(int defaultValue) { + super(defaultValue); + + Map mapping = new HashMap(); + mapping.put(1, "smallest"); + mapping.put(2, "smaller"); + mapping.put(3, "normal"); + mapping.put(4, "larger"); + mapping.put(5, "largest"); + mMapping = Collections.unmodifiableMap(mapping); + } + + @Override + protected Map getMapping() { + return mMapping; + } + + @Override + public Object fromString(String value) throws InvalidSettingValueException { + try { + Integer fontSize = Integer.parseInt(value); + if (mMapping.containsKey(fontSize)) { + return fontSize; + } + } catch (NumberFormatException e) { /* do nothing */ } + + throw new InvalidSettingValueException(); + } + } + + /** + * An integer settings whose values a limited to a certain range. + */ + public static class IntegerRangeSetting extends SettingsDescription { + private int mStart; + private int mEnd; + + public IntegerRangeSetting(int start, int end, int defaultValue) { + super(defaultValue); + mStart = start; + mEnd = end; + } + + @Override + public Object fromString(String value) throws InvalidSettingValueException { + try { + int intValue = Integer.parseInt(value); + if (mStart <= intValue && intValue <= mEnd) { + return intValue; + } + } catch (NumberFormatException e) { /* do nothing */ } + + throw new InvalidSettingValueException(); + } } } diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index ba98559cd..4bccb544e 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -29,6 +29,8 @@ import com.fsck.k9.mail.Store; import com.fsck.k9.mail.ServerSettings; import com.fsck.k9.mail.Transport; import com.fsck.k9.mail.store.LocalStore; +import com.fsck.k9.preferences.Settings.InvalidSettingValueException; +import com.fsck.k9.preferences.Settings.SettingsDescription; public class StorageExporter { @@ -183,11 +185,16 @@ public class StorageExporter { Map prefs) throws IOException { for (String key : GlobalSettings.SETTINGS.keySet()) { - Object value = prefs.get(key); + String valueString = prefs.get(key).toString(); - if (value != null) { - String outputValue = value.toString(); + try { + SettingsDescription setting = GlobalSettings.SETTINGS.get(key); + Object value = setting.fromString(valueString); + String outputValue = setting.toPrettyString(value); writeKeyValue(serializer, key, outputValue); + } catch (InvalidSettingValueException e) { + Log.w(K9.LOG_TAG, "Global setting \"" + key + "\" has invalid value \"" + + valueString + "\" in preference storage. This shouldn't happen!"); } } } @@ -268,7 +275,7 @@ public class StorageExporter { serializer.startTag(null, SETTINGS_ELEMENT); for (Map.Entry entry : prefs.entrySet()) { String key = entry.getKey(); - String value = entry.getValue().toString(); + String valueString = entry.getValue().toString(); String[] comps = key.split("\\."); if (comps.length < 2) { @@ -310,9 +317,18 @@ public class StorageExporter { keyPart = secondPart; } - if (AccountSettings.SETTINGS.containsKey(keyPart)) { + SettingsDescription setting = AccountSettings.SETTINGS.get(keyPart); + if (setting != null) { // Only export account settings that can be found in AccountSettings.SETTINGS - writeKeyValue(serializer, keyPart, value); + try { + Object value = setting.fromString(valueString); + String pretty = setting.toPrettyString(value); + writeKeyValue(serializer, keyPart, pretty); + } catch (InvalidSettingValueException e) { + Log.w(K9.LOG_TAG, "Account setting \"" + keyPart + "\" (" + + account.getDescription() + ") has invalid value \"" + valueString + + "\" in preference storage. This shouldn't happen!"); + } } } serializer.endTag(null, SETTINGS_ELEMENT); From 060d21db3eeea7d17cab1f54b39c9e31940d1507 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 7 Oct 2011 21:20:12 +0200 Subject: [PATCH 085/116] Make collections immutable --- .../fsck/k9/preferences/AccountSettings.java | 110 +++++++++--------- .../fsck/k9/preferences/GlobalSettings.java | 105 ++++++++--------- 2 files changed, 107 insertions(+), 108 deletions(-) diff --git a/src/com/fsck/k9/preferences/AccountSettings.java b/src/com/fsck/k9/preferences/AccountSettings.java index 4666f3afa..1bc693bb5 100644 --- a/src/com/fsck/k9/preferences/AccountSettings.java +++ b/src/com/fsck/k9/preferences/AccountSettings.java @@ -18,75 +18,73 @@ public class AccountSettings { public static final Map SETTINGS; static { - SETTINGS = new LinkedHashMap(); + Map s = new LinkedHashMap(); - SETTINGS.put("archiveFolderName", new StringSetting("Archive")); - SETTINGS.put("autoExpandFolderName", new StringSetting("INBOX")); - SETTINGS.put("automaticCheckIntervalMinutes", + s.put("archiveFolderName", new StringSetting("Archive")); + s.put("autoExpandFolderName", new StringSetting("INBOX")); + s.put("automaticCheckIntervalMinutes", new IntegerResourceSetting(-1, R.array.account_settings_check_frequency_values)); - SETTINGS.put("chipColor", new ColorSetting(0xFF0000FF)); - SETTINGS.put("cryptoApp", new StringSetting(Apg.NAME)); - SETTINGS.put("cryptoAutoSignature", new BooleanSetting(false)); - SETTINGS.put("deletePolicy", new DeletePolicySetting(Account.DELETE_POLICY_NEVER)); - SETTINGS.put("displayCount", new IntegerResourceSetting(K9.DEFAULT_VISIBLE_LIMIT, + s.put("chipColor", new ColorSetting(0xFF0000FF)); + s.put("cryptoApp", new StringSetting(Apg.NAME)); + s.put("cryptoAutoSignature", new BooleanSetting(false)); + s.put("deletePolicy", new DeletePolicySetting(Account.DELETE_POLICY_NEVER)); + s.put("displayCount", new IntegerResourceSetting(K9.DEFAULT_VISIBLE_LIMIT, R.array.account_settings_display_count_values)); - SETTINGS.put("draftsFolderName", new StringSetting("Drafts")); - SETTINGS.put("enableMoveButtons", new BooleanSetting(false)); - SETTINGS.put("expungePolicy", new StringResourceSetting(Account.EXPUNGE_IMMEDIATELY, + s.put("draftsFolderName", new StringSetting("Drafts")); + s.put("enableMoveButtons", new BooleanSetting(false)); + s.put("expungePolicy", new StringResourceSetting(Account.EXPUNGE_IMMEDIATELY, R.array.account_setup_expunge_policy_values)); - SETTINGS.put("folderDisplayMode", - new EnumSetting(FolderMode.class, FolderMode.NOT_SECOND_CLASS)); - SETTINGS.put("folderPushMode", new EnumSetting(FolderMode.class, FolderMode.FIRST_CLASS)); - SETTINGS.put("folderSyncMode", new EnumSetting(FolderMode.class, FolderMode.FIRST_CLASS)); - SETTINGS.put("folderTargetMode", - new EnumSetting(FolderMode.class, FolderMode.NOT_SECOND_CLASS)); - SETTINGS.put("goToUnreadMessageSearch", new BooleanSetting(false)); - SETTINGS.put("hideButtonsEnum", new EnumSetting(ScrollButtons.class, ScrollButtons.NEVER)); - SETTINGS.put("hideMoveButtonsEnum", - new EnumSetting(ScrollButtons.class, ScrollButtons.NEVER)); - SETTINGS.put("idleRefreshMinutes", new IntegerResourceSetting(24, + s.put("folderDisplayMode", new EnumSetting(FolderMode.class, FolderMode.NOT_SECOND_CLASS)); + s.put("folderPushMode", new EnumSetting(FolderMode.class, FolderMode.FIRST_CLASS)); + s.put("folderSyncMode", new EnumSetting(FolderMode.class, FolderMode.FIRST_CLASS)); + s.put("folderTargetMode", new EnumSetting(FolderMode.class, FolderMode.NOT_SECOND_CLASS)); + s.put("goToUnreadMessageSearch", new BooleanSetting(false)); + s.put("hideButtonsEnum", new EnumSetting(ScrollButtons.class, ScrollButtons.NEVER)); + s.put("hideMoveButtonsEnum", new EnumSetting(ScrollButtons.class, ScrollButtons.NEVER)); + s.put("idleRefreshMinutes", new IntegerResourceSetting(24, R.array.idle_refresh_period_values)); - SETTINGS.put("led", new BooleanSetting(true)); - SETTINGS.put("ledColor", new ColorSetting(0xFF0000FF)); - SETTINGS.put("localStorageProvider", new StorageProviderSetting()); - SETTINGS.put("maxPushFolders", new IntegerRangeSetting(0, 100, 10)); - SETTINGS.put("maximumAutoDownloadMessageSize", new IntegerResourceSetting(32768, + s.put("led", new BooleanSetting(true)); + s.put("ledColor", new ColorSetting(0xFF0000FF)); + s.put("localStorageProvider", new StorageProviderSetting()); + s.put("maxPushFolders", new IntegerRangeSetting(0, 100, 10)); + s.put("maximumAutoDownloadMessageSize", new IntegerResourceSetting(32768, R.array.account_settings_autodownload_message_size_values)); - SETTINGS.put("maximumPolledMessageAge", new IntegerResourceSetting(-1, + s.put("maximumPolledMessageAge", new IntegerResourceSetting(-1, R.array.account_settings_message_age_values)); - SETTINGS.put("messageFormat", + s.put("messageFormat", new EnumSetting(Account.MessageFormat.class, Account.DEFAULT_MESSAGE_FORMAT)); - SETTINGS.put("notificationUnreadCount", new BooleanSetting(true)); - SETTINGS.put("notifyMailCheck", new BooleanSetting(false)); - SETTINGS.put("notifyNewMail", new BooleanSetting(false)); - SETTINGS.put("notifySelfNewMail", new BooleanSetting(true)); - SETTINGS.put("pushPollOnConnect", new BooleanSetting(true)); - SETTINGS.put("quotePrefix", new StringSetting(Account.DEFAULT_QUOTE_PREFIX)); - SETTINGS.put("quoteStyle", + s.put("notificationUnreadCount", new BooleanSetting(true)); + s.put("notifyMailCheck", new BooleanSetting(false)); + s.put("notifyNewMail", new BooleanSetting(false)); + s.put("notifySelfNewMail", new BooleanSetting(true)); + s.put("pushPollOnConnect", new BooleanSetting(true)); + s.put("quotePrefix", new StringSetting(Account.DEFAULT_QUOTE_PREFIX)); + s.put("quoteStyle", new EnumSetting(Account.QuoteStyle.class, Account.DEFAULT_QUOTE_STYLE)); - SETTINGS.put("replyAfterQuote", new BooleanSetting(Account.DEFAULT_REPLY_AFTER_QUOTE)); - SETTINGS.put("ring", new BooleanSetting(true)); - SETTINGS.put("ringtone", - new RingtoneSetting("content://settings/system/notification_sound")); - SETTINGS.put("saveAllHeaders", new BooleanSetting(true)); - SETTINGS.put("searchableFolders", + s.put("replyAfterQuote", new BooleanSetting(Account.DEFAULT_REPLY_AFTER_QUOTE)); + s.put("ring", new BooleanSetting(true)); + s.put("ringtone", new RingtoneSetting("content://settings/system/notification_sound")); + s.put("saveAllHeaders", new BooleanSetting(true)); + s.put("searchableFolders", new EnumSetting(Account.Searchable.class, Account.Searchable.ALL)); - SETTINGS.put("sentFolderName", new StringSetting("Sent")); - SETTINGS.put("showPicturesEnum", + s.put("sentFolderName", new StringSetting("Sent")); + s.put("showPicturesEnum", new EnumSetting(Account.ShowPictures.class, Account.ShowPictures.NEVER)); - SETTINGS.put("signatureBeforeQuotedText", new BooleanSetting(false)); - SETTINGS.put("spamFolderName", new StringSetting("Spam")); - SETTINGS.put("subscribedFoldersOnly", new BooleanSetting(false)); - SETTINGS.put("syncRemoteDeletions", new BooleanSetting(true)); - SETTINGS.put("trashFolderName", new StringSetting("Trash")); - SETTINGS.put("useCompression.MOBILE", new BooleanSetting(true)); - SETTINGS.put("useCompression.OTHER", new BooleanSetting(true)); - SETTINGS.put("useCompression.WIFI", new BooleanSetting(true)); - SETTINGS.put("vibrate", new BooleanSetting(false)); - SETTINGS.put("vibratePattern", new IntegerResourceSetting(0, + s.put("signatureBeforeQuotedText", new BooleanSetting(false)); + s.put("spamFolderName", new StringSetting("Spam")); + s.put("subscribedFoldersOnly", new BooleanSetting(false)); + s.put("syncRemoteDeletions", new BooleanSetting(true)); + s.put("trashFolderName", new StringSetting("Trash")); + s.put("useCompression.MOBILE", new BooleanSetting(true)); + s.put("useCompression.OTHER", new BooleanSetting(true)); + s.put("useCompression.WIFI", new BooleanSetting(true)); + s.put("vibrate", new BooleanSetting(false)); + s.put("vibratePattern", new IntegerResourceSetting(0, R.array.account_settings_vibrate_pattern_values)); - SETTINGS.put("vibrateTimes", new IntegerResourceSetting(5, + s.put("vibrateTimes", new IntegerResourceSetting(5, R.array.account_settings_vibrate_times_label)); + + SETTINGS = Collections.unmodifiableMap(s); } public static Map validate(Map importedSettings, diff --git a/src/com/fsck/k9/preferences/GlobalSettings.java b/src/com/fsck/k9/preferences/GlobalSettings.java index e146bd450..912ff4856 100644 --- a/src/com/fsck/k9/preferences/GlobalSettings.java +++ b/src/com/fsck/k9/preferences/GlobalSettings.java @@ -16,60 +16,61 @@ public class GlobalSettings { public static final Map SETTINGS; static { - SETTINGS = new LinkedHashMap(); + Map s = new LinkedHashMap(); - SETTINGS.put("animations", new BooleanSetting(false)); - SETTINGS.put("backgroundOperations", + s.put("animations", new BooleanSetting(false)); + s.put("backgroundOperations", new EnumSetting(K9.BACKGROUND_OPS.class, K9.BACKGROUND_OPS.WHEN_CHECKED)); - SETTINGS.put("changeRegisteredNameColor", new BooleanSetting(false)); - SETTINGS.put("compactLayouts", new BooleanSetting(false)); - SETTINGS.put("confirmDelete", new BooleanSetting(false)); - SETTINGS.put("countSearchMessages", new BooleanSetting(false)); - SETTINGS.put("dateFormat", new DateFormatSetting(DateFormatter.DEFAULT_FORMAT)); - SETTINGS.put("enableDebugLogging", new BooleanSetting(false)); - SETTINGS.put("enableSensitiveLogging", new BooleanSetting(false)); - SETTINGS.put("fontSizeAccountDescription", new FontSizeSetting(FontSizes.SMALL)); - SETTINGS.put("fontSizeAccountName", new FontSizeSetting(FontSizes.MEDIUM)); - SETTINGS.put("fontSizeFolderName", new FontSizeSetting(FontSizes.LARGE)); - SETTINGS.put("fontSizeFolderStatus", new FontSizeSetting(FontSizes.SMALL)); - SETTINGS.put("fontSizeMessageListDate", new FontSizeSetting(FontSizes.SMALL)); - SETTINGS.put("fontSizeMessageListPreview", new FontSizeSetting(FontSizes.SMALL)); - SETTINGS.put("fontSizeMessageListSender", new FontSizeSetting(FontSizes.SMALL)); - SETTINGS.put("fontSizeMessageListSubject", new FontSizeSetting(FontSizes.FONT_16DIP)); - SETTINGS.put("fontSizeMessageViewAdditionalHeaders", - new FontSizeSetting(FontSizes.FONT_12DIP)); - SETTINGS.put("fontSizeMessageViewCC", new FontSizeSetting(FontSizes.FONT_12DIP)); - SETTINGS.put("fontSizeMessageViewContent", new WebFontSizeSetting(3)); - SETTINGS.put("fontSizeMessageViewDate", new FontSizeSetting(FontSizes.FONT_10DIP)); - SETTINGS.put("fontSizeMessageViewSender", new FontSizeSetting(FontSizes.SMALL)); - SETTINGS.put("fontSizeMessageViewSubject", new FontSizeSetting(FontSizes.FONT_12DIP)); - SETTINGS.put("fontSizeMessageViewTime", new FontSizeSetting(FontSizes.FONT_10DIP)); - SETTINGS.put("fontSizeMessageViewTo", new FontSizeSetting(FontSizes.FONT_12DIP)); - SETTINGS.put("gesturesEnabled", new BooleanSetting(true)); - SETTINGS.put("hideSpecialAccounts", new BooleanSetting(false)); - SETTINGS.put("keyguardPrivacy", new BooleanSetting(false)); - SETTINGS.put("language", new LanguageSetting()); - SETTINGS.put("manageBack", new BooleanSetting(false)); - SETTINGS.put("measureAccounts", new BooleanSetting(true)); - SETTINGS.put("messageListCheckboxes", new BooleanSetting(false)); - SETTINGS.put("messageListPreviewLines", new IntegerRangeSetting(1, 100, 2)); - SETTINGS.put("messageListStars", new BooleanSetting(true)); - SETTINGS.put("messageListTouchable", new BooleanSetting(false)); - SETTINGS.put("messageViewFixedWidthFont", new BooleanSetting(false)); - SETTINGS.put("messageViewReturnToList", new BooleanSetting(false)); - SETTINGS.put("mobileOptimizedLayout", new BooleanSetting(false)); - SETTINGS.put("quietTimeEnabled", new BooleanSetting(false)); - SETTINGS.put("quietTimeEnds", new TimeSetting("7:00")); - SETTINGS.put("quietTimeStarts", new TimeSetting("21:00")); - SETTINGS.put("registeredNameColor", new ColorSetting(0xFF00008F)); - SETTINGS.put("showContactName", new BooleanSetting(false)); - SETTINGS.put("showCorrespondentNames", new BooleanSetting(true)); - SETTINGS.put("startIntegratedInbox", new BooleanSetting(false)); - SETTINGS.put("theme", new ThemeSetting(android.R.style.Theme_Light)); - SETTINGS.put("useGalleryBugWorkaround", new GalleryBugWorkaroundSetting()); - SETTINGS.put("useVolumeKeysForListNavigation", new BooleanSetting(false)); - SETTINGS.put("useVolumeKeysForNavigation", new BooleanSetting(false)); - SETTINGS.put("zoomControlsEnabled", new BooleanSetting(false)); + s.put("changeRegisteredNameColor", new BooleanSetting(false)); + s.put("compactLayouts", new BooleanSetting(false)); + s.put("confirmDelete", new BooleanSetting(false)); + s.put("countSearchMessages", new BooleanSetting(false)); + s.put("dateFormat", new DateFormatSetting(DateFormatter.DEFAULT_FORMAT)); + s.put("enableDebugLogging", new BooleanSetting(false)); + s.put("enableSensitiveLogging", new BooleanSetting(false)); + s.put("fontSizeAccountDescription", new FontSizeSetting(FontSizes.SMALL)); + s.put("fontSizeAccountName", new FontSizeSetting(FontSizes.MEDIUM)); + s.put("fontSizeFolderName", new FontSizeSetting(FontSizes.LARGE)); + s.put("fontSizeFolderStatus", new FontSizeSetting(FontSizes.SMALL)); + s.put("fontSizeMessageListDate", new FontSizeSetting(FontSizes.SMALL)); + s.put("fontSizeMessageListPreview", new FontSizeSetting(FontSizes.SMALL)); + s.put("fontSizeMessageListSender", new FontSizeSetting(FontSizes.SMALL)); + s.put("fontSizeMessageListSubject", new FontSizeSetting(FontSizes.FONT_16DIP)); + s.put("fontSizeMessageViewAdditionalHeaders", new FontSizeSetting(FontSizes.FONT_12DIP)); + s.put("fontSizeMessageViewCC", new FontSizeSetting(FontSizes.FONT_12DIP)); + s.put("fontSizeMessageViewContent", new WebFontSizeSetting(3)); + s.put("fontSizeMessageViewDate", new FontSizeSetting(FontSizes.FONT_10DIP)); + s.put("fontSizeMessageViewSender", new FontSizeSetting(FontSizes.SMALL)); + s.put("fontSizeMessageViewSubject", new FontSizeSetting(FontSizes.FONT_12DIP)); + s.put("fontSizeMessageViewTime", new FontSizeSetting(FontSizes.FONT_10DIP)); + s.put("fontSizeMessageViewTo", new FontSizeSetting(FontSizes.FONT_12DIP)); + s.put("gesturesEnabled", new BooleanSetting(true)); + s.put("hideSpecialAccounts", new BooleanSetting(false)); + s.put("keyguardPrivacy", new BooleanSetting(false)); + s.put("language", new LanguageSetting()); + s.put("manageBack", new BooleanSetting(false)); + s.put("measureAccounts", new BooleanSetting(true)); + s.put("messageListCheckboxes", new BooleanSetting(false)); + s.put("messageListPreviewLines", new IntegerRangeSetting(1, 100, 2)); + s.put("messageListStars", new BooleanSetting(true)); + s.put("messageListTouchable", new BooleanSetting(false)); + s.put("messageViewFixedWidthFont", new BooleanSetting(false)); + s.put("messageViewReturnToList", new BooleanSetting(false)); + s.put("mobileOptimizedLayout", new BooleanSetting(false)); + s.put("quietTimeEnabled", new BooleanSetting(false)); + s.put("quietTimeEnds", new TimeSetting("7:00")); + s.put("quietTimeStarts", new TimeSetting("21:00")); + s.put("registeredNameColor", new ColorSetting(0xFF00008F)); + s.put("showContactName", new BooleanSetting(false)); + s.put("showCorrespondentNames", new BooleanSetting(true)); + s.put("startIntegratedInbox", new BooleanSetting(false)); + s.put("theme", new ThemeSetting(android.R.style.Theme_Light)); + s.put("useGalleryBugWorkaround", new GalleryBugWorkaroundSetting()); + s.put("useVolumeKeysForListNavigation", new BooleanSetting(false)); + s.put("useVolumeKeysForNavigation", new BooleanSetting(false)); + s.put("zoomControlsEnabled", new BooleanSetting(false)); + + SETTINGS = Collections.unmodifiableMap(s); } public static Map validate(Map importedSettings) { From f21e14afc7778eb7e88708b310de657e55d5f7a9 Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 8 Oct 2011 17:58:57 +0200 Subject: [PATCH 086/116] Added input validation for identity settings --- src/com/fsck/k9/Account.java | 11 --- .../fsck/k9/preferences/IdentitySettings.java | 99 +++++++++++++++++++ src/com/fsck/k9/preferences/Settings.java | 3 +- .../fsck/k9/preferences/StorageExporter.java | 56 +++++++---- .../fsck/k9/preferences/StorageImporter.java | 65 +++++++++--- 5 files changed, 185 insertions(+), 49 deletions(-) create mode 100644 src/com/fsck/k9/preferences/IdentitySettings.java diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java index cccabd157..94de6abb2 100644 --- a/src/com/fsck/k9/Account.java +++ b/src/com/fsck/k9/Account.java @@ -22,12 +22,10 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; -import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -63,15 +61,6 @@ public class Account implements BaseAccount { public static final String IDENTITY_EMAIL_KEY = "email"; public static final String IDENTITY_DESCRIPTION_KEY = "description"; - public static final Set IDENTITY_KEYS = new HashSet(); - static { - IDENTITY_KEYS.add(IDENTITY_NAME_KEY); - IDENTITY_KEYS.add(IDENTITY_EMAIL_KEY); - IDENTITY_KEYS.add(IDENTITY_DESCRIPTION_KEY); - IDENTITY_KEYS.add("signatureUse"); - IDENTITY_KEYS.add("signature"); - IDENTITY_KEYS.add("replyTo"); - } /** *
diff --git a/src/com/fsck/k9/preferences/IdentitySettings.java b/src/com/fsck/k9/preferences/IdentitySettings.java
new file mode 100644
index 000000000..f796446e1
--- /dev/null
+++ b/src/com/fsck/k9/preferences/IdentitySettings.java
@@ -0,0 +1,99 @@
+package com.fsck.k9.preferences;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import android.content.SharedPreferences;
+
+import com.fsck.k9.EmailAddressValidator;
+import com.fsck.k9.K9;
+import com.fsck.k9.R;
+import com.fsck.k9.preferences.Settings.*;
+
+public class IdentitySettings {
+    public static final Map SETTINGS;
+
+    static {
+        Map s = new LinkedHashMap();
+
+        s.put("signature", new SignatureSetting());
+        s.put("signatureUse", new BooleanSetting(true));
+        s.put("replyTo", new OptionalEmailAddressSetting());
+
+        SETTINGS = Collections.unmodifiableMap(s);
+    }
+
+    public static Map validate(Map importedSettings,
+            boolean useDefaultValues) {
+        return Settings.validate(SETTINGS, importedSettings, useDefaultValues);
+    }
+
+    public static Map getIdentitySettings(SharedPreferences storage, String uuid,
+            int identityIndex) {
+        Map result = new HashMap();
+        String prefix = uuid + ".";
+        String suffix = "." + Integer.toString(identityIndex);
+        for (String key : SETTINGS.keySet()) {
+            String value = storage.getString(prefix + key + suffix, null);
+            if (value != null) {
+                result.put(key, value);
+            }
+        }
+        return result;
+    }
+
+
+    public static boolean isEmailAddressValid(String email) {
+        return new EmailAddressValidator().isValidAddressOnly(email);
+    }
+
+    /**
+     * The message signature setting.
+     */
+    public static class SignatureSetting extends SettingsDescription {
+        public SignatureSetting() {
+            super(null);
+        }
+
+        @Override
+        public Object getDefaultValue() {
+            return K9.app.getResources().getString(R.string.default_signature);
+        }
+
+        @Override
+        public Object fromString(String value) throws InvalidSettingValueException {
+            return value;
+        }
+    }
+
+    /**
+     * An optional email address setting.
+     */
+    public static class OptionalEmailAddressSetting extends SettingsDescription {
+        private EmailAddressValidator mValidator;
+
+        public OptionalEmailAddressSetting() {
+            super(null);
+            mValidator = new EmailAddressValidator();
+        }
+
+        @Override
+        public Object fromString(String value) throws InvalidSettingValueException {
+            if (value != null && !mValidator.isValidAddressOnly(value)) {
+                throw new InvalidSettingValueException();
+            }
+            return value;
+        }
+
+        @Override
+        public String toPrettyString(Object value) {
+            return (value == null) ? "" : value.toString();
+        }
+
+        @Override
+        public Object fromPrettyString(String value) throws InvalidSettingValueException {
+            return ("".equals(value)) ? null : fromString(value);
+        }
+    }
+}
diff --git a/src/com/fsck/k9/preferences/Settings.java b/src/com/fsck/k9/preferences/Settings.java
index fca782242..29f02636c 100644
--- a/src/com/fsck/k9/preferences/Settings.java
+++ b/src/com/fsck/k9/preferences/Settings.java
@@ -64,7 +64,8 @@ public class Settings {
 
             if (useDefaultValue) {
                 Object defaultValue = desc.getDefaultValue();
-                validatedSettings.put(key, desc.toString(defaultValue));
+                String value = (defaultValue != null) ? desc.toString(defaultValue) : null;
+                validatedSettings.put(key, value);
             }
         }
 
diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index 4bccb544e..0134e947c 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -295,7 +295,7 @@ public class StorageExporter {
             if (comps.length >= 3) {
                 String thirdPart = comps[2];
 
-                if (Account.IDENTITY_KEYS.contains(secondPart)) {
+                if (Account.IDENTITY_DESCRIPTION_KEY.equals(secondPart)) {
                     // This is an identity key. Save identity index for later...
                     try {
                         identities.add(Integer.parseInt(thirdPart));
@@ -362,48 +362,62 @@ public class StorageExporter {
 
         serializer.startTag(null, IDENTITY_ELEMENT);
 
-        String name = (String) prefs.get(accountUuid + "." + Account.IDENTITY_NAME_KEY +
-                "." + identity);
+        String prefix = accountUuid + ".";
+        String suffix = "." + identity;
+
+        // Write name belonging to the identity
+        String name = (String) prefs.get(prefix + Account.IDENTITY_NAME_KEY + suffix);
         serializer.startTag(null, NAME_ELEMENT);
         serializer.text(name);
         serializer.endTag(null, NAME_ELEMENT);
 
-        String email = (String) prefs.get(accountUuid + "." + Account.IDENTITY_EMAIL_KEY +
-                "." + identity);
+        // Write email address belonging to the identity
+        String email = (String) prefs.get(prefix + Account.IDENTITY_EMAIL_KEY + suffix);
         serializer.startTag(null, EMAIL_ELEMENT);
         serializer.text(email);
         serializer.endTag(null, EMAIL_ELEMENT);
 
-        String description = (String) prefs.get(accountUuid + "." +
-                Account.IDENTITY_DESCRIPTION_KEY + "." + identity);
+        // Write identity description
+        String description = (String) prefs.get(prefix + Account.IDENTITY_DESCRIPTION_KEY + suffix);
         if (description != null) {
             serializer.startTag(null, DESCRIPTION_ELEMENT);
             serializer.text(description);
             serializer.endTag(null, DESCRIPTION_ELEMENT);
         }
 
+        // Write identity settings
         serializer.startTag(null, SETTINGS_ELEMENT);
         for (Map.Entry entry : prefs.entrySet()) {
             String key = entry.getKey();
-            String value = entry.getValue().toString();
+            String valueString = entry.getValue().toString();
             String[] comps = key.split("\\.");
-            if (comps.length >= 3) {
-                String keyUuid = comps[0];
-                String identityKey = comps[1];
-                String identityIndex = comps[2];
-                if (!keyUuid.equals(accountUuid) || !identityIndex.equals(identity)
-                        || !Account.IDENTITY_KEYS.contains(identityKey)
-                        || Account.IDENTITY_NAME_KEY.equals(identityKey)
-                        || Account.IDENTITY_EMAIL_KEY.equals(identityKey)
-                        || Account.IDENTITY_DESCRIPTION_KEY.equals(identityKey)) {
-                    continue;
-                }
-            } else {
+
+            if (comps.length < 3) {
                 // Skip non-identity config entries
                 continue;
             }
 
-            writeKeyValue(serializer, comps[1], value);
+            String keyUuid = comps[0];
+            String identityKey = comps[1];
+            String identityIndex = comps[2];
+            if (!keyUuid.equals(accountUuid) || !identityIndex.equals(identity)) {
+                // Skip entries that belong to another identity
+                continue;
+            }
+
+            SettingsDescription setting = IdentitySettings.SETTINGS.get(identityKey);
+            if (setting != null) {
+                // Only write settings that have an entry in IdentitySettings.SETTINGS
+                try {
+                    Object value = setting.fromString(valueString);
+                    String outputValue = setting.toPrettyString(value);
+                    writeKeyValue(serializer, identityKey, outputValue);
+                } catch (InvalidSettingValueException e) {
+                    Log.w(K9.LOG_TAG, "Identity setting \"" + identityKey +
+                            "\" has invalid value \"" + valueString +
+                            "\" in preference storage. This shouldn't happen!");
+                }
+            }
         }
         serializer.endTag(null, SETTINGS_ELEMENT);
 
diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index 76ac954d2..3fff2fea1 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -28,6 +28,7 @@ import com.fsck.k9.mail.ConnectionSecurity;
 import com.fsck.k9.mail.ServerSettings;
 import com.fsck.k9.mail.Store;
 import com.fsck.k9.mail.Transport;
+import com.fsck.k9.preferences.Settings.InvalidSettingValueException;
 
 public class StorageImporter {
 
@@ -277,7 +278,8 @@ public class StorageImporter {
     }
 
     private static AccountDescriptionPair importAccount(Context context,
-            SharedPreferences.Editor editor, ImportedAccount account, boolean overwrite) {
+            SharedPreferences.Editor editor, ImportedAccount account, boolean overwrite)
+            throws InvalidSettingValueException {
 
         AccountDescription original = new AccountDescription(account.name, account.uuid);
 
@@ -352,7 +354,7 @@ public class StorageImporter {
         }
 
         if (account.identities != null) {
-            importIdentities(editor, uuid, account, overwrite, existingAccount);
+            importIdentities(editor, uuid, account, overwrite, existingAccount, prefs);
         }
 
         // Write folder settings
@@ -374,7 +376,8 @@ public class StorageImporter {
     }
 
     private static void importIdentities(SharedPreferences.Editor editor, String uuid,
-            ImportedAccount account, boolean overwrite, Account existingAccount) {
+            ImportedAccount account, boolean overwrite, Account existingAccount,
+            Preferences prefs) throws InvalidSettingValueException {
 
         String accountKeyPrefix = uuid + ".";
 
@@ -391,17 +394,20 @@ public class StorageImporter {
         // Write identities
         for (ImportedIdentity identity : account.identities) {
             int writeIdentityIndex = nextIdentityIndex;
-            if (existingIdentities.size() > 0) {
+            boolean mergeSettings = false;
+            if (overwrite && existingIdentities.size() > 0) {
                 int identityIndex = findIdentity(identity, existingIdentities);
-                if (overwrite && identityIndex != -1) {
+                if (identityIndex != -1) {
                     writeIdentityIndex = identityIndex;
+                    mergeSettings = true;
                 }
             }
-            if (writeIdentityIndex == nextIdentityIndex) {
+            if (!mergeSettings) {
                 nextIdentityIndex++;
             }
 
-            String identityDescription = identity.description;
+            String identityDescription = (identity.description == null) ?
+                    "Imported" : identity.description;
             if (isIdentityDescriptionUsed(identityDescription, existingIdentities)) {
                 // Identity description is already in use. So generate a new one by appending
                 // " (x)", where x is the first number >= 1 that results in an unused identity
@@ -414,18 +420,45 @@ public class StorageImporter {
                 }
             }
 
-            editor.putString(accountKeyPrefix + Account.IDENTITY_NAME_KEY + "." +
-                    writeIdentityIndex, identity.name);
-            editor.putString(accountKeyPrefix + Account.IDENTITY_EMAIL_KEY + "." +
-                    writeIdentityIndex, identity.email);
-            editor.putString(accountKeyPrefix + Account.IDENTITY_DESCRIPTION_KEY + "." +
-                    writeIdentityIndex, identityDescription);
+            String identitySuffix = "." + writeIdentityIndex;
+
+            // Write name used in identity
+            String identityName = (identity.name == null) ? "" : identity.name;
+            editor.putString(accountKeyPrefix + Account.IDENTITY_NAME_KEY + identitySuffix,
+                    identityName);
+
+            // Validate email address
+            if (!IdentitySettings.isEmailAddressValid(identity.email)) {
+                throw new InvalidSettingValueException();
+            }
+
+            // Write email address
+            editor.putString(accountKeyPrefix + Account.IDENTITY_EMAIL_KEY + identitySuffix,
+                    identity.email);
+
+            // Write identity description
+            editor.putString(accountKeyPrefix + Account.IDENTITY_DESCRIPTION_KEY + identitySuffix,
+                    identityDescription);
+
+            // Validate identity settings
+            Map validatedSettings = IdentitySettings.validate(
+                    identity.settings.settings, !mergeSettings);
+
+            // Merge identity settings if necessary
+            Map writeSettings;
+            if (mergeSettings) {
+                writeSettings = new HashMap(IdentitySettings.getIdentitySettings(
+                        prefs.getPreferences(), uuid, writeIdentityIndex));
+                writeSettings.putAll(validatedSettings);
+            } else {
+                writeSettings = new HashMap(validatedSettings);
+            }
 
             // Write identity settings
-            for (Map.Entry setting : identity.settings.settings.entrySet()) {
-                String key = setting.getKey();
+            for (Map.Entry setting : writeSettings.entrySet()) {
+                String key = accountKeyPrefix + setting.getKey() + identitySuffix;
                 String value = setting.getValue();
-                editor.putString(accountKeyPrefix + key + "." + writeIdentityIndex, value);
+                editor.putString(key, value);
             }
         }
     }

From 1ab7dbae54b145f8480c9ab04a91e59d9f698f4a Mon Sep 17 00:00:00 2001
From: cketti 
Date: Sat, 8 Oct 2011 21:30:45 +0200
Subject: [PATCH 087/116] Added input validation for folder settings

---
 src/com/fsck/k9/mail/store/LocalStore.java    |  9 ---
 .../fsck/k9/preferences/FolderSettings.java   | 44 ++++++++++++
 .../fsck/k9/preferences/StorageExporter.java  | 42 +++++++----
 .../fsck/k9/preferences/StorageImporter.java  | 72 ++++++++++++-------
 4 files changed, 120 insertions(+), 47 deletions(-)
 create mode 100644 src/com/fsck/k9/preferences/FolderSettings.java

diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index 3b0137a00..d742c6a8b 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -88,15 +88,6 @@ public class LocalStore extends Store implements Serializable {
         HEADERS_TO_SAVE.add("User-Agent");
     }
 
-    public static final Set FOLDER_SETTINGS_KEYS = new HashSet();
-    static {
-        FOLDER_SETTINGS_KEYS.add("displayMode");
-        FOLDER_SETTINGS_KEYS.add("syncMode");
-        FOLDER_SETTINGS_KEYS.add("pushMode");
-        FOLDER_SETTINGS_KEYS.add("inTopGroup");
-        FOLDER_SETTINGS_KEYS.add("integrate");
-    }
-
     /*
      * a String containing the columns getMessages expects to work with
      * in the correct order.
diff --git a/src/com/fsck/k9/preferences/FolderSettings.java b/src/com/fsck/k9/preferences/FolderSettings.java
new file mode 100644
index 000000000..1a2af5502
--- /dev/null
+++ b/src/com/fsck/k9/preferences/FolderSettings.java
@@ -0,0 +1,44 @@
+package com.fsck.k9.preferences;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import android.content.SharedPreferences;
+
+import com.fsck.k9.mail.Folder.FolderClass;
+import com.fsck.k9.preferences.Settings.*;
+
+public class FolderSettings {
+    public static final Map SETTINGS;
+
+    static {
+        Map s = new LinkedHashMap();
+
+        s.put("displayMode", new EnumSetting(FolderClass.class, FolderClass.NO_CLASS));
+        s.put("syncMode", new EnumSetting(FolderClass.class, FolderClass.INHERITED));
+        s.put("pushMode", new EnumSetting(FolderClass.class, FolderClass.INHERITED));
+        s.put("inTopGroup", new BooleanSetting(false));
+        s.put("integrate", new BooleanSetting(false));
+
+        SETTINGS = Collections.unmodifiableMap(s);
+    }
+
+    public static Map validate(Map importedSettings,
+            boolean useDefaultValues) {
+        return Settings.validate(SETTINGS, importedSettings, useDefaultValues);
+    }
+
+    public static Map getFolderSettings(SharedPreferences storage, String uuid,
+            String folderName) {
+        Map result = new HashMap();
+        String prefix = uuid + "." + folderName + ".";
+        for (String key : SETTINGS.keySet()) {
+            String value = storage.getString(prefix + key, null);
+            if (value != null) {
+                result.put(key, value);
+            }
+        }
+        return result;
+    }
+}
diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index 0134e947c..b9de982e7 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -28,7 +28,6 @@ import com.fsck.k9.helper.Utility;
 import com.fsck.k9.mail.Store;
 import com.fsck.k9.mail.ServerSettings;
 import com.fsck.k9.mail.Transport;
-import com.fsck.k9.mail.store.LocalStore;
 import com.fsck.k9.preferences.Settings.InvalidSettingValueException;
 import com.fsck.k9.preferences.Settings.SettingsDescription;
 
@@ -304,7 +303,7 @@ public class StorageExporter {
                     continue;
                 }
 
-                if (LocalStore.FOLDER_SETTINGS_KEYS.contains(thirdPart)) {
+                if (FolderSettings.SETTINGS.containsKey(thirdPart)) {
                     // This is a folder key. Save folder name for later...
                     folders.add(secondPart);
                     // ... but don't write it now.
@@ -429,25 +428,42 @@ public class StorageExporter {
 
         serializer.startTag(null, FOLDER_ELEMENT);
         serializer.attribute(null, NAME_ATTRIBUTE, folder);
+
+        // Write folder settings
         for (Map.Entry entry : prefs.entrySet()) {
             String key = entry.getKey();
-            String value = entry.getValue().toString();
+            String valueString = entry.getValue().toString();
             String[] comps = key.split("\\.");
-            if (comps.length >= 3) {
-                String keyUuid = comps[0];
-                String folderName = comps[1];
-                String folderKey = comps[2];
-                if (!keyUuid.equals(accountUuid) || !folderName.equals(folder)
-                        || !LocalStore.FOLDER_SETTINGS_KEYS.contains(folderKey)) {
-                    continue;
-                }
-            } else {
+
+            if (comps.length < 3) {
                 // Skip non-folder config entries
                 continue;
             }
 
-            writeKeyValue(serializer, comps[2], value);
+            String keyUuid = comps[0];
+            String folderName = comps[1];
+            String folderKey = comps[2];
+
+            if (!keyUuid.equals(accountUuid) || !folderName.equals(folder)) {
+                // Skip entries that belong to another folder
+                continue;
+            }
+
+            SettingsDescription setting = FolderSettings.SETTINGS.get(folderKey);
+            if (setting != null) {
+                // Only write settings that have an entry in FolderSettings.SETTINGS
+                try {
+                    Object value = setting.fromString(valueString);
+                    String outputValue = setting.toPrettyString(value);
+                    writeKeyValue(serializer, folderKey, outputValue);
+                } catch (InvalidSettingValueException e) {
+                    Log.w(K9.LOG_TAG, "Folder setting \"" + folderKey +
+                            "\" has invalid value \"" + valueString +
+                            "\" in preference storage. This shouldn't happen!");
+                }
+            }
         }
+
         serializer.endTag(null, FOLDER_ELEMENT);
     }
 
diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index 3fff2fea1..8e415df7d 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -311,8 +311,6 @@ public class StorageImporter {
 
         //TODO: validate account name
         //TODO: validate server settings
-        //TODO: validate identity settings
-        //TODO: validate folder settings
 
 
         String accountName = account.name;
@@ -360,12 +358,7 @@ public class StorageImporter {
         // Write folder settings
         if (account.folders != null) {
             for (ImportedFolder folder : account.folders) {
-                String folderKeyPrefix = uuid + "." + folder.name + ".";
-                for (Map.Entry setting : folder.settings.settings.entrySet()) {
-                    String key = folderKeyPrefix + setting.getKey();
-                    String value = setting.getValue();
-                    editor.putString(key, value);
-                }
+                importFolder(editor, uuid, folder, mergeImportedAccount, prefs);
             }
         }
 
@@ -375,6 +368,33 @@ public class StorageImporter {
         return new AccountDescriptionPair(original, imported);
     }
 
+    private static void importFolder(SharedPreferences.Editor editor, String uuid,
+            ImportedFolder folder, boolean overwrite, Preferences prefs) {
+
+        // Validate folder settings
+        Map validatedFolderSettings =
+            FolderSettings.validate(folder.settings.settings, !overwrite);
+
+        // Merge folder settings if necessary
+        Map writeFolderSettings;
+        if (overwrite) {
+            writeFolderSettings = FolderSettings.getFolderSettings(prefs.getPreferences(),
+                    uuid, folder.name);
+            writeFolderSettings.putAll(validatedFolderSettings);
+        } else {
+            writeFolderSettings = new HashMap(validatedFolderSettings);
+        }
+
+        // Write folder settings
+        String folderKeyPrefix = uuid + "." + folder.name + ".";
+        for (Map.Entry setting : writeFolderSettings.entrySet()) {
+            String key = folderKeyPrefix + setting.getKey();
+            String value = setting.getValue();
+            Log.v(K9.LOG_TAG, "Writing " + key + "=" + value);
+            editor.putString(key, value);
+        }
+    }
+
     private static void importIdentities(SharedPreferences.Editor editor, String uuid,
             ImportedAccount account, boolean overwrite, Account existingAccount,
             Preferences prefs) throws InvalidSettingValueException {
@@ -440,25 +460,27 @@ public class StorageImporter {
             editor.putString(accountKeyPrefix + Account.IDENTITY_DESCRIPTION_KEY + identitySuffix,
                     identityDescription);
 
-            // Validate identity settings
-            Map validatedSettings = IdentitySettings.validate(
-                    identity.settings.settings, !mergeSettings);
+            if (identity.settings != null) {
+                // Validate identity settings
+                Map validatedSettings = IdentitySettings.validate(
+                        identity.settings.settings, !mergeSettings);
 
-            // Merge identity settings if necessary
-            Map writeSettings;
-            if (mergeSettings) {
-                writeSettings = new HashMap(IdentitySettings.getIdentitySettings(
-                        prefs.getPreferences(), uuid, writeIdentityIndex));
-                writeSettings.putAll(validatedSettings);
-            } else {
-                writeSettings = new HashMap(validatedSettings);
-            }
+                // Merge identity settings if necessary
+                Map writeSettings;
+                if (mergeSettings) {
+                    writeSettings = new HashMap(IdentitySettings.getIdentitySettings(
+                            prefs.getPreferences(), uuid, writeIdentityIndex));
+                    writeSettings.putAll(validatedSettings);
+                } else {
+                    writeSettings = new HashMap(validatedSettings);
+                }
 
-            // Write identity settings
-            for (Map.Entry setting : writeSettings.entrySet()) {
-                String key = accountKeyPrefix + setting.getKey() + identitySuffix;
-                String value = setting.getValue();
-                editor.putString(key, value);
+                // Write identity settings
+                for (Map.Entry setting : writeSettings.entrySet()) {
+                    String key = accountKeyPrefix + setting.getKey() + identitySuffix;
+                    String value = setting.getValue();
+                    editor.putString(key, value);
+                }
             }
         }
     }

From 6714a7ec5a3ca80e24350d3f7fc37442da77d517 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Sun, 9 Oct 2011 00:04:00 +0200
Subject: [PATCH 088/116] Improved logging in StorageImporter

---
 .../fsck/k9/preferences/StorageImporter.java  | 79 +++++++++++++++----
 1 file changed, 63 insertions(+), 16 deletions(-)

diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index 8e415df7d..49211c021 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -181,7 +181,16 @@ public class StorageImporter {
                         Log.w(K9.LOG_TAG, "Was asked to import global settings but none found.");
                     }
                     if (editor.commit()) {
+                        if (K9.DEBUG) {
+                            Log.v(K9.LOG_TAG, "Committed global settings to the preference " +
+                                    "storage.");
+                        }
                         globalSettingsImported = true;
+                    } else {
+                        if (K9.DEBUG) {
+                            Log.v(K9.LOG_TAG, "Failed to commit global settings to the " +
+                                    "preference storage");
+                        }
                     }
                 } catch (Exception e) {
                     Log.e(K9.LOG_TAG, "Exception while importing global settings", e);
@@ -205,12 +214,29 @@ public class StorageImporter {
                                     newUuids.add(newUuid);
                                 }
                                 if (editor.commit()) {
+                                    if (K9.DEBUG) {
+                                        Log.v(K9.LOG_TAG, "Committed settings for account \"" +
+                                                importResult.imported.name +
+                                                "\" to the settings database.");
+                                    }
                                     importedAccounts.add(importResult);
                                 } else {
+                                    if (K9.DEBUG) {
+                                        Log.w(K9.LOG_TAG, "Error while committing settings for " +
+                                                "account \"" + importResult.original.name +
+                                                "\" to the settings database.");
+                                    }
                                     errorneousAccounts.add(importResult.original);
                                 }
+                            } catch (InvalidSettingValueException e) {
+                                if (K9.DEBUG) {
+                                    Log.e(K9.LOG_TAG, "Encountered invalid setting while " +
+                                            "importing account \"" + account.name + "\"", e);
+                                }
+                                errorneousAccounts.add(new AccountDescription(account.name, account.uuid));
                             } catch (Exception e) {
-                                Log.e(K9.LOG_TAG, "Exception while importing account", e); //XXX
+                                Log.e(K9.LOG_TAG, "Exception while importing account \"" +
+                                        account.name + "\"", e);
                                 errorneousAccounts.add(new AccountDescription(account.name, account.uuid));
                             }
                         } else {
@@ -228,12 +254,12 @@ public class StorageImporter {
                         if (oldAccountUuids.length() > 0) {
                             prefix = oldAccountUuids + ",";
                         }
-                        editor.putString("accountUuids", prefix + appendUuids);
+                        putString(editor, "accountUuids", prefix + appendUuids);
                     }
 
                     String defaultAccountUuid = storage.getString("defaultAccountUuid", null);
                     if (defaultAccountUuid == null) {
-                        editor.putString("defaultAccountUuid", accountUuids.get(0));
+                        putString(editor, "defaultAccountUuid", accountUuids.get(0));
                     }
 
                     if (!editor.commit()) {
@@ -272,8 +298,7 @@ public class StorageImporter {
         for (Map.Entry setting : mergedSettings.entrySet()) {
             String key = setting.getKey();
             String value = setting.getValue();
-            Log.v(K9.LOG_TAG, "Write " + key + "=" + value);
-            editor.putString(key, value);
+            putString(editor, key, value);
         }
     }
 
@@ -326,29 +351,29 @@ public class StorageImporter {
         }
 
         String accountKeyPrefix = uuid + ".";
-        editor.putString(accountKeyPrefix + Account.ACCOUNT_DESCRIPTION_KEY, accountName);
+        putString(editor, accountKeyPrefix + Account.ACCOUNT_DESCRIPTION_KEY, accountName);
 
         // Write incoming server settings (storeUri)
         ServerSettings incoming = new ImportedServerSettings(account.incoming);
         String storeUri = Store.createStoreUri(incoming);
-        editor.putString(accountKeyPrefix + Account.STORE_URI_KEY, Utility.base64Encode(storeUri));
+        putString(editor, accountKeyPrefix + Account.STORE_URI_KEY, Utility.base64Encode(storeUri));
 
         // Write outgoing server settings (transportUri)
         ServerSettings outgoing = new ImportedServerSettings(account.outgoing);
         String transportUri = Transport.createTransportUri(outgoing);
-        editor.putString(accountKeyPrefix + Account.TRANSPORT_URI_KEY, Utility.base64Encode(transportUri));
+        putString(editor, accountKeyPrefix + Account.TRANSPORT_URI_KEY, Utility.base64Encode(transportUri));
 
         // Write account settings
         for (Map.Entry setting : writeSettings.entrySet()) {
             String key = accountKeyPrefix + setting.getKey();
             String value = setting.getValue();
-            editor.putString(key, value);
+            putString(editor, key, value);
         }
 
         // If it's a new account generate and write a new "accountNumber"
         if (!mergeImportedAccount) {
             int newAccountNumber = Account.generateAccountNumber(prefs);
-            editor.putString(accountKeyPrefix + "accountNumber", Integer.toString(newAccountNumber));
+            putString(editor, accountKeyPrefix + "accountNumber", Integer.toString(newAccountNumber));
         }
 
         if (account.identities != null) {
@@ -390,8 +415,7 @@ public class StorageImporter {
         for (Map.Entry setting : writeFolderSettings.entrySet()) {
             String key = folderKeyPrefix + setting.getKey();
             String value = setting.getValue();
-            Log.v(K9.LOG_TAG, "Writing " + key + "=" + value);
-            editor.putString(key, value);
+            putString(editor, key, value);
         }
     }
 
@@ -444,7 +468,7 @@ public class StorageImporter {
 
             // Write name used in identity
             String identityName = (identity.name == null) ? "" : identity.name;
-            editor.putString(accountKeyPrefix + Account.IDENTITY_NAME_KEY + identitySuffix,
+            putString(editor, accountKeyPrefix + Account.IDENTITY_NAME_KEY + identitySuffix,
                     identityName);
 
             // Validate email address
@@ -453,11 +477,11 @@ public class StorageImporter {
             }
 
             // Write email address
-            editor.putString(accountKeyPrefix + Account.IDENTITY_EMAIL_KEY + identitySuffix,
+            putString(editor, accountKeyPrefix + Account.IDENTITY_EMAIL_KEY + identitySuffix,
                     identity.email);
 
             // Write identity description
-            editor.putString(accountKeyPrefix + Account.IDENTITY_DESCRIPTION_KEY + identitySuffix,
+            putString(editor, accountKeyPrefix + Account.IDENTITY_DESCRIPTION_KEY + identitySuffix,
                     identityDescription);
 
             if (identity.settings != null) {
@@ -479,7 +503,7 @@ public class StorageImporter {
                 for (Map.Entry setting : writeSettings.entrySet()) {
                     String key = accountKeyPrefix + setting.getKey() + identitySuffix;
                     String value = setting.getValue();
-                    editor.putString(key, value);
+                    putString(editor, key, value);
                 }
             }
         }
@@ -515,6 +539,29 @@ public class StorageImporter {
         return -1;
     }
 
+    /**
+     * Write to an {@link SharedPreferences.Editor} while logging what is written if debug logging
+     * is enabled.
+     *
+     * @param editor
+     *         The {@code Editor} to write to.
+     * @param key
+     *         The name of the preference to modify.
+     * @param value
+     *         The new value for the preference.
+     */
+    private static void putString(SharedPreferences.Editor editor, String key, String value) {
+        if (K9.DEBUG) {
+            String outputValue = value;
+            if (!K9.DEBUG_SENSITIVE &&
+                    (key.endsWith(".transportUri") || key.endsWith(".storeUri"))) {
+                outputValue = "*sensitive*";
+            }
+            Log.v(K9.LOG_TAG, "Setting " + key + "=" + outputValue);
+        }
+        editor.putString(key, value);
+    }
+
     private static Imported parseSettings(InputStream inputStream, boolean globalSettings,
             List accountUuids, boolean overwrite, boolean overview)
     throws StorageImportExportException {

From d791770154843763ffc7301189604e8db27c9554 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Sun, 9 Oct 2011 00:31:33 +0200
Subject: [PATCH 089/116] Code cleanup

---
 .../fsck/k9/preferences/StorageImporter.java  | 54 +++++++++----------
 1 file changed, 27 insertions(+), 27 deletions(-)

diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index 49211c021..d0e0504ce 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -321,24 +321,8 @@ public class StorageImporter {
             uuid = UUID.randomUUID().toString();
         }
 
-        Map validatedSettings =
-            AccountSettings.validate(account.settings.settings, !mergeImportedAccount);
-
-        Map writeSettings;
-        if (mergeImportedAccount) {
-            writeSettings = new HashMap(
-                    AccountSettings.getAccountSettings(prefs.getPreferences(), uuid));
-            writeSettings.putAll(validatedSettings);
-        } else {
-            writeSettings = new HashMap(validatedSettings);
-        }
-
-
-        //TODO: validate account name
-        //TODO: validate server settings
-
-
-        String accountName = account.name;
+        // Make sure the account name is unique
+        String accountName = (account.name != null) ? account.name : "Imported";
         if (isAccountNameUsed(accountName, accounts)) {
             // Account name is already in use. So generate a new one by appending " (x)", where x
             // is the first number >= 1 that results in an unused account name.
@@ -350,6 +334,7 @@ public class StorageImporter {
             }
         }
 
+        // Write account name
         String accountKeyPrefix = uuid + ".";
         putString(editor, accountKeyPrefix + Account.ACCOUNT_DESCRIPTION_KEY, accountName);
 
@@ -363,6 +348,20 @@ public class StorageImporter {
         String transportUri = Transport.createTransportUri(outgoing);
         putString(editor, accountKeyPrefix + Account.TRANSPORT_URI_KEY, Utility.base64Encode(transportUri));
 
+        // Validate account settings
+        Map validatedSettings =
+            AccountSettings.validate(account.settings.settings, !mergeImportedAccount);
+
+        // Merge account settings if necessary
+        Map writeSettings;
+        if (mergeImportedAccount) {
+            writeSettings = new HashMap(
+                    AccountSettings.getAccountSettings(prefs.getPreferences(), uuid));
+            writeSettings.putAll(validatedSettings);
+        } else {
+            writeSettings = validatedSettings;
+        }
+
         // Write account settings
         for (Map.Entry setting : writeSettings.entrySet()) {
             String key = accountKeyPrefix + setting.getKey();
@@ -376,6 +375,7 @@ public class StorageImporter {
             putString(editor, accountKeyPrefix + "accountNumber", Integer.toString(newAccountNumber));
         }
 
+        // Write identities
         if (account.identities != null) {
             importIdentities(editor, uuid, account, overwrite, existingAccount, prefs);
         }
@@ -397,23 +397,23 @@ public class StorageImporter {
             ImportedFolder folder, boolean overwrite, Preferences prefs) {
 
         // Validate folder settings
-        Map validatedFolderSettings =
+        Map validatedSettings =
             FolderSettings.validate(folder.settings.settings, !overwrite);
 
         // Merge folder settings if necessary
-        Map writeFolderSettings;
+        Map writeSettings;
         if (overwrite) {
-            writeFolderSettings = FolderSettings.getFolderSettings(prefs.getPreferences(),
+            writeSettings = FolderSettings.getFolderSettings(prefs.getPreferences(),
                     uuid, folder.name);
-            writeFolderSettings.putAll(validatedFolderSettings);
+            writeSettings.putAll(validatedSettings);
         } else {
-            writeFolderSettings = new HashMap(validatedFolderSettings);
+            writeSettings = validatedSettings;
         }
 
         // Write folder settings
-        String folderKeyPrefix = uuid + "." + folder.name + ".";
-        for (Map.Entry setting : writeFolderSettings.entrySet()) {
-            String key = folderKeyPrefix + setting.getKey();
+        String prefix = uuid + "." + folder.name + ".";
+        for (Map.Entry setting : writeSettings.entrySet()) {
+            String key = prefix + setting.getKey();
             String value = setting.getValue();
             putString(editor, key, value);
         }
@@ -496,7 +496,7 @@ public class StorageImporter {
                             prefs.getPreferences(), uuid, writeIdentityIndex));
                     writeSettings.putAll(validatedSettings);
                 } else {
-                    writeSettings = new HashMap(validatedSettings);
+                    writeSettings = validatedSettings;
                 }
 
                 // Write identity settings

From 13b74d0d9e4f308ddb515ef5f39e2af0441d5131 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Sun, 9 Oct 2011 01:14:59 +0200
Subject: [PATCH 090/116] Added error logging

---
 src/com/fsck/k9/preferences/StorageExporter.java | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index b9de982e7..47c47c13c 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -81,14 +81,15 @@ public class StorageExporter {
             throws StorageImportExportException {
 
         OutputStream os = null;
+        String filename = null;
         try
         {
             File dir = new File(Environment.getExternalStorageDirectory() + File.separator
                                 + context.getPackageName());
             dir.mkdirs();
             File file = Utility.createUniqueFile(dir, EXPORT_FILENAME);
-            String fileName = file.getAbsolutePath();
-            os = new FileOutputStream(fileName);
+            filename = file.getAbsolutePath();
+            os = new FileOutputStream(filename);
 
             if (encryptionKey == null) {
                 exportPreferences(context, os, includeGlobals, accountUuids);
@@ -98,14 +99,16 @@ public class StorageExporter {
             }
 
             // If all went well, we return the name of the file just written.
-            return fileName;
+            return filename;
         } catch (Exception e) {
             throw new StorageImportExportException(e);
         } finally {
             if (os != null) {
                 try {
                     os.close();
-                } catch (IOException ioe) {}
+                } catch (IOException ioe) {
+                    Log.w(K9.LOG_TAG, "Couldn't close exported settings file: " + filename);
+                }
             }
         }
     }

From 1de28150e2930859b89e03e47d982297eaadf737 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Sun, 9 Oct 2011 01:23:45 +0200
Subject: [PATCH 091/116] Require accounts to at least have one identity when
 importing

---
 src/com/fsck/k9/preferences/StorageImporter.java | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java
index d0e0504ce..ac0968f01 100644
--- a/src/com/fsck/k9/preferences/StorageImporter.java
+++ b/src/com/fsck/k9/preferences/StorageImporter.java
@@ -378,6 +378,9 @@ public class StorageImporter {
         // Write identities
         if (account.identities != null) {
             importIdentities(editor, uuid, account, overwrite, existingAccount, prefs);
+        } else if (!mergeImportedAccount) {
+            // Require accounts to at least have one identity
+            throw new InvalidSettingValueException();
         }
 
         // Write folder settings

From 7a9c747db9bdf698abb4b6aeaa5e0f56c47462db Mon Sep 17 00:00:00 2001
From: cketti 
Date: Tue, 11 Oct 2011 03:12:18 +0200
Subject: [PATCH 092/116] Don't export incoming/outgoing server passwords

---
 src/com/fsck/k9/preferences/StorageExporter.java | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java
index 47c47c13c..a1f0d0732 100644
--- a/src/com/fsck/k9/preferences/StorageExporter.java
+++ b/src/com/fsck/k9/preferences/StorageExporter.java
@@ -231,8 +231,8 @@ public class StorageExporter {
         writeElement(serializer, CONNECTION_SECURITY_ELEMENT, incoming.connectionSecurity.name());
         writeElement(serializer, AUTHENTICATION_TYPE_ELEMENT, incoming.authenticationType);
         writeElement(serializer, USERNAME_ELEMENT, incoming.username);
-        //TODO: make saving the password optional
-        writeElement(serializer, PASSWORD_ELEMENT, incoming.password);
+        // XXX For now we don't export the password
+        //writeElement(serializer, PASSWORD_ELEMENT, incoming.password);
 
         Map extras = incoming.getExtra();
         if (extras != null && extras.size() > 0) {
@@ -258,8 +258,8 @@ public class StorageExporter {
         writeElement(serializer, CONNECTION_SECURITY_ELEMENT, outgoing.connectionSecurity.name());
         writeElement(serializer, AUTHENTICATION_TYPE_ELEMENT, outgoing.authenticationType);
         writeElement(serializer, USERNAME_ELEMENT, outgoing.username);
-        //TODO: make saving the password optional
-        writeElement(serializer, PASSWORD_ELEMENT, outgoing.password);
+        // XXX For now we don't export the password
+        //writeElement(serializer, PASSWORD_ELEMENT, outgoing.password);
 
         extras = outgoing.getExtra();
         if (extras != null && extras.size() > 0) {

From b05750c245faba90a526ac971454e9b92f617bb9 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Thu, 13 Oct 2011 02:35:08 +0200
Subject: [PATCH 093/116] Better input validation in *Store.createUri()

---
 src/com/fsck/k9/mail/store/ImapStore.java     | 13 ++++++++--
 src/com/fsck/k9/mail/store/Pop3Store.java     |  3 ++-
 src/com/fsck/k9/mail/store/WebDavStore.java   | 26 ++++++++++++-------
 .../fsck/k9/mail/transport/SmtpTransport.java | 13 +++++++---
 4 files changed, 40 insertions(+), 15 deletions(-)

diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java
index 382cf9268..89836f40c 100644
--- a/src/com/fsck/k9/mail/store/ImapStore.java
+++ b/src/com/fsck/k9/mail/store/ImapStore.java
@@ -237,7 +237,8 @@ public class ImapStore extends Store {
         String passwordEnc;
         try {
             userEnc = URLEncoder.encode(server.username, "UTF-8");
-            passwordEnc = URLEncoder.encode(server.password, "UTF-8");
+            passwordEnc = (server.password != null) ?
+                    URLEncoder.encode(server.password, "UTF-8") : "";
         }
         catch (UnsupportedEncodingException e) {
             throw new IllegalArgumentException("Could not encode username or password", e);
@@ -263,7 +264,15 @@ public class ImapStore extends Store {
                 break;
         }
 
-        String userInfo = server.authenticationType + ":" + userEnc + ":" + passwordEnc;
+        AuthType authType;
+        try {
+            authType = AuthType.valueOf(server.authenticationType);
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Invalid authentication type: " +
+                    server.authenticationType);
+        }
+
+        String userInfo = authType.toString() + ":" + userEnc + ":" + passwordEnc;
         try {
             Map extra = server.getExtra();
             String prefix = (extra != null) ? extra.get(ImapStoreSettings.PATH_PREFIX_KEY) : null;
diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java
index a38c58823..353c452e3 100644
--- a/src/com/fsck/k9/mail/store/Pop3Store.java
+++ b/src/com/fsck/k9/mail/store/Pop3Store.java
@@ -121,7 +121,8 @@ public class Pop3Store extends Store {
         String passwordEnc;
         try {
             userEnc = URLEncoder.encode(server.username, "UTF-8");
-            passwordEnc = URLEncoder.encode(server.password, "UTF-8");
+            passwordEnc = (server.password != null) ?
+                    URLEncoder.encode(server.password, "UTF-8") : "";
         }
         catch (UnsupportedEncodingException e) {
             throw new IllegalArgumentException("Could not encode username or password", e);
diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java
index 85d539e77..f3bae3174 100644
--- a/src/com/fsck/k9/mail/store/WebDavStore.java
+++ b/src/com/fsck/k9/mail/store/WebDavStore.java
@@ -203,7 +203,8 @@ public class WebDavStore extends Store {
         String passwordEnc;
         try {
             userEnc = URLEncoder.encode(server.username, "UTF-8");
-            passwordEnc = URLEncoder.encode(server.password, "UTF-8");
+            passwordEnc = (server.password != null) ?
+                    URLEncoder.encode(server.password, "UTF-8") : "";
         }
         catch (UnsupportedEncodingException e) {
             throw new IllegalArgumentException("Could not encode username or password", e);
@@ -229,15 +230,22 @@ public class WebDavStore extends Store {
                 break;
         }
 
-        Map extra = server.getExtra();
         String userInfo = userEnc + ":" + passwordEnc;
-        String path = extra.get(WebDavStoreSettings.PATH_KEY);
-        path = (path != null) ? path : "";
-        String authPath = extra.get(WebDavStoreSettings.AUTH_PATH_KEY);
-        authPath = (authPath != null) ? authPath : "";
-        String mailboxPath = extra.get(WebDavStoreSettings.MAILBOX_PATH_KEY);
-        mailboxPath = (mailboxPath != null) ? mailboxPath : "";
-        String uriPath = path + "|" + authPath + "|" + mailboxPath;
+
+        String uriPath;
+        Map extra = server.getExtra();
+        if (extra != null) {
+            String path = extra.get(WebDavStoreSettings.PATH_KEY);
+            path = (path != null) ? path : "";
+            String authPath = extra.get(WebDavStoreSettings.AUTH_PATH_KEY);
+            authPath = (authPath != null) ? authPath : "";
+            String mailboxPath = extra.get(WebDavStoreSettings.MAILBOX_PATH_KEY);
+            mailboxPath = (mailboxPath != null) ? mailboxPath : "";
+            uriPath = path + "|" + authPath + "|" + mailboxPath;
+        } else {
+            uriPath = "||";
+        }
+
         try {
             return new URI(scheme, userInfo, server.host, server.port, uriPath,
                 null, null).toString();
diff --git a/src/com/fsck/k9/mail/transport/SmtpTransport.java b/src/com/fsck/k9/mail/transport/SmtpTransport.java
index b92b8236f..e4e945e16 100644
--- a/src/com/fsck/k9/mail/transport/SmtpTransport.java
+++ b/src/com/fsck/k9/mail/transport/SmtpTransport.java
@@ -129,8 +129,10 @@ public class SmtpTransport extends Transport {
         String userEnc;
         String passwordEnc;
         try {
-            userEnc = URLEncoder.encode(server.username, "UTF-8");
-            passwordEnc = URLEncoder.encode(server.password, "UTF-8");
+            userEnc = (server.username != null) ?
+                    URLEncoder.encode(server.username, "UTF-8") : "";
+            passwordEnc = (server.password != null) ?
+                    URLEncoder.encode(server.password, "UTF-8") : "";
         }
         catch (UnsupportedEncodingException e) {
             throw new IllegalArgumentException("Could not encode username or password", e);
@@ -156,7 +158,12 @@ public class SmtpTransport extends Transport {
                 break;
         }
 
-        String userInfo = userEnc + ":" + passwordEnc + ":" + server.authenticationType;
+        String authType = server.authenticationType;
+        if (!"CRAM_MD5".equals(authType) && !"PLAIN".equals(authType)) {
+            throw new IllegalArgumentException("Invalid authentication type: " + authType);
+        }
+
+        String userInfo = userEnc + ":" + passwordEnc + ":" + authType;
         try {
             return new URI(scheme, userInfo, server.host, server.port, null, null,
                     null).toString();

From b146fcb2fde59bbd4a4bc02cecddfa3f791d1c68 Mon Sep 17 00:00:00 2001
From: cketti 
Date: Fri, 14 Oct 2011 02:52:32 +0200
Subject: [PATCH 094/116] Improved handling of object retention on
 configuration changes

---
 src/com/fsck/k9/activity/Accounts.java        | 404 ++++++++++--------
 .../k9/activity/misc/ExtendedAsyncTask.java   |  22 +-
 .../misc/NonConfigurationInstance.java        |  38 ++
 3 files changed, 277 insertions(+), 187 deletions(-)
 create mode 100644 src/com/fsck/k9/activity/misc/NonConfigurationInstance.java

diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java
index 646cd1712..87c76599b 100644
--- a/src/com/fsck/k9/activity/Accounts.java
+++ b/src/com/fsck/k9/activity/Accounts.java
@@ -12,6 +12,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.ProgressDialog;
@@ -60,6 +61,7 @@ import com.fsck.k9.R;
 import com.fsck.k9.SearchAccount;
 import com.fsck.k9.SearchSpecification;
 import com.fsck.k9.activity.misc.ExtendedAsyncTask;
+import com.fsck.k9.activity.misc.NonConfigurationInstance;
 import com.fsck.k9.activity.setup.AccountSettings;
 import com.fsck.k9.activity.setup.AccountSetupBasics;
 import com.fsck.k9.activity.setup.Prefs;
@@ -107,27 +109,11 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
     private FontSizes mFontSizes = K9.getFontSizes();
 
     /**
-     * Contains a reference to a {@link ExtendedAsyncTask} while it is running.
-     */
-    private ExtendedAsyncTask mAsyncTask;
-
-    /**
-     * Contains information about the currently displayed dialog (if available).
+     * Contains information about objects that need to be retained on configuration changes.
      *
-     * 

- * This object is returned from {@link #onRetainNonConfigurationInstance()} if a dialog is - * being displayed while the activity is being restarted. It is then used by the new activity - * instance to re-create that dialog. - *

+ * @see #onRetainNonConfigurationInstance() */ - private DialogInfo mDialogInfo; - - /** - * Reference to the dialog currently being displayed (if available). - * - * @see #showDialog(int, String) - */ - private AlertDialog mDialog; + private NonConfigurationInstance mNonConfigurationInstance; private static final int ACTIVITY_REQUEST_PICK_SETTINGS_FILE = 1; @@ -365,18 +351,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC restoreAccountStats(icicle); // Handle activity restarts because of a configuration change (e.g. rotating the screen) - Object retained = getLastNonConfigurationInstance(); - if (retained != null) { - // If we displayed a dialog before the configuration change, re-create it here - if (retained instanceof DialogInfo) { - DialogInfo dialogInfo = (DialogInfo) retained; - showDialog(dialogInfo.headerRes, dialogInfo.message); - } - // If there's an ExtendedAsyncTask running, update it with the new Activity - else if (retained instanceof ExtendedAsyncTask) { - mAsyncTask = (ExtendedAsyncTask) retained; - mAsyncTask.attach(this); - } + mNonConfigurationInstance = (NonConfigurationInstance) getLastNonConfigurationInstance(); + if (mNonConfigurationInstance != null) { + mNonConfigurationInstance.restore(this); } } @@ -435,12 +412,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override public Object onRetainNonConfigurationInstance() { Object retain = null; - if (mDialogInfo != null) { - retain = mDialogInfo; - dismissDialog(); - } else if (mAsyncTask != null) { - retain = mAsyncTask; - mAsyncTask.detach(); + if (mNonConfigurationInstance != null && mNonConfigurationInstance.retain()) { + retain = mNonConfigurationInstance; } return retain; } @@ -924,71 +897,213 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC Log.i(K9.LOG_TAG, "onImport importing from URI " + uri.toString()); - mAsyncTask = new ListImportContentsAsyncTask(this, uri, null); - mAsyncTask.execute(); + ListImportContentsAsyncTask asyncTask = new ListImportContentsAsyncTask(this, uri, null); + setNonConfigurationInstance(asyncTask); + asyncTask.execute(); } - private void asyncTaskFinished() { - mAsyncTask = null; + + private void showSimpleDialog(int headerRes, int messageRes, Object... args) { + SimpleDialog dialog = new SimpleDialog(headerRes, messageRes, args); + dialog.show(this); + setNonConfigurationInstance(dialog); } - /** - * Stores information about a dialog. - * - * @see Accounts#showDialog(int, String) - * @see Accounts#onCreate(Bundle) - */ - private static class DialogInfo { - public final int headerRes; + private static class SimpleDialog implements NonConfigurationInstance { + private final int mHeaderRes; + private final int mMessageRes; + private Object[] mArguments; + private Dialog mDialog; - //TODO: "message" is already localized. This is a problem if the activity is restarted when - // the system language was changed. We have to recreate the message string in that case. - public final String message; + SimpleDialog(int headerRes, int messageRes, Object... args) { + this.mHeaderRes = headerRes; + this.mMessageRes = messageRes; + this.mArguments = args; + } - DialogInfo(int headerRes, String message) { - this.headerRes = headerRes; - this.message = message; + @Override + public void restore(Activity activity) { + show(activity); + } + + @Override + public boolean retain() { + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + return true; + } + return false; + } + + public void show(final Activity activity) { + final String message = activity.getString(mMessageRes, mArguments); + + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(mHeaderRes); + builder.setMessage(message); + builder.setPositiveButton(R.string.okay_action, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + destroy(); + } + }); + mDialog = builder.show(); + } + + private void destroy() { + mDialog = null; + mArguments = null; } } - /** - * Show a dialog. - * - * @param headerRes - * The resource ID of the string that is used as title for the dialog box. - * @param message - * The message to display. - */ - private void showDialog(final int headerRes, final String message) { - runOnUiThread(new Runnable() { - @Override - public void run() { - // Store information about the dialog so it can be re-created when the activity is - // restarted due to a configuration change. - mDialogInfo = new DialogInfo(headerRes, message); - - final AlertDialog.Builder builder = new AlertDialog.Builder(Accounts.this); - builder.setTitle(headerRes); - builder.setMessage(message); - builder.setPositiveButton(R.string.okay_action, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismissDialog(); - } - }); - mDialog = builder.show(); - } - }); + private void showImportSelectionDialog(ImportContents importContents, Uri uri, + String encryptionKey) { + ImportSelectionDialog dialog = new ImportSelectionDialog(importContents, uri, encryptionKey); + dialog.show(this); + setNonConfigurationInstance(dialog); } - /** - * Dismiss the dialog that was created using {@link #showDialog(int, String)}. - */ - private void dismissDialog() { - mDialog.dismiss(); - mDialogInfo = null; - mDialog = null; + private static class ImportSelectionDialog implements NonConfigurationInstance { + private ImportContents mImportContents; + private Uri mUri; + private String mEncryptionKey; + private Dialog mDialog; + private ListView mImportSelectionView; + private SparseBooleanArray mSelection; + + + ImportSelectionDialog(ImportContents importContents, Uri uri, String encryptionKey) { + mImportContents = importContents; + mUri = uri; + mEncryptionKey = encryptionKey; + } + + @Override + public void restore(Activity activity) { + show((Accounts) activity, mSelection); + } + + @Override + public boolean retain() { + if (mDialog != null) { + // Save the selection state of each list item + mSelection = mImportSelectionView.getCheckedItemPositions(); + mImportSelectionView = null; + + mDialog.dismiss(); + mDialog = null; + return true; + } + return false; + } + + public void show(Accounts activity) { + show(activity, null); + } + + public void show(final Accounts activity, SparseBooleanArray selection) { + final ListView importSelectionView = new ListView(activity); + mImportSelectionView = importSelectionView; + List contents = new ArrayList(); + + if (mImportContents.globalSettings) { + //TODO: read from resources + contents.add("Global settings"); + } + + for (AccountDescription account : mImportContents.accounts) { + contents.add(account.name); + } + + importSelectionView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + importSelectionView.setAdapter(new ArrayAdapter(activity, + android.R.layout.simple_list_item_checked, contents)); + importSelectionView.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int pos, long id) { + CheckedTextView ctv = (CheckedTextView)view; + ctv.setChecked(!ctv.isChecked()); + } + + @Override + public void onNothingSelected(AdapterView arg0) { /* Do nothing */ } + }); + + if (selection != null) { + for (int i = 0, end = contents.size(); i < end; i++) { + importSelectionView.setItemChecked(i, selection.get(i)); + } + } + + //TODO: listview header: "Please select the settings you wish to import" + //TODO: listview footer: "Select all" / "Select none" buttons? + //TODO: listview footer: "Overwrite existing accounts?" checkbox + + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); + //TODO: read from resources + builder.setTitle("Import selection"); + builder.setView(importSelectionView); + builder.setInverseBackgroundForced(true); + builder.setPositiveButton(R.string.okay_action, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + ListAdapter adapter = importSelectionView.getAdapter(); + int count = adapter.getCount(); + SparseBooleanArray pos = importSelectionView.getCheckedItemPositions(); + + boolean includeGlobals = mImportContents.globalSettings ? pos.get(0) : false; + List accountUuids = new ArrayList(); + int start = mImportContents.globalSettings ? 1 : 0; + for (int i = start; i < count; i++) { + if (pos.get(i)) { + accountUuids.add(mImportContents.accounts.get(i-start).uuid); + } + } + + /* + * TODO: Think some more about this. Overwriting could change the store + * type. This requires some additional code in order to work smoothly + * while the app is running. + */ + boolean overwrite = false; + + dialog.dismiss(); + destroy(); + + ImportAsyncTask importAsyncTask = new ImportAsyncTask(activity, + includeGlobals, accountUuids, overwrite, mEncryptionKey, mUri); + activity.setNonConfigurationInstance(importAsyncTask); + importAsyncTask.execute(); + } + }); + builder.setNegativeButton(R.string.cancel_action, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + destroy(); + } + }); + mDialog = builder.show(); + } + + private void destroy() { + mDialog = null; + mImportContents = null; + mUri = null; + mEncryptionKey = null; + mSelection = null; + mImportSelectionView = null; + } + } + + private void setNonConfigurationInstance(NonConfigurationInstance inst) { + mNonConfigurationInstance = inst; } class AccountsAdapter extends ArrayAdapter { @@ -1213,8 +1328,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC }) .show(); */ - mAsyncTask = new ExportAsyncTask(this, includeGlobals, accountUuids, null); - mAsyncTask.execute(); + ExportAsyncTask asyncTask = new ExportAsyncTask(this, includeGlobals, accountUuids, null); + setNonConfigurationInstance(asyncTask); + asyncTask.execute(); } /** @@ -1259,17 +1375,17 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC Accounts activity = (Accounts) mActivity; // Let the activity know that the background task is complete - activity.asyncTaskFinished(); + activity.setNonConfigurationInstance(null); removeProgressDialog(); if (success) { - activity.showDialog(R.string.settings_export_success_header, - mContext.getString(R.string.settings_export_success, mFileName)); + activity.showSimpleDialog(R.string.settings_export_success_header, + R.string.settings_export_success, mFileName); } else { //TODO: make the exporter return an error code; translate that error code to a localized string here - activity.showDialog(R.string.settings_export_failed_header, - mContext.getString(R.string.settings_export_failure, "Something went wrong")); + activity.showSimpleDialog(R.string.settings_export_failed_header, + R.string.settings_export_failure, "Something went wrong"); } } } @@ -1330,7 +1446,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC Accounts activity = (Accounts) mActivity; // Let the activity know that the background task is complete - activity.asyncTaskFinished(); + activity.setNonConfigurationInstance(null); removeProgressDialog(); @@ -1339,14 +1455,14 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC //TODO: display names of imported accounts (name from file *and* possibly new name) - activity.showDialog(R.string.settings_import_success_header, + activity.showSimpleDialog(R.string.settings_import_success_header, //FIXME: use correct file name - mContext.getString(R.string.settings_import_success, imported, "filename")); + R.string.settings_import_success, imported, "filename"); activity.refresh(); } else { //TODO: make the importer return an error code; translate that error code to a localized string here - activity.showDialog(R.string.settings_import_failed_header, - mContext.getString(R.string.settings_import_failure, "unknown", "Something went wrong")); + activity.showSimpleDialog(R.string.settings_import_failed_header, + R.string.settings_import_failure, "unknown", "Something went wrong"); } } } @@ -1399,91 +1515,17 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC Accounts activity = (Accounts) mActivity; // Let the activity know that the background task is complete - activity.asyncTaskFinished(); + activity.setNonConfigurationInstance(null); removeProgressDialog(); if (success) { - showImportSelectionDialog(); + activity.showImportSelectionDialog(mImportContents, mUri, mEncryptionKey); } else { //TODO: make the importer return an error code; translate that error code to a localized string here - activity.showDialog(R.string.settings_import_failed_header, - mContext.getString(R.string.settings_import_failure, "unknown", "Something went wrong")); + activity.showSimpleDialog(R.string.settings_import_failed_header, + R.string.settings_import_failure, "unknown", "Something went wrong"); } } - - //TODO: we need to be able to re-create this dialog after a configuration change - private void showImportSelectionDialog() { - final ListView importSelectionView = new ListView(mActivity); - List contents = new ArrayList(); - if (mImportContents.globalSettings) { - contents.add("Global settings"); - } - for (AccountDescription account : mImportContents.accounts) { - contents.add(account.name); - } - importSelectionView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - importSelectionView.setAdapter(new ArrayAdapter(mActivity, android.R.layout.simple_list_item_checked, contents)); - importSelectionView.setOnItemSelectedListener(new OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int pos, long id) { - CheckedTextView ctv = (CheckedTextView)view; - ctv.setChecked(!ctv.isChecked()); - } - - @Override - public void onNothingSelected(AdapterView arg0) { /* Do nothing */ } - }); - - //TODO: listview header: "Please select the settings you wish to import" - //TODO: listview footer: "Select all" / "Select none" buttons? - //TODO: listview footer: "Overwrite existing accounts?" checkbox - - final AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); - builder.setTitle("Import selection"); - builder.setView(importSelectionView); - builder.setInverseBackgroundForced(true); - builder.setPositiveButton(R.string.okay_action, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - ListAdapter adapter = importSelectionView.getAdapter(); - int count = adapter.getCount(); - SparseBooleanArray pos = importSelectionView.getCheckedItemPositions(); - - boolean includeGlobals = mImportContents.globalSettings ? pos.get(0) : false; - List accountUuids = new ArrayList(); - int start = mImportContents.globalSettings ? 1 : 0; - for (int i = start; i < count; i++) { - if (pos.get(i)) { - accountUuids.add(mImportContents.accounts.get(i-start).uuid); - } - } - - /* - * TODO: Think some more about this. Overwriting could change the store - * type. This requires some additional code in order to work smoothly - * while the app is running. - */ - boolean overwrite = false; - - dialog.dismiss(); - Accounts activity = (Accounts) mActivity; - ImportAsyncTask importAsyncTask = new ImportAsyncTask(activity, - includeGlobals, accountUuids, overwrite, mEncryptionKey, mUri); - activity.mAsyncTask = importAsyncTask; - importAsyncTask.execute(); - } - }); - builder.setNegativeButton(R.string.cancel_action, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - builder.show(); - } } } diff --git a/src/com/fsck/k9/activity/misc/ExtendedAsyncTask.java b/src/com/fsck/k9/activity/misc/ExtendedAsyncTask.java index f488bc929..f8bef5741 100644 --- a/src/com/fsck/k9/activity/misc/ExtendedAsyncTask.java +++ b/src/com/fsck/k9/activity/misc/ExtendedAsyncTask.java @@ -24,11 +24,11 @@ import android.os.AsyncTask; * @param * see {@link AsyncTask} * - * @see #attach(Activity) - * @see #detach() + * @see #restore(Activity) + * @see #retain() */ public abstract class ExtendedAsyncTask - extends AsyncTask { + extends AsyncTask implements NonConfigurationInstance { protected Activity mActivity; protected Context mContext; protected ProgressDialog mProgressDialog; @@ -49,7 +49,8 @@ public abstract class ExtendedAsyncTask * @param activity * The new {@code Activity} instance. Never {@code null}. */ - public void attach(Activity activity) { + @Override + public void restore(Activity activity) { mActivity = activity; showProgressDialog(); } @@ -64,11 +65,20 @@ public abstract class ExtendedAsyncTask * being destroyed. *

* + * @return {@code true} if this instance should be retained; {@code false} otherwise. + * * @see Activity#onRetainNonConfigurationInstance() */ - public void detach() { - removeProgressDialog(); + @Override + public boolean retain() { + boolean retain = false; + if (mProgressDialog != null) { + removeProgressDialog(); + retain = true; + } mActivity = null; + + return retain; } /** diff --git a/src/com/fsck/k9/activity/misc/NonConfigurationInstance.java b/src/com/fsck/k9/activity/misc/NonConfigurationInstance.java new file mode 100644 index 000000000..d9f314581 --- /dev/null +++ b/src/com/fsck/k9/activity/misc/NonConfigurationInstance.java @@ -0,0 +1,38 @@ +package com.fsck.k9.activity.misc; + +import android.app.Activity; + + +public interface NonConfigurationInstance { + /** + * Decide whether to retain this {@code NonConfigurationInstance} and clean up resources if + * necessary. + * + *

+ * This needs to be called when the current activity is being destroyed during an activity + * restart due to a configuration change.
+ * Implementations should make sure that references to the {@code Activity} instance that is + * about to be destroyed are cleared to avoid memory leaks. This includes all UI elements that + * are bound to an activity (e.g. dialogs). They can be re-created in + * {@link #restore(Activity)}. + *

+ * + * @return {@code true} if this instance should be retained; {@code false} otherwise. + * + * @see Activity#onRetainNonConfigurationInstance() + */ + public boolean retain(); + + /** + * Connect this retained {@code NonConfigurationInstance} to the new {@link Activity} instance + * after the activity was restarted due to a configuration change. + * + *

+ * This also creates a new progress dialog that is bound to the new activity. + *

+ * + * @param activity + * The new {@code Activity} instance. Never {@code null}. + */ + public void restore(Activity activity); +} From 5b81dbc2edfc9f0be783cc6768132abb8bd8647b Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 14 Oct 2011 03:42:04 +0200 Subject: [PATCH 095/116] Use resource strings instead of hardcoding things --- res/values/strings.xml | 6 ++++-- src/com/fsck/k9/activity/Accounts.java | 23 +++++++++++------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 09880400a..337a43a98 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1037,6 +1037,8 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Import Export Import settings + Import selection + Global settings Exporting settings... Importing settings... Scanning file... @@ -1046,8 +1048,8 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin 1 account %s accounts - Failed to export settings: %s - Failed to import settings from %s: %s + Failed to export settings + Failed to import any settings from %s Export succeeded Export failed Import succeeded diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 87c76599b..070be42e8 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1010,8 +1010,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC List contents = new ArrayList(); if (mImportContents.globalSettings) { - //TODO: read from resources - contents.add("Global settings"); + contents.add(activity.getString(R.string.settings_import_global_settings)); } for (AccountDescription account : mImportContents.accounts) { @@ -1043,8 +1042,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC //TODO: listview footer: "Overwrite existing accounts?" checkbox final AlertDialog.Builder builder = new AlertDialog.Builder(activity); - //TODO: read from resources - builder.setTitle("Import selection"); + builder.setTitle(activity.getString(R.string.settings_import_selection)); builder.setView(importSelectionView); builder.setInverseBackgroundForced(true); builder.setPositiveButton(R.string.okay_action, @@ -1383,9 +1381,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC activity.showSimpleDialog(R.string.settings_export_success_header, R.string.settings_export_success, mFileName); } else { - //TODO: make the exporter return an error code; translate that error code to a localized string here + //TODO: better error messages activity.showSimpleDialog(R.string.settings_export_failed_header, - R.string.settings_export_failure, "Something went wrong"); + R.string.settings_export_failure); } } } @@ -1450,19 +1448,19 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC removeProgressDialog(); + String filename = mUri.getLastPathSegment(); if (success) { int imported = mImportResults.importedAccounts.size(); //TODO: display names of imported accounts (name from file *and* possibly new name) activity.showSimpleDialog(R.string.settings_import_success_header, - //FIXME: use correct file name - R.string.settings_import_success, imported, "filename"); + R.string.settings_import_success, imported, filename); activity.refresh(); } else { - //TODO: make the importer return an error code; translate that error code to a localized string here + //TODO: better error messages activity.showSimpleDialog(R.string.settings_import_failed_header, - R.string.settings_import_failure, "unknown", "Something went wrong"); + R.string.settings_import_failure, filename); } } } @@ -1522,9 +1520,10 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC if (success) { activity.showImportSelectionDialog(mImportContents, mUri, mEncryptionKey); } else { - //TODO: make the importer return an error code; translate that error code to a localized string here + String filename = mUri.getLastPathSegment(); + //TODO: better error messages activity.showSimpleDialog(R.string.settings_import_failed_header, - R.string.settings_import_failure, "unknown", "Something went wrong"); + R.string.settings_import_failure, filename); } } } From fd16ff8fe6e7080b08386de2deda35e54ea185eb Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 14 Oct 2011 04:15:45 +0200 Subject: [PATCH 096/116] Remove reference to NonConfigurationInstance when no longer needed --- src/com/fsck/k9/activity/Accounts.java | 27 ++++++++------------------ 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 070be42e8..b8a87113a 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -923,7 +923,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override public void restore(Activity activity) { - show(activity); + show((Accounts) activity); } @Override @@ -936,7 +936,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC return false; } - public void show(final Activity activity) { + public void show(final Accounts activity) { final String message = activity.getString(mMessageRes, mArguments); final AlertDialog.Builder builder = new AlertDialog.Builder(activity); @@ -947,16 +947,11 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); - destroy(); + activity.setNonConfigurationInstance(null); } }); mDialog = builder.show(); } - - private void destroy() { - mDialog = null; - mArguments = null; - } } private void showImportSelectionDialog(ImportContents importContents, Uri uri, @@ -1071,7 +1066,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC boolean overwrite = false; dialog.dismiss(); - destroy(); + activity.setNonConfigurationInstance(null); ImportAsyncTask importAsyncTask = new ImportAsyncTask(activity, includeGlobals, accountUuids, overwrite, mEncryptionKey, mUri); @@ -1084,20 +1079,11 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); - destroy(); + activity.setNonConfigurationInstance(null); } }); mDialog = builder.show(); } - - private void destroy() { - mDialog = null; - mImportContents = null; - mUri = null; - mEncryptionKey = null; - mSelection = null; - mImportSelectionView = null; - } } private void setNonConfigurationInstance(NonConfigurationInstance inst) { @@ -1435,6 +1421,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } catch (FileNotFoundException e) { Log.w(K9.LOG_TAG, "Couldn't open import file", e); return false; + } catch (Exception e) { + Log.w(K9.LOG_TAG, "Unknown error", e); + return false; } return true; } From 470ba8bb0397f487aead9cb93af19b6438078c04 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 14 Oct 2011 05:38:27 +0200 Subject: [PATCH 097/116] Removed everything related to settings file encryption The plan is to (re)add this feature later. With a proven method to encrypt files; maybe OpenPGP. --- res/values/strings.xml | 2 - src/com/fsck/k9/activity/Accounts.java | 57 ++++---------- .../fsck/k9/activity/PasswordEntryDialog.java | 78 ------------------- src/com/fsck/k9/preferences/K9Krypto.java | 63 --------------- .../fsck/k9/preferences/StorageExporter.java | 23 +----- .../fsck/k9/preferences/StorageImporter.java | 14 +--- 6 files changed, 19 insertions(+), 218 deletions(-) delete mode 100644 src/com/fsck/k9/activity/PasswordEntryDialog.java delete mode 100644 src/com/fsck/k9/preferences/K9Krypto.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 337a43a98..36a224a5a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1030,8 +1030,6 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Unable to connect. Settings Import & Export - Please enter a password to protect your exported settings: - Please enter the password you used when exporting your settings: Export account settings Export settings and accounts Import diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index b8a87113a..782a58ac9 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -893,11 +893,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } private void onImport(Uri uri) { - //Toast.makeText(this, "Import is disabled for now", Toast.LENGTH_SHORT).show(); - - Log.i(K9.LOG_TAG, "onImport importing from URI " + uri.toString()); - - ListImportContentsAsyncTask asyncTask = new ListImportContentsAsyncTask(this, uri, null); + ListImportContentsAsyncTask asyncTask = new ListImportContentsAsyncTask(this, uri); setNonConfigurationInstance(asyncTask); asyncTask.execute(); } @@ -954,9 +950,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } } - private void showImportSelectionDialog(ImportContents importContents, Uri uri, - String encryptionKey) { - ImportSelectionDialog dialog = new ImportSelectionDialog(importContents, uri, encryptionKey); + private void showImportSelectionDialog(ImportContents importContents, Uri uri) { + ImportSelectionDialog dialog = new ImportSelectionDialog(importContents, uri); dialog.show(this); setNonConfigurationInstance(dialog); } @@ -964,16 +959,14 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private static class ImportSelectionDialog implements NonConfigurationInstance { private ImportContents mImportContents; private Uri mUri; - private String mEncryptionKey; private Dialog mDialog; private ListView mImportSelectionView; private SparseBooleanArray mSelection; - ImportSelectionDialog(ImportContents importContents, Uri uri, String encryptionKey) { + ImportSelectionDialog(ImportContents importContents, Uri uri) { mImportContents = importContents; mUri = uri; - mEncryptionKey = encryptionKey; } @Override @@ -1069,7 +1062,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC activity.setNonConfigurationInstance(null); ImportAsyncTask importAsyncTask = new ImportAsyncTask(activity, - includeGlobals, accountUuids, overwrite, mEncryptionKey, mUri); + includeGlobals, accountUuids, overwrite, mUri); activity.setNonConfigurationInstance(importAsyncTask); importAsyncTask.execute(); } @@ -1296,23 +1289,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC accountUuids.add(account.getUuid()); } - /* Disabled for now - // Prompt the user for a password - new PasswordEntryDialog(this, - getString(R.string.settings_export_encryption_password_prompt), - new PasswordEntryDialog.PasswordEntryListener() { - public void passwordChosen(final String chosenPassword) { - // Got the password. Now run export task in the background. - new ExportAsyncTask(includeGlobals, accountUuids, chosenPassword).execute(); - } - - public void cancel() { - // User cancelled the export. Nothing more to do. - } - }) - .show(); - */ - ExportAsyncTask asyncTask = new ExportAsyncTask(this, includeGlobals, accountUuids, null); + ExportAsyncTask asyncTask = new ExportAsyncTask(this, includeGlobals, accountUuids); setNonConfigurationInstance(asyncTask); asyncTask.execute(); } @@ -1323,16 +1300,14 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private static class ExportAsyncTask extends ExtendedAsyncTask { private boolean mIncludeGlobals; private Set mAccountUuids; - private String mEncryptionKey; private String mFileName; private ExportAsyncTask(Accounts activity, boolean includeGlobals, - Set accountUuids, String encryptionKey) { + Set accountUuids) { super(activity); mIncludeGlobals = includeGlobals; mAccountUuids = accountUuids; - mEncryptionKey = encryptionKey; } @Override @@ -1346,7 +1321,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC protected Boolean doInBackground(Void... params) { try { mFileName = StorageExporter.exportToFile(mContext, mIncludeGlobals, - mAccountUuids, mEncryptionKey); + mAccountUuids); } catch (StorageImportExportException e) { Log.w(K9.LOG_TAG, "Exception during export", e); return false; @@ -1381,18 +1356,15 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private boolean mIncludeGlobals; private List mAccountUuids; private boolean mOverwrite; - private String mEncryptionKey; private Uri mUri; private ImportResults mImportResults; private ImportAsyncTask(Accounts activity, boolean includeGlobals, - List accountUuids, boolean overwrite, String encryptionKey, - Uri uri) { + List accountUuids, boolean overwrite, Uri uri) { super(activity); mIncludeGlobals = includeGlobals; mAccountUuids = accountUuids; mOverwrite = overwrite; - mEncryptionKey = encryptionKey; mUri = uri; } @@ -1409,7 +1381,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC InputStream is = mContext.getContentResolver().openInputStream(mUri); try { mImportResults = StorageImporter.importSettings(mContext, is, - mEncryptionKey, mIncludeGlobals, mAccountUuids, mOverwrite); + mIncludeGlobals, mAccountUuids, mOverwrite); } finally { try { is.close(); @@ -1456,14 +1428,12 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private static class ListImportContentsAsyncTask extends ExtendedAsyncTask { private Uri mUri; - private String mEncryptionKey; private ImportContents mImportContents; - private ListImportContentsAsyncTask(Accounts activity, Uri uri, String encryptionKey) { + private ListImportContentsAsyncTask(Accounts activity, Uri uri) { super(activity); mUri = uri; - mEncryptionKey = encryptionKey; } @Override @@ -1479,8 +1449,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC ContentResolver resolver = mContext.getContentResolver(); InputStream is = resolver.openInputStream(mUri); try { - mImportContents = StorageImporter.getImportStreamContents(mContext, is, - mEncryptionKey); + mImportContents = StorageImporter.getImportStreamContents(mContext, is); } finally { try { is.close(); @@ -1507,7 +1476,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC removeProgressDialog(); if (success) { - activity.showImportSelectionDialog(mImportContents, mUri, mEncryptionKey); + activity.showImportSelectionDialog(mImportContents, mUri); } else { String filename = mUri.getLastPathSegment(); //TODO: better error messages diff --git a/src/com/fsck/k9/activity/PasswordEntryDialog.java b/src/com/fsck/k9/activity/PasswordEntryDialog.java deleted file mode 100644 index c24d23456..000000000 --- a/src/com/fsck/k9/activity/PasswordEntryDialog.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.fsck.k9.activity; - -import com.fsck.k9.R; - -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.content.Context; -import android.content.DialogInterface; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; - -public class PasswordEntryDialog { - public interface PasswordEntryListener { - void passwordChosen(String chosenPassword); - void cancel(); - } - PasswordEntryListener listener; - private EditText passwordView; - AlertDialog dialog; - public PasswordEntryDialog(Context context, String headerText, PasswordEntryListener listener) { - this.listener = listener; - View view = LayoutInflater.from(context).inflate(R.layout.password_entry_dialog, null); - Builder builder = new AlertDialog.Builder(context); - passwordView = (EditText)view.findViewById(R.id.password_text_box); - - builder.setView(view); - builder.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (PasswordEntryDialog.this.listener != null) { - String chosenPassword = passwordView.getText().toString(); - PasswordEntryDialog.this.listener.passwordChosen(chosenPassword); - } - } - }); - builder.setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (PasswordEntryDialog.this.listener != null) { - PasswordEntryDialog.this.listener.cancel(); - } - } - }); - dialog = builder.create(); - passwordView.addTextChangedListener(new TextWatcher() { - - @Override - public void afterTextChanged(Editable arg0) { } - - @Override - public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } - - @Override - public void onTextChanged(CharSequence arg0, int arg1, int arg2, - int arg3) { - - Button okButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); - String chosenPassword = passwordView.getText().toString(); - okButton.setEnabled(chosenPassword.length() > 0); - - } - }); - - dialog.setMessage(headerText); - - - } - public void show() { - dialog.show(); - Button okButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); - okButton.setEnabled(false); - } - -} diff --git a/src/com/fsck/k9/preferences/K9Krypto.java b/src/com/fsck/k9/preferences/K9Krypto.java deleted file mode 100644 index adacbf1fb..000000000 --- a/src/com/fsck/k9/preferences/K9Krypto.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.fsck.k9.preferences; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyGenerator; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.codec.binary.Base64; - -public class K9Krypto { - final Base64 mBase64; - final Cipher mCipher; - - private final static String AES = "AES"; - private final static String SECURE_RANDOM_TYPE = "SHA1PRNG"; - - public enum MODE { - ENCRYPT(Cipher.ENCRYPT_MODE), DECRYPT(Cipher.DECRYPT_MODE); - - int mode; - private MODE(int nMode) { - mode = nMode; - } - } - - public K9Krypto(String key, MODE mode) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException { - mBase64 = new Base64(); - KeyGenerator keyGenerator = KeyGenerator.getInstance(AES); - SecureRandom secureRandom = SecureRandom.getInstance(SECURE_RANDOM_TYPE); - secureRandom.setSeed(key.getBytes()); - keyGenerator.init(128, secureRandom); - SecretKey secretKey = keyGenerator.generateKey(); - byte[] processedKey = secretKey.getEncoded(); - mCipher = setupCipher(mode.mode, processedKey); - } - - public String encrypt(String plainText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { - byte[] encryptedText = mCipher.doFinal(plainText.getBytes()); - byte[] encryptedEncodedText = mBase64.encode(encryptedText); - return new String(encryptedEncodedText); - } - - public String decrypt(String encryptedEncodedText) throws IllegalBlockSizeException, BadPaddingException { - byte[] encryptedText = mBase64.decode(encryptedEncodedText.getBytes()); - byte[] plainText = mCipher.doFinal(encryptedText); - return new String(plainText); - } - - private Cipher setupCipher(int mode, byte[] processedKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException { - SecretKeySpec secretKeySpec = new SecretKeySpec(processedKey, AES); - Cipher cipher = Cipher.getInstance(AES); - cipher.init(mode, secretKeySpec); - return cipher; - } - -} diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/StorageExporter.java index a1f0d0732..7c0289c33 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/StorageExporter.java @@ -12,7 +12,6 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.Map.Entry; -import javax.crypto.CipherOutputStream; import org.xmlpull.v1.XmlSerializer; import android.content.Context; @@ -77,7 +76,7 @@ public class StorageExporter { public static String exportToFile(Context context, boolean includeGlobals, - Set accountUuids, String encryptionKey) + Set accountUuids) throws StorageImportExportException { OutputStream os = null; @@ -91,12 +90,7 @@ public class StorageExporter { filename = file.getAbsolutePath(); os = new FileOutputStream(filename); - if (encryptionKey == null) { - exportPreferences(context, os, includeGlobals, accountUuids); - } else { - exportPreferencesEncrypted(context, os, includeGlobals, accountUuids, - encryptionKey); - } + exportPreferences(context, os, includeGlobals, accountUuids); // If all went well, we return the name of the file just written. return filename; @@ -113,19 +107,6 @@ public class StorageExporter { } } - public static void exportPreferencesEncrypted(Context context, OutputStream os, boolean includeGlobals, - Set accountUuids, String encryptionKey) throws StorageImportExportException { - - try { - K9Krypto k = new K9Krypto(encryptionKey, K9Krypto.MODE.ENCRYPT); - CipherOutputStream cos = new CipherOutputStream(os, k.mCipher); - - exportPreferences(context, cos, includeGlobals, accountUuids); - } catch (Exception e) { - throw new StorageImportExportException(); - } - } - public static void exportPreferences(Context context, OutputStream os, boolean includeGlobals, Set accountUuids) throws StorageImportExportException { diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index ac0968f01..f8bab9c3b 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -35,7 +35,7 @@ public class StorageImporter { /** * Class to list the contents of an import file/stream. * - * @see StorageImporter#getImportStreamContents(Context,InputStream,String) + * @see StorageImporter#getImportStreamContents(Context,InputStream) */ public static class ImportContents { /** @@ -100,10 +100,6 @@ public class StorageImporter { } } - public static boolean isImportStreamEncrypted(Context context, InputStream inputStream) { - return false; - } - /** * Parses an import {@link InputStream} and returns information on whether it contains global * settings and/or account settings. For all account configurations found, the name of the @@ -111,12 +107,11 @@ public class StorageImporter { * * @param context * @param inputStream - * @param encryptionKey * @return * @throws StorageImportExportException */ - public static ImportContents getImportStreamContents(Context context, InputStream inputStream, - String encryptionKey) throws StorageImportExportException { + public static ImportContents getImportStreamContents(Context context, InputStream inputStream) + throws StorageImportExportException { try { // Parse the import stream but don't save individual settings (overview=true) @@ -151,13 +146,12 @@ public class StorageImporter { * * @param context * @param inputStream - * @param encryptionKey * @param globalSettings * @param accountUuids * @param overwrite * @throws StorageImportExportException */ - public static ImportResults importSettings(Context context, InputStream inputStream, String encryptionKey, + public static ImportResults importSettings(Context context, InputStream inputStream, boolean globalSettings, List accountUuids, boolean overwrite) throws StorageImportExportException { From 0ae176bab3092381596250506bb21ec6deaae4d4 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 14 Oct 2011 05:54:23 +0200 Subject: [PATCH 098/116] Code/documentation cleanup. No functional changes. --- .../fsck/k9/preferences/StorageImporter.java | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index f8bab9c3b..96a86e6e1 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -35,7 +35,7 @@ public class StorageImporter { /** * Class to list the contents of an import file/stream. * - * @see StorageImporter#getImportStreamContents(Context,InputStream) + * @see StorageImporter#getImportStreamContents(InputStream) */ public static class ImportContents { /** @@ -105,17 +105,21 @@ public class StorageImporter { * settings and/or account settings. For all account configurations found, the name of the * account along with the account UUID is returned. * - * @param context * @param inputStream - * @return + * An {@code InputStream} to read the settings from. + * + * @return An {@link ImportContents} instance containing information about the contents of the + * settings file. + * * @throws StorageImportExportException + * In case of an error. */ - public static ImportContents getImportStreamContents(Context context, InputStream inputStream) + public static ImportContents getImportStreamContents(InputStream inputStream) throws StorageImportExportException { try { // Parse the import stream but don't save individual settings (overview=true) - Imported imported = parseSettings(inputStream, false, null, false, true); + Imported imported = parseSettings(inputStream, false, null, true); // If the stream contains global settings the "globalSettings" member will not be null boolean globalSettings = (imported.globalSettings != null); @@ -145,11 +149,24 @@ public class StorageImporter { * configurations specified by the arguments. * * @param context + * A {@link Context} instance. * @param inputStream + * The {@code InputStream} to read the settings from. * @param globalSettings + * {@code true} if global settings should be imported from the file. * @param accountUuids + * A list of UUIDs of the accounts that should be imported. * @param overwrite + * {@code true} if existing accounts should be overwritten when an account with the + * same UUID is found in the settings file.
+ * Note: This can have side-effects we currently don't handle, e.g. + * changing the account type from IMAP to POP3. So don't use this for now! + * + * @return An {@link ImportResults} instance containing information about errors and + * successfully imported accounts. + * * @throws StorageImportExportException + * In case of an error. */ public static ImportResults importSettings(Context context, InputStream inputStream, boolean globalSettings, List accountUuids, boolean overwrite) @@ -161,7 +178,7 @@ public class StorageImporter { List importedAccounts = new ArrayList(); List errorneousAccounts = new ArrayList(); - Imported imported = parseSettings(inputStream, globalSettings, accountUuids, overwrite, false); + Imported imported = parseSettings(inputStream, globalSettings, accountUuids, false); Preferences preferences = Preferences.getPreferences(context); SharedPreferences storage = preferences.getPreferences(); @@ -560,7 +577,7 @@ public class StorageImporter { } private static Imported parseSettings(InputStream inputStream, boolean globalSettings, - List accountUuids, boolean overwrite, boolean overview) + List accountUuids, boolean overview) throws StorageImportExportException { if (!overview && accountUuids == null) { From cf8bdef0a81cc825d1485e757623f78b197973f3 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 14 Oct 2011 05:58:15 +0200 Subject: [PATCH 099/116] Renamed StorageExporter to SettingsExporter --- src/com/fsck/k9/activity/Accounts.java | 6 +- src/com/fsck/k9/preferences/Settings.java | 2 +- ...ageExporter.java => SettingsExporter.java} | 2 +- .../fsck/k9/preferences/StorageImporter.java | 100 +++++++++--------- 4 files changed, 55 insertions(+), 55 deletions(-) rename src/com/fsck/k9/preferences/{StorageExporter.java => SettingsExporter.java} (99%) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 782a58ac9..5fd9af324 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -72,7 +72,7 @@ import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.view.ColorChip; -import com.fsck.k9.preferences.StorageExporter; +import com.fsck.k9.preferences.SettingsExporter; import com.fsck.k9.preferences.StorageImportExportException; import com.fsck.k9.preferences.StorageImporter; import com.fsck.k9.preferences.StorageImporter.AccountDescription; @@ -1320,7 +1320,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override protected Boolean doInBackground(Void... params) { try { - mFileName = StorageExporter.exportToFile(mContext, mIncludeGlobals, + mFileName = SettingsExporter.exportToFile(mContext, mIncludeGlobals, mAccountUuids); } catch (StorageImportExportException e) { Log.w(K9.LOG_TAG, "Exception during export", e); @@ -1449,7 +1449,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC ContentResolver resolver = mContext.getContentResolver(); InputStream is = resolver.openInputStream(mUri); try { - mImportContents = StorageImporter.getImportStreamContents(mContext, is); + mImportContents = StorageImporter.getImportStreamContents(is); } finally { try { is.close(); diff --git a/src/com/fsck/k9/preferences/Settings.java b/src/com/fsck/k9/preferences/Settings.java index 29f02636c..8b2b00ace 100644 --- a/src/com/fsck/k9/preferences/Settings.java +++ b/src/com/fsck/k9/preferences/Settings.java @@ -30,7 +30,7 @@ public class Settings { * is changed (e.g. add a value to an enum). *

* - * @see StorageExporter + * @see SettingsExporter */ public static final int VERSION = 1; diff --git a/src/com/fsck/k9/preferences/StorageExporter.java b/src/com/fsck/k9/preferences/SettingsExporter.java similarity index 99% rename from src/com/fsck/k9/preferences/StorageExporter.java rename to src/com/fsck/k9/preferences/SettingsExporter.java index 7c0289c33..376ca3c28 100644 --- a/src/com/fsck/k9/preferences/StorageExporter.java +++ b/src/com/fsck/k9/preferences/SettingsExporter.java @@ -31,7 +31,7 @@ import com.fsck.k9.preferences.Settings.InvalidSettingValueException; import com.fsck.k9.preferences.Settings.SettingsDescription; -public class StorageExporter { +public class SettingsExporter { private static final String EXPORT_FILENAME = "settings.k9s"; /** diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/StorageImporter.java index 96a86e6e1..cab7ff2d5 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/StorageImporter.java @@ -596,7 +596,7 @@ public class StorageImporter { int eventType = xpp.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { if(eventType == XmlPullParser.START_TAG) { - if (StorageExporter.ROOT_ELEMENT.equals(xpp.getName())) { + if (SettingsExporter.ROOT_ELEMENT.equals(xpp.getName())) { imported = parseRoot(xpp, globalSettings, accountUuids, overview); } else { Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName()); @@ -645,28 +645,28 @@ public class StorageImporter { int eventType = xpp.next(); while (!(eventType == XmlPullParser.END_TAG && - StorageExporter.ROOT_ELEMENT.equals(xpp.getName()))) { + SettingsExporter.ROOT_ELEMENT.equals(xpp.getName()))) { if(eventType == XmlPullParser.START_TAG) { String element = xpp.getName(); - if (StorageExporter.GLOBAL_ELEMENT.equals(element)) { + if (SettingsExporter.GLOBAL_ELEMENT.equals(element)) { if (overview || globalSettings) { if (result.globalSettings == null) { if (overview) { result.globalSettings = new ImportedSettings(); - skipToEndTag(xpp, StorageExporter.GLOBAL_ELEMENT); + skipToEndTag(xpp, SettingsExporter.GLOBAL_ELEMENT); } else { - result.globalSettings = parseSettings(xpp, StorageExporter.GLOBAL_ELEMENT); + result.globalSettings = parseSettings(xpp, SettingsExporter.GLOBAL_ELEMENT); } } else { - skipToEndTag(xpp, StorageExporter.GLOBAL_ELEMENT); + skipToEndTag(xpp, SettingsExporter.GLOBAL_ELEMENT); Log.w(K9.LOG_TAG, "More than one global settings element. Only using the first one!"); } } else { - skipToEndTag(xpp, StorageExporter.GLOBAL_ELEMENT); + skipToEndTag(xpp, SettingsExporter.GLOBAL_ELEMENT); Log.i(K9.LOG_TAG, "Skipping global settings"); } - } else if (StorageExporter.ACCOUNTS_ELEMENT.equals(element)) { + } else if (SettingsExporter.ACCOUNTS_ELEMENT.equals(element)) { if (result.accounts == null) { result.accounts = parseAccounts(xpp, accountUuids, overview); } else { @@ -692,8 +692,8 @@ public class StorageImporter { if(eventType == XmlPullParser.START_TAG) { String element = xpp.getName(); - if (StorageExporter.VALUE_ELEMENT.equals(element)) { - String key = xpp.getAttributeValue(null, StorageExporter.KEY_ATTRIBUTE); + if (SettingsExporter.VALUE_ELEMENT.equals(element)) { + String key = xpp.getAttributeValue(null, SettingsExporter.KEY_ATTRIBUTE); String value = getText(xpp); if (result == null) { @@ -723,11 +723,11 @@ public class StorageImporter { int eventType = xpp.next(); while (!(eventType == XmlPullParser.END_TAG && - StorageExporter.ACCOUNTS_ELEMENT.equals(xpp.getName()))) { + SettingsExporter.ACCOUNTS_ELEMENT.equals(xpp.getName()))) { if(eventType == XmlPullParser.START_TAG) { String element = xpp.getName(); - if (StorageExporter.ACCOUNT_ELEMENT.equals(element)) { + if (SettingsExporter.ACCOUNT_ELEMENT.equals(element)) { if (accounts == null) { accounts = new HashMap(); } @@ -756,45 +756,45 @@ public class StorageImporter { ImportedAccount account = new ImportedAccount(); - String uuid = xpp.getAttributeValue(null, StorageExporter.UUID_ATTRIBUTE); + String uuid = xpp.getAttributeValue(null, SettingsExporter.UUID_ATTRIBUTE); account.uuid = uuid; if (overview || accountUuids.contains(uuid)) { int eventType = xpp.next(); while (!(eventType == XmlPullParser.END_TAG && - StorageExporter.ACCOUNT_ELEMENT.equals(xpp.getName()))) { + SettingsExporter.ACCOUNT_ELEMENT.equals(xpp.getName()))) { if(eventType == XmlPullParser.START_TAG) { String element = xpp.getName(); - if (StorageExporter.NAME_ELEMENT.equals(element)) { + if (SettingsExporter.NAME_ELEMENT.equals(element)) { account.name = getText(xpp); - } else if (StorageExporter.INCOMING_SERVER_ELEMENT.equals(element)) { + } else if (SettingsExporter.INCOMING_SERVER_ELEMENT.equals(element)) { if (overview) { - skipToEndTag(xpp, StorageExporter.INCOMING_SERVER_ELEMENT); + skipToEndTag(xpp, SettingsExporter.INCOMING_SERVER_ELEMENT); } else { - account.incoming = parseServerSettings(xpp, StorageExporter.INCOMING_SERVER_ELEMENT); + account.incoming = parseServerSettings(xpp, SettingsExporter.INCOMING_SERVER_ELEMENT); } - } else if (StorageExporter.OUTGOING_SERVER_ELEMENT.equals(element)) { + } else if (SettingsExporter.OUTGOING_SERVER_ELEMENT.equals(element)) { if (overview) { - skipToEndTag(xpp, StorageExporter.OUTGOING_SERVER_ELEMENT); + skipToEndTag(xpp, SettingsExporter.OUTGOING_SERVER_ELEMENT); } else { - account.outgoing = parseServerSettings(xpp, StorageExporter.OUTGOING_SERVER_ELEMENT); + account.outgoing = parseServerSettings(xpp, SettingsExporter.OUTGOING_SERVER_ELEMENT); } - } else if (StorageExporter.SETTINGS_ELEMENT.equals(element)) { + } else if (SettingsExporter.SETTINGS_ELEMENT.equals(element)) { if (overview) { - skipToEndTag(xpp, StorageExporter.SETTINGS_ELEMENT); + skipToEndTag(xpp, SettingsExporter.SETTINGS_ELEMENT); } else { - account.settings = parseSettings(xpp, StorageExporter.SETTINGS_ELEMENT); + account.settings = parseSettings(xpp, SettingsExporter.SETTINGS_ELEMENT); } - } else if (StorageExporter.IDENTITIES_ELEMENT.equals(element)) { + } else if (SettingsExporter.IDENTITIES_ELEMENT.equals(element)) { if (overview) { - skipToEndTag(xpp, StorageExporter.IDENTITIES_ELEMENT); + skipToEndTag(xpp, SettingsExporter.IDENTITIES_ELEMENT); } else { account.identities = parseIdentities(xpp); } - } else if (StorageExporter.FOLDERS_ELEMENT.equals(element)) { + } else if (SettingsExporter.FOLDERS_ELEMENT.equals(element)) { if (overview) { - skipToEndTag(xpp, StorageExporter.FOLDERS_ELEMENT); + skipToEndTag(xpp, SettingsExporter.FOLDERS_ELEMENT); } else { account.folders = parseFolders(xpp); } @@ -805,7 +805,7 @@ public class StorageImporter { eventType = xpp.next(); } } else { - skipToEndTag(xpp, StorageExporter.ACCOUNT_ELEMENT); + skipToEndTag(xpp, SettingsExporter.ACCOUNT_ELEMENT); Log.i(K9.LOG_TAG, "Skipping account with UUID " + uuid); } @@ -816,26 +816,26 @@ public class StorageImporter { throws XmlPullParserException, IOException { ImportedServer server = new ImportedServer(); - server.type = xpp.getAttributeValue(null, StorageExporter.TYPE_ATTRIBUTE); + server.type = xpp.getAttributeValue(null, SettingsExporter.TYPE_ATTRIBUTE); int eventType = xpp.next(); while (!(eventType == XmlPullParser.END_TAG && endTag.equals(xpp.getName()))) { if(eventType == XmlPullParser.START_TAG) { String element = xpp.getName(); - if (StorageExporter.HOST_ELEMENT.equals(element)) { + if (SettingsExporter.HOST_ELEMENT.equals(element)) { server.host = getText(xpp); - } else if (StorageExporter.PORT_ELEMENT.equals(element)) { + } else if (SettingsExporter.PORT_ELEMENT.equals(element)) { server.port = getText(xpp); - } else if (StorageExporter.CONNECTION_SECURITY_ELEMENT.equals(element)) { + } else if (SettingsExporter.CONNECTION_SECURITY_ELEMENT.equals(element)) { server.connectionSecurity = getText(xpp); - } else if (StorageExporter.AUTHENTICATION_TYPE_ELEMENT.equals(element)) { + } else if (SettingsExporter.AUTHENTICATION_TYPE_ELEMENT.equals(element)) { server.authenticationType = getText(xpp); - } else if (StorageExporter.USERNAME_ELEMENT.equals(element)) { + } else if (SettingsExporter.USERNAME_ELEMENT.equals(element)) { server.username = getText(xpp); - } else if (StorageExporter.PASSWORD_ELEMENT.equals(element)) { + } else if (SettingsExporter.PASSWORD_ELEMENT.equals(element)) { server.password = getText(xpp); - } else if (StorageExporter.EXTRA_ELEMENT.equals(element)) { - server.extras = parseSettings(xpp, StorageExporter.EXTRA_ELEMENT); + } else if (SettingsExporter.EXTRA_ELEMENT.equals(element)) { + server.extras = parseSettings(xpp, SettingsExporter.EXTRA_ELEMENT); } else { Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName()); } @@ -852,11 +852,11 @@ public class StorageImporter { int eventType = xpp.next(); while (!(eventType == XmlPullParser.END_TAG && - StorageExporter.IDENTITIES_ELEMENT.equals(xpp.getName()))) { + SettingsExporter.IDENTITIES_ELEMENT.equals(xpp.getName()))) { if(eventType == XmlPullParser.START_TAG) { String element = xpp.getName(); - if (StorageExporter.IDENTITY_ELEMENT.equals(element)) { + if (SettingsExporter.IDENTITY_ELEMENT.equals(element)) { if (identities == null) { identities = new ArrayList(); } @@ -879,18 +879,18 @@ public class StorageImporter { int eventType = xpp.next(); while (!(eventType == XmlPullParser.END_TAG && - StorageExporter.IDENTITY_ELEMENT.equals(xpp.getName()))) { + SettingsExporter.IDENTITY_ELEMENT.equals(xpp.getName()))) { if(eventType == XmlPullParser.START_TAG) { String element = xpp.getName(); - if (StorageExporter.NAME_ELEMENT.equals(element)) { + if (SettingsExporter.NAME_ELEMENT.equals(element)) { identity.name = getText(xpp); - } else if (StorageExporter.EMAIL_ELEMENT.equals(element)) { + } else if (SettingsExporter.EMAIL_ELEMENT.equals(element)) { identity.email = getText(xpp); - } else if (StorageExporter.DESCRIPTION_ELEMENT.equals(element)) { + } else if (SettingsExporter.DESCRIPTION_ELEMENT.equals(element)) { identity.description = getText(xpp); - } else if (StorageExporter.SETTINGS_ELEMENT.equals(element)) { - identity.settings = parseSettings(xpp, StorageExporter.SETTINGS_ELEMENT); + } else if (SettingsExporter.SETTINGS_ELEMENT.equals(element)) { + identity.settings = parseSettings(xpp, SettingsExporter.SETTINGS_ELEMENT); } else { Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName()); } @@ -907,11 +907,11 @@ public class StorageImporter { int eventType = xpp.next(); while (!(eventType == XmlPullParser.END_TAG && - StorageExporter.FOLDERS_ELEMENT.equals(xpp.getName()))) { + SettingsExporter.FOLDERS_ELEMENT.equals(xpp.getName()))) { if(eventType == XmlPullParser.START_TAG) { String element = xpp.getName(); - if (StorageExporter.FOLDER_ELEMENT.equals(element)) { + if (SettingsExporter.FOLDER_ELEMENT.equals(element)) { if (folders == null) { folders = new ArrayList(); } @@ -932,10 +932,10 @@ public class StorageImporter { throws XmlPullParserException, IOException { ImportedFolder folder = new ImportedFolder(); - String name = xpp.getAttributeValue(null, StorageExporter.NAME_ATTRIBUTE); + String name = xpp.getAttributeValue(null, SettingsExporter.NAME_ATTRIBUTE); folder.name = name; - folder.settings = parseSettings(xpp, StorageExporter.FOLDER_ELEMENT); + folder.settings = parseSettings(xpp, SettingsExporter.FOLDER_ELEMENT); return folder; } From c835bb757a22f32f50b0d8de846e7a438e60463a Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 14 Oct 2011 05:58:55 +0200 Subject: [PATCH 100/116] Renamed StorageImporter to SettingsImporter --- src/com/fsck/k9/activity/Accounts.java | 12 ++++++------ src/com/fsck/k9/preferences/SettingsExporter.java | 2 +- .../{StorageImporter.java => SettingsImporter.java} | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) rename src/com/fsck/k9/preferences/{StorageImporter.java => SettingsImporter.java} (99%) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 5fd9af324..388fcfbe2 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -74,10 +74,10 @@ import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.view.ColorChip; import com.fsck.k9.preferences.SettingsExporter; import com.fsck.k9.preferences.StorageImportExportException; -import com.fsck.k9.preferences.StorageImporter; -import com.fsck.k9.preferences.StorageImporter.AccountDescription; -import com.fsck.k9.preferences.StorageImporter.ImportContents; -import com.fsck.k9.preferences.StorageImporter.ImportResults; +import com.fsck.k9.preferences.SettingsImporter; +import com.fsck.k9.preferences.SettingsImporter.AccountDescription; +import com.fsck.k9.preferences.SettingsImporter.ImportContents; +import com.fsck.k9.preferences.SettingsImporter.ImportResults; public class Accounts extends K9ListActivity implements OnItemClickListener, OnClickListener { @@ -1380,7 +1380,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC try { InputStream is = mContext.getContentResolver().openInputStream(mUri); try { - mImportResults = StorageImporter.importSettings(mContext, is, + mImportResults = SettingsImporter.importSettings(mContext, is, mIncludeGlobals, mAccountUuids, mOverwrite); } finally { try { @@ -1449,7 +1449,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC ContentResolver resolver = mContext.getContentResolver(); InputStream is = resolver.openInputStream(mUri); try { - mImportContents = StorageImporter.getImportStreamContents(is); + mImportContents = SettingsImporter.getImportStreamContents(is); } finally { try { is.close(); diff --git a/src/com/fsck/k9/preferences/SettingsExporter.java b/src/com/fsck/k9/preferences/SettingsExporter.java index 376ca3c28..9bdb3a875 100644 --- a/src/com/fsck/k9/preferences/SettingsExporter.java +++ b/src/com/fsck/k9/preferences/SettingsExporter.java @@ -40,7 +40,7 @@ public class SettingsExporter { *

* Increment this if you need to change the structure of the settings file. When you do this * remember that we also have to be able to handle old file formats. So have fun adding support - * for that to {@link StorageImporter} :) + * for that to {@link SettingsImporter} :) *

*/ public static final int FILE_FORMAT_VERSION = 1; diff --git a/src/com/fsck/k9/preferences/StorageImporter.java b/src/com/fsck/k9/preferences/SettingsImporter.java similarity index 99% rename from src/com/fsck/k9/preferences/StorageImporter.java rename to src/com/fsck/k9/preferences/SettingsImporter.java index cab7ff2d5..bbed9590b 100644 --- a/src/com/fsck/k9/preferences/StorageImporter.java +++ b/src/com/fsck/k9/preferences/SettingsImporter.java @@ -30,12 +30,12 @@ import com.fsck.k9.mail.Store; import com.fsck.k9.mail.Transport; import com.fsck.k9.preferences.Settings.InvalidSettingValueException; -public class StorageImporter { +public class SettingsImporter { /** * Class to list the contents of an import file/stream. * - * @see StorageImporter#getImportStreamContents(InputStream) + * @see SettingsImporter#getImportStreamContents(InputStream) */ public static class ImportContents { /** From f5e684310ed03ab9192747591ceeedc2fb281cce Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 14 Oct 2011 06:00:10 +0200 Subject: [PATCH 101/116] Renamed StorageImportExportException to SettingsImportExportException --- src/com/fsck/k9/activity/Accounts.java | 8 +++---- .../fsck/k9/preferences/SettingsExporter.java | 8 +++---- .../SettingsImportExportException.java | 21 ++++++++++++++++ .../fsck/k9/preferences/SettingsImporter.java | 24 +++++++++---------- .../StorageImportExportException.java | 21 ---------------- 5 files changed, 41 insertions(+), 41 deletions(-) create mode 100644 src/com/fsck/k9/preferences/SettingsImportExportException.java delete mode 100644 src/com/fsck/k9/preferences/StorageImportExportException.java diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 388fcfbe2..f3262ad3c 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -73,7 +73,7 @@ import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.view.ColorChip; import com.fsck.k9.preferences.SettingsExporter; -import com.fsck.k9.preferences.StorageImportExportException; +import com.fsck.k9.preferences.SettingsImportExportException; import com.fsck.k9.preferences.SettingsImporter; import com.fsck.k9.preferences.SettingsImporter.AccountDescription; import com.fsck.k9.preferences.SettingsImporter.ImportContents; @@ -1322,7 +1322,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC try { mFileName = SettingsExporter.exportToFile(mContext, mIncludeGlobals, mAccountUuids); - } catch (StorageImportExportException e) { + } catch (SettingsImportExportException e) { Log.w(K9.LOG_TAG, "Exception during export", e); return false; } @@ -1387,7 +1387,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC is.close(); } catch (IOException e) { /* Ignore */ } } - } catch (StorageImportExportException e) { + } catch (SettingsImportExportException e) { Log.w(K9.LOG_TAG, "Exception during import", e); return false; } catch (FileNotFoundException e) { @@ -1455,7 +1455,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC is.close(); } catch (IOException e) { /* Ignore */ } } - } catch (StorageImportExportException e) { + } catch (SettingsImportExportException e) { Log.w(K9.LOG_TAG, "Exception during export", e); return false; } diff --git a/src/com/fsck/k9/preferences/SettingsExporter.java b/src/com/fsck/k9/preferences/SettingsExporter.java index 9bdb3a875..825f98c4d 100644 --- a/src/com/fsck/k9/preferences/SettingsExporter.java +++ b/src/com/fsck/k9/preferences/SettingsExporter.java @@ -77,7 +77,7 @@ public class SettingsExporter { public static String exportToFile(Context context, boolean includeGlobals, Set accountUuids) - throws StorageImportExportException { + throws SettingsImportExportException { OutputStream os = null; String filename = null; @@ -95,7 +95,7 @@ public class SettingsExporter { // If all went well, we return the name of the file just written. return filename; } catch (Exception e) { - throw new StorageImportExportException(e); + throw new SettingsImportExportException(e); } finally { if (os != null) { try { @@ -108,7 +108,7 @@ public class SettingsExporter { } public static void exportPreferences(Context context, OutputStream os, boolean includeGlobals, - Set accountUuids) throws StorageImportExportException { + Set accountUuids) throws SettingsImportExportException { try { XmlSerializer serializer = Xml.newSerializer(); @@ -160,7 +160,7 @@ public class SettingsExporter { serializer.flush(); } catch (Exception e) { - throw new StorageImportExportException(e.getLocalizedMessage(), e); + throw new SettingsImportExportException(e.getLocalizedMessage(), e); } } diff --git a/src/com/fsck/k9/preferences/SettingsImportExportException.java b/src/com/fsck/k9/preferences/SettingsImportExportException.java new file mode 100644 index 000000000..616a466bf --- /dev/null +++ b/src/com/fsck/k9/preferences/SettingsImportExportException.java @@ -0,0 +1,21 @@ +package com.fsck.k9.preferences; + +public class SettingsImportExportException extends Exception { + + public SettingsImportExportException() { + super(); + } + + public SettingsImportExportException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public SettingsImportExportException(String detailMessage) { + super(detailMessage); + } + + public SettingsImportExportException(Throwable throwable) { + super(throwable); + } + +} diff --git a/src/com/fsck/k9/preferences/SettingsImporter.java b/src/com/fsck/k9/preferences/SettingsImporter.java index bbed9590b..4799525d3 100644 --- a/src/com/fsck/k9/preferences/SettingsImporter.java +++ b/src/com/fsck/k9/preferences/SettingsImporter.java @@ -111,11 +111,11 @@ public class SettingsImporter { * @return An {@link ImportContents} instance containing information about the contents of the * settings file. * - * @throws StorageImportExportException + * @throws SettingsImportExportException * In case of an error. */ public static ImportContents getImportStreamContents(InputStream inputStream) - throws StorageImportExportException { + throws SettingsImportExportException { try { // Parse the import stream but don't save individual settings (overview=true) @@ -137,10 +137,10 @@ public class SettingsImporter { return new ImportContents(globalSettings, accounts); - } catch (StorageImportExportException e) { + } catch (SettingsImportExportException e) { throw e; } catch (Exception e) { - throw new StorageImportExportException(e); + throw new SettingsImportExportException(e); } } @@ -165,12 +165,12 @@ public class SettingsImporter { * @return An {@link ImportResults} instance containing information about errors and * successfully imported accounts. * - * @throws StorageImportExportException + * @throws SettingsImportExportException * In case of an error. */ public static ImportResults importSettings(Context context, InputStream inputStream, boolean globalSettings, List accountUuids, boolean overwrite) - throws StorageImportExportException { + throws SettingsImportExportException { try { @@ -274,7 +274,7 @@ public class SettingsImporter { } if (!editor.commit()) { - throw new StorageImportExportException("Failed to set default account"); + throw new SettingsImportExportException("Failed to set default account"); } } else { Log.w(K9.LOG_TAG, "Was asked to import at least one account but none found."); @@ -288,10 +288,10 @@ public class SettingsImporter { return new ImportResults(globalSettingsImported, importedAccounts, errorneousAccounts); - } catch (StorageImportExportException e) { + } catch (SettingsImportExportException e) { throw e; } catch (Exception e) { - throw new StorageImportExportException(e); + throw new SettingsImportExportException(e); } } @@ -578,7 +578,7 @@ public class SettingsImporter { private static Imported parseSettings(InputStream inputStream, boolean globalSettings, List accountUuids, boolean overview) - throws StorageImportExportException { + throws SettingsImportExportException { if (!overview && accountUuids == null) { throw new IllegalArgumentException("Argument 'accountUuids' must not be null."); @@ -607,12 +607,12 @@ public class SettingsImporter { if (imported == null || (overview && imported.globalSettings == null && imported.accounts == null)) { - throw new StorageImportExportException("Invalid import data"); + throw new SettingsImportExportException("Invalid import data"); } return imported; } catch (Exception e) { - throw new StorageImportExportException(e); + throw new SettingsImportExportException(e); } } diff --git a/src/com/fsck/k9/preferences/StorageImportExportException.java b/src/com/fsck/k9/preferences/StorageImportExportException.java deleted file mode 100644 index bc2e10592..000000000 --- a/src/com/fsck/k9/preferences/StorageImportExportException.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fsck.k9.preferences; - -public class StorageImportExportException extends Exception { - - public StorageImportExportException() { - super(); - } - - public StorageImportExportException(String detailMessage, Throwable throwable) { - super(detailMessage, throwable); - } - - public StorageImportExportException(String detailMessage) { - super(detailMessage); - } - - public StorageImportExportException(Throwable throwable) { - super(throwable); - } - -} From effb45c4638fc5e4098c5e959ac377239521e444 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 14 Oct 2011 06:19:30 +0200 Subject: [PATCH 102/116] Cleaned up success message after settings import --- res/values/strings.xml | 1 + src/com/fsck/k9/activity/Accounts.java | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 36a224a5a..ebc0a1d02 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1041,6 +1041,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Importing settings... Scanning file... Saved exported settings to %s + Imported global settings from %s Imported %s from %s 1 account diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index f3262ad3c..50cf27e8a 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1410,13 +1410,22 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC removeProgressDialog(); String filename = mUri.getLastPathSegment(); - if (success) { - int imported = mImportResults.importedAccounts.size(); + boolean globalSettings = mImportResults.globalSettings; + int imported = mImportResults.importedAccounts.size(); + if (success && (globalSettings || imported > 0)) { //TODO: display names of imported accounts (name from file *and* possibly new name) - activity.showSimpleDialog(R.string.settings_import_success_header, - R.string.settings_import_success, imported, filename); + if (imported == 0) { + activity.showSimpleDialog(R.string.settings_import_success_header, + R.string.settings_import_global_settings_success, filename); + } else { + String importedAccounts = activity.getResources().getQuantityString( + R.plurals.settings_import_success, imported); + activity.showSimpleDialog(R.string.settings_import_success_header, + R.string.settings_import_success, importedAccounts, filename); + } + activity.refresh(); } else { //TODO: better error messages From 115742a2a9a7ee9d4a81fde5b35e80deb095ad50 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 14 Oct 2011 21:24:46 +0200 Subject: [PATCH 103/116] Change launchMode of Accounts activity startActivityForResult() doesn't work if the launchMode is "singleInstance". But we need this when selecting a file to import the settings from. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 28592e1cb..b084b16d9 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -67,7 +67,7 @@ From 849a4e37a044fbb873863a722933d73b4231c5b8 Mon Sep 17 00:00:00 2001 From: cketti Date: Sun, 16 Oct 2011 19:24:31 +0200 Subject: [PATCH 104/116] Mark accounts as disabled on import If the settings file doesn't contain passwords for the incoming and outgoing servers we disable those accounts. --- .../fsck/k9/preferences/SettingsImporter.java | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/com/fsck/k9/preferences/SettingsImporter.java b/src/com/fsck/k9/preferences/SettingsImporter.java index 4799525d3..0d280e591 100644 --- a/src/com/fsck/k9/preferences/SettingsImporter.java +++ b/src/com/fsck/k9/preferences/SettingsImporter.java @@ -28,6 +28,7 @@ import com.fsck.k9.mail.ConnectionSecurity; import com.fsck.k9.mail.ServerSettings; import com.fsck.k9.mail.Store; import com.fsck.k9.mail.Transport; +import com.fsck.k9.mail.store.WebDavStore; import com.fsck.k9.preferences.Settings.InvalidSettingValueException; public class SettingsImporter { @@ -349,15 +350,41 @@ public class SettingsImporter { String accountKeyPrefix = uuid + "."; putString(editor, accountKeyPrefix + Account.ACCOUNT_DESCRIPTION_KEY, accountName); + if (account.incoming == null) { + // We don't import accounts without incoming server settings + throw new InvalidSettingValueException(); + } + // Write incoming server settings (storeUri) ServerSettings incoming = new ImportedServerSettings(account.incoming); String storeUri = Store.createStoreUri(incoming); putString(editor, accountKeyPrefix + Account.STORE_URI_KEY, Utility.base64Encode(storeUri)); - // Write outgoing server settings (transportUri) - ServerSettings outgoing = new ImportedServerSettings(account.outgoing); - String transportUri = Transport.createTransportUri(outgoing); - putString(editor, accountKeyPrefix + Account.TRANSPORT_URI_KEY, Utility.base64Encode(transportUri)); + // Mark account as disabled if the settings file didn't contain a password + boolean createAccountDisabled = (incoming.password == null || + incoming.password.length() == 0); + + if (account.outgoing == null && !WebDavStore.STORE_TYPE.equals(account.incoming.type)) { + // All account types except WebDAV need to provide outgoing server settings + throw new InvalidSettingValueException(); + } + + if (account.outgoing != null) { + // Write outgoing server settings (transportUri) + ServerSettings outgoing = new ImportedServerSettings(account.outgoing); + String transportUri = Transport.createTransportUri(outgoing); + putString(editor, accountKeyPrefix + Account.TRANSPORT_URI_KEY, Utility.base64Encode(transportUri)); + + // Mark account as disabled if the settings file didn't contain a password + if (outgoing.password == null || outgoing.password.length() == 0) { + createAccountDisabled = true; + } + } + + // Write key to mark account as disabled if necessary + if (createAccountDisabled) { + editor.putBoolean(accountKeyPrefix + "enabled", false); + } // Validate account settings Map validatedSettings = From 52a92d93a13887b924b971ea67d8526928155783 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 17 Oct 2011 04:34:26 +0200 Subject: [PATCH 105/116] Add dialog to ask for server passwords after import --- res/layout/accounts_password_prompt.xml | 58 +++ res/menu/disabled_accounts_context.xml | 19 + res/values/strings.xml | 13 + src/com/fsck/k9/Account.java | 21 + src/com/fsck/k9/Preferences.java | 29 +- src/com/fsck/k9/activity/Accounts.java | 441 +++++++++++++++++- src/com/fsck/k9/mail/ServerSettings.java | 5 + src/com/fsck/k9/mail/store/ImapStore.java | 6 + src/com/fsck/k9/mail/store/WebDavStore.java | 6 + .../fsck/k9/preferences/SettingsImporter.java | 2 +- 10 files changed, 583 insertions(+), 17 deletions(-) create mode 100644 res/layout/accounts_password_prompt.xml create mode 100644 res/menu/disabled_accounts_context.xml diff --git a/res/layout/accounts_password_prompt.xml b/res/layout/accounts_password_prompt.xml new file mode 100644 index 000000000..59b33ecbd --- /dev/null +++ b/res/layout/accounts_password_prompt.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/res/menu/disabled_accounts_context.xml b/res/menu/disabled_accounts_context.xml new file mode 100644 index 000000000..54d6c2afc --- /dev/null +++ b/res/menu/disabled_accounts_context.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index c68dc99bd..b5484097c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1093,6 +1093,19 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Export failed Import succeeded Import failed + Activate account + To be able to use the account \"%s\" you need to provide the %s. + + server password + server passwords + + Incoming server (%s): + Outgoing server (%s): + + Setting password... + Setting passwords... + + Activate Unable to handle file of version %s diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java index 50f9d8459..34779d19f 100644 --- a/src/com/fsck/k9/Account.java +++ b/src/com/fsck/k9/Account.java @@ -148,6 +148,16 @@ public class Account implements BaseAccount { private CryptoProvider mCryptoProvider = null; + /** + * Indicates whether this account is enabled, i.e. ready for use, or not. + * + *

+ * Right now newly imported accounts are disabled if the settings file didn't contain a + * password for the incoming and/or outgoing server. + *

+ */ + private boolean mEnabled; + /** * Name of the folder that was last selected for a copy or move operation. * @@ -224,6 +234,7 @@ public class Account implements BaseAccount { mSyncRemoteDeletions = true; mCryptoApp = Apg.NAME; mCryptoAutoSignature = false; + mEnabled = true; searchableFolders = Searchable.ALL; @@ -386,6 +397,7 @@ public class Account implements BaseAccount { mCryptoApp = prefs.getString(mUuid + ".cryptoApp", Apg.NAME); mCryptoAutoSignature = prefs.getBoolean(mUuid + ".cryptoAutoSignature", false); + mEnabled = prefs.getBoolean(mUuid + ".enabled", true); } private String combineUuids(String[] uuids) { @@ -467,6 +479,7 @@ public class Account implements BaseAccount { editor.remove(mUuid + ".replyAfterQuote"); editor.remove(mUuid + ".cryptoApp"); editor.remove(mUuid + ".cryptoAutoSignature"); + editor.remove(mUuid + ".enabled"); editor.remove(mUuid + ".enableMoveButtons"); editor.remove(mUuid + ".hideMoveButtonsEnum"); for (String type : networkTypes) { @@ -619,6 +632,7 @@ public class Account implements BaseAccount { editor.putBoolean(mUuid + ".replyAfterQuote", mReplyAfterQuote); editor.putString(mUuid + ".cryptoApp", mCryptoApp); editor.putBoolean(mUuid + ".cryptoAutoSignature", mCryptoAutoSignature); + editor.putBoolean(mUuid + ".enabled", mEnabled); editor.putBoolean(mUuid + ".vibrate", mNotificationSetting.shouldVibrate()); editor.putInt(mUuid + ".vibratePattern", mNotificationSetting.getVibratePattern()); @@ -1480,4 +1494,11 @@ public class Account implements BaseAccount { return StorageManager.getInstance(K9.app).isReady(localStorageProviderId); } + public synchronized boolean isEnabled() { + return mEnabled; + } + + public synchronized void setEnabled(boolean enabled) { + mEnabled = enabled; + } } diff --git a/src/com/fsck/k9/Preferences.java b/src/com/fsck/k9/Preferences.java index 460dcb4ae..2def52c9b 100644 --- a/src/com/fsck/k9/Preferences.java +++ b/src/com/fsck/k9/Preferences.java @@ -98,6 +98,23 @@ public class Preferences { return retval; } + /** + * Returns all enabled accounts. + * + * @return All accounts with {@link Account#isEnabled()} + */ + public List getEnabledAccounts() { + Account[] allAccounts = getAccounts(); + List enabledAccounts = new ArrayList(); + for (Account account : allAccounts) { + if (account.isEnabled()) { + enabledAccounts.add(account); + } + } + + return enabledAccounts; + } + public synchronized Account getAccount(String uuid) { if (accounts == null) { loadAccounts(); @@ -116,12 +133,12 @@ public class Preferences { } public synchronized void deleteAccount(Account account) { - if (accounts != null) { - accounts.remove(account.getUuid()); - } - if (accountsInOrder != null) { - accountsInOrder.remove(account); - } + if (accounts != null) { + accounts.remove(account.getUuid()); + } + if (accountsInOrder != null) { + accountsInOrder.remove(account); + } account.delete(this); diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 0f951e7d8..98b3af58f 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -26,6 +26,8 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.text.Editable; +import android.text.TextWatcher; import android.util.Log; import android.util.SparseBooleanArray; import android.util.TypedValue; @@ -40,17 +42,22 @@ import android.view.View.OnClickListener; import android.webkit.WebView; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.CheckBox; import android.widget.CheckedTextView; +import android.widget.CompoundButton; +import android.widget.EditText; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.RelativeLayout; +import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.CompoundButton.OnCheckedChangeListener; import com.fsck.k9.Account; import com.fsck.k9.AccountStats; @@ -70,13 +77,18 @@ import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.helper.SizeFormatter; import com.fsck.k9.mail.Flag; +import com.fsck.k9.mail.ServerSettings; +import com.fsck.k9.mail.Store; +import com.fsck.k9.mail.Transport; import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.StorageManager; +import com.fsck.k9.mail.store.WebDavStore; import com.fsck.k9.view.ColorChip; import com.fsck.k9.preferences.SettingsExporter; import com.fsck.k9.preferences.SettingsImportExportException; import com.fsck.k9.preferences.SettingsImporter; import com.fsck.k9.preferences.SettingsImporter.AccountDescription; +import com.fsck.k9.preferences.SettingsImporter.AccountDescriptionPair; import com.fsck.k9.preferences.SettingsImporter.ImportContents; import com.fsck.k9.preferences.SettingsImporter.ImportResults; @@ -542,7 +554,10 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC MessageList.actionHandle(this, searchAccount.getDescription(), searchAccount); } else { Account realAccount = (Account)account; - if (!realAccount.isAvailable(this)) { + if (!realAccount.isEnabled()) { + onActivateAccount(realAccount); + return false; + } else if (!realAccount.isAvailable(this)) { String toastText = getString(R.string.account_unavailable, account.getDescription()); Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_SHORT); toast.show(); @@ -559,6 +574,311 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC return true; } + private void onActivateAccount(Account account) { + List disabledAccounts = new ArrayList(); + disabledAccounts.add(account); + promptForServerPasswords(disabledAccounts); + } + + /** + * Ask the user to enter the server passwords for disabled accounts. + * + * @param disabledAccounts + * A non-empty list of {@link Account}s to ask the user for passwords. Never + * {@code null}. + *

Note: Calling this method will modify the supplied list.

+ */ + private void promptForServerPasswords(final List disabledAccounts) { + Account account = disabledAccounts.remove(0); + PasswordPromptDialog dialog = new PasswordPromptDialog(account, disabledAccounts); + setNonConfigurationInstance(dialog); + dialog.show(this); + } + + /** + * Ask the user for the incoming/outgoing server passwords. + */ + private static class PasswordPromptDialog implements NonConfigurationInstance, TextWatcher { + private AlertDialog mDialog; + private EditText mIncomingPasswordView; + private EditText mOutgoingPasswordView; + private CheckBox mUseIncomingView; + + private Account mAccount; + private List mRemainingAccounts; + private String mIncomingPassword; + private String mOutgoingPassword; + private boolean mUseIncoming; + + /** + * Constructor + * + * @param account + * The {@link Account} to ask the server passwords for. Never {@code null}. + * @param accounts + * The (possibly empty) list of remaining accounts to ask passwords for. Never + * {@code null}. + */ + PasswordPromptDialog(Account account, List accounts) { + mAccount = account; + mRemainingAccounts = accounts; + } + + @Override + public void restore(Activity activity) { + show((Accounts) activity, true); + } + + @Override + public boolean retain() { + if (mDialog != null) { + // Retain entered passwords and checkbox state + mIncomingPassword = mIncomingPasswordView.getText().toString(); + if (mOutgoingPasswordView != null) { + mOutgoingPassword = mOutgoingPasswordView.getText().toString(); + mUseIncoming = mUseIncomingView.isChecked(); + } + + // Dismiss dialog + mDialog.dismiss(); + + // Clear all references to UI objects + mDialog = null; + mIncomingPasswordView = null; + mOutgoingPasswordView = null; + mUseIncomingView = null; + return true; + } + return false; + } + + public void show(Accounts activity) { + show(activity, false); + } + + private void show(final Accounts activity, boolean restore) { + ServerSettings incoming = Store.decodeStoreUri(mAccount.getStoreUri()); + ServerSettings outgoing = Transport.decodeTransportUri(mAccount.getTransportUri()); + + // Don't ask for the password to the outgoing server for WebDAV accounts, because + // incoming and outgoing servers are identical for this account type. + boolean configureOutgoingServer = !WebDavStore.STORE_TYPE.equals(outgoing.type); + + // Create a ScrollView that will be used as container for the whole layout + final ScrollView scrollView = new ScrollView(activity); + + // Create the dialog + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(activity.getString(R.string.settings_import_activate_account_header)); + builder.setView(scrollView); + builder.setPositiveButton(activity.getString(R.string.okay_action), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String incomingPassword = mIncomingPasswordView.getText().toString(); + String outgoingPassword = null; + if (mOutgoingPasswordView != null) { + outgoingPassword = (mUseIncomingView.isChecked()) ? + incomingPassword : mOutgoingPasswordView.getText().toString(); + } + + dialog.dismiss(); + + // Set the server passwords in the background + SetPasswordsAsyncTask asyncTask = new SetPasswordsAsyncTask(activity, mAccount, + incomingPassword, outgoingPassword, mRemainingAccounts); + activity.setNonConfigurationInstance(asyncTask); + asyncTask.execute(); + } + }); + builder.setNegativeButton(activity.getString(R.string.cancel_action), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + activity.setNonConfigurationInstance(null); + } + }); + mDialog = builder.create(); + + // Use the dialog's layout inflater so its theme is used (and not the activity's theme). + View layout = mDialog.getLayoutInflater().inflate( + R.layout.accounts_password_prompt, null); + + // Set the intro text that tells the user what to do + TextView intro = (TextView) layout.findViewById(R.id.password_prompt_intro); + String serverPasswords = activity.getResources().getQuantityString( + R.plurals.settings_import_server_passwords, + (configureOutgoingServer) ? 2 : 1); + intro.setText(activity.getString(R.string.settings_import_activate_account_intro, + mAccount.getDescription(), serverPasswords)); + + // Display the hostname of the incoming server + TextView incomingText = (TextView) layout.findViewById( + R.id.password_prompt_incoming_server); + incomingText.setText(activity.getString(R.string.settings_import_incoming_server, + incoming.host)); + + mIncomingPasswordView = (EditText) layout.findViewById(R.id.incoming_server_password); + mIncomingPasswordView.addTextChangedListener(this); + + if (configureOutgoingServer) { + // Display the hostname of the outgoing server + TextView outgoingText = (TextView) layout.findViewById( + R.id.password_prompt_outgoing_server); + outgoingText.setText(activity.getString(R.string.settings_import_outgoing_server, + outgoing.host)); + + mOutgoingPasswordView = (EditText) layout.findViewById( + R.id.outgoing_server_password); + mOutgoingPasswordView.addTextChangedListener(this); + + mUseIncomingView = (CheckBox) layout.findViewById( + R.id.use_incoming_server_password); + mUseIncomingView.setChecked(true); + mUseIncomingView.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + mOutgoingPasswordView.setText(null); + mOutgoingPasswordView.setEnabled(false); + } else { + mOutgoingPasswordView.setText(mIncomingPasswordView.getText()); + mOutgoingPasswordView.setEnabled(true); + } + } + }); + } else { + layout.findViewById(R.id.outgoing_server_prompt).setVisibility(View.GONE); + } + + // Add the layout to the ScrollView + scrollView.addView(layout); + + // Show the dialog + mDialog.show(); + + // Restore the contents of the password boxes and the checkbox (if the dialog was + // retained during a configuration change). + if (restore) { + mIncomingPasswordView.setText(mIncomingPassword); + if (configureOutgoingServer) { + mOutgoingPasswordView.setText(mOutgoingPassword); + mUseIncomingView.setChecked(mUseIncoming); + } + } else { + // Trigger afterTextChanged() being called + // Work around this bug: https://code.google.com/p/android/issues/detail?id=6360 + mIncomingPasswordView.setText(mIncomingPasswordView.getText()); + } + } + + @Override + public void afterTextChanged(Editable arg0) { + boolean enable = false; + // Is the password box for the incoming server password empty? + if (mIncomingPasswordView.getText().length() > 0) { + // Do we need to check the outgoing server password box? + if (mOutgoingPasswordView == null) { + enable = true; + } + // If the checkbox to use the incoming server password is checked we need to make + // sure that the password box for the outgoing server isn't empty. + else if (mUseIncomingView.isChecked() || + mOutgoingPasswordView.getText().length() > 0) { + enable = true; + } + } + + // Disable "OK" button if the user hasn't specified all necessary passwords. + mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(enable); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Not used + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Not used + } + } + + /** + * Set the incoming/outgoing server password in the background. + */ + private static class SetPasswordsAsyncTask extends ExtendedAsyncTask { + private Account mAccount; + private String mIncomingPassword; + private String mOutgoingPassword; + private List mRemainingAccounts; + + protected SetPasswordsAsyncTask(Activity activity, Account account, + String incomingPassword, String outgoingPassword, + List remainingAccounts) { + super(activity); + mAccount = account; + mIncomingPassword = incomingPassword; + mOutgoingPassword = outgoingPassword; + mRemainingAccounts = remainingAccounts; + } + + @Override + protected void showProgressDialog() { + String title = mActivity.getString(R.string.settings_import_activate_account_header); + int passwordCount = (mOutgoingPassword == null) ? 1 : 2; + String message = mActivity.getResources().getQuantityString( + R.plurals.settings_import_setting_passwords, passwordCount); + mProgressDialog = ProgressDialog.show(mActivity, title, message, true); + } + + @Override + protected Void doInBackground(Void... params) { + try { + // Set incoming server password + String storeUri = mAccount.getStoreUri(); + ServerSettings incoming = Store.decodeStoreUri(storeUri); + ServerSettings newIncoming = incoming.newPassword(mIncomingPassword); + String newStoreUri = Store.createStoreUri(newIncoming); + mAccount.setStoreUri(newStoreUri); + + if (mOutgoingPassword != null) { + // Set outgoing server password + String transportUri = mAccount.getTransportUri(); + ServerSettings outgoing = Transport.decodeTransportUri(transportUri); + ServerSettings newOutgoing = outgoing.newPassword(mOutgoingPassword); + String newTransportUri = Transport.createTransportUri(newOutgoing); + mAccount.setTransportUri(newTransportUri); + } + + // Mark account as enabled + mAccount.setEnabled(true); + + // Save the account settings + mAccount.save(Preferences.getPreferences(mContext)); + } catch (Exception e) { + Log.e(K9.LOG_TAG, "Something went while setting account passwords", e); + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + Accounts activity = (Accounts) mActivity; + + // Let the activity know that the background task is complete + activity.setNonConfigurationInstance(null); + + activity.refresh(); + removeProgressDialog(); + + if (mRemainingAccounts.size() > 0) { + activity.promptForServerPasswords(mRemainingAccounts); + } + } + } + public void onClick(View view) { if (view.getId() == R.id.next) { onAddNewAccount(); @@ -683,6 +1003,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC case R.id.open: onOpenAccount(mSelectedContextAccount); break; + case R.id.activate: + onActivateAccount(realAccount); + break; case R.id.check_mail: onCheckMail(realAccount); break; @@ -866,10 +1189,16 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); menu.setHeaderTitle(R.string.accounts_context_menu_title); - getMenuInflater().inflate(R.menu.accounts_context, menu); AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; BaseAccount account = mAdapter.getItem(info.position); + + if ((account instanceof Account) && !((Account) account).isEnabled()) { + getMenuInflater().inflate(R.menu.disabled_accounts_context, menu); + } else { + getMenuInflater().inflate(R.menu.accounts_context, menu); + } + if (account instanceof SearchAccount) { for (int i = 0; i < menu.size(); i++) { MenuItem item = menu.getItem(i); @@ -930,6 +1259,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC setNonConfigurationInstance(dialog); } + /** + * A simple dialog. + */ private static class SimpleDialog implements NonConfigurationInstance { private final int mHeaderRes; private final int mMessageRes; @@ -958,7 +1290,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } public void show(final Accounts activity) { - final String message = activity.getString(mMessageRes, mArguments); + final String message = generateMessage(activity); final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(mHeaderRes); @@ -969,18 +1301,105 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); activity.setNonConfigurationInstance(null); + okayAction(activity); } }); mDialog = builder.show(); } + + /** + * Returns the message the dialog should display. + * + * @param activity + * The {@code Activity} this dialog belongs to. + * + * @return The message the dialog should display + */ + protected String generateMessage(Accounts activity) { + return activity.getString(mMessageRes, mArguments); + } + + /** + * This method is called after the "OK" button was pressed. + * + * @param activity + * The {@code Activity} this dialog belongs to. + */ + protected void okayAction(Accounts activity) { + // Do nothing + } } + /** + * Shows a dialog that displays how many accounts were successfully imported. + * + * @param importResults + * The {@link ImportResults} instance returned by the {@link SettingsImporter}. + * @param filename + * The name of the settings file that was imported. + */ + private void showAccountsImportedDialog(ImportResults importResults, String filename) { + AccountsImportedDialog dialog = new AccountsImportedDialog(importResults, filename); + dialog.show(this); + setNonConfigurationInstance(dialog); + } + + /** + * A dialog that displays how many accounts were successfully imported. + */ + private static class AccountsImportedDialog extends SimpleDialog { + private ImportResults mImportResults; + private String mFilename; + + AccountsImportedDialog(ImportResults importResults, String filename) { + super(R.string.settings_import_success_header, R.string.settings_import_success); + mImportResults = importResults; + mFilename = filename; + } + + @Override + protected String generateMessage(Accounts activity) { + //TODO: display names of imported accounts (name from file *and* possibly new name) + + int imported = mImportResults.importedAccounts.size(); + String accounts = activity.getResources().getQuantityString( + R.plurals.settings_import_success, imported, imported); + return activity.getString(R.string.settings_import_success, accounts, mFilename); + } + + @Override + protected void okayAction(Accounts activity) { + Context context = activity.getApplicationContext(); + Preferences preferences = Preferences.getPreferences(context); + List disabledAccounts = new ArrayList(); + for (AccountDescriptionPair accountPair : mImportResults.importedAccounts) { + Account account = preferences.getAccount(accountPair.imported.uuid); + if (!account.isEnabled()) { + disabledAccounts.add(account); + } + } + activity.promptForServerPasswords(disabledAccounts); + } + } + + /** + * Display a dialog that lets the user select which accounts to import from the settings file. + * + * @param importContents + * The {@link ImportContents} instance returned by + * {@link SettingsImporter#getImportStreamContents(InputStream)} + * @param uri + * The (content) URI of the settings file. + */ private void showImportSelectionDialog(ImportContents importContents, Uri uri) { ImportSelectionDialog dialog = new ImportSelectionDialog(importContents, uri); dialog.show(this); setNonConfigurationInstance(dialog); } + /** + * A dialog that lets the user select which accounts to import from the settings file. + */ private static class ImportSelectionDialog implements NonConfigurationInstance { private ImportContents mImportContents; private Uri mUri; @@ -1104,6 +1523,14 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } } + /** + * Set the {@code NonConfigurationInstance} this activity should retain on configuration + * changes. + * + * @param inst + * The {@link NonConfigurationInstance} that should be retained when + * {@link Accounts#onRetainNonConfigurationInstance()} is called. + */ private void setNonConfigurationInstance(NonConfigurationInstance inst) { mNonConfigurationInstance = inst; } @@ -1438,17 +1865,11 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC boolean globalSettings = mImportResults.globalSettings; int imported = mImportResults.importedAccounts.size(); if (success && (globalSettings || imported > 0)) { - - //TODO: display names of imported accounts (name from file *and* possibly new name) - if (imported == 0) { activity.showSimpleDialog(R.string.settings_import_success_header, R.string.settings_import_global_settings_success, filename); } else { - String importedAccounts = activity.getResources().getQuantityString( - R.plurals.settings_import_success, imported); - activity.showSimpleDialog(R.string.settings_import_success_header, - R.string.settings_import_success, importedAccounts, filename); + activity.showAccountsImportedDialog(mImportResults, filename); } activity.refresh(); diff --git a/src/com/fsck/k9/mail/ServerSettings.java b/src/com/fsck/k9/mail/ServerSettings.java index ef237e0a4..f7f127d29 100644 --- a/src/com/fsck/k9/mail/ServerSettings.java +++ b/src/com/fsck/k9/mail/ServerSettings.java @@ -125,4 +125,9 @@ public class ServerSettings { map.put(key, value); } } + + public ServerSettings newPassword(String newPassword) { + return new ServerSettings(type, host, port, connectionSecurity, authenticationType, + username, newPassword); + } } \ No newline at end of file diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 0a86619a3..e1af45a78 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -305,6 +305,12 @@ public class ImapStore extends Store { putIfNotNull(extra, PATH_PREFIX_KEY, pathPrefix); return extra; } + + @Override + public ServerSettings newPassword(String newPassword) { + return new ImapStoreSettings(host, port, connectionSecurity, authenticationType, + username, newPassword, pathPrefix); + } } diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java index ef9a6278e..001ed61ac 100644 --- a/src/com/fsck/k9/mail/store/WebDavStore.java +++ b/src/com/fsck/k9/mail/store/WebDavStore.java @@ -298,6 +298,12 @@ public class WebDavStore extends Store { putIfNotNull(extra, MAILBOX_PATH_KEY, mailboxPath); return extra; } + + @Override + public ServerSettings newPassword(String newPassword) { + return new WebDavStoreSettings(host, port, connectionSecurity, authenticationType, + username, newPassword, alias, path, authPath, mailboxPath); + } } diff --git a/src/com/fsck/k9/preferences/SettingsImporter.java b/src/com/fsck/k9/preferences/SettingsImporter.java index 0d280e591..82f95a886 100644 --- a/src/com/fsck/k9/preferences/SettingsImporter.java +++ b/src/com/fsck/k9/preferences/SettingsImporter.java @@ -77,7 +77,7 @@ public class SettingsImporter { } } - private static class AccountDescriptionPair { + public static class AccountDescriptionPair { public final AccountDescription original; public final AccountDescription imported; From 3967654a3e673a5de3d36f9eacc69f159d908ac8 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 17 Oct 2011 03:58:42 +0200 Subject: [PATCH 106/116] Show short message when moving accounts --- res/values/strings.xml | 1 + src/com/fsck/k9/activity/Accounts.java | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index b5484097c..464d65747 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1117,5 +1117,6 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Move up Move down + Moving account... diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 98b3af58f..9a983df96 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -1953,10 +1953,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override protected void showProgressDialog() { - //FIXME - String title = ""; - String message = ""; - mProgressDialog = ProgressDialog.show(mActivity, title, message, true); + String message = mActivity.getString(R.string.manage_accounts_moving_message); + mProgressDialog = ProgressDialog.show(mActivity, null, message, true); } @Override From f08b1ef739f21104abe3fff5c9e3d4e664ef9c4d Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 17 Oct 2011 04:30:26 +0200 Subject: [PATCH 107/116] Added new settings to AccountSettings --- src/com/fsck/k9/preferences/AccountSettings.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/com/fsck/k9/preferences/AccountSettings.java b/src/com/fsck/k9/preferences/AccountSettings.java index 1bc693bb5..13c4d089a 100644 --- a/src/com/fsck/k9/preferences/AccountSettings.java +++ b/src/com/fsck/k9/preferences/AccountSettings.java @@ -27,6 +27,7 @@ public class AccountSettings { s.put("chipColor", new ColorSetting(0xFF0000FF)); s.put("cryptoApp", new StringSetting(Apg.NAME)); s.put("cryptoAutoSignature", new BooleanSetting(false)); + s.put("defaultQuotedTextShown", new BooleanSetting(Account.DEFAULT_QUOTED_TEXT_SHOWN)); s.put("deletePolicy", new DeletePolicySetting(Account.DELETE_POLICY_NEVER)); s.put("displayCount", new IntegerResourceSetting(K9.DEFAULT_VISIBLE_LIMIT, R.array.account_settings_display_count_values)); @@ -43,6 +44,7 @@ public class AccountSettings { s.put("hideMoveButtonsEnum", new EnumSetting(ScrollButtons.class, ScrollButtons.NEVER)); s.put("idleRefreshMinutes", new IntegerResourceSetting(24, R.array.idle_refresh_period_values)); + s.put("inboxFolderName", new StringSetting("INBOX")); s.put("led", new BooleanSetting(true)); s.put("ledColor", new ColorSetting(0xFF0000FF)); s.put("localStorageProvider", new StorageProviderSetting()); @@ -53,6 +55,7 @@ public class AccountSettings { R.array.account_settings_message_age_values)); s.put("messageFormat", new EnumSetting(Account.MessageFormat.class, Account.DEFAULT_MESSAGE_FORMAT)); + s.put("messageReadReceipt", new BooleanSetting(Account.DEFAULT_MESSAGE_READ_RECEIPT)); s.put("notificationUnreadCount", new BooleanSetting(true)); s.put("notifyMailCheck", new BooleanSetting(false)); s.put("notifyNewMail", new BooleanSetting(false)); From 75dc38a1c40e9741f5589be6632ddfa2f4137d4c Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 17 Oct 2011 04:52:33 +0200 Subject: [PATCH 108/116] Added new settings to GlobalSettings --- .../fsck/k9/preferences/GlobalSettings.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/com/fsck/k9/preferences/GlobalSettings.java b/src/com/fsck/k9/preferences/GlobalSettings.java index 912ff4856..746d96dbd 100644 --- a/src/com/fsck/k9/preferences/GlobalSettings.java +++ b/src/com/fsck/k9/preferences/GlobalSettings.java @@ -1,11 +1,14 @@ package com.fsck.k9.preferences; +import java.io.File; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import android.content.SharedPreferences; +import android.os.Environment; + import com.fsck.k9.FontSizes; import com.fsck.k9.K9; import com.fsck.k9.R; @@ -19,11 +22,15 @@ public class GlobalSettings { Map s = new LinkedHashMap(); s.put("animations", new BooleanSetting(false)); + s.put("attachmentdefaultpath", + new DirectorySetting(Environment.getExternalStorageDirectory().toString())); s.put("backgroundOperations", new EnumSetting(K9.BACKGROUND_OPS.class, K9.BACKGROUND_OPS.WHEN_CHECKED)); s.put("changeRegisteredNameColor", new BooleanSetting(false)); s.put("compactLayouts", new BooleanSetting(false)); s.put("confirmDelete", new BooleanSetting(false)); + s.put("confirmMarkAllAsRead", new BooleanSetting(false)); + s.put("confirmSpam", new BooleanSetting(false)); s.put("countSearchMessages", new BooleanSetting(false)); s.put("dateFormat", new DateFormatSetting(DateFormatter.DEFAULT_FORMAT)); s.put("enableDebugLogging", new BooleanSetting(false)); @@ -56,6 +63,7 @@ public class GlobalSettings { s.put("messageListTouchable", new BooleanSetting(false)); s.put("messageViewFixedWidthFont", new BooleanSetting(false)); s.put("messageViewReturnToList", new BooleanSetting(false)); + s.put("messageViewShowNext", new BooleanSetting(false)); s.put("mobileOptimizedLayout", new BooleanSetting(false)); s.put("quietTimeEnabled", new BooleanSetting(false)); s.put("quietTimeEnds", new TimeSetting("7:00")); @@ -225,4 +233,24 @@ public class GlobalSettings { return value; } } + + /** + * A directory on the file system. + */ + public static class DirectorySetting extends SettingsDescription { + public DirectorySetting(String defaultValue) { + super(defaultValue); + } + + @Override + public Object fromString(String value) throws InvalidSettingValueException { + try { + if (new File(value).isDirectory()) { + return value; + } + } catch (Exception e) { /* do nothing */ } + + throw new InvalidSettingValueException(); + } + } } From d87e25dde1faeb60c6cebe5268ff90ef2c6890fe Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 17 Oct 2011 04:57:01 +0200 Subject: [PATCH 109/116] Added new authentication methods to SmtpTransport.createUri() --- src/com/fsck/k9/mail/transport/SmtpTransport.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/com/fsck/k9/mail/transport/SmtpTransport.java b/src/com/fsck/k9/mail/transport/SmtpTransport.java index b8273a820..302acd3f1 100644 --- a/src/com/fsck/k9/mail/transport/SmtpTransport.java +++ b/src/com/fsck/k9/mail/transport/SmtpTransport.java @@ -165,7 +165,10 @@ public class SmtpTransport extends Transport { } String authType = server.authenticationType; - if (!"CRAM_MD5".equals(authType) && !"PLAIN".equals(authType)) { + if (!(AUTH_AUTOMATIC.equals(authType) || + AUTH_LOGIN.equals(authType) || + AUTH_PLAIN.equals(authType) || + AUTH_CRAM_MD5.equals(authType))) { throw new IllegalArgumentException("Invalid authentication type: " + authType); } From 6b5b4e474e46c080f65cd89b39dd28d2a0c8b8e7 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 17 Oct 2011 05:59:36 +0200 Subject: [PATCH 110/116] Fixed store URI encoding/decoding in Pop3Store --- src/com/fsck/k9/mail/store/Pop3Store.java | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java index 9deb5b3ea..8144124fc 100644 --- a/src/com/fsck/k9/mail/store/Pop3Store.java +++ b/src/com/fsck/k9/mail/store/Pop3Store.java @@ -34,6 +34,11 @@ public class Pop3Store extends Store { public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3; public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4; + private enum AuthType { + PLAIN, + CRAM_MD5 + } + private static final String STLS_COMMAND = "STLS"; private static final String USER_COMMAND = "USER"; private static final String PASS_COMMAND = "PASS"; @@ -106,7 +111,7 @@ public class Pop3Store extends Store { port = pop3Uri.getPort(); } - String authType = ""; + String authType = AuthType.PLAIN.name(); if (pop3Uri.getUserInfo() != null) { try { int userIndex = 0, passwordIndex = 1; @@ -173,7 +178,14 @@ public class Pop3Store extends Store { break; } - String userInfo = userEnc + ":" + passwordEnc; + try { + AuthType.valueOf(server.authenticationType); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid authentication type (" + + server.authenticationType + ")"); + } + + String userInfo = server.authenticationType + ":" + userEnc + ":" + passwordEnc; try { return new URI(scheme, userInfo, server.host, server.port, null, null, null).toString(); @@ -187,7 +199,7 @@ public class Pop3Store extends Store { private int mPort; private String mUsername; private String mPassword; - private boolean useCramMd5; + private AuthType mAuthType; private int mConnectionSecurity; private HashMap mFolders = new HashMap(); private Pop3Capabilities mCapabilities; @@ -233,6 +245,7 @@ public class Pop3Store extends Store { mUsername = settings.username; mPassword = settings.password; + mAuthType = AuthType.valueOf(settings.authenticationType); } @Override @@ -352,7 +365,7 @@ public class Pop3Store extends Store { } } - if (useCramMd5) { + if (mAuthType == AuthType.CRAM_MD5) { try { String b64Nonce = executeSimpleCommand("AUTH CRAM-MD5").replace("+ ", ""); From e7ad0e296ef6f39e1586a417f9c1bc63673df233 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 17 Oct 2011 23:33:32 +0200 Subject: [PATCH 111/116] Validate account UUID on import --- .../fsck/k9/preferences/SettingsImporter.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/com/fsck/k9/preferences/SettingsImporter.java b/src/com/fsck/k9/preferences/SettingsImporter.java index 82f95a886..05cdecc39 100644 --- a/src/com/fsck/k9/preferences/SettingsImporter.java +++ b/src/com/fsck/k9/preferences/SettingsImporter.java @@ -761,7 +761,9 @@ public class SettingsImporter { ImportedAccount account = parseAccount(xpp, accountUuids, overview); - if (!accounts.containsKey(account.uuid)) { + if (account == null) { + // Do nothing - parseAccount() already logged a message + } else if (!accounts.containsKey(account.uuid)) { accounts.put(account.uuid, account); } else { Log.w(K9.LOG_TAG, "Duplicate account entries with UUID " + account.uuid + @@ -781,9 +783,17 @@ public class SettingsImporter { boolean overview) throws XmlPullParserException, IOException { - ImportedAccount account = new ImportedAccount(); - String uuid = xpp.getAttributeValue(null, SettingsExporter.UUID_ATTRIBUTE); + + try { + UUID.fromString(uuid); + } catch (Exception e) { + skipToEndTag(xpp, SettingsExporter.ACCOUNT_ELEMENT); + Log.w(K9.LOG_TAG, "Skipping account with invalid UUID " + uuid); + return null; + } + + ImportedAccount account = new ImportedAccount(); account.uuid = uuid; if (overview || accountUuids.contains(uuid)) { From 9fa802afe2922317f23b703090d7117a0cb86452 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 18 Oct 2011 01:30:41 +0200 Subject: [PATCH 112/116] Validate file format version and content version of settings file --- .../fsck/k9/preferences/SettingsImporter.java | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/preferences/SettingsImporter.java b/src/com/fsck/k9/preferences/SettingsImporter.java index 05cdecc39..3c1b36475 100644 --- a/src/com/fsck/k9/preferences/SettingsImporter.java +++ b/src/com/fsck/k9/preferences/SettingsImporter.java @@ -664,11 +664,17 @@ public class SettingsImporter { private static Imported parseRoot(XmlPullParser xpp, boolean globalSettings, List accountUuids, boolean overview) - throws XmlPullParserException, IOException { + throws XmlPullParserException, IOException, SettingsImportExportException { Imported result = new Imported(); - //TODO: check version attribute + String fileFormatVersionString = xpp.getAttributeValue(null, + SettingsExporter.FILE_FORMAT_ATTRIBUTE); + validateFileFormatVersion(fileFormatVersionString); + + String contentVersionString = xpp.getAttributeValue(null, + SettingsExporter.VERSION_ATTRIBUTE); + validateContentVersion(contentVersionString); int eventType = xpp.next(); while (!(eventType == XmlPullParser.END_TAG && @@ -709,6 +715,52 @@ public class SettingsImporter { return result; } + private static int validateFileFormatVersion(String versionString) + throws SettingsImportExportException { + + if (versionString == null) { + throw new SettingsImportExportException("Missing file format version"); + } + + int version; + try { + version = Integer.parseInt(versionString); + } catch (NumberFormatException e) { + throw new SettingsImportExportException("Invalid file format version: " + + versionString); + } + + if (version != SettingsExporter.FILE_FORMAT_VERSION) { + throw new SettingsImportExportException("Unsupported file format version: " + + versionString); + } + + return version; + } + + private static int validateContentVersion(String versionString) + throws SettingsImportExportException { + + if (versionString == null) { + throw new SettingsImportExportException("Missing content version"); + } + + int version; + try { + version = Integer.parseInt(versionString); + } catch (NumberFormatException e) { + throw new SettingsImportExportException("Invalid content version: " + + versionString); + } + + if (version != Settings.VERSION) { + throw new SettingsImportExportException("Unsupported content version: " + + versionString); + } + + return version; + } + private static ImportedSettings parseSettings(XmlPullParser xpp, String endTag) throws XmlPullParserException, IOException { From dd8cd33c5aebc930c17ba3ad09eef6ea0a2fe1d6 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 18 Oct 2011 05:05:40 +0200 Subject: [PATCH 113/116] Fixed decoding of store URIs with empty passwords --- src/com/fsck/k9/mail/store/ImapStore.java | 10 ++++++++-- src/com/fsck/k9/mail/store/Pop3Store.java | 7 +++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index e1af45a78..b88d84f76 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -191,8 +191,14 @@ public class ImapStore extends Store { if (imapUri.getUserInfo() != null) { try { - String[] userInfoParts = imapUri.getUserInfo().split(":"); - if (userInfoParts.length == 2) { + String userinfo = imapUri.getUserInfo(); + String[] userInfoParts = userinfo.split(":"); + + if (userinfo.endsWith(":")) { + // Password is empty. This can only happen after an account was imported. + authenticationType = AuthType.valueOf(userInfoParts[0]).name(); + username = URLDecoder.decode(userInfoParts[1], "UTF-8"); + } else if (userInfoParts.length == 2) { authenticationType = AuthType.PLAIN.name(); username = URLDecoder.decode(userInfoParts[0], "UTF-8"); password = URLDecoder.decode(userInfoParts[1], "UTF-8"); diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java index 8144124fc..4b11cdf83 100644 --- a/src/com/fsck/k9/mail/store/Pop3Store.java +++ b/src/com/fsck/k9/mail/store/Pop3Store.java @@ -115,8 +115,11 @@ public class Pop3Store extends Store { if (pop3Uri.getUserInfo() != null) { try { int userIndex = 0, passwordIndex = 1; - String[] userInfoParts = pop3Uri.getUserInfo().split(":"); - if (userInfoParts.length > 2) { + String userinfo = pop3Uri.getUserInfo(); + String[] userInfoParts = userinfo.split(":"); + if (userInfoParts.length > 2 || userinfo.endsWith(":") ) { + // If 'userinfo' ends with ":" the password is empty. This can only happen + // after an account was imported (so authType and username are present). userIndex++; passwordIndex++; authType = userInfoParts[0]; From 4d6946f47c35f96023471e7c1243f2d2c2a4e57d Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 18 Oct 2011 05:06:32 +0200 Subject: [PATCH 114/116] Start services and list folders after account has been activated --- src/com/fsck/k9/activity/Accounts.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 9a983df96..e70a2bbf4 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -15,6 +15,7 @@ import java.util.concurrent.ConcurrentHashMap; import android.app.Activity; import android.app.AlertDialog; +import android.app.Application; import android.app.Dialog; import android.app.ProgressDialog; import android.content.ContentResolver; @@ -813,6 +814,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private String mIncomingPassword; private String mOutgoingPassword; private List mRemainingAccounts; + private Application mApplication; protected SetPasswordsAsyncTask(Activity activity, Account account, String incomingPassword, String outgoingPassword, @@ -822,6 +824,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC mIncomingPassword = incomingPassword; mOutgoingPassword = outgoingPassword; mRemainingAccounts = remainingAccounts; + mApplication = mActivity.getApplication(); } @Override @@ -857,6 +860,12 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC // Save the account settings mAccount.save(Preferences.getPreferences(mContext)); + + // Start services if necessary + K9.setServicesEnabled(mContext); + + // Get list of folders from remote server + MessagingController.getInstance(mApplication).listFolders(mAccount, true, null); } catch (Exception e) { Log.e(K9.LOG_TAG, "Something went while setting account passwords", e); } From afd355f83cce7356bf60822f264d3850e501777b Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 18 Oct 2011 05:09:20 +0200 Subject: [PATCH 115/116] Use Preferences.getAvailableAccounts() where appropriate --- src/com/fsck/k9/Preferences.java | 19 +------------------ src/com/fsck/k9/activity/ChooseAccount.java | 12 ++++++++---- .../k9/controller/MessagingController.java | 19 ++++++------------- src/com/fsck/k9/service/MailService.java | 6 +++--- 4 files changed, 18 insertions(+), 38 deletions(-) diff --git a/src/com/fsck/k9/Preferences.java b/src/com/fsck/k9/Preferences.java index 2def52c9b..ca7abf204 100644 --- a/src/com/fsck/k9/Preferences.java +++ b/src/com/fsck/k9/Preferences.java @@ -90,7 +90,7 @@ public class Preferences { Account[] allAccounts = getAccounts(); Collection retval = new ArrayList(accounts.size()); for (Account account : allAccounts) { - if (account.isAvailable(mContext)) { + if (account.isEnabled() && account.isAvailable(mContext)) { retval.add(account); } } @@ -98,23 +98,6 @@ public class Preferences { return retval; } - /** - * Returns all enabled accounts. - * - * @return All accounts with {@link Account#isEnabled()} - */ - public List getEnabledAccounts() { - Account[] allAccounts = getAccounts(); - List enabledAccounts = new ArrayList(); - for (Account account : allAccounts) { - if (account.isEnabled()) { - enabledAccounts.add(account); - } - } - - return enabledAccounts; - } - public synchronized Account getAccount(String uuid) { if (accounts == null) { loadAccounts(); diff --git a/src/com/fsck/k9/activity/ChooseAccount.java b/src/com/fsck/k9/activity/ChooseAccount.java index 6b04f6b76..0c1c0041c 100644 --- a/src/com/fsck/k9/activity/ChooseAccount.java +++ b/src/com/fsck/k9/activity/ChooseAccount.java @@ -28,6 +28,7 @@ import java.util.List; * @see K9ExpandableListActivity */ public class ChooseAccount extends K9ExpandableListActivity { + private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[0]; /** * {@link Intent} extended data name for storing {@link Account#getUuid() @@ -50,7 +51,7 @@ public class ChooseAccount extends K9ExpandableListActivity { final ExpandableListView expandableListView = getExpandableListView(); expandableListView.setItemsCanFocus(false); - final ExpandableListAdapter adapter = createAdapter(); + final IdentitiesAdapter adapter = createAdapter(); setListAdapter(adapter); expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @@ -77,7 +78,7 @@ public class ChooseAccount extends K9ExpandableListActivity { final Bundle extras = getIntent().getExtras(); final String uuid = extras.getString(EXTRA_ACCOUNT); if (uuid != null) { - final Account[] accounts = Preferences.getPreferences(this).getAccounts(); + final Account[] accounts = adapter.getAccounts(); final int length = accounts.length; for (int i = 0; i < length; i++) { final Account account = accounts[i]; @@ -106,7 +107,7 @@ public class ChooseAccount extends K9ExpandableListActivity { } } - private ExpandableListAdapter createAdapter() { + private IdentitiesAdapter createAdapter() { return new IdentitiesAdapter(this, getLayoutInflater()); } @@ -123,10 +124,13 @@ public class ChooseAccount extends K9ExpandableListActivity { private Context mContext; private LayoutInflater mLayoutInflater; + private Account[] mAccounts; public IdentitiesAdapter(final Context context, final LayoutInflater layoutInflater) { mContext = context; mLayoutInflater = layoutInflater; + Preferences prefs = Preferences.getPreferences(mContext); + mAccounts = prefs.getAvailableAccounts().toArray(EMPTY_ACCOUNT_ARRAY); } @Override @@ -233,7 +237,7 @@ public class ChooseAccount extends K9ExpandableListActivity { } private Account[] getAccounts() { - return Preferences.getPreferences(mContext).getAccounts(); + return mAccounts; } } } diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 8b188c298..42708de8d 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -650,15 +650,10 @@ public class MessagingController implements Runnable { accountUuidsSet.addAll(Arrays.asList(accountUuids)); } final Preferences prefs = Preferences.getPreferences(mApplication.getApplicationContext()); - Account[] accounts = prefs.getAccounts(); List foldersToSearch = null; boolean displayableOnly = false; boolean noSpecialFolders = true; - for (final Account account : accounts) { - if (!account.isAvailable(mApplication)) { - Log.d(K9.LOG_TAG, "searchLocalMessagesSynchronous() ignores account that is not available"); - continue; - } + for (final Account account : prefs.getAvailableAccounts()) { if (accountUuids != null && !accountUuidsSet.contains(account.getUuid())) { continue; } @@ -2837,8 +2832,7 @@ public class MessagingController implements Runnable { public void sendPendingMessages(MessagingListener listener) { final Preferences prefs = Preferences.getPreferences(mApplication.getApplicationContext()); - Account[] accounts = prefs.getAccounts(); - for (Account account : accounts) { + for (Account account : prefs.getAvailableAccounts()) { sendPendingMessages(account, listener); } } @@ -3596,13 +3590,12 @@ public class MessagingController implements Runnable { Log.i(K9.LOG_TAG, "Starting mail check"); Preferences prefs = Preferences.getPreferences(context); - Account[] accounts; + Collection accounts; if (account != null) { - accounts = new Account[] { - account - }; + accounts = new ArrayList(1); + accounts.add(account); } else { - accounts = prefs.getAccounts(); + accounts = prefs.getAvailableAccounts(); } for (final Account account : accounts) { diff --git a/src/com/fsck/k9/service/MailService.java b/src/com/fsck/k9/service/MailService.java index e8e3fc1a2..df3058bb2 100644 --- a/src/com/fsck/k9/service/MailService.java +++ b/src/com/fsck/k9/service/MailService.java @@ -146,7 +146,7 @@ public class MailService extends CoreService { } else if (ACTION_RESET.equals(intent.getAction())) { if (K9.DEBUG) Log.v(K9.LOG_TAG, "***** MailService *****: reschedule"); - rescheduleAll(hasConnectivity, doBackground, startId); + rescheduleAll(hasConnectivity, doBackground, startId); } else if (ACTION_RESTART_PUSHERS.equals(intent.getAction())) { if (K9.DEBUG) Log.v(K9.LOG_TAG, "***** MailService *****: restarting pushers"); @@ -228,7 +228,7 @@ public class MailService extends CoreService { } - for (Account account : prefs.getAccounts()) { + for (Account account : prefs.getAvailableAccounts()) { if (account.getAutomaticCheckIntervalMinutes() != -1 && account.getFolderSyncMode() != FolderMode.NONE && (account.getAutomaticCheckIntervalMinutes() < shortestInterval || shortestInterval == -1)) { @@ -323,7 +323,7 @@ public class MailService extends CoreService { for (Account account : Preferences.getPreferences(MailService.this).getAccounts()) { if (K9.DEBUG) Log.i(K9.LOG_TAG, "Setting up pushers for account " + account.getDescription()); - if (account.isAvailable(getApplicationContext())) { + if (account.isEnabled() && account.isAvailable(getApplicationContext())) { pushing |= MessagingController.getInstance(getApplication()).setupPushing(account); } else { //TODO: setupPushing of unavailable accounts when they become available (sd-card inserted) From ae465789b7fb6206f91ee12f53404b70446b2e35 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 26 Oct 2011 05:13:22 +0200 Subject: [PATCH 116/116] Export default value for keys not found in preference storage --- .../fsck/k9/preferences/SettingsExporter.java | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/com/fsck/k9/preferences/SettingsExporter.java b/src/com/fsck/k9/preferences/SettingsExporter.java index 825f98c4d..cabc8cdf2 100644 --- a/src/com/fsck/k9/preferences/SettingsExporter.java +++ b/src/com/fsck/k9/preferences/SettingsExporter.java @@ -168,16 +168,27 @@ public class SettingsExporter { Map prefs) throws IOException { for (String key : GlobalSettings.SETTINGS.keySet()) { - String valueString = prefs.get(key).toString(); + String valueString = (String) prefs.get(key); + if (valueString != null) { + try { + SettingsDescription setting = GlobalSettings.SETTINGS.get(key); + Object value = setting.fromString(valueString); + String outputValue = setting.toPrettyString(value); + writeKeyValue(serializer, key, outputValue); + } catch (InvalidSettingValueException e) { + Log.w(K9.LOG_TAG, "Global setting \"" + key + "\" has invalid value \"" + + valueString + "\" in preference storage. This shouldn't happen!"); + } + } else { + if (K9.DEBUG) { + Log.d(K9.LOG_TAG, "Couldn't find key \"" + key + "\" in preference storage." + + "Using default value."); + } - try { SettingsDescription setting = GlobalSettings.SETTINGS.get(key); - Object value = setting.fromString(valueString); + Object value = setting.getDefaultValue(); String outputValue = setting.toPrettyString(value); writeKeyValue(serializer, key, outputValue); - } catch (InvalidSettingValueException e) { - Log.w(K9.LOG_TAG, "Global setting \"" + key + "\" has invalid value \"" + - valueString + "\" in preference storage. This shouldn't happen!"); } } }