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