mobile/android/base/GeckoSharedPrefs.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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 }

mercurial