|
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/. */ |
|
4 |
|
5 package org.mozilla.gecko.sync.config; |
|
6 |
|
7 import java.io.FileOutputStream; |
|
8 import java.io.PrintStream; |
|
9 |
|
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; |
|
16 |
|
17 import android.accounts.Account; |
|
18 import android.content.Context; |
|
19 |
|
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"; |
|
54 |
|
55 public static final long VERSION = 1; |
|
56 |
|
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 } |
|
67 |
|
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())); |
|
107 |
|
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 } |
|
122 |
|
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 } |
|
138 |
|
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 } |
|
146 |
|
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 } |
|
155 |
|
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 } |
|
163 |
|
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 } |
|
169 |
|
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 } |
|
177 |
|
178 Logger.info(LOG_TAG, "Un-pickled Android account named " + params.username + " (version " + version + ", pickled at " + timestamp + ")."); |
|
179 |
|
180 return account; |
|
181 } |
|
182 } |