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.annotation.RobocopTarget; 9 import org.mozilla.gecko.annotation.WrapForJNI; 10 11 import android.support.v4.util.SimpleArrayMap; 12 import android.util.Log; 13 import android.util.SparseArray; 14 15 import java.util.ArrayList; 16 import java.util.HashSet; 17 import java.util.Iterator; 18 import java.util.List; 19 20 /** 21 * Helper class to get/set gecko prefs. 22 */ 23 public final class PrefsHelper { 24 private static final String LOGTAG = "GeckoPrefsHelper"; 25 26 // Map pref name to ArrayList for multiple observers or PrefHandler for single observer. 27 private static final SimpleArrayMap<String, Object> OBSERVERS = new SimpleArrayMap<>(); 28 private static final HashSet<String> INT_TO_STRING_PREFS = new HashSet<>(8); 29 private static final HashSet<String> INT_TO_BOOL_PREFS = new HashSet<>(2); 30 31 static { 32 INT_TO_STRING_PREFS.add("browser.chrome.titlebarMode"); 33 INT_TO_STRING_PREFS.add("network.cookie.cookieBehavior"); 34 INT_TO_STRING_PREFS.add("home.sync.updateMode"); 35 INT_TO_STRING_PREFS.add("browser.image_blocking"); 36 INT_TO_BOOL_PREFS.add("browser.display.use_document_fonts"); 37 } 38 39 @WrapForJNI 40 private static final int PREF_INVALID = -1; 41 @WrapForJNI 42 private static final int PREF_FINISH = 0; 43 @WrapForJNI 44 private static final int PREF_BOOL = 1; 45 @WrapForJNI 46 private static final int PREF_INT = 2; 47 @WrapForJNI 48 private static final int PREF_STRING = 3; 49 50 @WrapForJNI(stubName = "GetPrefs", dispatchTo = "gecko") nativeGetPrefs(String[] prefNames, PrefHandler handler)51 private static native void nativeGetPrefs(String[] prefNames, PrefHandler handler); 52 @WrapForJNI(stubName = "SetPref", dispatchTo = "gecko") nativeSetPref(String prefName, boolean flush, int type, boolean boolVal, int intVal, String strVal)53 private static native void nativeSetPref(String prefName, boolean flush, int type, 54 boolean boolVal, int intVal, String strVal); 55 @WrapForJNI(stubName = "AddObserver", dispatchTo = "gecko") nativeAddObserver(String[] prefNames, PrefHandler handler, String[] prefsToObserve)56 private static native void nativeAddObserver(String[] prefNames, PrefHandler handler, 57 String[] prefsToObserve); 58 @WrapForJNI(stubName = "RemoveObserver", dispatchTo = "gecko") nativeRemoveObserver(String[] prefToUnobserve)59 private static native void nativeRemoveObserver(String[] prefToUnobserve); 60 61 @RobocopTarget getPrefs(final String[] prefNames, final PrefHandler callback)62 public static void getPrefs(final String[] prefNames, final PrefHandler callback) { 63 if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { 64 nativeGetPrefs(prefNames, callback); 65 } else { 66 GeckoThread.queueNativeCallUntil( 67 GeckoThread.State.PROFILE_READY, PrefsHelper.class, "nativeGetPrefs", 68 String[].class, prefNames, PrefHandler.class, callback); 69 } 70 } 71 getPref(final String prefName, final PrefHandler callback)72 public static void getPref(final String prefName, final PrefHandler callback) { 73 getPrefs(new String[] { prefName }, callback); 74 } 75 getPrefs(final ArrayList<String> prefNames, final PrefHandler callback)76 public static void getPrefs(final ArrayList<String> prefNames, final PrefHandler callback) { 77 getPrefs(prefNames.toArray(new String[prefNames.size()]), callback); 78 } 79 80 @RobocopTarget setPref(final String pref, final Object value, final boolean flush)81 public static void setPref(final String pref, final Object value, final boolean flush) { 82 final int type; 83 boolean boolVal = false; 84 int intVal = 0; 85 String strVal = null; 86 87 if (INT_TO_STRING_PREFS.contains(pref)) { 88 // When sending to Java, we normalized special preferences that use integers 89 // and strings to represent booleans. Here, we convert them back to their 90 // actual types so we can store them. 91 type = PREF_INT; 92 intVal = Integer.parseInt(String.valueOf(value)); 93 } else if (INT_TO_BOOL_PREFS.contains(pref)) { 94 type = PREF_INT; 95 intVal = (Boolean) value ? 1 : 0; 96 } else if (value instanceof Boolean) { 97 type = PREF_BOOL; 98 boolVal = (Boolean) value; 99 } else if (value instanceof Integer) { 100 type = PREF_INT; 101 intVal = (Integer) value; 102 } else { 103 type = PREF_STRING; 104 strVal = String.valueOf(value); 105 } 106 107 if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { 108 nativeSetPref(pref, flush, type, boolVal, intVal, strVal); 109 } else { 110 GeckoThread.queueNativeCallUntil( 111 GeckoThread.State.PROFILE_READY, PrefsHelper.class, "nativeSetPref", 112 String.class, pref, flush, type, boolVal, intVal, String.class, strVal); 113 } 114 } 115 setPref(final String pref, final Object value)116 public static void setPref(final String pref, final Object value) { 117 setPref(pref, value, /* flush */ false); 118 } 119 120 @RobocopTarget addObserver(final String[] prefNames, final PrefHandler handler)121 public synchronized static void addObserver(final String[] prefNames, 122 final PrefHandler handler) { 123 List<String> prefsToObserve = null; 124 125 for (String pref : prefNames) { 126 final Object existing = OBSERVERS.get(pref); 127 128 if (existing == null) { 129 // Not observing yet, so add observer. 130 if (prefsToObserve == null) { 131 prefsToObserve = new ArrayList<>(prefNames.length); 132 } 133 prefsToObserve.add(pref); 134 OBSERVERS.put(pref, handler); 135 136 } else if (existing instanceof PrefHandler) { 137 // Already observing one, so turn it into an array. 138 final List<PrefHandler> handlerList = new ArrayList<>(2); 139 handlerList.add((PrefHandler) existing); 140 handlerList.add(handler); 141 OBSERVERS.put(pref, handlerList); 142 143 } else { 144 // Already observing multiple, so add to existing array. 145 @SuppressWarnings("unchecked") 146 final List<PrefHandler> handlerList = (List) existing; 147 handlerList.add(handler); 148 } 149 } 150 151 final String[] namesToObserve = prefsToObserve == null ? null : 152 prefsToObserve.toArray(new String[prefsToObserve.size()]); 153 154 if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { 155 nativeAddObserver(prefNames, handler, namesToObserve); 156 } else { 157 GeckoThread.queueNativeCallUntil( 158 GeckoThread.State.PROFILE_READY, PrefsHelper.class, "nativeAddObserver", 159 String[].class, prefNames, PrefHandler.class, handler, 160 String[].class, namesToObserve); 161 } 162 } 163 164 @RobocopTarget removeObserver(final PrefHandler handler)165 public synchronized static void removeObserver(final PrefHandler handler) { 166 List<String> prefsToUnobserve = null; 167 168 for (int i = OBSERVERS.size() - 1; i >= 0; i--) { 169 final Object existing = OBSERVERS.valueAt(i); 170 boolean removeObserver = false; 171 172 if (existing == handler) { 173 removeObserver = true; 174 175 } else if (!(existing instanceof PrefHandler)) { 176 // Removing existing handler from list. 177 @SuppressWarnings("unchecked") 178 final List<PrefHandler> handlerList = (List) existing; 179 if (handlerList.remove(handler) && handlerList.isEmpty()) { 180 removeObserver = true; 181 } 182 } 183 184 if (removeObserver) { 185 // Removed last handler, so remove observer. 186 if (prefsToUnobserve == null) { 187 prefsToUnobserve = new ArrayList<>(); 188 } 189 prefsToUnobserve.add(OBSERVERS.keyAt(i)); 190 OBSERVERS.removeAt(i); 191 } 192 } 193 194 if (prefsToUnobserve == null) { 195 return; 196 } 197 198 final String[] namesToUnobserve = 199 prefsToUnobserve.toArray(new String[prefsToUnobserve.size()]); 200 201 if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { 202 nativeRemoveObserver(namesToUnobserve); 203 } else { 204 GeckoThread.queueNativeCallUntil( 205 GeckoThread.State.PROFILE_READY, PrefsHelper.class, "nativeRemoveObserver", 206 String[].class, namesToUnobserve); 207 } 208 } 209 210 @WrapForJNI(calledFrom = "gecko") callPrefHandler(final PrefHandler handler, int type, final String pref, boolean boolVal, int intVal, String strVal)211 private static void callPrefHandler(final PrefHandler handler, int type, final String pref, 212 boolean boolVal, int intVal, String strVal) { 213 214 // Some Gecko preferences use integers or strings to reference state instead of 215 // directly representing the value. Since the Java UI uses the type to determine 216 // which ui elements to show and how to handle them, we need to normalize these 217 // preferences to the correct type. 218 if (INT_TO_STRING_PREFS.contains(pref)) { 219 type = PREF_STRING; 220 strVal = String.valueOf(intVal); 221 } else if (INT_TO_BOOL_PREFS.contains(pref)) { 222 type = PREF_BOOL; 223 boolVal = intVal == 1; 224 } 225 226 switch (type) { 227 case PREF_FINISH: 228 handler.finish(); 229 return; 230 case PREF_BOOL: 231 handler.prefValue(pref, boolVal); 232 return; 233 case PREF_INT: 234 handler.prefValue(pref, intVal); 235 return; 236 case PREF_STRING: 237 handler.prefValue(pref, strVal); 238 return; 239 } 240 throw new IllegalArgumentException(); 241 } 242 243 @WrapForJNI(calledFrom = "gecko") onPrefChange(final String pref, final int type, final boolean boolVal, final int intVal, final String strVal)244 private synchronized static void onPrefChange(final String pref, final int type, 245 final boolean boolVal, final int intVal, 246 final String strVal) { 247 final Object existing = OBSERVERS.get(pref); 248 249 if (existing == null) { 250 return; 251 } 252 253 final Iterator<PrefHandler> itor; 254 PrefHandler handler; 255 256 if (existing instanceof PrefHandler) { 257 itor = null; 258 handler = (PrefHandler) existing; 259 } else { 260 @SuppressWarnings("unchecked") 261 final List<PrefHandler> handlerList = (List) existing; 262 if (handlerList.isEmpty()) { 263 return; 264 } 265 itor = handlerList.iterator(); 266 handler = itor.next(); 267 } 268 269 do { 270 callPrefHandler(handler, type, pref, boolVal, intVal, strVal); 271 handler.finish(); 272 273 handler = itor != null && itor.hasNext() ? itor.next() : null; 274 } while (handler != null); 275 } 276 277 public interface PrefHandler { prefValue(String pref, boolean value)278 void prefValue(String pref, boolean value); prefValue(String pref, int value)279 void prefValue(String pref, int value); prefValue(String pref, String value)280 void prefValue(String pref, String value); finish()281 void finish(); 282 } 283 284 public static abstract class PrefHandlerBase implements PrefHandler { 285 @Override prefValue(String pref, boolean value)286 public void prefValue(String pref, boolean value) { 287 throw new UnsupportedOperationException( 288 "Unhandled boolean pref " + pref + "; wrong type?"); 289 } 290 291 @Override prefValue(String pref, int value)292 public void prefValue(String pref, int value) { 293 throw new UnsupportedOperationException( 294 "Unhandled int pref " + pref + "; wrong type?"); 295 } 296 297 @Override prefValue(String pref, String value)298 public void prefValue(String pref, String value) { 299 throw new UnsupportedOperationException( 300 "Unhandled String pref " + pref + "; wrong type?"); 301 } 302 303 @Override finish()304 public void finish() { 305 } 306 } 307 } 308