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