Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | package org.mozilla.gecko; |
michael@0 | 7 | |
michael@0 | 8 | import org.mozilla.gecko.EventDispatcher; |
michael@0 | 9 | import org.mozilla.gecko.util.GeckoEventListener; |
michael@0 | 10 | |
michael@0 | 11 | import org.json.JSONArray; |
michael@0 | 12 | import org.json.JSONException; |
michael@0 | 13 | import org.json.JSONObject; |
michael@0 | 14 | |
michael@0 | 15 | import android.content.Context; |
michael@0 | 16 | import android.content.SharedPreferences; |
michael@0 | 17 | import android.util.Log; |
michael@0 | 18 | |
michael@0 | 19 | import java.util.Map; |
michael@0 | 20 | import java.util.HashMap; |
michael@0 | 21 | |
michael@0 | 22 | /** |
michael@0 | 23 | * Helper class to get, set, and observe Android Shared Preferences. |
michael@0 | 24 | */ |
michael@0 | 25 | public final class SharedPreferencesHelper |
michael@0 | 26 | implements GeckoEventListener |
michael@0 | 27 | { |
michael@0 | 28 | public static final String LOGTAG = "GeckoAndSharedPrefs"; |
michael@0 | 29 | |
michael@0 | 30 | protected final Context mContext; |
michael@0 | 31 | |
michael@0 | 32 | // mListeners is not synchronized because it is only updated in |
michael@0 | 33 | // handleObserve, which is called from Gecko serially. |
michael@0 | 34 | protected final Map<String, SharedPreferences.OnSharedPreferenceChangeListener> mListeners; |
michael@0 | 35 | |
michael@0 | 36 | public SharedPreferencesHelper(Context context) { |
michael@0 | 37 | mContext = context; |
michael@0 | 38 | |
michael@0 | 39 | mListeners = new HashMap<String, SharedPreferences.OnSharedPreferenceChangeListener>(); |
michael@0 | 40 | |
michael@0 | 41 | EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher(); |
michael@0 | 42 | if (dispatcher == null) { |
michael@0 | 43 | Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException()); |
michael@0 | 44 | return; |
michael@0 | 45 | } |
michael@0 | 46 | dispatcher.registerEventListener("SharedPreferences:Set", this); |
michael@0 | 47 | dispatcher.registerEventListener("SharedPreferences:Get", this); |
michael@0 | 48 | dispatcher.registerEventListener("SharedPreferences:Observe", this); |
michael@0 | 49 | } |
michael@0 | 50 | |
michael@0 | 51 | public synchronized void uninit() { |
michael@0 | 52 | EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher(); |
michael@0 | 53 | if (dispatcher == null) { |
michael@0 | 54 | Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException()); |
michael@0 | 55 | return; |
michael@0 | 56 | } |
michael@0 | 57 | |
michael@0 | 58 | dispatcher.unregisterEventListener("SharedPreferences:Set", this); |
michael@0 | 59 | dispatcher.unregisterEventListener("SharedPreferences:Get", this); |
michael@0 | 60 | dispatcher.unregisterEventListener("SharedPreferences:Observe", this); |
michael@0 | 61 | } |
michael@0 | 62 | |
michael@0 | 63 | private SharedPreferences getSharedPreferences(String branch) { |
michael@0 | 64 | if (branch == null) { |
michael@0 | 65 | return GeckoSharedPrefs.forApp(mContext); |
michael@0 | 66 | } else { |
michael@0 | 67 | return mContext.getSharedPreferences(branch, Context.MODE_PRIVATE); |
michael@0 | 68 | } |
michael@0 | 69 | } |
michael@0 | 70 | |
michael@0 | 71 | /** |
michael@0 | 72 | * Set many SharedPreferences in Android. |
michael@0 | 73 | * |
michael@0 | 74 | * message.branch must exist, and should be a String SharedPreferences |
michael@0 | 75 | * branch name, or null for the default branch. |
michael@0 | 76 | * message.preferences should be an array of preferences. Each preference |
michael@0 | 77 | * must include a String name, a String type in ["bool", "int", "string"], |
michael@0 | 78 | * and an Object value. |
michael@0 | 79 | */ |
michael@0 | 80 | private void handleSet(JSONObject message) throws JSONException { |
michael@0 | 81 | if (!message.has("branch")) { |
michael@0 | 82 | Log.e(LOGTAG, "No branch specified for SharedPreference:Set; aborting."); |
michael@0 | 83 | return; |
michael@0 | 84 | } |
michael@0 | 85 | |
michael@0 | 86 | String branch = message.isNull("branch") ? null : message.getString("branch"); |
michael@0 | 87 | SharedPreferences.Editor editor = getSharedPreferences(branch).edit(); |
michael@0 | 88 | |
michael@0 | 89 | JSONArray jsonPrefs = message.getJSONArray("preferences"); |
michael@0 | 90 | |
michael@0 | 91 | for (int i = 0; i < jsonPrefs.length(); i++) { |
michael@0 | 92 | JSONObject pref = jsonPrefs.getJSONObject(i); |
michael@0 | 93 | String name = pref.getString("name"); |
michael@0 | 94 | String type = pref.getString("type"); |
michael@0 | 95 | if ("bool".equals(type)) { |
michael@0 | 96 | editor.putBoolean(name, pref.getBoolean("value")); |
michael@0 | 97 | } else if ("int".equals(type)) { |
michael@0 | 98 | editor.putInt(name, pref.getInt("value")); |
michael@0 | 99 | } else if ("string".equals(type)) { |
michael@0 | 100 | editor.putString(name, pref.getString("value")); |
michael@0 | 101 | } else { |
michael@0 | 102 | Log.w(LOGTAG, "Unknown pref value type [" + type + "] for pref [" + name + "]"); |
michael@0 | 103 | } |
michael@0 | 104 | editor.commit(); |
michael@0 | 105 | } |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | /** |
michael@0 | 109 | * Get many SharedPreferences from Android. |
michael@0 | 110 | * |
michael@0 | 111 | * message.branch must exist, and should be a String SharedPreferences |
michael@0 | 112 | * branch name, or null for the default branch. |
michael@0 | 113 | * message.preferences should be an array of preferences. Each preference |
michael@0 | 114 | * must include a String name, and a String type in ["bool", "int", |
michael@0 | 115 | * "string"]. |
michael@0 | 116 | */ |
michael@0 | 117 | private JSONArray handleGet(JSONObject message) throws JSONException { |
michael@0 | 118 | if (!message.has("branch")) { |
michael@0 | 119 | Log.e(LOGTAG, "No branch specified for SharedPreference:Get; aborting."); |
michael@0 | 120 | return null; |
michael@0 | 121 | } |
michael@0 | 122 | |
michael@0 | 123 | String branch = message.isNull("branch") ? null : message.getString("branch"); |
michael@0 | 124 | SharedPreferences prefs = getSharedPreferences(branch); |
michael@0 | 125 | JSONArray jsonPrefs = message.getJSONArray("preferences"); |
michael@0 | 126 | JSONArray jsonValues = new JSONArray(); |
michael@0 | 127 | |
michael@0 | 128 | for (int i = 0; i < jsonPrefs.length(); i++) { |
michael@0 | 129 | JSONObject pref = jsonPrefs.getJSONObject(i); |
michael@0 | 130 | String name = pref.getString("name"); |
michael@0 | 131 | String type = pref.getString("type"); |
michael@0 | 132 | JSONObject jsonValue = new JSONObject(); |
michael@0 | 133 | jsonValue.put("name", name); |
michael@0 | 134 | jsonValue.put("type", type); |
michael@0 | 135 | try { |
michael@0 | 136 | if ("bool".equals(type)) { |
michael@0 | 137 | boolean value = prefs.getBoolean(name, false); |
michael@0 | 138 | jsonValue.put("value", value); |
michael@0 | 139 | } else if ("int".equals(type)) { |
michael@0 | 140 | int value = prefs.getInt(name, 0); |
michael@0 | 141 | jsonValue.put("value", value); |
michael@0 | 142 | } else if ("string".equals(type)) { |
michael@0 | 143 | String value = prefs.getString(name, ""); |
michael@0 | 144 | jsonValue.put("value", value); |
michael@0 | 145 | } else { |
michael@0 | 146 | Log.w(LOGTAG, "Unknown pref value type [" + type + "] for pref [" + name + "]"); |
michael@0 | 147 | } |
michael@0 | 148 | } catch (ClassCastException e) { |
michael@0 | 149 | // Thrown if there is a preference with the given name that is |
michael@0 | 150 | // not the right type. |
michael@0 | 151 | Log.w(LOGTAG, "Wrong pref value type [" + type + "] for pref [" + name + "]"); |
michael@0 | 152 | } |
michael@0 | 153 | jsonValues.put(jsonValue); |
michael@0 | 154 | } |
michael@0 | 155 | |
michael@0 | 156 | return jsonValues; |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | private static class ChangeListener |
michael@0 | 160 | implements SharedPreferences.OnSharedPreferenceChangeListener { |
michael@0 | 161 | public final String branch; |
michael@0 | 162 | |
michael@0 | 163 | public ChangeListener(final String branch) { |
michael@0 | 164 | this.branch = branch; |
michael@0 | 165 | } |
michael@0 | 166 | |
michael@0 | 167 | @Override |
michael@0 | 168 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { |
michael@0 | 169 | if (Log.isLoggable(LOGTAG, Log.VERBOSE)) { |
michael@0 | 170 | Log.v(LOGTAG, "Got onSharedPreferenceChanged"); |
michael@0 | 171 | } |
michael@0 | 172 | try { |
michael@0 | 173 | final JSONObject msg = new JSONObject(); |
michael@0 | 174 | msg.put("branch", this.branch); |
michael@0 | 175 | msg.put("key", key); |
michael@0 | 176 | |
michael@0 | 177 | // Truly, this is awful, but the API impedence is strong: there |
michael@0 | 178 | // is no way to get a single untyped value from a |
michael@0 | 179 | // SharedPreferences instance. |
michael@0 | 180 | msg.put("value", sharedPreferences.getAll().get(key)); |
michael@0 | 181 | |
michael@0 | 182 | GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SharedPreferences:Changed", msg.toString())); |
michael@0 | 183 | } catch (JSONException e) { |
michael@0 | 184 | Log.e(LOGTAG, "Got exception creating JSON object", e); |
michael@0 | 185 | return; |
michael@0 | 186 | } |
michael@0 | 187 | } |
michael@0 | 188 | } |
michael@0 | 189 | |
michael@0 | 190 | /** |
michael@0 | 191 | * Register or unregister a SharedPreferences.OnSharedPreferenceChangeListener. |
michael@0 | 192 | * |
michael@0 | 193 | * message.branch must exist, and should be a String SharedPreferences |
michael@0 | 194 | * branch name, or null for the default branch. |
michael@0 | 195 | * message.enable should be a boolean: true to enable listening, false to |
michael@0 | 196 | * disable listening. |
michael@0 | 197 | */ |
michael@0 | 198 | private void handleObserve(JSONObject message) throws JSONException { |
michael@0 | 199 | if (!message.has("branch")) { |
michael@0 | 200 | Log.e(LOGTAG, "No branch specified for SharedPreference:Observe; aborting."); |
michael@0 | 201 | return; |
michael@0 | 202 | } |
michael@0 | 203 | |
michael@0 | 204 | String branch = message.isNull("branch") ? null : message.getString("branch"); |
michael@0 | 205 | SharedPreferences prefs = getSharedPreferences(branch); |
michael@0 | 206 | boolean enable = message.getBoolean("enable"); |
michael@0 | 207 | |
michael@0 | 208 | // mListeners is only modified in this one observer, which is called |
michael@0 | 209 | // from Gecko serially. |
michael@0 | 210 | if (enable && !this.mListeners.containsKey(branch)) { |
michael@0 | 211 | SharedPreferences.OnSharedPreferenceChangeListener listener = new ChangeListener(branch); |
michael@0 | 212 | this.mListeners.put(branch, listener); |
michael@0 | 213 | prefs.registerOnSharedPreferenceChangeListener(listener); |
michael@0 | 214 | } |
michael@0 | 215 | if (!enable && this.mListeners.containsKey(branch)) { |
michael@0 | 216 | SharedPreferences.OnSharedPreferenceChangeListener listener = this.mListeners.remove(branch); |
michael@0 | 217 | prefs.unregisterOnSharedPreferenceChangeListener(listener); |
michael@0 | 218 | } |
michael@0 | 219 | } |
michael@0 | 220 | |
michael@0 | 221 | @Override |
michael@0 | 222 | public void handleMessage(String event, JSONObject message) { |
michael@0 | 223 | // Everything here is synchronous and serial, so we need not worry about |
michael@0 | 224 | // overwriting an in-progress response. |
michael@0 | 225 | try { |
michael@0 | 226 | if (event.equals("SharedPreferences:Set")) { |
michael@0 | 227 | if (Log.isLoggable(LOGTAG, Log.VERBOSE)) { |
michael@0 | 228 | Log.v(LOGTAG, "Got SharedPreferences:Set message."); |
michael@0 | 229 | } |
michael@0 | 230 | handleSet(message); |
michael@0 | 231 | } else if (event.equals("SharedPreferences:Get")) { |
michael@0 | 232 | if (Log.isLoggable(LOGTAG, Log.VERBOSE)) { |
michael@0 | 233 | Log.v(LOGTAG, "Got SharedPreferences:Get message."); |
michael@0 | 234 | } |
michael@0 | 235 | JSONObject obj = new JSONObject(); |
michael@0 | 236 | obj.put("values", handleGet(message)); |
michael@0 | 237 | EventDispatcher.sendResponse(message, obj); |
michael@0 | 238 | } else if (event.equals("SharedPreferences:Observe")) { |
michael@0 | 239 | if (Log.isLoggable(LOGTAG, Log.VERBOSE)) { |
michael@0 | 240 | Log.v(LOGTAG, "Got SharedPreferences:Observe message."); |
michael@0 | 241 | } |
michael@0 | 242 | handleObserve(message); |
michael@0 | 243 | } else { |
michael@0 | 244 | Log.e(LOGTAG, "SharedPreferencesHelper got unexpected message " + event); |
michael@0 | 245 | return; |
michael@0 | 246 | } |
michael@0 | 247 | } catch (JSONException e) { |
michael@0 | 248 | Log.e(LOGTAG, "Got exception in handleMessage handling event " + event, e); |
michael@0 | 249 | return; |
michael@0 | 250 | } |
michael@0 | 251 | } |
michael@0 | 252 | } |