mobile/android/base/sync/config/AccountPickler.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 package org.mozilla.gecko.sync.config;
michael@0 6
michael@0 7 import java.io.FileOutputStream;
michael@0 8 import java.io.PrintStream;
michael@0 9
michael@0 10 import org.mozilla.gecko.background.common.log.Logger;
michael@0 11 import org.mozilla.gecko.sync.ExtendedJSONObject;
michael@0 12 import org.mozilla.gecko.sync.Utils;
michael@0 13 import org.mozilla.gecko.sync.setup.Constants;
michael@0 14 import org.mozilla.gecko.sync.setup.SyncAccounts;
michael@0 15 import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
michael@0 16
michael@0 17 import android.accounts.Account;
michael@0 18 import android.content.Context;
michael@0 19
michael@0 20 /**
michael@0 21 * Bug 768102: Android deletes Account objects when the Authenticator that owns
michael@0 22 * the Account disappears. This happens when an App is installed to the SD card
michael@0 23 * and the SD card is un-mounted or the device is rebooted.
michael@0 24 * <p>
michael@0 25 * Bug 769745: Work around this by pickling the current Sync account data every
michael@0 26 * sync.
michael@0 27 * <p>
michael@0 28 * Bug 735842: Work around this by un-pickling when we check if Sync accounts
michael@0 29 * exist (called from Fennec).
michael@0 30 * <p>
michael@0 31 * Android just doesn't support installing Apps that define long-lived Services
michael@0 32 * and/or own Account types onto the SD card. The documentation says not to do
michael@0 33 * it. There are hordes of developers who want to do it, and have tried to
michael@0 34 * register for almost every "package installation changed" broadcast intent
michael@0 35 * that Android supports. They all explicitly state that the package that has
michael@0 36 * changed does *not* receive the broadcast intent, thereby preventing an App
michael@0 37 * from re-establishing its state.
michael@0 38 * <p>
michael@0 39 * <a href="http://developer.android.com/guide/topics/data/install-location.html">Reference.</a>
michael@0 40 * <p>
michael@0 41 * <b>Quote</b>: Your AbstractThreadedSyncAdapter and all its sync functionality
michael@0 42 * will not work until external storage is remounted.
michael@0 43 * <p>
michael@0 44 * <b>Quote</b>: Your running Service will be killed and will not be restarted
michael@0 45 * when external storage is remounted. You can, however, register for the
michael@0 46 * ACTION_EXTERNAL_APPLICATIONS_AVAILABLE broadcast Intent, which will notify
michael@0 47 * your application when applications installed on external storage have become
michael@0 48 * available to the system again. At which time, you can restart your Service.
michael@0 49 * <p>
michael@0 50 * Problem: <a href="http://code.google.com/p/android/issues/detail?id=8485">that intent doesn't work</a>!
michael@0 51 */
michael@0 52 public class AccountPickler {
michael@0 53 public static final String LOG_TAG = "AccountPickler";
michael@0 54
michael@0 55 public static final long VERSION = 1;
michael@0 56
michael@0 57 /**
michael@0 58 * Remove Sync account persisted to disk.
michael@0 59 *
michael@0 60 * @param context Android context.
michael@0 61 * @param filename name of persisted pickle file; must not contain path separators.
michael@0 62 * @return <code>true</code> if given pickle existed and was successfully deleted.
michael@0 63 */
michael@0 64 public static boolean deletePickle(final Context context, final String filename) {
michael@0 65 return context.deleteFile(filename);
michael@0 66 }
michael@0 67
michael@0 68 /**
michael@0 69 * Persist Sync account to disk as a JSON object.
michael@0 70 * <p>
michael@0 71 * JSON object has keys:
michael@0 72 * <ul>
michael@0 73 * <li><code>Constants.JSON_KEY_ACCOUNT</code>: the Sync account's un-encoded username,
michael@0 74 * like "test@mozilla.com".</li>
michael@0 75 *
michael@0 76 * <li><code>Constants.JSON_KEY_PASSWORD</code>: the Sync account's password;</li>
michael@0 77 *
michael@0 78 * <li><code>Constants.JSON_KEY_SERVER</code>: the Sync account's server;</li>
michael@0 79 *
michael@0 80 * <li><code>Constants.JSON_KEY_SYNCKEY</code>: the Sync account's sync key;</li>
michael@0 81 *
michael@0 82 * <li><code>Constants.JSON_KEY_CLUSTER</code>: the Sync account's cluster (may be null);</li>
michael@0 83 *
michael@0 84 * <li><code>Constants.JSON_KEY_CLIENT_NAME</code>: the Sync account's client name (may be null);</li>
michael@0 85 *
michael@0 86 * <li><code>Constants.JSON_KEY_CLIENT_GUID</code>: the Sync account's client GUID (may be null);</li>
michael@0 87 *
michael@0 88 * <li><code>Constants.JSON_KEY_SYNC_AUTOMATICALLY</code>: true if the Android Account is syncing automically;</li>
michael@0 89 *
michael@0 90 * <li><code>Constants.JSON_KEY_VERSION</code>: version of this file;</li>
michael@0 91 *
michael@0 92 * <li><code>Constants.JSON_KEY_TIMESTAMP</code>: when this file was written.</li>
michael@0 93 * </ul>
michael@0 94 *
michael@0 95 *
michael@0 96 * @param context Android context.
michael@0 97 * @param filename name of file to persist to; must not contain path separators.
michael@0 98 * @param params the Sync account's parameters.
michael@0 99 * @param syncAutomatically whether the Android Account object is syncing automatically.
michael@0 100 */
michael@0 101 public static void pickle(final Context context, final String filename,
michael@0 102 final SyncAccountParameters params, final boolean syncAutomatically) {
michael@0 103 final ExtendedJSONObject o = params.asJSON();
michael@0 104 o.put(Constants.JSON_KEY_SYNC_AUTOMATICALLY, Boolean.valueOf(syncAutomatically));
michael@0 105 o.put(Constants.JSON_KEY_VERSION, new Long(VERSION));
michael@0 106 o.put(Constants.JSON_KEY_TIMESTAMP, new Long(System.currentTimeMillis()));
michael@0 107
michael@0 108 PrintStream ps = null;
michael@0 109 try {
michael@0 110 final FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
michael@0 111 ps = new PrintStream(fos);
michael@0 112 ps.print(o.toJSONString());
michael@0 113 Logger.debug(LOG_TAG, "Persisted " + o.keySet().size() + " account settings to " + filename + ".");
michael@0 114 } catch (Exception e) {
michael@0 115 Logger.warn(LOG_TAG, "Caught exception persisting account settings to " + filename + "; ignoring.", e);
michael@0 116 } finally {
michael@0 117 if (ps != null) {
michael@0 118 ps.close();
michael@0 119 }
michael@0 120 }
michael@0 121 }
michael@0 122
michael@0 123 /**
michael@0 124 * Create Android account from saved JSON object.
michael@0 125 *
michael@0 126 * @param context
michael@0 127 * Android context.
michael@0 128 * @param filename
michael@0 129 * name of file to read from; must not contain path separators.
michael@0 130 * @return created Android account, or null on error.
michael@0 131 */
michael@0 132 public static Account unpickle(final Context context, final String filename) {
michael@0 133 final String jsonString = Utils.readFile(context, filename);
michael@0 134 if (jsonString == null) {
michael@0 135 Logger.info(LOG_TAG, "Pickle file '" + filename + "' not found; aborting.");
michael@0 136 return null;
michael@0 137 }
michael@0 138
michael@0 139 ExtendedJSONObject json = null;
michael@0 140 try {
michael@0 141 json = ExtendedJSONObject.parseJSONObject(jsonString);
michael@0 142 } catch (Exception e) {
michael@0 143 Logger.warn(LOG_TAG, "Got exception reading pickle file '" + filename + "'; aborting.", e);
michael@0 144 return null;
michael@0 145 }
michael@0 146
michael@0 147 SyncAccountParameters params = null;
michael@0 148 try {
michael@0 149 // Null checking of inputs is done in constructor.
michael@0 150 params = new SyncAccountParameters(context, null, json);
michael@0 151 } catch (IllegalArgumentException e) {
michael@0 152 Logger.warn(LOG_TAG, "Un-pickled data included null username, password, or serverURL; aborting.", e);
michael@0 153 return null;
michael@0 154 }
michael@0 155
michael@0 156 // Default to syncing automatically.
michael@0 157 boolean syncAutomatically = true;
michael@0 158 if (json.containsKey(Constants.JSON_KEY_SYNC_AUTOMATICALLY)) {
michael@0 159 if (Boolean.FALSE.equals(json.get(Constants.JSON_KEY_SYNC_AUTOMATICALLY))) {
michael@0 160 syncAutomatically = false;
michael@0 161 }
michael@0 162 }
michael@0 163
michael@0 164 final Account account = SyncAccounts.createSyncAccountPreservingExistingPreferences(params, syncAutomatically);
michael@0 165 if (account == null) {
michael@0 166 Logger.warn(LOG_TAG, "Failed to add Android Account; aborting.");
michael@0 167 return null;
michael@0 168 }
michael@0 169
michael@0 170 Integer version = json.getIntegerSafely(Constants.JSON_KEY_VERSION);
michael@0 171 Integer timestamp = json.getIntegerSafely(Constants.JSON_KEY_TIMESTAMP);
michael@0 172 if (version == null || timestamp == null) {
michael@0 173 Logger.warn(LOG_TAG, "Did not find version or timestamp in pickle file; ignoring.");
michael@0 174 version = new Integer(-1);
michael@0 175 timestamp = new Integer(-1);
michael@0 176 }
michael@0 177
michael@0 178 Logger.info(LOG_TAG, "Un-pickled Android account named " + params.username + " (version " + version + ", pickled at " + timestamp + ").");
michael@0 179
michael@0 180 return account;
michael@0 181 }
michael@0 182 }

mercurial