/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.sync.config;
import java.io.FileOutputStream;
import java.io.PrintStream;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Logger;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.setup.Constants;
import org.mozilla.gecko.sync.setup.SyncAccounts;
import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
import android.accounts.Account;
import android.content.Context;
/**
* Bug 768102: Android deletes Account objects when the Authenticator that owns
* the Account disappears. This happens when an App is installed to the SD card
* and the SD card is un-mounted.
*
* Bug 769745: We work around this by pickling the current Sync account data
* every sync and un-pickling when we check if Sync accounts exist (called from
* Fennec).
*/
public class AccountPickler {
public static final String LOG_TAG = "AccountPickler";
public static final long VERSION = 1;
/**
* Remove Sync account persisted to disk.
*
* @param context Android context.
* @param filename name of persisted pickle file; must not contain path separators.
* @return true
if given pickle existed and was successfully deleted.
*/
public static boolean deletePickle(final Context context, final String filename) {
return context.deleteFile(filename);
}
/**
* Persist Sync account to disk as a JSON object.
*
* JSON object has keys:
*
* Constants.JSON_KEY_ACCOUNT
: the Sync account's un-encoded username,
* like "test@mozilla.com".
*
* Constants.JSON_KEY_PASSWORD
: the Sync account's password;
*
* Constants.JSON_KEY_SERVER
: the Sync account's server;
*
* Constants.JSON_KEY_SYNCKEY
: the Sync account's sync key;
*
* Constants.JSON_KEY_CLUSTER
: the Sync account's cluster (may be null);
*
* Constants.JSON_KEY_CLIENT_NAME
: the Sync account's client name (may be null);
*
* Constants.JSON_KEY_CLIENT_GUID
: the Sync account's client GUID (may be null);
*
* Constants.JSON_KEY_SYNC_AUTOMATICALLY
: true if the Android Account is syncing automically;
*
* Constants.JSON_KEY_VERSION
: version of this file;
*
* Constants.JSON_KEY_TIMESTAMP
: when this file was written.
*
*
*
* @param context Android context.
* @param filename name of file to persist to; must not contain path separators.
* @param params the Sync account's parameters.
* @param syncAutomatically whether the Android Account object is syncing automatically.
*/
public static void pickle(final Context context, final String filename,
final SyncAccountParameters params, final boolean syncAutomatically) {
final ExtendedJSONObject o = params.asJSON();
o.put(Constants.JSON_KEY_SYNC_AUTOMATICALLY, new Boolean(syncAutomatically));
o.put(Constants.JSON_KEY_VERSION, new Long(VERSION));
o.put(Constants.JSON_KEY_TIMESTAMP, new Long(System.currentTimeMillis()));
PrintStream ps = null;
try {
final FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
ps = new PrintStream(fos);
ps.print(o.toJSONString());
Logger.debug(LOG_TAG, "Persisted " + o.keySet().size() + " account settings to " + filename + ".");
} catch (Exception e) {
Logger.warn(LOG_TAG, "Caught exception persisting account settings to " + filename + "; ignoring.", e);
} finally {
if (ps != null) {
ps.close();
}
}
}
/**
* Create Android account from saved JSON object.
*
* @param context
* Android context.
* @param filename
* name of file to read from; must not contain path separators.
* @return created Android account, or null on error.
*/
public static Account unpickle(final Context context, final String filename) {
final String jsonString = Utils.readFile(context, filename);
if (jsonString == null) {
Logger.info(LOG_TAG, "Pickle file '" + filename + "' not found; aborting.");
return null;
}
ExtendedJSONObject json = null;
try {
json = ExtendedJSONObject.parseJSONObject(jsonString);
} catch (Exception e) {
Logger.warn(LOG_TAG, "Got exception reading pickle file '" + filename + "'; aborting.", e);
return null;
}
SyncAccountParameters params = null;
try {
// Null checking of inputs is done in constructor.
params = new SyncAccountParameters(context, null, json);
} catch (IllegalArgumentException e) {
Logger.warn(LOG_TAG, "Un-pickled data included null username, password, or serverURL; aborting.", e);
return null;
}
// Default to syncing automatically.
boolean syncAutomatically = true;
if (json.containsKey(Constants.JSON_KEY_SYNC_AUTOMATICALLY)) {
if ((new Boolean(false)).equals(json.get(Constants.JSON_KEY_SYNC_AUTOMATICALLY))) {
syncAutomatically = false;
}
}
final Account account = SyncAccounts.createSyncAccountPreservingExistingPreferences(params, syncAutomatically);
if (account == null) {
Logger.warn(LOG_TAG, "Failed to add Android Account; aborting.");
return null;
}
Integer version = json.getIntegerSafely(Constants.JSON_KEY_VERSION);
Integer timestamp = json.getIntegerSafely(Constants.JSON_KEY_TIMESTAMP);
if (version == null || timestamp == null) {
Logger.warn(LOG_TAG, "Did not find version or timestamp in pickle file; ignoring.");
version = new Integer(-1);
timestamp = new Integer(-1);
}
Logger.info(LOG_TAG, "Un-pickled Android account named " + params.username + " (version " + version + ", pickled at " + timestamp + ").");
return account;
}
}