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;
7 import org.mozilla.gecko.mozglue.RobocopTarget;
8 import org.mozilla.gecko.util.ThreadUtils;
10 import android.content.Context;
11 import android.content.SharedPreferences;
12 import android.content.SharedPreferences.Editor;
13 import android.os.Build;
14 import android.os.StrictMode;
15 import android.preference.PreferenceManager;
16 import android.util.Log;
18 import java.util.Arrays;
19 import java.util.EnumSet;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
24 /**
25 * {@code GeckoSharedPrefs} provides scoped SharedPreferences instances.
26 * You should use this API instead of using Context.getSharedPreferences()
27 * directly. There are three methods to get scoped SharedPreferences instances:
28 *
29 * forApp()
30 * Use it for app-wide, cross-profile pref keys.
31 * forProfile()
32 * Use it to fetch and store keys for the current profile.
33 * forProfileName()
34 * Use it to fetch and store keys from/for a specific profile.
35 *
36 * {@code GeckoSharedPrefs} has a notion of migrations. Migrations can used to
37 * migrate keys from one scope to another. You can trigger a new migration by
38 * incrementing PREFS_VERSION and updating migrateIfNecessary() accordingly.
39 *
40 * Migration history:
41 * 1: Move all PreferenceManager keys to app/profile scopes
42 */
43 @RobocopTarget
44 public final class GeckoSharedPrefs {
45 private static final String LOGTAG = "GeckoSharedPrefs";
47 // Increment it to trigger a new migration
48 public static final int PREFS_VERSION = 1;
50 // Name for app-scoped prefs
51 public static final String APP_PREFS_NAME = "GeckoApp";
53 // The prefs key that holds the current migration
54 private static final String PREFS_VERSION_KEY = "gecko_shared_prefs_migration";
56 // For disabling migration when getting a SharedPreferences instance
57 private static final EnumSet<Flags> disableMigrations = EnumSet.of(Flags.DISABLE_MIGRATIONS);
59 // Timeout for migration commits to be done (10 seconds)
60 private static final int MIGRATION_COMMIT_TIMEOUT_MSEC = 10000;
62 // The keys that have to be moved from ProfileManager's default
63 // shared prefs to the profile from version 0 to 1.
64 private static final String[] PROFILE_MIGRATIONS_0_TO_1 = {
65 "home_panels",
66 "home_locale"
67 };
69 // For optimizing the migration check in subsequent get() calls
70 private static volatile boolean migrationDone = false;
72 public enum Flags {
73 DISABLE_MIGRATIONS
74 }
76 // Used when fetching profile-scoped prefs.
77 private static final String PROFILE_PREFS_NAME_PREFIX = "GeckoProfile-";
79 public static SharedPreferences forApp(Context context) {
80 return forApp(context, EnumSet.noneOf(Flags.class));
81 }
83 /**
84 * Returns an app-scoped SharedPreferences instance. You can disable
85 * migrations by using the DISABLE_MIGRATIONS flag.
86 */
87 public static SharedPreferences forApp(Context context, EnumSet<Flags> flags) {
88 if (flags != null && !flags.contains(Flags.DISABLE_MIGRATIONS)) {
89 migrateIfNecessary(context);
90 }
92 return context.getSharedPreferences(APP_PREFS_NAME, 0);
93 }
95 public static SharedPreferences forProfile(Context context) {
96 return forProfile(context, EnumSet.noneOf(Flags.class));
97 }
99 /**
100 * Returns a SharedPreferences instance scoped to the current profile
101 * in the app. You can disable migrations by using the DISABLE_MIGRATIONS
102 * flag.
103 */
104 public static SharedPreferences forProfile(Context context, EnumSet<Flags> flags) {
105 String profileName = GeckoProfile.get(context).getName();
106 if (profileName == null) {
107 throw new IllegalStateException("Could not get current profile name");
108 }
110 return forProfileName(context, profileName, flags);
111 }
113 public static SharedPreferences forProfileName(Context context, String profileName) {
114 return forProfileName(context, profileName, EnumSet.noneOf(Flags.class));
115 }
117 /**
118 * Returns an SharedPreferences instance scoped to the given profile name.
119 * You can disable migrations by using the DISABLE_MIGRATION flag.
120 */
121 public static SharedPreferences forProfileName(Context context, String profileName,
122 EnumSet<Flags> flags) {
123 if (flags != null && !flags.contains(Flags.DISABLE_MIGRATIONS)) {
124 migrateIfNecessary(context);
125 }
127 final String prefsName = PROFILE_PREFS_NAME_PREFIX + profileName;
128 return context.getSharedPreferences(prefsName, 0);
129 }
131 /**
132 * Returns the current version of the prefs.
133 */
134 public static int getVersion(Context context) {
135 return forApp(context, disableMigrations).getInt(PREFS_VERSION_KEY, 0);
136 }
138 /**
139 * Resets migration flag. Should only be used in tests.
140 */
141 public static synchronized void reset() {
142 migrationDone = false;
143 }
145 /**
146 * Performs all prefs migrations in the background thread to avoid StrictMode
147 * exceptions from reading/writing in the UI thread. This method will block
148 * the current thread until the migration is finished.
149 */
150 private static synchronized void migrateIfNecessary(final Context context) {
151 if (migrationDone) {
152 return;
153 }
155 // We deliberatly perform the migration in the current thread (which
156 // is likely the UI thread) as this is actually cheaper than enforcing a
157 // context switch to another thread (see bug 940575).
158 if (Build.VERSION.SDK_INT < 9) {
159 performMigration(context);
160 } else {
161 // Avoid strict mode warnings.
162 final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
163 StrictMode.allowThreadDiskWrites();
165 try {
166 performMigration(context);
167 } finally {
168 StrictMode.setThreadPolicy(savedPolicy);
169 }
170 }
172 migrationDone = true;
173 }
175 private static void performMigration(Context context) {
176 final SharedPreferences appPrefs = forApp(context, disableMigrations);
178 final int currentVersion = appPrefs.getInt(PREFS_VERSION_KEY, 0);
179 Log.d(LOGTAG, "Current version = " + currentVersion + ", prefs version = " + PREFS_VERSION);
181 if (currentVersion == PREFS_VERSION) {
182 return;
183 }
185 Log.d(LOGTAG, "Performing migration");
187 final Editor appEditor = appPrefs.edit();
189 // The migration always moves prefs to the default profile, not
190 // the current one. We might have to revisit this if we ever support
191 // multiple profiles.
192 final String defaultProfileName;
193 try {
194 defaultProfileName = GeckoProfile.getDefaultProfileName(context);
195 } catch (Exception e) {
196 throw new IllegalStateException("Failed to get default profile name for migration");
197 }
199 final Editor profileEditor = forProfileName(context, defaultProfileName, disableMigrations).edit();
201 List<String> profileKeys;
202 Editor pmEditor = null;
204 for (int v = currentVersion + 1; v <= PREFS_VERSION; v++) {
205 Log.d(LOGTAG, "Migrating to version = " + v);
207 switch (v) {
208 case 1:
209 profileKeys = Arrays.asList(PROFILE_MIGRATIONS_0_TO_1);
210 pmEditor = migrateFromPreferenceManager(context, appEditor, profileEditor, profileKeys);
211 break;
212 }
213 }
215 // Update prefs version accordingly.
216 appEditor.putInt(PREFS_VERSION_KEY, PREFS_VERSION);
218 appEditor.commit();
219 profileEditor.commit();
220 if (pmEditor != null) {
221 pmEditor.commit();
222 }
224 Log.d(LOGTAG, "All keys have been migrated");
225 }
227 /**
228 * Moves all preferences stored in PreferenceManager's default prefs
229 * to either app or profile scopes. The profile-scoped keys are defined
230 * in given profileKeys list, all other keys are moved to the app scope.
231 */
232 public static Editor migrateFromPreferenceManager(Context context, Editor appEditor,
233 Editor profileEditor, List<String> profileKeys) {
234 Log.d(LOGTAG, "Migrating from PreferenceManager");
236 final SharedPreferences pmPrefs =
237 PreferenceManager.getDefaultSharedPreferences(context);
239 for (Map.Entry<String, ?> entry : pmPrefs.getAll().entrySet()) {
240 final String key = entry.getKey();
242 final Editor to;
243 if (profileKeys.contains(key)) {
244 to = profileEditor;
245 } else {
246 to = appEditor;
247 }
249 putEntry(to, key, entry.getValue());
250 }
252 // Clear PreferenceManager's prefs once we're done
253 // and return the Editor to be committed.
254 return pmPrefs.edit().clear();
255 }
257 @SuppressWarnings("unchecked")
258 private static void putEntry(Editor to, String key, Object value) {
259 Log.d(LOGTAG, "Migrating key = " + key + " with value = " + value);
261 if (value instanceof String) {
262 to.putString(key, (String) value);
263 } else if (value instanceof Boolean) {
264 to.putBoolean(key, (Boolean) value);
265 } else if (value instanceof Long) {
266 to.putLong(key, (Long) value);
267 } else if (value instanceof Float) {
268 to.putFloat(key, (Float) value);
269 } else if (value instanceof Integer) {
270 to.putInt(key, (Integer) value);
271 } else {
272 throw new IllegalStateException("Unrecognized value type for key: " + key);
273 }
274 }
275 }