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