2011-02-26 12:31:56 -05:00
|
|
|
package com.fsck.k9.preferences;
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
import java.io.IOException;
|
2011-02-26 12:31:56 -05:00
|
|
|
import java.io.InputStream;
|
2011-04-12 21:37:44 -04:00
|
|
|
import java.io.InputStreamReader;
|
|
|
|
import java.util.ArrayList;
|
2011-06-08 23:50:43 -04:00
|
|
|
import java.util.Collections;
|
2011-02-26 12:31:56 -05:00
|
|
|
import java.util.HashMap;
|
2011-03-30 15:00:34 -04:00
|
|
|
import java.util.List;
|
2011-02-26 12:31:56 -05:00
|
|
|
import java.util.Map;
|
2011-03-30 15:00:34 -04:00
|
|
|
import java.util.UUID;
|
2011-02-26 12:31:56 -05:00
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
import org.xmlpull.v1.XmlPullParser;
|
|
|
|
import org.xmlpull.v1.XmlPullParserException;
|
|
|
|
import org.xmlpull.v1.XmlPullParserFactory;
|
2011-02-26 12:31:56 -05:00
|
|
|
|
2011-03-30 15:00:34 -04:00
|
|
|
import android.content.Context;
|
2011-02-26 12:31:56 -05:00
|
|
|
import android.content.SharedPreferences;
|
|
|
|
import android.util.Log;
|
|
|
|
|
2011-03-30 15:00:34 -04:00
|
|
|
import com.fsck.k9.Account;
|
2011-04-12 21:37:44 -04:00
|
|
|
import com.fsck.k9.Identity;
|
2011-02-26 12:31:56 -05:00
|
|
|
import com.fsck.k9.K9;
|
|
|
|
import com.fsck.k9.Preferences;
|
2011-02-26 20:28:47 -05:00
|
|
|
import com.fsck.k9.helper.DateFormatter;
|
2011-04-12 21:37:44 -04:00
|
|
|
import com.fsck.k9.helper.Utility;
|
2011-06-08 23:50:43 -04:00
|
|
|
import com.fsck.k9.mail.ConnectionSecurity;
|
|
|
|
import com.fsck.k9.mail.ServerSettings;
|
|
|
|
import com.fsck.k9.mail.Store;
|
|
|
|
import com.fsck.k9.mail.Transport;
|
2011-10-16 13:24:31 -04:00
|
|
|
import com.fsck.k9.mail.store.WebDavStore;
|
2011-10-08 11:58:57 -04:00
|
|
|
import com.fsck.k9.preferences.Settings.InvalidSettingValueException;
|
2011-02-26 12:31:56 -05:00
|
|
|
|
2011-10-13 23:58:55 -04:00
|
|
|
public class SettingsImporter {
|
2011-03-03 11:14:19 -05:00
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
/**
|
|
|
|
* Class to list the contents of an import file/stream.
|
|
|
|
*
|
2011-10-13 23:58:55 -04:00
|
|
|
* @see SettingsImporter#getImportStreamContents(InputStream)
|
2011-04-12 21:37:44 -04:00
|
|
|
*/
|
|
|
|
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<AccountDescription> accounts;
|
|
|
|
|
|
|
|
private ImportContents(boolean globalSettings, List<AccountDescription> 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-16 22:34:26 -04:00
|
|
|
public static class AccountDescriptionPair {
|
2011-04-28 22:29:16 -04:00
|
|
|
public final AccountDescription original;
|
|
|
|
public final AccountDescription imported;
|
2011-11-06 19:42:08 -05:00
|
|
|
public final boolean overwritten;
|
2011-04-28 22:29:16 -04:00
|
|
|
|
2011-11-06 19:42:08 -05:00
|
|
|
private AccountDescriptionPair(AccountDescription original, AccountDescription imported,
|
|
|
|
boolean overwritten) {
|
2011-04-28 22:29:16 -04:00
|
|
|
this.original = original;
|
|
|
|
this.imported = imported;
|
2011-11-06 19:42:08 -05:00
|
|
|
this.overwritten = overwritten;
|
2011-04-28 22:29:16 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class ImportResults {
|
|
|
|
public final boolean globalSettings;
|
|
|
|
public final List<AccountDescriptionPair> importedAccounts;
|
|
|
|
public final List<AccountDescription> errorneousAccounts;
|
|
|
|
|
|
|
|
private ImportResults(boolean globalSettings,
|
|
|
|
List<AccountDescriptionPair> importedAccounts,
|
|
|
|
List<AccountDescription> errorneousAccounts) {
|
|
|
|
this.globalSettings = globalSettings;
|
|
|
|
this.importedAccounts = importedAccounts;
|
|
|
|
this.errorneousAccounts = errorneousAccounts;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
/**
|
|
|
|
* 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 inputStream
|
2011-10-13 23:54:23 -04:00
|
|
|
* An {@code InputStream} to read the settings from.
|
|
|
|
*
|
|
|
|
* @return An {@link ImportContents} instance containing information about the contents of the
|
|
|
|
* settings file.
|
|
|
|
*
|
2011-10-14 00:00:10 -04:00
|
|
|
* @throws SettingsImportExportException
|
2011-10-13 23:54:23 -04:00
|
|
|
* In case of an error.
|
2011-04-12 21:37:44 -04:00
|
|
|
*/
|
2011-10-13 23:54:23 -04:00
|
|
|
public static ImportContents getImportStreamContents(InputStream inputStream)
|
2011-10-14 00:00:10 -04:00
|
|
|
throws SettingsImportExportException {
|
2011-03-30 15:00:34 -04:00
|
|
|
|
2011-03-03 11:14:19 -05:00
|
|
|
try {
|
2011-04-12 21:37:44 -04:00
|
|
|
// Parse the import stream but don't save individual settings (overview=true)
|
2011-10-13 23:54:23 -04:00
|
|
|
Imported imported = parseSettings(inputStream, false, null, true);
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
// If the stream contains global settings the "globalSettings" member will not be null
|
|
|
|
boolean globalSettings = (imported.globalSettings != null);
|
|
|
|
|
|
|
|
final List<AccountDescription> accounts = new ArrayList<AccountDescription>();
|
|
|
|
// 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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-19 17:04:43 -04:00
|
|
|
//TODO: throw exception if neither global settings nor account settings could be found
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
return new ImportContents(globalSettings, accounts);
|
2011-02-26 19:39:06 -05:00
|
|
|
|
2011-10-14 00:00:10 -04:00
|
|
|
} catch (SettingsImportExportException e) {
|
2011-04-12 21:37:44 -04:00
|
|
|
throw e;
|
|
|
|
} catch (Exception e) {
|
2011-10-14 00:00:10 -04:00
|
|
|
throw new SettingsImportExportException(e);
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
}
|
2011-02-26 19:39:06 -05:00
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
/**
|
|
|
|
* Reads an import {@link InputStream} and imports the global settings and/or account
|
|
|
|
* configurations specified by the arguments.
|
|
|
|
*
|
|
|
|
* @param context
|
2011-10-13 23:54:23 -04:00
|
|
|
* A {@link Context} instance.
|
2011-04-12 21:37:44 -04:00
|
|
|
* @param inputStream
|
2011-10-13 23:54:23 -04:00
|
|
|
* The {@code InputStream} to read the settings from.
|
2011-04-12 21:37:44 -04:00
|
|
|
* @param globalSettings
|
2011-10-13 23:54:23 -04:00
|
|
|
* {@code true} if global settings should be imported from the file.
|
2011-04-12 21:37:44 -04:00
|
|
|
* @param accountUuids
|
2011-10-13 23:54:23 -04:00
|
|
|
* A list of UUIDs of the accounts that should be imported.
|
2011-04-12 21:37:44 -04:00
|
|
|
* @param overwrite
|
2011-10-13 23:54:23 -04:00
|
|
|
* {@code true} if existing accounts should be overwritten when an account with the
|
|
|
|
* same UUID is found in the settings file.<br>
|
|
|
|
* <strong>Note:</strong> 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.
|
|
|
|
*
|
2011-10-14 00:00:10 -04:00
|
|
|
* @throws SettingsImportExportException
|
2011-10-13 23:54:23 -04:00
|
|
|
* In case of an error.
|
2011-04-12 21:37:44 -04:00
|
|
|
*/
|
2011-10-13 23:38:27 -04:00
|
|
|
public static ImportResults importSettings(Context context, InputStream inputStream,
|
2011-04-19 17:04:43 -04:00
|
|
|
boolean globalSettings, List<String> accountUuids, boolean overwrite)
|
2011-10-14 00:00:10 -04:00
|
|
|
throws SettingsImportExportException {
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2011-04-28 22:29:16 -04:00
|
|
|
boolean globalSettingsImported = false;
|
|
|
|
List<AccountDescriptionPair> importedAccounts = new ArrayList<AccountDescriptionPair>();
|
|
|
|
List<AccountDescription> errorneousAccounts = new ArrayList<AccountDescription>();
|
|
|
|
|
2011-10-13 23:54:23 -04:00
|
|
|
Imported imported = parseSettings(inputStream, globalSettings, accountUuids, false);
|
2011-02-26 19:39:06 -05:00
|
|
|
|
2011-03-30 15:00:34 -04:00
|
|
|
Preferences preferences = Preferences.getPreferences(context);
|
|
|
|
SharedPreferences storage = preferences.getPreferences();
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
if (globalSettings) {
|
2011-04-28 22:29:16 -04:00
|
|
|
try {
|
|
|
|
SharedPreferences.Editor editor = storage.edit();
|
|
|
|
if (imported.globalSettings != null) {
|
2011-12-10 23:16:22 -05:00
|
|
|
importGlobalSettings(storage, editor, imported.contentVersion,
|
|
|
|
imported.globalSettings);
|
2011-04-28 22:29:16 -04:00
|
|
|
} else {
|
|
|
|
Log.w(K9.LOG_TAG, "Was asked to import global settings but none found.");
|
|
|
|
}
|
|
|
|
if (editor.commit()) {
|
2011-10-08 18:04:00 -04:00
|
|
|
if (K9.DEBUG) {
|
|
|
|
Log.v(K9.LOG_TAG, "Committed global settings to the preference " +
|
|
|
|
"storage.");
|
|
|
|
}
|
2011-04-28 22:29:16 -04:00
|
|
|
globalSettingsImported = true;
|
2011-10-08 18:04:00 -04:00
|
|
|
} else {
|
|
|
|
if (K9.DEBUG) {
|
|
|
|
Log.v(K9.LOG_TAG, "Failed to commit global settings to the " +
|
|
|
|
"preference storage");
|
|
|
|
}
|
2011-04-28 22:29:16 -04:00
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
|
|
Log.e(K9.LOG_TAG, "Exception while importing global settings", e);
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (accountUuids != null && accountUuids.size() > 0) {
|
|
|
|
if (imported.accounts != null) {
|
|
|
|
for (String accountUuid : accountUuids) {
|
|
|
|
if (imported.accounts.containsKey(accountUuid)) {
|
2011-04-28 22:29:16 -04:00
|
|
|
ImportedAccount account = imported.accounts.get(accountUuid);
|
|
|
|
try {
|
|
|
|
SharedPreferences.Editor editor = storage.edit();
|
|
|
|
|
|
|
|
AccountDescriptionPair importResult = importAccount(context,
|
2011-12-10 23:16:22 -05:00
|
|
|
editor, imported.contentVersion, account, overwrite);
|
2011-04-28 22:29:16 -04:00
|
|
|
|
|
|
|
if (editor.commit()) {
|
2011-10-08 18:04:00 -04:00
|
|
|
if (K9.DEBUG) {
|
|
|
|
Log.v(K9.LOG_TAG, "Committed settings for account \"" +
|
|
|
|
importResult.imported.name +
|
|
|
|
"\" to the settings database.");
|
|
|
|
}
|
2012-01-20 21:10:40 -05:00
|
|
|
|
|
|
|
// Add UUID of the account we just imported to the list of
|
|
|
|
// account UUIDs
|
|
|
|
if (!importResult.overwritten) {
|
|
|
|
editor = storage.edit();
|
|
|
|
|
|
|
|
String newUuid = importResult.imported.uuid;
|
|
|
|
String oldAccountUuids = storage.getString("accountUuids", "");
|
|
|
|
String newAccountUuids = (oldAccountUuids.length() > 0) ?
|
|
|
|
oldAccountUuids + "," + newUuid : newUuid;
|
|
|
|
|
|
|
|
putString(editor, "accountUuids", newAccountUuids);
|
|
|
|
|
|
|
|
if (!editor.commit()) {
|
|
|
|
throw new SettingsImportExportException("Failed to set account UUID list");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reload accounts
|
|
|
|
preferences.loadAccounts();
|
|
|
|
|
2011-04-28 22:29:16 -04:00
|
|
|
importedAccounts.add(importResult);
|
|
|
|
} else {
|
2011-10-08 18:04:00 -04:00
|
|
|
if (K9.DEBUG) {
|
|
|
|
Log.w(K9.LOG_TAG, "Error while committing settings for " +
|
|
|
|
"account \"" + importResult.original.name +
|
|
|
|
"\" to the settings database.");
|
|
|
|
}
|
2011-04-28 22:29:16 -04:00
|
|
|
errorneousAccounts.add(importResult.original);
|
|
|
|
}
|
2011-10-08 18:04:00 -04:00
|
|
|
} 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));
|
2011-04-28 22:29:16 -04:00
|
|
|
} catch (Exception e) {
|
2011-10-08 18:04:00 -04:00
|
|
|
Log.e(K9.LOG_TAG, "Exception while importing account \"" +
|
|
|
|
account.name + "\"", e);
|
2011-04-28 22:29:16 -04:00
|
|
|
errorneousAccounts.add(new AccountDescription(account.name, account.uuid));
|
2011-03-20 12:52:13 -04:00
|
|
|
}
|
2011-04-12 21:37:44 -04:00
|
|
|
} else {
|
|
|
|
Log.w(K9.LOG_TAG, "Was asked to import account with UUID " +
|
|
|
|
accountUuid + ". But this account wasn't found.");
|
|
|
|
}
|
|
|
|
}
|
2011-04-19 17:04:43 -04:00
|
|
|
|
2011-04-28 22:29:16 -04:00
|
|
|
SharedPreferences.Editor editor = storage.edit();
|
|
|
|
|
2011-04-19 17:04:43 -04:00
|
|
|
String defaultAccountUuid = storage.getString("defaultAccountUuid", null);
|
|
|
|
if (defaultAccountUuid == null) {
|
2011-10-08 18:04:00 -04:00
|
|
|
putString(editor, "defaultAccountUuid", accountUuids.get(0));
|
2011-04-19 17:04:43 -04:00
|
|
|
}
|
|
|
|
|
2011-04-28 22:29:16 -04:00
|
|
|
if (!editor.commit()) {
|
2011-10-14 00:00:10 -04:00
|
|
|
throw new SettingsImportExportException("Failed to set default account");
|
2011-04-28 22:29:16 -04:00
|
|
|
}
|
2011-04-12 21:37:44 -04:00
|
|
|
} else {
|
|
|
|
Log.w(K9.LOG_TAG, "Was asked to import at least one account but none found.");
|
2011-03-30 15:00:34 -04:00
|
|
|
}
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
2011-03-30 15:00:34 -04:00
|
|
|
|
2011-04-12 21:44:43 -04:00
|
|
|
preferences.loadAccounts();
|
2011-03-30 15:00:34 -04:00
|
|
|
DateFormatter.clearChosenFormat();
|
2011-04-12 21:37:44 -04:00
|
|
|
K9.loadPrefs(preferences);
|
2011-03-30 15:00:34 -04:00
|
|
|
K9.setServicesEnabled(context);
|
|
|
|
|
2011-04-28 22:29:16 -04:00
|
|
|
return new ImportResults(globalSettingsImported, importedAccounts, errorneousAccounts);
|
|
|
|
|
2011-10-14 00:00:10 -04:00
|
|
|
} catch (SettingsImportExportException e) {
|
2011-04-12 21:37:44 -04:00
|
|
|
throw e;
|
|
|
|
} catch (Exception e) {
|
2011-10-14 00:00:10 -04:00
|
|
|
throw new SettingsImportExportException(e);
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-01 22:06:22 -04:00
|
|
|
private static void importGlobalSettings(SharedPreferences storage,
|
2011-12-10 23:16:22 -05:00
|
|
|
SharedPreferences.Editor editor, int contentVersion, ImportedSettings settings) {
|
2011-04-12 21:37:44 -04:00
|
|
|
|
2011-12-10 22:57:47 -05:00
|
|
|
// Validate global settings
|
2011-12-11 00:02:31 -05:00
|
|
|
Map<String, Object> validatedSettings = GlobalSettings.validate(contentVersion,
|
2011-12-10 23:16:22 -05:00
|
|
|
settings.settings);
|
2011-04-20 15:37:48 -04:00
|
|
|
|
2011-12-10 22:57:47 -05:00
|
|
|
// Upgrade global settings to current content version
|
|
|
|
if (contentVersion != Settings.VERSION) {
|
|
|
|
GlobalSettings.upgrade(contentVersion, validatedSettings);
|
|
|
|
}
|
|
|
|
|
2011-12-11 00:02:31 -05:00
|
|
|
// Convert global settings to the string representation used in preference storage
|
|
|
|
Map<String, String> stringSettings = GlobalSettings.convert(validatedSettings);
|
|
|
|
|
2011-05-01 22:06:22 -04:00
|
|
|
// Use current global settings as base and overwrite with validated settings read from the
|
|
|
|
// import file.
|
|
|
|
Map<String, String> mergedSettings =
|
|
|
|
new HashMap<String, String>(GlobalSettings.getGlobalSettings(storage));
|
2011-12-11 00:02:31 -05:00
|
|
|
mergedSettings.putAll(stringSettings);
|
2011-05-01 22:06:22 -04:00
|
|
|
|
|
|
|
for (Map.Entry<String, String> setting : mergedSettings.entrySet()) {
|
2011-04-12 21:37:44 -04:00
|
|
|
String key = setting.getKey();
|
|
|
|
String value = setting.getValue();
|
2011-10-08 18:04:00 -04:00
|
|
|
putString(editor, key, value);
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-28 22:29:16 -04:00
|
|
|
private static AccountDescriptionPair importAccount(Context context,
|
2011-12-10 23:16:22 -05:00
|
|
|
SharedPreferences.Editor editor, int contentVersion, ImportedAccount account,
|
|
|
|
boolean overwrite) throws InvalidSettingValueException {
|
2011-04-28 22:29:16 -04:00
|
|
|
|
|
|
|
AccountDescription original = new AccountDescription(account.name, account.uuid);
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
Preferences prefs = Preferences.getPreferences(context);
|
|
|
|
Account[] accounts = prefs.getAccounts();
|
|
|
|
|
|
|
|
String uuid = account.uuid;
|
|
|
|
Account existingAccount = prefs.getAccount(uuid);
|
2011-06-06 13:44:01 -04:00
|
|
|
boolean mergeImportedAccount = (overwrite && existingAccount != null);
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2011-10-08 18:31:33 -04:00
|
|
|
// Make sure the account name is unique
|
|
|
|
String accountName = (account.name != null) ? account.name : "Imported";
|
2011-04-12 21:37:44 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-08 18:31:33 -04:00
|
|
|
// Write account name
|
2011-04-12 21:37:44 -04:00
|
|
|
String accountKeyPrefix = uuid + ".";
|
2011-10-08 18:04:00 -04:00
|
|
|
putString(editor, accountKeyPrefix + Account.ACCOUNT_DESCRIPTION_KEY, accountName);
|
2011-04-12 21:37:44 -04:00
|
|
|
|
2011-10-16 13:24:31 -04:00
|
|
|
if (account.incoming == null) {
|
|
|
|
// We don't import accounts without incoming server settings
|
|
|
|
throw new InvalidSettingValueException();
|
|
|
|
}
|
|
|
|
|
2011-06-08 23:50:43 -04:00
|
|
|
// Write incoming server settings (storeUri)
|
|
|
|
ServerSettings incoming = new ImportedServerSettings(account.incoming);
|
|
|
|
String storeUri = Store.createStoreUri(incoming);
|
2011-10-08 18:04:00 -04:00
|
|
|
putString(editor, accountKeyPrefix + Account.STORE_URI_KEY, Utility.base64Encode(storeUri));
|
2011-06-08 23:50:43 -04:00
|
|
|
|
2011-10-16 13:24:31 -04:00
|
|
|
// 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);
|
|
|
|
}
|
2011-06-08 23:50:43 -04:00
|
|
|
|
2011-10-08 18:31:33 -04:00
|
|
|
// Validate account settings
|
2011-12-11 00:02:31 -05:00
|
|
|
Map<String, Object> validatedSettings =
|
2011-12-10 23:16:22 -05:00
|
|
|
AccountSettings.validate(contentVersion, account.settings.settings,
|
|
|
|
!mergeImportedAccount);
|
2011-10-08 18:31:33 -04:00
|
|
|
|
2011-12-10 22:57:47 -05:00
|
|
|
// Upgrade account settings to current content version
|
|
|
|
if (contentVersion != Settings.VERSION) {
|
|
|
|
AccountSettings.upgrade(contentVersion, validatedSettings);
|
|
|
|
}
|
|
|
|
|
2011-12-11 00:02:31 -05:00
|
|
|
// Convert account settings to the string representation used in preference storage
|
|
|
|
Map<String, String> stringSettings = AccountSettings.convert(validatedSettings);
|
|
|
|
|
2011-10-08 18:31:33 -04:00
|
|
|
// Merge account settings if necessary
|
|
|
|
Map<String, String> writeSettings;
|
|
|
|
if (mergeImportedAccount) {
|
|
|
|
writeSettings = new HashMap<String, String>(
|
|
|
|
AccountSettings.getAccountSettings(prefs.getPreferences(), uuid));
|
2011-12-11 00:02:31 -05:00
|
|
|
writeSettings.putAll(stringSettings);
|
2011-10-08 18:31:33 -04:00
|
|
|
} else {
|
2011-12-11 00:02:31 -05:00
|
|
|
writeSettings = stringSettings;
|
2011-10-08 18:31:33 -04:00
|
|
|
}
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
// Write account settings
|
2011-06-06 13:44:01 -04:00
|
|
|
for (Map.Entry<String, String> setting : writeSettings.entrySet()) {
|
2011-04-12 21:37:44 -04:00
|
|
|
String key = accountKeyPrefix + setting.getKey();
|
|
|
|
String value = setting.getValue();
|
2011-10-08 18:04:00 -04:00
|
|
|
putString(editor, key, value);
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// If it's a new account generate and write a new "accountNumber"
|
2011-06-06 13:44:01 -04:00
|
|
|
if (!mergeImportedAccount) {
|
2011-04-12 21:37:44 -04:00
|
|
|
int newAccountNumber = Account.generateAccountNumber(prefs);
|
2011-10-08 18:04:00 -04:00
|
|
|
putString(editor, accountKeyPrefix + "accountNumber", Integer.toString(newAccountNumber));
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
|
2011-10-08 18:31:33 -04:00
|
|
|
// Write identities
|
2011-04-12 21:37:44 -04:00
|
|
|
if (account.identities != null) {
|
2011-12-10 23:16:22 -05:00
|
|
|
importIdentities(editor, contentVersion, uuid, account, overwrite, existingAccount,
|
|
|
|
prefs);
|
2011-10-08 19:23:45 -04:00
|
|
|
} else if (!mergeImportedAccount) {
|
|
|
|
// Require accounts to at least have one identity
|
|
|
|
throw new InvalidSettingValueException();
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Write folder settings
|
|
|
|
if (account.folders != null) {
|
|
|
|
for (ImportedFolder folder : account.folders) {
|
2011-12-10 23:16:22 -05:00
|
|
|
importFolder(editor, contentVersion, uuid, folder, mergeImportedAccount, prefs);
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//TODO: sync folder settings with localstore?
|
|
|
|
|
2011-04-28 22:29:16 -04:00
|
|
|
AccountDescription imported = new AccountDescription(accountName, uuid);
|
2011-11-06 19:42:08 -05:00
|
|
|
return new AccountDescriptionPair(original, imported, mergeImportedAccount);
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
|
2011-12-10 23:16:22 -05:00
|
|
|
private static void importFolder(SharedPreferences.Editor editor, int contentVersion,
|
|
|
|
String uuid, ImportedFolder folder, boolean overwrite, Preferences prefs) {
|
2011-10-08 15:30:45 -04:00
|
|
|
|
|
|
|
// Validate folder settings
|
2011-12-11 00:02:31 -05:00
|
|
|
Map<String, Object> validatedSettings =
|
2011-12-10 23:16:22 -05:00
|
|
|
FolderSettings.validate(contentVersion, folder.settings.settings, !overwrite);
|
2011-10-08 15:30:45 -04:00
|
|
|
|
2011-12-10 22:57:47 -05:00
|
|
|
// Upgrade folder settings to current content version
|
|
|
|
if (contentVersion != Settings.VERSION) {
|
|
|
|
FolderSettings.upgrade(contentVersion, validatedSettings);
|
|
|
|
}
|
|
|
|
|
2011-12-11 00:02:31 -05:00
|
|
|
// Convert folder settings to the string representation used in preference storage
|
|
|
|
Map<String, String> stringSettings = FolderSettings.convert(validatedSettings);
|
|
|
|
|
2011-10-08 15:30:45 -04:00
|
|
|
// Merge folder settings if necessary
|
2011-10-08 18:31:33 -04:00
|
|
|
Map<String, String> writeSettings;
|
2011-10-08 15:30:45 -04:00
|
|
|
if (overwrite) {
|
2011-10-08 18:31:33 -04:00
|
|
|
writeSettings = FolderSettings.getFolderSettings(prefs.getPreferences(),
|
2011-10-08 15:30:45 -04:00
|
|
|
uuid, folder.name);
|
2011-12-11 00:02:31 -05:00
|
|
|
writeSettings.putAll(stringSettings);
|
2011-10-08 15:30:45 -04:00
|
|
|
} else {
|
2011-12-11 00:02:31 -05:00
|
|
|
writeSettings = stringSettings;
|
2011-10-08 15:30:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Write folder settings
|
2011-10-08 18:31:33 -04:00
|
|
|
String prefix = uuid + "." + folder.name + ".";
|
|
|
|
for (Map.Entry<String, String> setting : writeSettings.entrySet()) {
|
|
|
|
String key = prefix + setting.getKey();
|
2011-10-08 15:30:45 -04:00
|
|
|
String value = setting.getValue();
|
2011-10-08 18:04:00 -04:00
|
|
|
putString(editor, key, value);
|
2011-10-08 15:30:45 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-10 23:16:22 -05:00
|
|
|
private static void importIdentities(SharedPreferences.Editor editor, int contentVersion,
|
|
|
|
String uuid, ImportedAccount account, boolean overwrite, Account existingAccount,
|
2011-10-08 11:58:57 -04:00
|
|
|
Preferences prefs) throws InvalidSettingValueException {
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
String accountKeyPrefix = uuid + ".";
|
|
|
|
|
|
|
|
// Gather information about existing identities for this account (if any)
|
|
|
|
int nextIdentityIndex = 0;
|
|
|
|
final List<Identity> existingIdentities;
|
|
|
|
if (overwrite && existingAccount != null) {
|
|
|
|
existingIdentities = existingAccount.getIdentities();
|
|
|
|
nextIdentityIndex = existingIdentities.size();
|
|
|
|
} else {
|
|
|
|
existingIdentities = new ArrayList<Identity>();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write identities
|
|
|
|
for (ImportedIdentity identity : account.identities) {
|
|
|
|
int writeIdentityIndex = nextIdentityIndex;
|
2011-10-08 11:58:57 -04:00
|
|
|
boolean mergeSettings = false;
|
|
|
|
if (overwrite && existingIdentities.size() > 0) {
|
2011-04-12 21:37:44 -04:00
|
|
|
int identityIndex = findIdentity(identity, existingIdentities);
|
2011-10-08 11:58:57 -04:00
|
|
|
if (identityIndex != -1) {
|
2011-04-12 21:37:44 -04:00
|
|
|
writeIdentityIndex = identityIndex;
|
2011-10-08 11:58:57 -04:00
|
|
|
mergeSettings = true;
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
}
|
2011-10-08 11:58:57 -04:00
|
|
|
if (!mergeSettings) {
|
2011-04-12 21:37:44 -04:00
|
|
|
nextIdentityIndex++;
|
|
|
|
}
|
|
|
|
|
2011-10-08 11:58:57 -04:00
|
|
|
String identityDescription = (identity.description == null) ?
|
|
|
|
"Imported" : identity.description;
|
2011-04-12 21:37:44 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-08 11:58:57 -04:00
|
|
|
String identitySuffix = "." + writeIdentityIndex;
|
|
|
|
|
|
|
|
// Write name used in identity
|
|
|
|
String identityName = (identity.name == null) ? "" : identity.name;
|
2011-10-08 18:04:00 -04:00
|
|
|
putString(editor, accountKeyPrefix + Account.IDENTITY_NAME_KEY + identitySuffix,
|
2011-10-08 11:58:57 -04:00
|
|
|
identityName);
|
|
|
|
|
|
|
|
// Validate email address
|
|
|
|
if (!IdentitySettings.isEmailAddressValid(identity.email)) {
|
|
|
|
throw new InvalidSettingValueException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write email address
|
2011-10-08 18:04:00 -04:00
|
|
|
putString(editor, accountKeyPrefix + Account.IDENTITY_EMAIL_KEY + identitySuffix,
|
2011-10-08 11:58:57 -04:00
|
|
|
identity.email);
|
|
|
|
|
|
|
|
// Write identity description
|
2011-10-08 18:04:00 -04:00
|
|
|
putString(editor, accountKeyPrefix + Account.IDENTITY_DESCRIPTION_KEY + identitySuffix,
|
2011-10-08 11:58:57 -04:00
|
|
|
identityDescription);
|
|
|
|
|
2011-10-08 15:30:45 -04:00
|
|
|
if (identity.settings != null) {
|
|
|
|
// Validate identity settings
|
2011-12-11 00:02:31 -05:00
|
|
|
Map<String, Object> validatedSettings = IdentitySettings.validate(
|
2011-12-10 23:16:22 -05:00
|
|
|
contentVersion, identity.settings.settings, !mergeSettings);
|
2011-10-08 15:30:45 -04:00
|
|
|
|
2011-12-10 22:57:47 -05:00
|
|
|
// Upgrade identity settings to current content version
|
|
|
|
if (contentVersion != Settings.VERSION) {
|
|
|
|
IdentitySettings.upgrade(contentVersion, validatedSettings);
|
|
|
|
}
|
|
|
|
|
2011-12-11 00:02:31 -05:00
|
|
|
// Convert identity settings to the representation used in preference storage
|
|
|
|
Map<String, String> stringSettings = IdentitySettings.convert(validatedSettings);
|
|
|
|
|
2011-10-08 15:30:45 -04:00
|
|
|
// Merge identity settings if necessary
|
|
|
|
Map<String, String> writeSettings;
|
|
|
|
if (mergeSettings) {
|
|
|
|
writeSettings = new HashMap<String, String>(IdentitySettings.getIdentitySettings(
|
|
|
|
prefs.getPreferences(), uuid, writeIdentityIndex));
|
2011-12-11 00:02:31 -05:00
|
|
|
writeSettings.putAll(stringSettings);
|
2011-10-08 15:30:45 -04:00
|
|
|
} else {
|
2011-12-11 00:02:31 -05:00
|
|
|
writeSettings = stringSettings;
|
2011-10-08 15:30:45 -04:00
|
|
|
}
|
2011-04-12 21:37:44 -04:00
|
|
|
|
2011-10-08 15:30:45 -04:00
|
|
|
// Write identity settings
|
|
|
|
for (Map.Entry<String, String> setting : writeSettings.entrySet()) {
|
|
|
|
String key = accountKeyPrefix + setting.getKey() + identitySuffix;
|
|
|
|
String value = setting.getValue();
|
2011-10-08 18:04:00 -04:00
|
|
|
putString(editor, key, value);
|
2011-10-08 15:30:45 -04:00
|
|
|
}
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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<Identity> identities) {
|
|
|
|
for (Identity identitiy : identities) {
|
|
|
|
if (identitiy.getDescription().equals(description)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static int findIdentity(ImportedIdentity identity,
|
|
|
|
List<Identity> 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;
|
|
|
|
}
|
|
|
|
|
2011-10-08 18:04:00 -04:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
private static Imported parseSettings(InputStream inputStream, boolean globalSettings,
|
2011-10-13 23:54:23 -04:00
|
|
|
List<String> accountUuids, boolean overview)
|
2011-10-14 00:00:10 -04:00
|
|
|
throws SettingsImportExportException {
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
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) {
|
2011-10-13 23:58:15 -04:00
|
|
|
if (SettingsExporter.ROOT_ELEMENT.equals(xpp.getName())) {
|
2011-04-12 21:37:44 -04:00
|
|
|
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)) {
|
2011-10-14 00:00:10 -04:00
|
|
|
throw new SettingsImportExportException("Invalid import data");
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return imported;
|
2011-03-30 15:00:34 -04:00
|
|
|
} catch (Exception e) {
|
2011-10-14 00:00:10 -04:00
|
|
|
throw new SettingsImportExportException(e);
|
2011-03-30 15:00:34 -04:00
|
|
|
}
|
|
|
|
}
|
2011-03-20 16:21:24 -04:00
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
private static void skipToEndTag(XmlPullParser xpp, String endTag)
|
|
|
|
throws XmlPullParserException, IOException {
|
2011-02-26 19:39:06 -05:00
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
int eventType = xpp.next();
|
|
|
|
while (!(eventType == XmlPullParser.END_TAG && endTag.equals(xpp.getName()))) {
|
|
|
|
eventType = xpp.next();
|
|
|
|
}
|
2011-02-26 12:31:56 -05:00
|
|
|
}
|
2011-02-26 19:39:06 -05:00
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
private static String getText(XmlPullParser xpp)
|
|
|
|
throws XmlPullParserException, IOException {
|
2011-02-26 12:31:56 -05:00
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
int eventType = xpp.next();
|
|
|
|
if (eventType != XmlPullParser.TEXT) {
|
2011-04-20 15:37:48 -04:00
|
|
|
return "";
|
2011-02-26 12:31:56 -05:00
|
|
|
}
|
2011-04-12 21:37:44 -04:00
|
|
|
return xpp.getText();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Imported parseRoot(XmlPullParser xpp, boolean globalSettings,
|
2011-04-19 17:04:43 -04:00
|
|
|
List<String> accountUuids, boolean overview)
|
2011-10-17 19:30:41 -04:00
|
|
|
throws XmlPullParserException, IOException, SettingsImportExportException {
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
Imported result = new Imported();
|
2011-02-26 12:31:56 -05:00
|
|
|
|
2011-10-17 19:30:41 -04:00
|
|
|
String fileFormatVersionString = xpp.getAttributeValue(null,
|
|
|
|
SettingsExporter.FILE_FORMAT_ATTRIBUTE);
|
|
|
|
validateFileFormatVersion(fileFormatVersionString);
|
|
|
|
|
|
|
|
String contentVersionString = xpp.getAttributeValue(null,
|
|
|
|
SettingsExporter.VERSION_ATTRIBUTE);
|
2011-12-10 23:16:22 -05:00
|
|
|
result.contentVersion = validateContentVersion(contentVersionString);
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
int eventType = xpp.next();
|
|
|
|
while (!(eventType == XmlPullParser.END_TAG &&
|
2011-10-13 23:58:15 -04:00
|
|
|
SettingsExporter.ROOT_ELEMENT.equals(xpp.getName()))) {
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
if(eventType == XmlPullParser.START_TAG) {
|
|
|
|
String element = xpp.getName();
|
2011-10-13 23:58:15 -04:00
|
|
|
if (SettingsExporter.GLOBAL_ELEMENT.equals(element)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
if (overview || globalSettings) {
|
|
|
|
if (result.globalSettings == null) {
|
|
|
|
if (overview) {
|
|
|
|
result.globalSettings = new ImportedSettings();
|
2011-10-13 23:58:15 -04:00
|
|
|
skipToEndTag(xpp, SettingsExporter.GLOBAL_ELEMENT);
|
2011-04-12 21:37:44 -04:00
|
|
|
} else {
|
2011-10-13 23:58:15 -04:00
|
|
|
result.globalSettings = parseSettings(xpp, SettingsExporter.GLOBAL_ELEMENT);
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
|
|
|
} else {
|
2011-10-13 23:58:15 -04:00
|
|
|
skipToEndTag(xpp, SettingsExporter.GLOBAL_ELEMENT);
|
2011-04-12 21:37:44 -04:00
|
|
|
Log.w(K9.LOG_TAG, "More than one global settings element. Only using the first one!");
|
|
|
|
}
|
|
|
|
} else {
|
2011-10-13 23:58:15 -04:00
|
|
|
skipToEndTag(xpp, SettingsExporter.GLOBAL_ELEMENT);
|
2011-04-12 21:37:44 -04:00
|
|
|
Log.i(K9.LOG_TAG, "Skipping global settings");
|
|
|
|
}
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.ACCOUNTS_ELEMENT.equals(element)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-10-17 19:30:41 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2011-12-10 23:16:22 -05:00
|
|
|
if (version < 1 || version > Settings.VERSION) {
|
2011-10-17 19:30:41 -04:00
|
|
|
throw new SettingsImportExportException("Unsupported content version: " +
|
|
|
|
versionString);
|
|
|
|
}
|
|
|
|
|
|
|
|
return version;
|
|
|
|
}
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
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();
|
2011-10-13 23:58:15 -04:00
|
|
|
if (SettingsExporter.VALUE_ELEMENT.equals(element)) {
|
|
|
|
String key = xpp.getAttributeValue(null, SettingsExporter.KEY_ATTRIBUTE);
|
2011-04-12 21:37:44 -04:00
|
|
|
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<String, ImportedAccount> parseAccounts(XmlPullParser xpp,
|
2011-04-19 17:04:43 -04:00
|
|
|
List<String> accountUuids, boolean overview)
|
2011-04-12 21:37:44 -04:00
|
|
|
throws XmlPullParserException, IOException {
|
|
|
|
|
|
|
|
Map<String, ImportedAccount> accounts = null;
|
|
|
|
|
|
|
|
int eventType = xpp.next();
|
|
|
|
while (!(eventType == XmlPullParser.END_TAG &&
|
2011-10-13 23:58:15 -04:00
|
|
|
SettingsExporter.ACCOUNTS_ELEMENT.equals(xpp.getName()))) {
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
if(eventType == XmlPullParser.START_TAG) {
|
|
|
|
String element = xpp.getName();
|
2011-10-13 23:58:15 -04:00
|
|
|
if (SettingsExporter.ACCOUNT_ELEMENT.equals(element)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
if (accounts == null) {
|
|
|
|
accounts = new HashMap<String, ImportedAccount>();
|
|
|
|
}
|
|
|
|
|
|
|
|
ImportedAccount account = parseAccount(xpp, accountUuids, overview);
|
|
|
|
|
2011-10-17 17:33:32 -04:00
|
|
|
if (account == null) {
|
|
|
|
// Do nothing - parseAccount() already logged a message
|
|
|
|
} else if (!accounts.containsKey(account.uuid)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
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();
|
2011-02-26 12:31:56 -05:00
|
|
|
}
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
return accounts;
|
|
|
|
}
|
|
|
|
|
2011-04-19 17:04:43 -04:00
|
|
|
private static ImportedAccount parseAccount(XmlPullParser xpp, List<String> accountUuids,
|
2011-04-12 21:37:44 -04:00
|
|
|
boolean overview)
|
|
|
|
throws XmlPullParserException, IOException {
|
|
|
|
|
2011-10-13 23:58:15 -04:00
|
|
|
String uuid = xpp.getAttributeValue(null, SettingsExporter.UUID_ATTRIBUTE);
|
2011-10-17 17:33:32 -04:00
|
|
|
|
|
|
|
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();
|
2011-04-12 21:37:44 -04:00
|
|
|
account.uuid = uuid;
|
|
|
|
|
|
|
|
if (overview || accountUuids.contains(uuid)) {
|
|
|
|
int eventType = xpp.next();
|
|
|
|
while (!(eventType == XmlPullParser.END_TAG &&
|
2011-10-13 23:58:15 -04:00
|
|
|
SettingsExporter.ACCOUNT_ELEMENT.equals(xpp.getName()))) {
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
if(eventType == XmlPullParser.START_TAG) {
|
|
|
|
String element = xpp.getName();
|
2011-10-13 23:58:15 -04:00
|
|
|
if (SettingsExporter.NAME_ELEMENT.equals(element)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
account.name = getText(xpp);
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.INCOMING_SERVER_ELEMENT.equals(element)) {
|
2011-06-08 23:50:43 -04:00
|
|
|
if (overview) {
|
2011-10-13 23:58:15 -04:00
|
|
|
skipToEndTag(xpp, SettingsExporter.INCOMING_SERVER_ELEMENT);
|
2011-06-08 23:50:43 -04:00
|
|
|
} else {
|
2011-10-13 23:58:15 -04:00
|
|
|
account.incoming = parseServerSettings(xpp, SettingsExporter.INCOMING_SERVER_ELEMENT);
|
2011-06-08 23:50:43 -04:00
|
|
|
}
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.OUTGOING_SERVER_ELEMENT.equals(element)) {
|
2011-06-08 23:50:43 -04:00
|
|
|
if (overview) {
|
2011-10-13 23:58:15 -04:00
|
|
|
skipToEndTag(xpp, SettingsExporter.OUTGOING_SERVER_ELEMENT);
|
2011-06-08 23:50:43 -04:00
|
|
|
} else {
|
2011-10-13 23:58:15 -04:00
|
|
|
account.outgoing = parseServerSettings(xpp, SettingsExporter.OUTGOING_SERVER_ELEMENT);
|
2011-06-08 23:50:43 -04:00
|
|
|
}
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.SETTINGS_ELEMENT.equals(element)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
if (overview) {
|
2011-10-13 23:58:15 -04:00
|
|
|
skipToEndTag(xpp, SettingsExporter.SETTINGS_ELEMENT);
|
2011-04-12 21:37:44 -04:00
|
|
|
} else {
|
2011-10-13 23:58:15 -04:00
|
|
|
account.settings = parseSettings(xpp, SettingsExporter.SETTINGS_ELEMENT);
|
2011-04-12 21:37:44 -04:00
|
|
|
}
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.IDENTITIES_ELEMENT.equals(element)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
if (overview) {
|
2011-10-13 23:58:15 -04:00
|
|
|
skipToEndTag(xpp, SettingsExporter.IDENTITIES_ELEMENT);
|
2011-04-12 21:37:44 -04:00
|
|
|
} else {
|
|
|
|
account.identities = parseIdentities(xpp);
|
|
|
|
}
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.FOLDERS_ELEMENT.equals(element)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
if (overview) {
|
2011-10-13 23:58:15 -04:00
|
|
|
skipToEndTag(xpp, SettingsExporter.FOLDERS_ELEMENT);
|
2011-04-12 21:37:44 -04:00
|
|
|
} else {
|
|
|
|
account.folders = parseFolders(xpp);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
eventType = xpp.next();
|
|
|
|
}
|
|
|
|
} else {
|
2011-10-13 23:58:15 -04:00
|
|
|
skipToEndTag(xpp, SettingsExporter.ACCOUNT_ELEMENT);
|
2011-04-12 21:37:44 -04:00
|
|
|
Log.i(K9.LOG_TAG, "Skipping account with UUID " + uuid);
|
2011-02-26 12:31:56 -05:00
|
|
|
}
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
return account;
|
|
|
|
}
|
|
|
|
|
2011-06-08 23:50:43 -04:00
|
|
|
private static ImportedServer parseServerSettings(XmlPullParser xpp, String endTag)
|
|
|
|
throws XmlPullParserException, IOException {
|
|
|
|
ImportedServer server = new ImportedServer();
|
|
|
|
|
2011-10-13 23:58:15 -04:00
|
|
|
server.type = xpp.getAttributeValue(null, SettingsExporter.TYPE_ATTRIBUTE);
|
2011-06-08 23:50:43 -04:00
|
|
|
|
|
|
|
int eventType = xpp.next();
|
|
|
|
while (!(eventType == XmlPullParser.END_TAG && endTag.equals(xpp.getName()))) {
|
|
|
|
if(eventType == XmlPullParser.START_TAG) {
|
|
|
|
String element = xpp.getName();
|
2011-10-13 23:58:15 -04:00
|
|
|
if (SettingsExporter.HOST_ELEMENT.equals(element)) {
|
2011-06-08 23:50:43 -04:00
|
|
|
server.host = getText(xpp);
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.PORT_ELEMENT.equals(element)) {
|
2011-06-08 23:50:43 -04:00
|
|
|
server.port = getText(xpp);
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.CONNECTION_SECURITY_ELEMENT.equals(element)) {
|
2011-06-08 23:50:43 -04:00
|
|
|
server.connectionSecurity = getText(xpp);
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.AUTHENTICATION_TYPE_ELEMENT.equals(element)) {
|
2011-06-08 23:50:43 -04:00
|
|
|
server.authenticationType = getText(xpp);
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.USERNAME_ELEMENT.equals(element)) {
|
2011-06-08 23:50:43 -04:00
|
|
|
server.username = getText(xpp);
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.PASSWORD_ELEMENT.equals(element)) {
|
2011-06-08 23:50:43 -04:00
|
|
|
server.password = getText(xpp);
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.EXTRA_ELEMENT.equals(element)) {
|
|
|
|
server.extras = parseSettings(xpp, SettingsExporter.EXTRA_ELEMENT);
|
2011-06-08 23:50:43 -04:00
|
|
|
} else {
|
|
|
|
Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
eventType = xpp.next();
|
|
|
|
}
|
|
|
|
|
|
|
|
return server;
|
|
|
|
}
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
private static List<ImportedIdentity> parseIdentities(XmlPullParser xpp)
|
|
|
|
throws XmlPullParserException, IOException {
|
|
|
|
List<ImportedIdentity> identities = null;
|
|
|
|
|
|
|
|
int eventType = xpp.next();
|
|
|
|
while (!(eventType == XmlPullParser.END_TAG &&
|
2011-10-13 23:58:15 -04:00
|
|
|
SettingsExporter.IDENTITIES_ELEMENT.equals(xpp.getName()))) {
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
if(eventType == XmlPullParser.START_TAG) {
|
|
|
|
String element = xpp.getName();
|
2011-10-13 23:58:15 -04:00
|
|
|
if (SettingsExporter.IDENTITY_ELEMENT.equals(element)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
if (identities == null) {
|
|
|
|
identities = new ArrayList<ImportedIdentity>();
|
|
|
|
}
|
|
|
|
|
|
|
|
ImportedIdentity identity = parseIdentity(xpp);
|
|
|
|
identities.add(identity);
|
|
|
|
} else {
|
|
|
|
Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
|
|
|
|
}
|
2011-02-26 12:31:56 -05:00
|
|
|
}
|
2011-04-12 21:37:44 -04:00
|
|
|
eventType = xpp.next();
|
2011-02-26 12:31:56 -05:00
|
|
|
}
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
return identities;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static ImportedIdentity parseIdentity(XmlPullParser xpp)
|
|
|
|
throws XmlPullParserException, IOException {
|
|
|
|
ImportedIdentity identity = new ImportedIdentity();
|
|
|
|
|
|
|
|
int eventType = xpp.next();
|
|
|
|
while (!(eventType == XmlPullParser.END_TAG &&
|
2011-10-13 23:58:15 -04:00
|
|
|
SettingsExporter.IDENTITY_ELEMENT.equals(xpp.getName()))) {
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
if(eventType == XmlPullParser.START_TAG) {
|
|
|
|
String element = xpp.getName();
|
2011-10-13 23:58:15 -04:00
|
|
|
if (SettingsExporter.NAME_ELEMENT.equals(element)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
identity.name = getText(xpp);
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.EMAIL_ELEMENT.equals(element)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
identity.email = getText(xpp);
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.DESCRIPTION_ELEMENT.equals(element)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
identity.description = getText(xpp);
|
2011-10-13 23:58:15 -04:00
|
|
|
} else if (SettingsExporter.SETTINGS_ELEMENT.equals(element)) {
|
|
|
|
identity.settings = parseSettings(xpp, SettingsExporter.SETTINGS_ELEMENT);
|
2011-04-12 21:37:44 -04:00
|
|
|
} else {
|
|
|
|
Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
|
|
|
|
}
|
2011-02-26 12:31:56 -05:00
|
|
|
}
|
2011-04-12 21:37:44 -04:00
|
|
|
eventType = xpp.next();
|
2011-02-26 12:31:56 -05:00
|
|
|
}
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
return identity;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static List<ImportedFolder> parseFolders(XmlPullParser xpp)
|
|
|
|
throws XmlPullParserException, IOException {
|
|
|
|
List<ImportedFolder> folders = null;
|
|
|
|
|
|
|
|
int eventType = xpp.next();
|
|
|
|
while (!(eventType == XmlPullParser.END_TAG &&
|
2011-10-13 23:58:15 -04:00
|
|
|
SettingsExporter.FOLDERS_ELEMENT.equals(xpp.getName()))) {
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
if(eventType == XmlPullParser.START_TAG) {
|
|
|
|
String element = xpp.getName();
|
2011-10-13 23:58:15 -04:00
|
|
|
if (SettingsExporter.FOLDER_ELEMENT.equals(element)) {
|
2011-04-12 21:37:44 -04:00
|
|
|
if (folders == null) {
|
|
|
|
folders = new ArrayList<ImportedFolder>();
|
|
|
|
}
|
|
|
|
|
|
|
|
ImportedFolder folder = parseFolder(xpp);
|
|
|
|
folders.add(folder);
|
|
|
|
} else {
|
|
|
|
Log.w(K9.LOG_TAG, "Unexpected start tag: " + xpp.getName());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
eventType = xpp.next();
|
2011-02-26 12:31:56 -05:00
|
|
|
}
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
return folders;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static ImportedFolder parseFolder(XmlPullParser xpp)
|
|
|
|
throws XmlPullParserException, IOException {
|
|
|
|
ImportedFolder folder = new ImportedFolder();
|
|
|
|
|
2011-10-13 23:58:15 -04:00
|
|
|
String name = xpp.getAttributeValue(null, SettingsExporter.NAME_ATTRIBUTE);
|
2011-04-12 21:37:44 -04:00
|
|
|
folder.name = name;
|
|
|
|
|
2011-10-13 23:58:15 -04:00
|
|
|
folder.settings = parseSettings(xpp, SettingsExporter.FOLDER_ELEMENT);
|
2011-04-12 21:37:44 -04:00
|
|
|
|
|
|
|
return folder;
|
|
|
|
}
|
|
|
|
|
2011-06-08 23:50:43 -04:00
|
|
|
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<String, String> 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
private static class Imported {
|
2011-12-10 23:16:22 -05:00
|
|
|
public int contentVersion;
|
2011-04-12 21:37:44 -04:00
|
|
|
public ImportedSettings globalSettings;
|
|
|
|
public Map<String, ImportedAccount> accounts;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class ImportedSettings {
|
|
|
|
public Map<String, String> settings = new HashMap<String, String>();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class ImportedAccount {
|
|
|
|
public String uuid;
|
|
|
|
public String name;
|
2011-06-08 23:50:43 -04:00
|
|
|
public ImportedServer incoming;
|
|
|
|
public ImportedServer outgoing;
|
2011-04-12 21:37:44 -04:00
|
|
|
public ImportedSettings settings;
|
|
|
|
public List<ImportedIdentity> identities;
|
|
|
|
public List<ImportedFolder> folders;
|
|
|
|
}
|
|
|
|
|
2011-06-08 23:50:43 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-04-12 21:37:44 -04:00
|
|
|
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;
|
2011-02-26 12:31:56 -05:00
|
|
|
}
|
|
|
|
}
|