1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/SharedPreferencesHelper.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,252 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko; 1.10 + 1.11 +import org.mozilla.gecko.EventDispatcher; 1.12 +import org.mozilla.gecko.util.GeckoEventListener; 1.13 + 1.14 +import org.json.JSONArray; 1.15 +import org.json.JSONException; 1.16 +import org.json.JSONObject; 1.17 + 1.18 +import android.content.Context; 1.19 +import android.content.SharedPreferences; 1.20 +import android.util.Log; 1.21 + 1.22 +import java.util.Map; 1.23 +import java.util.HashMap; 1.24 + 1.25 +/** 1.26 + * Helper class to get, set, and observe Android Shared Preferences. 1.27 + */ 1.28 +public final class SharedPreferencesHelper 1.29 + implements GeckoEventListener 1.30 +{ 1.31 + public static final String LOGTAG = "GeckoAndSharedPrefs"; 1.32 + 1.33 + protected final Context mContext; 1.34 + 1.35 + // mListeners is not synchronized because it is only updated in 1.36 + // handleObserve, which is called from Gecko serially. 1.37 + protected final Map<String, SharedPreferences.OnSharedPreferenceChangeListener> mListeners; 1.38 + 1.39 + public SharedPreferencesHelper(Context context) { 1.40 + mContext = context; 1.41 + 1.42 + mListeners = new HashMap<String, SharedPreferences.OnSharedPreferenceChangeListener>(); 1.43 + 1.44 + EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher(); 1.45 + if (dispatcher == null) { 1.46 + Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException()); 1.47 + return; 1.48 + } 1.49 + dispatcher.registerEventListener("SharedPreferences:Set", this); 1.50 + dispatcher.registerEventListener("SharedPreferences:Get", this); 1.51 + dispatcher.registerEventListener("SharedPreferences:Observe", this); 1.52 + } 1.53 + 1.54 + public synchronized void uninit() { 1.55 + EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher(); 1.56 + if (dispatcher == null) { 1.57 + Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException()); 1.58 + return; 1.59 + } 1.60 + 1.61 + dispatcher.unregisterEventListener("SharedPreferences:Set", this); 1.62 + dispatcher.unregisterEventListener("SharedPreferences:Get", this); 1.63 + dispatcher.unregisterEventListener("SharedPreferences:Observe", this); 1.64 + } 1.65 + 1.66 + private SharedPreferences getSharedPreferences(String branch) { 1.67 + if (branch == null) { 1.68 + return GeckoSharedPrefs.forApp(mContext); 1.69 + } else { 1.70 + return mContext.getSharedPreferences(branch, Context.MODE_PRIVATE); 1.71 + } 1.72 + } 1.73 + 1.74 + /** 1.75 + * Set many SharedPreferences in Android. 1.76 + * 1.77 + * message.branch must exist, and should be a String SharedPreferences 1.78 + * branch name, or null for the default branch. 1.79 + * message.preferences should be an array of preferences. Each preference 1.80 + * must include a String name, a String type in ["bool", "int", "string"], 1.81 + * and an Object value. 1.82 + */ 1.83 + private void handleSet(JSONObject message) throws JSONException { 1.84 + if (!message.has("branch")) { 1.85 + Log.e(LOGTAG, "No branch specified for SharedPreference:Set; aborting."); 1.86 + return; 1.87 + } 1.88 + 1.89 + String branch = message.isNull("branch") ? null : message.getString("branch"); 1.90 + SharedPreferences.Editor editor = getSharedPreferences(branch).edit(); 1.91 + 1.92 + JSONArray jsonPrefs = message.getJSONArray("preferences"); 1.93 + 1.94 + for (int i = 0; i < jsonPrefs.length(); i++) { 1.95 + JSONObject pref = jsonPrefs.getJSONObject(i); 1.96 + String name = pref.getString("name"); 1.97 + String type = pref.getString("type"); 1.98 + if ("bool".equals(type)) { 1.99 + editor.putBoolean(name, pref.getBoolean("value")); 1.100 + } else if ("int".equals(type)) { 1.101 + editor.putInt(name, pref.getInt("value")); 1.102 + } else if ("string".equals(type)) { 1.103 + editor.putString(name, pref.getString("value")); 1.104 + } else { 1.105 + Log.w(LOGTAG, "Unknown pref value type [" + type + "] for pref [" + name + "]"); 1.106 + } 1.107 + editor.commit(); 1.108 + } 1.109 + } 1.110 + 1.111 + /** 1.112 + * Get many SharedPreferences from Android. 1.113 + * 1.114 + * message.branch must exist, and should be a String SharedPreferences 1.115 + * branch name, or null for the default branch. 1.116 + * message.preferences should be an array of preferences. Each preference 1.117 + * must include a String name, and a String type in ["bool", "int", 1.118 + * "string"]. 1.119 + */ 1.120 + private JSONArray handleGet(JSONObject message) throws JSONException { 1.121 + if (!message.has("branch")) { 1.122 + Log.e(LOGTAG, "No branch specified for SharedPreference:Get; aborting."); 1.123 + return null; 1.124 + } 1.125 + 1.126 + String branch = message.isNull("branch") ? null : message.getString("branch"); 1.127 + SharedPreferences prefs = getSharedPreferences(branch); 1.128 + JSONArray jsonPrefs = message.getJSONArray("preferences"); 1.129 + JSONArray jsonValues = new JSONArray(); 1.130 + 1.131 + for (int i = 0; i < jsonPrefs.length(); i++) { 1.132 + JSONObject pref = jsonPrefs.getJSONObject(i); 1.133 + String name = pref.getString("name"); 1.134 + String type = pref.getString("type"); 1.135 + JSONObject jsonValue = new JSONObject(); 1.136 + jsonValue.put("name", name); 1.137 + jsonValue.put("type", type); 1.138 + try { 1.139 + if ("bool".equals(type)) { 1.140 + boolean value = prefs.getBoolean(name, false); 1.141 + jsonValue.put("value", value); 1.142 + } else if ("int".equals(type)) { 1.143 + int value = prefs.getInt(name, 0); 1.144 + jsonValue.put("value", value); 1.145 + } else if ("string".equals(type)) { 1.146 + String value = prefs.getString(name, ""); 1.147 + jsonValue.put("value", value); 1.148 + } else { 1.149 + Log.w(LOGTAG, "Unknown pref value type [" + type + "] for pref [" + name + "]"); 1.150 + } 1.151 + } catch (ClassCastException e) { 1.152 + // Thrown if there is a preference with the given name that is 1.153 + // not the right type. 1.154 + Log.w(LOGTAG, "Wrong pref value type [" + type + "] for pref [" + name + "]"); 1.155 + } 1.156 + jsonValues.put(jsonValue); 1.157 + } 1.158 + 1.159 + return jsonValues; 1.160 + } 1.161 + 1.162 + private static class ChangeListener 1.163 + implements SharedPreferences.OnSharedPreferenceChangeListener { 1.164 + public final String branch; 1.165 + 1.166 + public ChangeListener(final String branch) { 1.167 + this.branch = branch; 1.168 + } 1.169 + 1.170 + @Override 1.171 + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 1.172 + if (Log.isLoggable(LOGTAG, Log.VERBOSE)) { 1.173 + Log.v(LOGTAG, "Got onSharedPreferenceChanged"); 1.174 + } 1.175 + try { 1.176 + final JSONObject msg = new JSONObject(); 1.177 + msg.put("branch", this.branch); 1.178 + msg.put("key", key); 1.179 + 1.180 + // Truly, this is awful, but the API impedence is strong: there 1.181 + // is no way to get a single untyped value from a 1.182 + // SharedPreferences instance. 1.183 + msg.put("value", sharedPreferences.getAll().get(key)); 1.184 + 1.185 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SharedPreferences:Changed", msg.toString())); 1.186 + } catch (JSONException e) { 1.187 + Log.e(LOGTAG, "Got exception creating JSON object", e); 1.188 + return; 1.189 + } 1.190 + } 1.191 + } 1.192 + 1.193 + /** 1.194 + * Register or unregister a SharedPreferences.OnSharedPreferenceChangeListener. 1.195 + * 1.196 + * message.branch must exist, and should be a String SharedPreferences 1.197 + * branch name, or null for the default branch. 1.198 + * message.enable should be a boolean: true to enable listening, false to 1.199 + * disable listening. 1.200 + */ 1.201 + private void handleObserve(JSONObject message) throws JSONException { 1.202 + if (!message.has("branch")) { 1.203 + Log.e(LOGTAG, "No branch specified for SharedPreference:Observe; aborting."); 1.204 + return; 1.205 + } 1.206 + 1.207 + String branch = message.isNull("branch") ? null : message.getString("branch"); 1.208 + SharedPreferences prefs = getSharedPreferences(branch); 1.209 + boolean enable = message.getBoolean("enable"); 1.210 + 1.211 + // mListeners is only modified in this one observer, which is called 1.212 + // from Gecko serially. 1.213 + if (enable && !this.mListeners.containsKey(branch)) { 1.214 + SharedPreferences.OnSharedPreferenceChangeListener listener = new ChangeListener(branch); 1.215 + this.mListeners.put(branch, listener); 1.216 + prefs.registerOnSharedPreferenceChangeListener(listener); 1.217 + } 1.218 + if (!enable && this.mListeners.containsKey(branch)) { 1.219 + SharedPreferences.OnSharedPreferenceChangeListener listener = this.mListeners.remove(branch); 1.220 + prefs.unregisterOnSharedPreferenceChangeListener(listener); 1.221 + } 1.222 + } 1.223 + 1.224 + @Override 1.225 + public void handleMessage(String event, JSONObject message) { 1.226 + // Everything here is synchronous and serial, so we need not worry about 1.227 + // overwriting an in-progress response. 1.228 + try { 1.229 + if (event.equals("SharedPreferences:Set")) { 1.230 + if (Log.isLoggable(LOGTAG, Log.VERBOSE)) { 1.231 + Log.v(LOGTAG, "Got SharedPreferences:Set message."); 1.232 + } 1.233 + handleSet(message); 1.234 + } else if (event.equals("SharedPreferences:Get")) { 1.235 + if (Log.isLoggable(LOGTAG, Log.VERBOSE)) { 1.236 + Log.v(LOGTAG, "Got SharedPreferences:Get message."); 1.237 + } 1.238 + JSONObject obj = new JSONObject(); 1.239 + obj.put("values", handleGet(message)); 1.240 + EventDispatcher.sendResponse(message, obj); 1.241 + } else if (event.equals("SharedPreferences:Observe")) { 1.242 + if (Log.isLoggable(LOGTAG, Log.VERBOSE)) { 1.243 + Log.v(LOGTAG, "Got SharedPreferences:Observe message."); 1.244 + } 1.245 + handleObserve(message); 1.246 + } else { 1.247 + Log.e(LOGTAG, "SharedPreferencesHelper got unexpected message " + event); 1.248 + return; 1.249 + } 1.250 + } catch (JSONException e) { 1.251 + Log.e(LOGTAG, "Got exception in handleMessage handling event " + event, e); 1.252 + return; 1.253 + } 1.254 + } 1.255 +}