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