diff options
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java')
-rw-r--r-- | mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java b/mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java new file mode 100644 index 000000000..1066da079 --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java @@ -0,0 +1,311 @@ +/* -*- 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; + +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; +import java.util.HashMap; + +/** + * Helper class to get, set, and observe Android Shared Preferences. + */ +public final class SharedPreferencesHelper + implements GeckoEventListener +{ + public static final String LOGTAG = "GeckoAndSharedPrefs"; + + // Calculate this once, at initialization. isLoggable is too expensive to + // have in-line in each log call. + private static final boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE); + + 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 + // 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>(); + + EventDispatcher dispatcher = GeckoApp.getEventDispatcher(); + if (dispatcher == null) { + Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException()); + return; + } + dispatcher.registerGeckoThreadListener(this, + "SharedPreferences:Set", + "SharedPreferences:Get", + "SharedPreferences:Observe"); + } + + public synchronized void uninit() { + EventDispatcher dispatcher = GeckoApp.getEventDispatcher(); + if (dispatcher == null) { + Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException()); + return; + } + dispatcher.unregisterGeckoThreadListener(this, + "SharedPreferences:Set", + "SharedPreferences:Get", + "SharedPreferences:Observe"); + } + + 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; + } + + /** + * 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 { + SharedPreferences.Editor editor = getSharedPreferences(message).edit(); + + 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 + "]"); + } + editor.apply(); + } + } + + /** + * 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"]. + */ + private JSONArray handleGet(JSONObject message) throws JSONException { + SharedPreferences prefs = getSharedPreferences(message); + 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); + } + + return jsonValues; + } + + private static class ChangeListener + implements SharedPreferences.OnSharedPreferenceChangeListener { + public final Scope scope; + public final String branch; + public final String profileName; + + public ChangeListener(final Scope scope, final String branch, final String profileName) { + this.scope = scope; + this.branch = branch; + this.profileName = profileName; + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (logVerbose) { + Log.v(LOGTAG, "Got onSharedPreferenceChanged"); + } + 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 impedance is strong: there + // is no way to get a single untyped value from a + // SharedPreferences instance. + msg.put("value", sharedPreferences.getAll().get(key)); + + GeckoAppShell.notifyObservers("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 { + 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; + } + + // 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(scope, branch, profileName); + this.mListeners.put(branch, listener); + prefs.registerOnSharedPreferenceChangeListener(listener); + } + if (!enable && this.mListeners.containsKey(branch)) { + SharedPreferences.OnSharedPreferenceChangeListener listener + = this.mListeners.remove(branch); + 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 (logVerbose) { + Log.v(LOGTAG, "Got SharedPreferences:Set message."); + } + handleSet(message); + } else if (event.equals("SharedPreferences:Get")) { + if (logVerbose) { + Log.v(LOGTAG, "Got SharedPreferences:Get message."); + } + JSONObject obj = new JSONObject(); + obj.put("values", handleGet(message)); + EventDispatcher.sendResponse(message, obj); + } else if (event.equals("SharedPreferences:Observe")) { + if (logVerbose) { + 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; + } + } +} |