Bug 997328 - Update SharedPreferences.jsm to use scopes and mirror GeckoSharedPrefs. r=nalexander

This commit is contained in:
Josh Dover 2014-05-10 08:39:53 -07:00
parent 183f00dd6f
commit 2664427548
7 changed files with 191 additions and 54 deletions

View File

@ -50,6 +50,9 @@ public final class GeckoSharedPrefs {
// Name for app-scoped prefs
public static final String APP_PREFS_NAME = "GeckoApp";
// Used when fetching profile-scoped prefs.
public static final String PROFILE_PREFS_NAME_PREFIX = "GeckoProfile-";
// The prefs key that holds the current migration
private static final String PREFS_VERSION_KEY = "gecko_shared_prefs_migration";
@ -73,9 +76,6 @@ public final class GeckoSharedPrefs {
DISABLE_MIGRATIONS
}
// Used when fetching profile-scoped prefs.
private static final String PROFILE_PREFS_NAME_PREFIX = "GeckoProfile-";
public static SharedPreferences forApp(Context context) {
return forApp(context, EnumSet.noneOf(Flags.class));
}

View File

@ -7,13 +7,13 @@ package org.mozilla.gecko;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import java.util.Map;
@ -27,6 +27,28 @@ public final class SharedPreferencesHelper
{
public static final String LOGTAG = "GeckoAndSharedPrefs";
private enum Scope {
APP("app"),
PROFILE("profile"),
GLOBAL("global");
public final String key;
private Scope(String key) {
this.key = key;
}
public static Scope forKey(String key) {
for (Scope scope : values()) {
if (scope.key.equals(key)) {
return scope;
}
}
throw new IllegalStateException("SharedPreferences scope must be valid.");
}
}
protected final Context mContext;
// mListeners is not synchronized because it is only updated in
@ -61,12 +83,45 @@ public final class SharedPreferencesHelper
"SharedPreferences:Observe");
}
private SharedPreferences getSharedPreferences(String branch) {
if (branch == null) {
return GeckoSharedPrefs.forApp(mContext);
} else {
return mContext.getSharedPreferences(branch, Context.MODE_PRIVATE);
private SharedPreferences getSharedPreferences(JSONObject message) throws JSONException {
final Scope scope = Scope.forKey(message.getString("scope"));
switch (scope) {
case APP:
return GeckoSharedPrefs.forApp(mContext);
case PROFILE:
final String profileName = message.optString("profileName", null);
if (profileName == null) {
return GeckoSharedPrefs.forProfile(mContext);
} else {
return GeckoSharedPrefs.forProfileName(mContext, profileName);
}
case GLOBAL:
final String branch = message.optString("branch", null);
if (branch == null) {
return PreferenceManager.getDefaultSharedPreferences(mContext);
} else {
return mContext.getSharedPreferences(branch, Context.MODE_PRIVATE);
}
}
return null;
}
private String getBranch(Scope scope, String profileName, String branch) {
switch (scope) {
case APP:
return GeckoSharedPrefs.APP_PREFS_NAME;
case PROFILE:
if (profileName == null) {
profileName = GeckoProfile.get(mContext).getName();
}
return GeckoSharedPrefs.PROFILE_PREFS_NAME_PREFIX + profileName;
case GLOBAL:
return branch;
}
return null;
}
/**
@ -79,13 +134,7 @@ public final class SharedPreferencesHelper
* and an Object value.
*/
private void handleSet(JSONObject message) throws JSONException {
if (!message.has("branch")) {
Log.e(LOGTAG, "No branch specified for SharedPreference:Set; aborting.");
return;
}
String branch = message.isNull("branch") ? null : message.getString("branch");
SharedPreferences.Editor editor = getSharedPreferences(branch).edit();
SharedPreferences.Editor editor = getSharedPreferences(message).edit();
JSONArray jsonPrefs = message.getJSONArray("preferences");
@ -116,13 +165,7 @@ public final class SharedPreferencesHelper
* "string"].
*/
private JSONArray handleGet(JSONObject message) throws JSONException {
if (!message.has("branch")) {
Log.e(LOGTAG, "No branch specified for SharedPreference:Get; aborting.");
return null;
}
String branch = message.isNull("branch") ? null : message.getString("branch");
SharedPreferences prefs = getSharedPreferences(branch);
SharedPreferences prefs = getSharedPreferences(message);
JSONArray jsonPrefs = message.getJSONArray("preferences");
JSONArray jsonValues = new JSONArray();
@ -159,10 +202,14 @@ public final class SharedPreferencesHelper
private static class ChangeListener
implements SharedPreferences.OnSharedPreferenceChangeListener {
public final Scope scope;
public final String branch;
public final String profileName;
public ChangeListener(final String branch) {
public ChangeListener(final Scope scope, final String branch, final String profileName) {
this.scope = scope;
this.branch = branch;
this.profileName = profileName;
}
@Override
@ -172,7 +219,9 @@ public final class SharedPreferencesHelper
}
try {
final JSONObject msg = new JSONObject();
msg.put("scope", this.scope.key);
msg.put("branch", this.branch);
msg.put("profileName", this.profileName);
msg.put("key", key);
// Truly, this is awful, but the API impedence is strong: there
@ -197,24 +246,29 @@ public final class SharedPreferencesHelper
* disable listening.
*/
private void handleObserve(JSONObject message) throws JSONException {
if (!message.has("branch")) {
final SharedPreferences prefs = getSharedPreferences(message);
final boolean enable = message.getBoolean("enable");
final Scope scope = Scope.forKey(message.getString("scope"));
final String profileName = message.optString("profileName", null);
final String branch = getBranch(scope, profileName, message.optString("branch", null));
if (branch == null) {
Log.e(LOGTAG, "No branch specified for SharedPreference:Observe; aborting.");
return;
}
String branch = message.isNull("branch") ? null : message.getString("branch");
SharedPreferences prefs = getSharedPreferences(branch);
boolean enable = message.getBoolean("enable");
// mListeners is only modified in this one observer, which is called
// from Gecko serially.
if (enable && !this.mListeners.containsKey(branch)) {
SharedPreferences.OnSharedPreferenceChangeListener listener = new ChangeListener(branch);
SharedPreferences.OnSharedPreferenceChangeListener listener
= new ChangeListener(scope, branch, profileName);
this.mListeners.put(branch, listener);
prefs.registerOnSharedPreferenceChangeListener(listener);
}
if (!enable && this.mListeners.containsKey(branch)) {
SharedPreferences.OnSharedPreferenceChangeListener listener = this.mListeners.remove(branch);
SharedPreferences.OnSharedPreferenceChangeListener listener
= this.mListeners.remove(branch);
prefs.unregisterOnSharedPreferenceChangeListener(listener);
}
}

View File

@ -14,6 +14,7 @@ import org.mozilla.gecko.FennecTalosAssert;
import android.app.Activity;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
public abstract class BaseRobocopTest extends ActivityInstrumentationTestCase2<Activity> {
public enum Type {
@ -21,6 +22,8 @@ public abstract class BaseRobocopTest extends ActivityInstrumentationTestCase2<A
TALOS
}
private static final String DEFAULT_ROOT_PATH = "/mnt/sdcard/tests";
protected Assert mAsserter;
protected String mLogFile;
@ -66,6 +69,10 @@ public abstract class BaseRobocopTest extends ActivityInstrumentationTestCase2<A
protected void setUp() throws Exception {
// Load config file from root path (set up by Python script).
mRootPath = FennecInstrumentationTestRunner.getFennecArguments().getString("deviceroot");
if (mRootPath == null) {
Log.w("Robocop", "Did not find deviceroot in arguments; falling back to: " + DEFAULT_ROOT_PATH);
mRootPath = DEFAULT_ROOT_PATH;
}
String configFile = FennecNativeDriver.getFile(mRootPath + "/robotium.config");
mConfig = FennecNativeDriver.convertTextToTable(configFile);
mLogFile = (String) mConfig.get("logfile");

View File

@ -28,7 +28,7 @@ function makeObserver() {
};
add_task(function test_get_set() {
let branch = new SharedPreferences("test");
let branch = SharedPreferences.forAndroid("test");
branch.setBoolPref("boolKey", true);
branch.setCharPref("charKey", "string value");
@ -52,7 +52,7 @@ add_task(function test_get_set() {
});
add_task(function test_default() {
let branch = new SharedPreferences();
let branch = SharedPreferences.forAndroid();
branch.setBoolPref("boolKey", true);
branch.setCharPref("charKey", "string value");
@ -76,8 +76,8 @@ add_task(function test_default() {
});
add_task(function test_multiple_branches() {
let branch1 = new SharedPreferences("test1");
let branch2 = new SharedPreferences("test2");
let branch1 = SharedPreferences.forAndroid("test1");
let branch2 = SharedPreferences.forAndroid("test2");
branch1.setBoolPref("boolKey", true);
branch2.setBoolPref("boolKey", false);
@ -93,7 +93,7 @@ add_task(function test_multiple_branches() {
});
add_task(function test_add_remove_observer() {
let branch = new SharedPreferences("test");
let branch = SharedPreferences.forAndroid("test");
branch.setBoolPref("boolKey", false);
do_check_eq(branch.getBoolPref("boolKey"), false);
@ -145,7 +145,7 @@ add_task(function test_add_remove_observer() {
});
add_task(function test_observer_ignores() {
let branch = new SharedPreferences("test");
let branch = SharedPreferences.forAndroid("test");
branch.setCharPref("charKey", "first value");
do_check_eq(branch.getCharPref("charKey"), "first value");
@ -176,7 +176,7 @@ add_task(function test_observer_ignores() {
});
add_task(function test_observer_ignores_branches() {
let branch = new SharedPreferences("test");
let branch = SharedPreferences.forAndroid("test");
branch.setCharPref("charKey", "first value");
do_check_eq(branch.getCharPref("charKey"), "first value");
@ -186,9 +186,9 @@ add_task(function test_observer_ignores_branches() {
try {
// These should all be ignored.
let branch2 = new SharedPreferences("test2");
let branch2 = SharedPreferences.forAndroid("test2");
branch2.setCharPref("charKey", "a wrong value");
let branch3 = new SharedPreferences("test.2");
let branch3 = SharedPreferences.forAndroid("test.2");
branch3.setCharPref("charKey", "a different wrong value");
// This should not be ignored.
@ -208,4 +208,24 @@ add_task(function test_observer_ignores_branches() {
}
});
add_task(function test_scopes() {
let forApp = SharedPreferences.forApp();
let forProfile = SharedPreferences.forProfile();
let forProfileName = SharedPreferences.forProfileName("testProfile");
let forAndroidDefault = SharedPreferences.forAndroid();
let forAndroidBranch = SharedPreferences.forAndroid("testBranch");
forApp.setCharPref("charKey", "forApp");
forProfile.setCharPref("charKey", "forProfile");
forProfileName.setCharPref("charKey", "forProfileName");
forAndroidDefault.setCharPref("charKey", "forAndroidDefault");
forAndroidBranch.setCharPref("charKey", "forAndroidBranch");
do_check_eq(forApp.getCharPref("charKey"), "forApp");
do_check_eq(forProfile.getCharPref("charKey"), "forProfile");
do_check_eq(forProfileName.getCharPref("charKey"), "forProfileName");
do_check_eq(forAndroidDefault.getCharPref("charKey"), "forAndroidDefault");
do_check_eq(forAndroidBranch.getCharPref("charKey"), "forAndroidBranch");
});
run_next_test();

View File

@ -27,7 +27,7 @@ const EVENT_HEALTH_RESPONSE = "HealthReport:Response";
// about:healthreport prefs are stored in Firefox's default Android
// SharedPreferences.
let sharedPrefs = new SharedPreferences();
let sharedPrefs = SharedPreferences.forApp();
let healthReportWrapper = {
init: function () {
@ -190,4 +190,4 @@ let healthReportWrapper = {
};
window.addEventListener("load", healthReportWrapper.init.bind(healthReportWrapper), false);
window.addEventListener("unload", healthReportWrapper.uninit.bind(healthReportWrapper), false);
window.addEventListener("unload", healthReportWrapper.uninit.bind(healthReportWrapper), false);

View File

@ -457,7 +457,7 @@ let HomePanels = (function () {
_assertPanelExists(id);
let authKey = PREFS_PANEL_AUTH_PREFIX + id;
let sharedPrefs = new SharedPreferences();
let sharedPrefs = SharedPreferences.forProfile();
sharedPrefs.setBoolPref(authKey, isAuthenticated);
}
});

View File

@ -13,26 +13,74 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
let Scope = Object.freeze({
APP: "app",
PROFILE: "profile",
GLOBAL: "global"
});
/**
* Public API to getting a SharedPreferencesImpl instance. These scopes mirror GeckoSharedPrefs.
*/
let SharedPreferences = {
forApp: function() {
return new SharedPreferencesImpl({ scope: Scope.APP });
},
forProfile: function() {
return new SharedPreferencesImpl({ scope: Scope.PROFILE });
},
/**
* Get SharedPreferences for the named profile; if the profile name is null,
* returns the preferences for the current profile (just like |forProfile|).
*/
forProfileName: function(profileName) {
return new SharedPreferencesImpl({ scope: Scope.PROFILE, profileName: profileName });
},
/**
* Get SharedPreferences for the given Android branch; if the branch is null,
* returns the default preferences branch for the application, which is the
* output of |PreferenceManager.getDefaultSharedPreferences|.
*/
forAndroid: function(branch) {
return new SharedPreferencesImpl({ scope: Scope.GLOBAL, branch: branch });
}
};
/**
* Create an interface to an Android SharedPreferences branch.
*
* branch {String} should be a string describing a preferences branch,
* like "UpdateService" or "background.data", or null to access the
* default preferences branch for the application.
* options {Object} with the following valid keys:
* - scope {String} (required) specifies the scope of preferences that should be accessed.
* - branch {String} (only when using Scope.GLOBAL) should be a string describing a preferences branch,
* like "UpdateService" or "background.data", or null to access the
* default preferences branch for the application.
* - profileName {String} (optional, only valid when using Scope.PROFILE)
*/
function SharedPreferences(branch) {
if (!(this instanceof SharedPreferences)) {
return new SharedPreferences(branch);
function SharedPreferencesImpl(options = {}) {
if (!(this instanceof SharedPreferencesImpl)) {
return new SharedPreferencesImpl(level);
}
this._branch = branch || null;
this._observers = {};
};
SharedPreferences.prototype = Object.freeze({
if (options.scope == null || options.scope == undefined) {
throw "Shared Preferences must specifiy a scope.";
}
this._scope = options.scope;
this._profileName = options.profileName;
this._branch = options.branch;
this._observers = {};
}
SharedPreferencesImpl.prototype = Object.freeze({
_set: function _set(prefs) {
sendMessageToJava({
type: "SharedPreferences:Set",
preferences: prefs,
scope: this._scope,
profileName: this._profileName,
branch: this._branch,
});
},
@ -64,6 +112,8 @@ SharedPreferences.prototype = Object.freeze({
sendMessageToJava({
type: "SharedPreferences:Get",
preferences: prefs,
scope: this._scope,
profileName: this._profileName,
branch: this._branch,
}, (data) => {
result = data.values;
@ -159,6 +209,8 @@ SharedPreferences.prototype = Object.freeze({
sendMessageToJava({
type: "SharedPreferences:Observe",
enable: true,
scope: this._scope,
profileName: this._profileName,
branch: this._branch,
});
},
@ -169,7 +221,9 @@ SharedPreferences.prototype = Object.freeze({
}
let msg = JSON.parse(data);
if (msg.branch != this._branch) {
if (msg.scope !== this._scope ||
((this._scope === Scope.PROFILE) && (msg.profileName !== this._profileName)) ||
((this._scope === Scope.GLOBAL) && (msg.branch !== this._branch))) {
return;
}
@ -192,6 +246,8 @@ SharedPreferences.prototype = Object.freeze({
sendMessageToJava({
type: "SharedPreferences:Observe",
enable: false,
scope: this._scope,
profileName: this._profileName,
branch: this._branch,
});
},