2013-05-11 02:46:25 +00:00
|
|
|
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
package org.mozilla.gecko;
|
|
|
|
|
2014-02-11 17:16:00 +00:00
|
|
|
import org.mozilla.gecko.EventDispatcher;
|
|
|
|
import org.mozilla.gecko.util.GeckoEventListener;
|
2013-05-11 02:46:25 +00:00
|
|
|
import org.json.JSONArray;
|
|
|
|
import org.json.JSONException;
|
|
|
|
import org.json.JSONObject;
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.SharedPreferences;
|
2014-05-10 15:39:53 +00:00
|
|
|
import android.preference.PreferenceManager;
|
2013-05-11 02:46:25 +00:00
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper class to get, set, and observe Android Shared Preferences.
|
|
|
|
*/
|
|
|
|
public final class SharedPreferencesHelper
|
2014-02-11 17:16:00 +00:00
|
|
|
implements GeckoEventListener
|
2013-05-11 02:46:25 +00:00
|
|
|
{
|
|
|
|
public static final String LOGTAG = "GeckoAndSharedPrefs";
|
|
|
|
|
2014-05-10 15:39:53 +00:00
|
|
|
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.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-11 02:46:25 +00:00
|
|
|
protected final Context mContext;
|
|
|
|
|
|
|
|
// mListeners is not synchronized because it is only updated in
|
|
|
|
// handleObserve, which is called from Gecko serially.
|
|
|
|
protected final Map<String, SharedPreferences.OnSharedPreferenceChangeListener> mListeners;
|
|
|
|
|
|
|
|
public SharedPreferencesHelper(Context context) {
|
|
|
|
mContext = context;
|
|
|
|
|
|
|
|
mListeners = new HashMap<String, SharedPreferences.OnSharedPreferenceChangeListener>();
|
|
|
|
|
2014-05-02 18:34:40 +00:00
|
|
|
EventDispatcher dispatcher = EventDispatcher.getInstance();
|
2013-05-11 02:46:25 +00:00
|
|
|
if (dispatcher == null) {
|
|
|
|
Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
|
|
|
|
return;
|
|
|
|
}
|
2014-05-02 18:34:40 +00:00
|
|
|
dispatcher.registerGeckoThreadListener(this,
|
|
|
|
"SharedPreferences:Set",
|
|
|
|
"SharedPreferences:Get",
|
|
|
|
"SharedPreferences:Observe");
|
2013-05-11 02:46:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized void uninit() {
|
2014-05-02 18:34:40 +00:00
|
|
|
EventDispatcher dispatcher = EventDispatcher.getInstance();
|
2013-05-11 02:46:25 +00:00
|
|
|
if (dispatcher == null) {
|
|
|
|
Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
|
|
|
|
return;
|
|
|
|
}
|
2014-05-02 18:34:40 +00:00
|
|
|
dispatcher.unregisterGeckoThreadListener(this,
|
|
|
|
"SharedPreferences:Set",
|
|
|
|
"SharedPreferences:Get",
|
|
|
|
"SharedPreferences:Observe");
|
2013-05-11 02:46:25 +00:00
|
|
|
}
|
|
|
|
|
2014-05-10 15:39:53 +00:00
|
|
|
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);
|
|
|
|
}
|
2013-05-11 02:46:25 +00:00
|
|
|
}
|
2014-05-10 15:39:53 +00:00
|
|
|
|
|
|
|
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;
|
2013-05-11 02:46:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set many SharedPreferences in Android.
|
|
|
|
*
|
|
|
|
* message.branch must exist, and should be a String SharedPreferences
|
|
|
|
* branch name, or null for the default branch.
|
|
|
|
* message.preferences should be an array of preferences. Each preference
|
|
|
|
* must include a String name, a String type in ["bool", "int", "string"],
|
|
|
|
* and an Object value.
|
|
|
|
*/
|
|
|
|
private void handleSet(JSONObject message) throws JSONException {
|
2014-05-10 15:39:53 +00:00
|
|
|
SharedPreferences.Editor editor = getSharedPreferences(message).edit();
|
2013-05-11 02:46:25 +00:00
|
|
|
|
|
|
|
JSONArray jsonPrefs = message.getJSONArray("preferences");
|
|
|
|
|
|
|
|
for (int i = 0; i < jsonPrefs.length(); i++) {
|
|
|
|
JSONObject pref = jsonPrefs.getJSONObject(i);
|
|
|
|
String name = pref.getString("name");
|
|
|
|
String type = pref.getString("type");
|
|
|
|
if ("bool".equals(type)) {
|
|
|
|
editor.putBoolean(name, pref.getBoolean("value"));
|
|
|
|
} else if ("int".equals(type)) {
|
|
|
|
editor.putInt(name, pref.getInt("value"));
|
|
|
|
} else if ("string".equals(type)) {
|
|
|
|
editor.putString(name, pref.getString("value"));
|
|
|
|
} else {
|
|
|
|
Log.w(LOGTAG, "Unknown pref value type [" + type + "] for pref [" + name + "]");
|
|
|
|
}
|
2014-09-03 06:00:40 +00:00
|
|
|
editor.apply();
|
2013-05-11 02:46:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get many SharedPreferences from Android.
|
|
|
|
*
|
|
|
|
* message.branch must exist, and should be a String SharedPreferences
|
|
|
|
* branch name, or null for the default branch.
|
|
|
|
* message.preferences should be an array of preferences. Each preference
|
|
|
|
* must include a String name, and a String type in ["bool", "int",
|
|
|
|
* "string"].
|
|
|
|
*/
|
2014-02-11 17:16:00 +00:00
|
|
|
private JSONArray handleGet(JSONObject message) throws JSONException {
|
2014-05-10 15:39:53 +00:00
|
|
|
SharedPreferences prefs = getSharedPreferences(message);
|
2013-05-11 02:46:25 +00:00
|
|
|
JSONArray jsonPrefs = message.getJSONArray("preferences");
|
|
|
|
JSONArray jsonValues = new JSONArray();
|
|
|
|
|
|
|
|
for (int i = 0; i < jsonPrefs.length(); i++) {
|
|
|
|
JSONObject pref = jsonPrefs.getJSONObject(i);
|
|
|
|
String name = pref.getString("name");
|
|
|
|
String type = pref.getString("type");
|
|
|
|
JSONObject jsonValue = new JSONObject();
|
|
|
|
jsonValue.put("name", name);
|
|
|
|
jsonValue.put("type", type);
|
|
|
|
try {
|
|
|
|
if ("bool".equals(type)) {
|
|
|
|
boolean value = prefs.getBoolean(name, false);
|
|
|
|
jsonValue.put("value", value);
|
|
|
|
} else if ("int".equals(type)) {
|
|
|
|
int value = prefs.getInt(name, 0);
|
|
|
|
jsonValue.put("value", value);
|
|
|
|
} else if ("string".equals(type)) {
|
|
|
|
String value = prefs.getString(name, "");
|
|
|
|
jsonValue.put("value", value);
|
|
|
|
} else {
|
|
|
|
Log.w(LOGTAG, "Unknown pref value type [" + type + "] for pref [" + name + "]");
|
|
|
|
}
|
|
|
|
} catch (ClassCastException e) {
|
|
|
|
// Thrown if there is a preference with the given name that is
|
|
|
|
// not the right type.
|
|
|
|
Log.w(LOGTAG, "Wrong pref value type [" + type + "] for pref [" + name + "]");
|
|
|
|
}
|
|
|
|
jsonValues.put(jsonValue);
|
|
|
|
}
|
|
|
|
|
2014-02-11 17:16:00 +00:00
|
|
|
return jsonValues;
|
2013-05-11 02:46:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private static class ChangeListener
|
|
|
|
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
2014-05-10 15:39:53 +00:00
|
|
|
public final Scope scope;
|
2013-05-11 02:46:25 +00:00
|
|
|
public final String branch;
|
2014-05-10 15:39:53 +00:00
|
|
|
public final String profileName;
|
2013-05-11 02:46:25 +00:00
|
|
|
|
2014-05-10 15:39:53 +00:00
|
|
|
public ChangeListener(final Scope scope, final String branch, final String profileName) {
|
|
|
|
this.scope = scope;
|
2013-05-11 02:46:25 +00:00
|
|
|
this.branch = branch;
|
2014-05-10 15:39:53 +00:00
|
|
|
this.profileName = profileName;
|
2013-05-11 02:46:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
|
|
|
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
|
|
|
Log.v(LOGTAG, "Got onSharedPreferenceChanged");
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
final JSONObject msg = new JSONObject();
|
2014-05-10 15:39:53 +00:00
|
|
|
msg.put("scope", this.scope.key);
|
2013-05-11 02:46:25 +00:00
|
|
|
msg.put("branch", this.branch);
|
2014-05-10 15:39:53 +00:00
|
|
|
msg.put("profileName", this.profileName);
|
2013-05-11 02:46:25 +00:00
|
|
|
msg.put("key", key);
|
|
|
|
|
|
|
|
// Truly, this is awful, but the API impedence is strong: there
|
|
|
|
// is no way to get a single untyped value from a
|
|
|
|
// SharedPreferences instance.
|
|
|
|
msg.put("value", sharedPreferences.getAll().get(key));
|
|
|
|
|
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SharedPreferences:Changed", msg.toString()));
|
|
|
|
} catch (JSONException e) {
|
|
|
|
Log.e(LOGTAG, "Got exception creating JSON object", e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register or unregister a SharedPreferences.OnSharedPreferenceChangeListener.
|
|
|
|
*
|
|
|
|
* message.branch must exist, and should be a String SharedPreferences
|
|
|
|
* branch name, or null for the default branch.
|
|
|
|
* message.enable should be a boolean: true to enable listening, false to
|
|
|
|
* disable listening.
|
|
|
|
*/
|
|
|
|
private void handleObserve(JSONObject message) throws JSONException {
|
2014-05-10 15:39:53 +00:00
|
|
|
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) {
|
2013-05-11 02:46:25 +00:00
|
|
|
Log.e(LOGTAG, "No branch specified for SharedPreference:Observe; aborting.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// mListeners is only modified in this one observer, which is called
|
|
|
|
// from Gecko serially.
|
|
|
|
if (enable && !this.mListeners.containsKey(branch)) {
|
2014-05-10 15:39:53 +00:00
|
|
|
SharedPreferences.OnSharedPreferenceChangeListener listener
|
|
|
|
= new ChangeListener(scope, branch, profileName);
|
2013-05-11 02:46:25 +00:00
|
|
|
this.mListeners.put(branch, listener);
|
|
|
|
prefs.registerOnSharedPreferenceChangeListener(listener);
|
|
|
|
}
|
|
|
|
if (!enable && this.mListeners.containsKey(branch)) {
|
2014-05-10 15:39:53 +00:00
|
|
|
SharedPreferences.OnSharedPreferenceChangeListener listener
|
|
|
|
= this.mListeners.remove(branch);
|
2013-05-11 02:46:25 +00:00
|
|
|
prefs.unregisterOnSharedPreferenceChangeListener(listener);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void handleMessage(String event, JSONObject message) {
|
|
|
|
// Everything here is synchronous and serial, so we need not worry about
|
|
|
|
// overwriting an in-progress response.
|
|
|
|
try {
|
|
|
|
if (event.equals("SharedPreferences:Set")) {
|
|
|
|
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
|
|
|
Log.v(LOGTAG, "Got SharedPreferences:Set message.");
|
|
|
|
}
|
|
|
|
handleSet(message);
|
|
|
|
} else if (event.equals("SharedPreferences:Get")) {
|
|
|
|
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
|
|
|
Log.v(LOGTAG, "Got SharedPreferences:Get message.");
|
|
|
|
}
|
2014-02-11 17:16:00 +00:00
|
|
|
JSONObject obj = new JSONObject();
|
|
|
|
obj.put("values", handleGet(message));
|
|
|
|
EventDispatcher.sendResponse(message, obj);
|
2013-05-11 02:46:25 +00:00
|
|
|
} else if (event.equals("SharedPreferences:Observe")) {
|
|
|
|
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
|
|
|
Log.v(LOGTAG, "Got SharedPreferences:Observe message.");
|
|
|
|
}
|
|
|
|
handleObserve(message);
|
|
|
|
} else {
|
|
|
|
Log.e(LOGTAG, "SharedPreferencesHelper got unexpected message " + event);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} catch (JSONException e) {
|
|
|
|
Log.e(LOGTAG, "Got exception in handleMessage handling event " + event, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|