Bug 1243049 - Update robocop tests to use new prefs API; r=gbrown
authorJim Chen <nchen@mozilla.com>
Mon, 01 Feb 2016 17:38:14 -0500
changeset 282631 24209390c44725189f73d4653317cf34400853e4
parent 282630 c4b3686d3283f6e0b1e6540b320b40982232ee73
child 282632 a628353b83b83e812c660347af761c1ec10b8a88
push id17362
push usercbook@mozilla.com
push dateTue, 02 Feb 2016 10:54:53 +0000
treeherderfx-team@e5f1b4782e38 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgbrown
bugs1243049
milestone47.0a1
Bug 1243049 - Update robocop tests to use new prefs API; r=gbrown Change old robocop prefs API to the new API and add helper classes for getting prefs. Also switch all tests that use prefs to use the new API.
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/Actions.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeActions.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseTest.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAdobeFlash.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testCheck2.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testCheck3.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testDistribution.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testDoorHanger.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testPasswordEncrypt.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testPrefsObserver.java
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/Actions.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/Actions.java
@@ -46,38 +46,49 @@ public interface Actions {
 
     /**
      * Sends an event to Gecko.
      *
      * @param geckoEvent The geckoEvent JSONObject's type
      */
     void sendGeckoEvent(String geckoEvent, String data);
 
-    /**
-     * Sends a preferences get event to Gecko.
-     *
-     * @param requestId The id of this request.
-     * @param prefNames The preferences being requested.
-     */
-    void sendPreferencesGetEvent(int requestId, String[] prefNames);
+    public interface PrefWaiter {
+        boolean isFinished();
+        void waitForFinish();
+        void waitForFinish(long timeoutMillis, boolean failOnTimeout);
+    }
+
+    public abstract static class PrefHandlerBase implements PrefsHelper.PrefHandler {
+        /* package */ Assert asserter;
+
+        @Override // PrefsHelper.PrefHandler
+        public void prefValue(String pref, boolean value) {
+            asserter.ok(false, "Unexpected pref callback", "");
+        }
 
-    /**
-     * Sends a preferences observe event to Gecko.
-     *
-     * @param requestId The id of this request.
-     * @param prefNames The preferences being requested.
-     */
-    void sendPreferencesObserveEvent(int requestId, String[] prefNames);
+        @Override // PrefsHelper.PrefHandler
+        public void prefValue(String pref, int value) {
+            asserter.ok(false, "Unexpected pref callback", "");
+        }
+
+        @Override // PrefsHelper.PrefHandler
+        public void prefValue(String pref, String value) {
+            asserter.ok(false, "Unexpected pref callback", "");
+        }
 
-    /**
-     * Sends a preferences remove observers event to Gecko.
-     *
-     * @param requestId The id of this request.
-     */
-    void sendPreferencesRemoveObserversEvent(int requestid);
+        @Override // PrefsHelper.PrefHandler
+        public void finish() {
+        }
+    }
+
+    PrefWaiter getPrefs(String[] prefNames, PrefHandlerBase handler);
+    void setPref(String pref, Object value, boolean flush);
+    PrefWaiter addPrefsObserver(String[] prefNames, PrefHandlerBase handler);
+    void removePrefsObserver(PrefWaiter handler);
 
     /**
      * Listens for a gecko event to be sent from the Gecko instance.
      * The returned object can be used to test if the event has been
      * received. Note that only one event is listened for.
      *
      * @param geckoEvent The geckoEvent JSONObject's type
      */
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeActions.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeActions.java
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import java.util.ArrayList;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.FennecNativeDriver.LogLevel;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.LayerView.DrawListener;
@@ -185,26 +186,122 @@ public class FennecNativeActions impleme
         FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, "waiting for " + geckoEvent);
         return new GeckoEventExpecter(geckoEvent);
     }
 
     public void sendGeckoEvent(final String geckoEvent, final String data) {
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(geckoEvent, data));
     }
 
-    public void sendPreferencesGetEvent(int requestId, String[] prefNames) {
-        PrefsHelper.getPrefsById(requestId, prefNames, /* observe */ false);
+    public static final class PrefProxy implements PrefsHelper.PrefHandler, PrefWaiter {
+        public static final int MAX_WAIT_MS = 180000;
+
+        /* package */ final PrefHandlerBase target;
+        private final String[] expectedPrefs;
+        private final ArrayList<String> seenPrefs = new ArrayList<>();
+        private boolean finished = false;
+
+        /* package */ PrefProxy(PrefHandlerBase target, String[] expectedPrefs, Assert asserter) {
+            this.target = target;
+            this.expectedPrefs = expectedPrefs;
+            target.asserter = asserter;
+        }
+
+        @Override // PrefsHelper.PrefHandler
+        public void prefValue(String pref, boolean value) {
+            target.prefValue(pref, value);
+            seenPrefs.add(pref);
+        }
+
+        @Override // PrefsHelper.PrefHandler
+        public void prefValue(String pref, int value) {
+            target.prefValue(pref, value);
+            seenPrefs.add(pref);
+        }
+
+        @Override // PrefsHelper.PrefHandler
+        public void prefValue(String pref, String value) {
+            target.prefValue(pref, value);
+            seenPrefs.add(pref);
+        }
+
+        @Override // PrefsHelper.PrefHandler
+        public synchronized void finish() {
+            target.finish();
+
+            for (String pref : expectedPrefs) {
+                target.asserter.ok(seenPrefs.remove(pref), "Checking pref was seen", pref);
+            }
+            target.asserter.ok(seenPrefs.isEmpty(), "Checking unexpected prefs",
+                               TextUtils.join(", ", seenPrefs));
+
+            finished = true;
+            this.notifyAll();
+        }
+
+        @Override // PrefWaiter
+        public synchronized boolean isFinished() {
+            return finished;
+        }
+
+        @Override // PrefWaiter
+        public void waitForFinish() {
+            waitForFinish(MAX_WAIT_MS, /* failOnTimeout */ true);
+        }
+
+        @Override // PrefWaiter
+        public synchronized void waitForFinish(long timeoutMillis, boolean failOnTimeout) {
+            final long startTime = System.nanoTime();
+            while (!finished) {
+                if (System.nanoTime() - startTime
+                        >= timeoutMillis * 1e6 /* ns per ms */) {
+                    final String prefsLog = "expected " +
+                            TextUtils.join(", ", expectedPrefs) + "; got " +
+                            TextUtils.join(", ", seenPrefs.toArray()) + ".";
+                    if (failOnTimeout) {
+                        FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR);
+                        target.asserter.ok(false, "Timeout waiting for pref", prefsLog);
+                    } else {
+                        FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
+                                               "Pref timeout (" + prefsLog + ")");
+                    }
+                    break;
+                }
+                try {
+                    this.wait(1000); // Wait for 1 second at a time.
+                } catch (final InterruptedException e) {
+                    // Attempt waiting again.
+                }
+            }
+            finished = false;
+        }
     }
 
-    public void sendPreferencesObserveEvent(int requestId, String[] prefNames) {
-        PrefsHelper.getPrefsById(requestId, prefNames, /* observe */ true);
+    @Override // Actions
+    public PrefWaiter getPrefs(String[] prefNames, PrefHandlerBase handler) {
+        final PrefProxy proxy = new PrefProxy(handler, prefNames, mAsserter);
+        PrefsHelper.getPrefs(prefNames, proxy);
+        return proxy;
     }
 
-    public void sendPreferencesRemoveObserversEvent(int requestId) {
-        PrefsHelper.removePrefsObserver(requestId);
+    @Override // Actions
+    public void setPref(String pref, Object value, boolean flush) {
+        PrefsHelper.setPref(pref, value, flush);
+    }
+
+    @Override // Actions
+    public PrefWaiter addPrefsObserver(String[] prefNames, PrefHandlerBase handler) {
+        final PrefProxy proxy = new PrefProxy(handler, prefNames, mAsserter);
+        PrefsHelper.addObserver(prefNames, proxy);
+        return proxy;
+    }
+
+    @Override // Actions
+    public void removePrefsObserver(PrefWaiter proxy) {
+        PrefsHelper.removeObserver((PrefProxy) proxy);
     }
 
     class PaintExpecter implements RepeatedEventExpecter {
         private static final int MAX_WAIT_MS = 90000;
 
         private boolean mPaintDone;
         private boolean mListening;
 
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseTest.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseTest.java
@@ -57,21 +57,19 @@ import com.jayway.android.robotium.solo.
 abstract class BaseTest extends BaseRobocopTest {
     private static final int VERIFY_URL_TIMEOUT = 2000;
     private static final int MAX_WAIT_ENABLED_TEXT_MS = 10000;
     private static final int MAX_WAIT_HOME_PAGER_HIDDEN_MS = 15000;
     private static final int MAX_WAIT_VERIFY_PAGE_TITLE_MS = 15000;
     public static final int MAX_WAIT_MS = 4500;
     public static final int LONG_PRESS_TIME = 6000;
     private static final int GECKO_READY_WAIT_MS = 180000;
-    public static final int MAX_WAIT_BLOCK_FOR_EVENT_DATA_MS = 90000;
 
     protected static final String URL_HTTP_PREFIX = "http://";
 
-    private int mPreferenceRequestID = 0;
     public Device mDevice;
     protected DatabaseHelper mDatabaseHelper;
     protected int mScreenMidWidth;
     protected int mScreenMidHeight;
     private final HashSet<Integer> mKnownTabIDs = new HashSet<Integer>();
 
     protected void blockForDelayedStartup() {
         try {
@@ -881,65 +879,39 @@ abstract class BaseTest extends BaseRobo
                              "");
             }
         }
     }
 
     /**
      * Set the preference and wait for it to change before proceeding with the test.
      */
-    public void setPreferenceAndWaitForChange(final JSONObject jsonPref) {
+    public void setPreferenceAndWaitForChange(final String name, final Object value) {
         blockForGeckoReady();
-        mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
-
-        // Get the preference name from the json and store it in an array. This array
-        // will be used later while fetching the preference data.
-        String[] prefNames = new String[1];
-        try {
-            prefNames[0] = jsonPref.getString("name");
-        } catch (JSONException e) {
-            mAsserter.ok(false, "Exception in setPreferenceAndWaitForChange", getStackTraceString(e));
-        }
+        mActions.setPref(name, value, /* flush */ false);
 
         // Wait for confirmation of the pref change before proceeding with the test.
-        final int ourRequestID = mPreferenceRequestID--;
-        final Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-        mActions.sendPreferencesGetEvent(ourRequestID, prefNames);
-
-        // Wait until we get the correct "Preferences:Data" event
-        waitForCondition(new Condition() {
-            final long endTime = SystemClock.elapsedRealtime() + MAX_WAIT_BLOCK_FOR_EVENT_DATA_MS;
+        mActions.getPrefs(new String[] { name }, new Actions.PrefHandlerBase() {
 
-            @Override
-            public boolean isSatisfied() {
-                try {
-                    long timeout = endTime - SystemClock.elapsedRealtime();
-                    if (timeout < 0) {
-                        timeout = 0;
-                    }
+            @Override // Actions.PrefHandlerBase
+            public void prefValue(String pref, boolean changedValue) {
+                mAsserter.is(pref, name, "Expecting correct pref name");
+                mAsserter.ok(value instanceof Boolean, "Expecting boolean pref", "");
+                mAsserter.is(changedValue, value, "Expecting matching pref value");
+            }
 
-                    JSONObject data = new JSONObject(eventExpecter.blockForEventDataWithTimeout(timeout));
-                    int requestID = data.getInt("requestId");
-                    if (requestID != ourRequestID) {
-                        return false;
-                    }
+            @Override // Actions.PrefHandlerBase
+            public void prefValue(String pref, int changedValue) {
+                mAsserter.is(pref, name, "Expecting correct pref name");
+                mAsserter.ok(value instanceof Integer, "Expecting int pref", "");
+                mAsserter.is(changedValue, value, "Expecting matching pref value");
+            }
 
-                    JSONArray preferences = data.getJSONArray("preferences");
-                    mAsserter.is(preferences.length(), 1, "Expecting preference array to have one element");
-                    JSONObject prefs = (JSONObject) preferences.get(0);
-                    mAsserter.is(prefs.getString("name"), jsonPref.getString("name"),
-                            "Expecting returned preference name to be the same as the set name");
-                    mAsserter.is(prefs.getString("type"), jsonPref.getString("type"),
-                            "Expecting returned preference type to be the same as the set type");
-                    mAsserter.is(prefs.get("value"), jsonPref.get("value"),
-                            "Expecting returned preference value to be the same as the set value");
-                    return true;
-                } catch(JSONException e) {
-                    mAsserter.ok(false, "Exception in setPreferenceAndWaitForChange", getStackTraceString(e));
-                    // Please the java compiler
-                    return false;
-                }
+            @Override // Actions.PrefHandlerBase
+            public void prefValue(String pref, String changedValue) {
+                mAsserter.is(pref, name, "Expecting correct pref name");
+                mAsserter.ok(value instanceof CharSequence, "Expecting string pref", "");
+                mAsserter.is(changedValue, value, "Expecting matching pref value");
             }
-        }, MAX_WAIT_BLOCK_FOR_EVENT_DATA_MS);
 
-        eventExpecter.unregisterListener();
+        }).waitForFinish();
     }
 }
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAdobeFlash.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAdobeFlash.java
@@ -18,25 +18,17 @@ public class testAdobeFlash extends Pixe
     public void testLoad() {
         // This test only works on ICS and higher
         if (Build.VERSION.SDK_INT < 15) {
             blockForGeckoReady();
             return;
         }
 
         // Enable plugins
-        JSONObject jsonPref = new JSONObject();
-        try {
-            jsonPref.put("name", "plugin.enable");
-            jsonPref.put("type", "string");
-            jsonPref.put("value", "1");
-            setPreferenceAndWaitForChange(jsonPref);
-        } catch (Exception ex) {
-            mAsserter.ok(false, "exception in testAdobeFlash", ex.toString());
-        }
+        setPreferenceAndWaitForChange("plugin.enable", "1");
 
         blockForGeckoReady();
 
         String url = getAbsoluteUrl(mStringHelper.ROBOCOP_ADOBE_FLASH_URL);
         PaintedSurface painted = loadAndGetPainted(url);
 
         mAsserter.ispixel(painted.getPixelAt(0, 0), 0, 0xff, 0, "Pixel at 0, 0");
         mAsserter.ispixel(painted.getPixelAt(50, 50), 0, 0xff, 0, "Pixel at 50, 50");
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testCheck2.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testCheck2.java
@@ -11,25 +11,17 @@ public class testCheck2 extends PixelTes
     protected Type getTestType() {
         return Type.TALOS;
     }
 
     public void testCheck2() {
         String url = getAbsoluteUrl("/startup_test/fennecmark/cnn/cnn.com/index.html");
 
         // Enable double-tap zooming
-        JSONObject jsonPref = new JSONObject();
-        try {
-            jsonPref.put("name", "browser.ui.zoom.force-user-scalable");
-            jsonPref.put("type", "bool");
-            jsonPref.put("value", true);
-            setPreferenceAndWaitForChange(jsonPref);
-        } catch (Exception ex) {
-            mAsserter.ok(false, "exception in testCheck2", ex.toString());
-        }
+        setPreferenceAndWaitForChange("browser.ui.zoom.force-user-scalable", true);
 
         blockForGeckoReady();
         loadAndPaint(url);
 
         mDriver.setupScrollHandling();
 
         /*
          * for this test, we load the timecube page, and replay a recorded sequence of events
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testCheck3.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testCheck3.java
@@ -11,25 +11,17 @@ public class testCheck3 extends PixelTes
     protected Type getTestType() {
         return Type.TALOS;
     }
 
     public void testCheck3() {
         String url = getAbsoluteUrl("/facebook.com/www.facebook.com/barackobama.html");
 
         // Enable double-tap zooming
-        JSONObject jsonPref = new JSONObject();
-        try {
-            jsonPref.put("name", "browser.ui.zoom.force-user-scalable");
-            jsonPref.put("type", "bool");
-            jsonPref.put("value", true);
-            setPreferenceAndWaitForChange(jsonPref);
-        } catch (Exception ex) {
-            mAsserter.ok(false, "exception in testCheck3", ex.toString());
-        }
+        setPreferenceAndWaitForChange("browser.ui.zoom.force-user-scalable", true);
 
         blockForGeckoReady();
         loadAndPaint(url);
 
         mDriver.setupScrollHandling();
 
         /*
          * for this test, we load the timecube page, and replay a recorded sequence of events
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testDistribution.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testDistribution.java
@@ -387,88 +387,87 @@ public class testDistribution extends Co
             final JSONObject pref = (JSONObject) preferences.get(0);
             mAsserter.is(pref.getBoolean("value"), true, "check distribution add-on is enabled");
         } catch (JSONException e) {
             mAsserter.ok(false, "exception getting preferences", e.toString());
         }
     }
 
     private JSONArray getPrefs(String[] prefNames) throws JSONException {
-        Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-        mActions.sendPreferencesGetEvent(PREF_REQUEST_ID, prefNames);
+        final JSONArray result = new JSONArray();
 
-        JSONObject data = null;
-        int requestId = -1;
+        mActions.getPrefs(prefNames, new Actions.PrefHandlerBase() {
+            private void addItem(String pref, Object value) {
+                try {
+                    final JSONObject item = new JSONObject();
+                    item.put("name", pref).put("value", value);
+                    result.put(item);
+                } catch (final JSONException e) {
+                    mAsserter.ok(false, "exception getting prefs", e.toString());
+                }
+            }
 
-        // Wait until we get the correct "Preferences:Data" event
-        while (requestId != PREF_REQUEST_ID) {
-            data = new JSONObject(eventExpecter.blockForEventData());
-            requestId = data.getInt("requestId");
-        }
-        eventExpecter.unregisterListener();
+            @Override // Actions.PrefHandlerBase
+            public void prefValue(String pref, boolean value) {
+                addItem(pref, value);
+            }
 
-        return data.getJSONArray("preferences");
+            @Override // Actions.PrefHandlerBase
+            public void prefValue(String pref, int value) {
+                addItem(pref, value);
+            }
+
+            @Override // Actions.PrefHandlerBase
+            public void prefValue(String pref, String value) {
+                addItem(pref, value);
+            }
+        }).waitForFinish();
+
+        return result;
     }
 
     // Sets the distribution locale preference for the test.
     private void setTestLocale(String locale) {
         BrowserLocaleManager.getInstance().setSelectedLocale(mActivity, locale);
     }
 
     // Test localized distribution and preferences values stored in preferences.json
-    private void checkLocalizedPreferences(String aLocale) {
-        String prefAbout = "distribution.about";
-        String prefLocalizeable = "distribution.test.localizeable";
-        String prefLocalizeableOverride = "distribution.test.localizeable-override";
-
-        try {
-            final String[] prefNames = { prefAbout, prefLocalizeable, prefLocalizeableOverride };
-
-            Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendPreferencesGetEvent(PREF_REQUEST_ID, prefNames);
+    private void checkLocalizedPreferences(final String aLocale) {
+        final String prefAbout = "distribution.about";
+        final String prefLocalizeable = "distribution.test.localizeable";
+        final String prefLocalizeableOverride = "distribution.test.localizeable-override";
+        final String[] prefNames = { prefAbout, prefLocalizeable, prefLocalizeableOverride };
 
-            JSONObject data = null;
-            int requestId = -1;
-
-            // Wait until we get the correct "Preferences:Data" event
-            while (requestId != PREF_REQUEST_ID) {
-                data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getInt("requestId");
-            }
-            eventExpecter.unregisterListener();
-
-            JSONArray preferences = data.getJSONArray("preferences");
-            for (int i = 0; i < preferences.length(); i++) {
-                JSONObject pref = (JSONObject) preferences.get(i);
-                String name = pref.getString("name");
-
+        mActions.getPrefs(prefNames, new Actions.PrefHandlerBase() {
+            @Override // Actions.PrefHandlerBase
+            public void prefValue(String name, String value) {
                 if (name.equals(prefAbout)) {
                     if (aLocale.equals("en-US")) {
-                        mAsserter.is(pref.getString("value"), "Test Partner", "check " + prefAbout);
+                        mAsserter.is(value, "Test Partner", "check " + prefAbout);
                     } else if (aLocale.equals("es-MX")) {
-                        mAsserter.is(pref.getString("value"), "Afiliado de Prueba", "check " + prefAbout);
+                        mAsserter.is(value, "Afiliado de Prueba", "check " + prefAbout);
                     }
                 } else if (name.equals(prefLocalizeable)) {
                     if (aLocale.equals("en-US")) {
-                        mAsserter.is(pref.getString("value"), "http://test.org/en-US/en-US/", "check " + prefLocalizeable);
+                        mAsserter.is(value, "http://test.org/en-US/en-US/", "check " + prefLocalizeable);
                     } else if (aLocale.equals("es-MX")) {
-                        mAsserter.is(pref.getString("value"), "http://test.org/es-MX/es-MX/", "check " + prefLocalizeable);
+                        mAsserter.is(value, "http://test.org/es-MX/es-MX/", "check " + prefLocalizeable);
                     }
                 } else if (name.equals(prefLocalizeableOverride)) {
                     if (aLocale.equals("en-US")) {
-                        mAsserter.is(pref.getString("value"), "http://cheese.com", "check " + prefLocalizeableOverride);
+                        mAsserter.is(value, "http://cheese.com", "check " + prefLocalizeableOverride);
                     } else if (aLocale.equals("es-MX")) {
-                        mAsserter.is(pref.getString("value"), "http://test.org/es-MX/", "check " + prefLocalizeableOverride);
+                        mAsserter.is(value, "http://test.org/es-MX/", "check " + prefLocalizeableOverride);
                     }
+                } else {
+                    // Raise exception.
+                    super.prefValue(name, value);
                 }
             }
-
-        } catch (JSONException e) {
-            mAsserter.ok(false, "exception getting preferences", e.toString());
-        }
+        }).waitForFinish();
     }
 
     // Copies the mock package to the data directory and returns the file path to it.
     private String getMockPackagePath() {
         String mockPackagePath = "";
 
         try {
             InputStream inStream = getAsset(MOCK_PACKAGE);
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testDoorHanger.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testDoorHanger.java
@@ -15,16 +15,18 @@ import org.mozilla.gecko.Actions;
 /* This test will test if doorhangers are displayed and dismissed
    The test will test:
    * geolocation doorhangers - sharing and not sharing the location dismisses the doorhanger
    * opening a new tab hides the doorhanger
    * offline storage permission doorhangers - allowing and not allowing offline storage dismisses the doorhanger
    * Password Manager doorhangers - Remember and Not Now options dismiss the doorhanger
 */
 public class testDoorHanger extends BaseTest {
+    private boolean offlineAllowedByDefault = true;
+
     public void testDoorHanger() {
         String GEO_URL = getAbsoluteUrl(mStringHelper.ROBOCOP_GEOLOCATION_URL);
         String BLANK_URL = getAbsoluteUrl(mStringHelper.ROBOCOP_BLANK_PAGE_01_URL);
         String OFFLINE_STORAGE_URL = getAbsoluteUrl(mStringHelper.ROBOCOP_OFFLINE_STORAGE_URL);
 
         blockForGeckoReady();
 
         // Test geolocation notification
@@ -57,48 +59,27 @@ public class testDoorHanger extends Base
 
         // Add a new tab
         addTab(BLANK_URL);
 
         // Make sure doorhanger is hidden
         mAsserter.is(mSolo.searchText(GEO_MESSAGE), false, "Geolocation doorhanger notification is hidden when opening a new tab");
         */
 
-        boolean offlineAllowedByDefault = true;
         // Save offline-allow-by-default preferences first
-        final String[] prefNames = { "offline-apps.allow_by_default" };
-        final int ourRequestId = 0x7357;
-        final Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-        mActions.sendPreferencesGetEvent(ourRequestId, prefNames);
-        try {
-            JSONObject data = null;
-            int requestId = -1;
-
-            // Wait until we get the correct "Preferences:Data" event
-            while (requestId != ourRequestId) {
-                data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getInt("requestId");
+        mActions.getPrefs(new String[] { "offline-apps.allow_by_default" },
+                          new Actions.PrefHandlerBase() {
+            @Override // Actions.PrefHandlerBase
+            public void prefValue(String pref, boolean value) {
+                mAsserter.is(pref, "offline-apps.allow_by_default", "Expecting correct pref name");
+                offlineAllowedByDefault = value;
             }
-            eventExpecter.unregisterListener();
-
-            JSONArray preferences = data.getJSONArray("preferences");
-            if (preferences.length() > 0) {
-                JSONObject pref = (JSONObject) preferences.get(0);
-                offlineAllowedByDefault = pref.getBoolean("value");
-            }
+        }).waitForFinish();
 
-            // Turn off offline-allow-by-default
-            JSONObject jsonPref = new JSONObject();
-            jsonPref.put("name", "offline-apps.allow_by_default");
-            jsonPref.put("type", "bool");
-            jsonPref.put("value", false);
-            setPreferenceAndWaitForChange(jsonPref);
-        } catch (JSONException e) {
-            mAsserter.ok(false, "exception getting preference", e.toString());
-        }
+        setPreferenceAndWaitForChange("offline-apps.allow_by_default", false);
 
         // Load offline storage page
         loadUrlAndWait(OFFLINE_STORAGE_URL);
         waitForText(mStringHelper.OFFLINE_MESSAGE);
 
         // Test doorhanger dismissed when tapping "Don't share"
         waitForCheckBox();
         mSolo.clickOnCheckBox(0);
@@ -112,26 +93,18 @@ public class testDoorHanger extends Base
 
         // Test doorhanger dismissed when tapping "Allow" and is not displayed again
         mSolo.clickOnButton(mStringHelper.OFFLINE_ALLOW);
         waitForTextDismissed(mStringHelper.OFFLINE_MESSAGE);
         mAsserter.is(mSolo.searchText(mStringHelper.OFFLINE_MESSAGE), false, "Offline storage doorhanger notification is hidden when allowing storage");
         loadUrlAndWait(OFFLINE_STORAGE_URL);
         mAsserter.is(mSolo.searchText(mStringHelper.OFFLINE_MESSAGE), false, "Offline storage doorhanger is no longer triggered");
 
-        try {
-            // Revert offline setting
-            JSONObject jsonPref = new JSONObject();
-            jsonPref.put("name", "offline-apps.allow_by_default");
-            jsonPref.put("type", "bool");
-            jsonPref.put("value", offlineAllowedByDefault);
-            setPreferenceAndWaitForChange(jsonPref);
-        } catch (JSONException e) {
-            mAsserter.ok(false, "exception setting preference", e.toString());
-        }
+        // Revert offline setting
+        setPreferenceAndWaitForChange("offline-apps.allow_by_default", offlineAllowedByDefault);
 
         // Load new login page
         loadUrlAndWait(getAbsoluteUrl(mStringHelper.ROBOCOP_LOGIN_01_URL));
         waitForText(mStringHelper.LOGIN_MESSAGE);
 
         // Test doorhanger is dismissed when tapping "Remember".
         mSolo.clickOnButton(mStringHelper.LOGIN_ALLOW);
         waitForTextDismissed(mStringHelper.LOGIN_MESSAGE);
@@ -147,25 +120,17 @@ public class testDoorHanger extends Base
         mAsserter.is(mSolo.searchText(mStringHelper.LOGIN_MESSAGE), false, "Login doorhanger notification is hidden when denying saving password");
 
         testPopupBlocking();
     }
 
     private void testPopupBlocking() {
         String POPUP_URL = getAbsoluteUrl(mStringHelper.ROBOCOP_POPUP_URL);
 
-        try {
-            JSONObject jsonPref = new JSONObject();
-            jsonPref.put("name", "dom.disable_open_during_load");
-            jsonPref.put("type", "bool");
-            jsonPref.put("value", true);
-            setPreferenceAndWaitForChange(jsonPref);
-        } catch (JSONException e) {
-            mAsserter.ok(false, "exception setting preference", e.toString());
-        }
+        setPreferenceAndWaitForChange("dom.disable_open_during_load", true);
 
         // Load page with popup
         loadUrlAndWait(POPUP_URL);
         waitForText(mStringHelper.POPUP_MESSAGE);
         mAsserter.is(mSolo.searchText(mStringHelper.POPUP_MESSAGE), true, "Popup blocker is displayed");
 
         // Wait for the popup to be shown.
         Actions.EventExpecter tabEventExpecter = mActions.expectGeckoEvent("Tab:Added");
@@ -199,25 +164,17 @@ public class testDoorHanger extends Base
         mSolo.clickOnCheckBox(0);
         mSolo.clickOnButton(mStringHelper.POPUP_DENY);
         waitForTextDismissed(mStringHelper.POPUP_MESSAGE);
         mAsserter.is(mSolo.searchText(mStringHelper.POPUP_MESSAGE), false, "Popup blocker is hidden when popup denied");
 
         // Check that we're on the same page to verify that the popup was not shown.
         verifyUrl(POPUP_URL);
 
-        try {
-            JSONObject jsonPref = new JSONObject();
-            jsonPref.put("name", "dom.disable_open_during_load");
-            jsonPref.put("type", "bool");
-            jsonPref.put("value", false);
-            setPreferenceAndWaitForChange(jsonPref);
-        } catch (JSONException e) {
-            mAsserter.ok(false, "exception setting preference", e.toString());
-        }
+        setPreferenceAndWaitForChange("dom.disable_open_during_load", false);
     }
 
     // wait for a CheckBox view that is clickable
     private void waitForCheckBox() {
         waitForCondition(new Condition() {
             @Override
             public boolean isSatisfied() {
                 for (CheckBox view : mSolo.getCurrentViews(CheckBox.class)) {
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testPasswordEncrypt.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testPasswordEncrypt.java
@@ -101,25 +101,17 @@ public class testPasswordEncrypt extends
             // Password provider currently can not throw across process
             // so we should not catch this exception here
             mAsserter.ok(false, "Caught exception", ex.toString());
         }
         toggleMasterPassword("password");
     }
 
     private void toggleMasterPassword(String passwd) {
-        JSONObject jsonPref = new JSONObject();
-        try {
-            jsonPref.put("name", "privacy.masterpassword.enabled");
-            jsonPref.put("type", "string");
-            jsonPref.put("value", passwd);
-            setPreferenceAndWaitForChange(jsonPref);
-        } catch (Exception ex) { 
-            mAsserter.ok(false, "exception in toggleMasterPassword", ex.toString());
-        }
+        setPreferenceAndWaitForChange("privacy.masterpassword.enabled", passwd);
     }
 
     @Override
     public void tearDown() throws Exception {
         // remove the entire signons.sqlite file
         File profile = new File(mProfile);
         File db = new File(profile, "signons.sqlite");
         if (db.delete()) {
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testPrefsObserver.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testPrefsObserver.java
@@ -1,112 +1,81 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.tests;
 
-import org.json.JSONException;
-import org.json.JSONObject;
 import org.mozilla.gecko.Actions;
 
 /**
  * Basic test to check bounce-back from overscroll.
  * - Load the page and verify it draws
  * - Drag page downwards by 100 pixels into overscroll, verify it snaps back.
  * - Drag page rightwards by 100 pixels into overscroll, verify it snaps back.
  */
 public class testPrefsObserver extends BaseTest {
     private static final String PREF_TEST_PREF = "robocop.tests.dummy";
-    private static final int PREF_OBSERVE_REQUEST_ID = 0x7357;
-    private static final long PREF_TIMEOUT = 10000;
 
-    private Actions.RepeatedEventExpecter mExpecter;
+    private Actions.PrefWaiter prefWaiter;
+    private boolean prefValue;
 
-    public void setPref(boolean value) throws JSONException {
+    public void setPref(boolean value) {
         mAsserter.dumpLog("Setting pref");
-
-        JSONObject jsonPref = new JSONObject();
-        jsonPref.put("name", PREF_TEST_PREF);
-        jsonPref.put("type", "bool");
-        jsonPref.put("value", value);
-        mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
+        mActions.setPref(PREF_TEST_PREF, value, /* flush */ false);
     }
 
-    public void waitAndCheckPref(boolean value) throws JSONException {
+    public void waitAndCheckPref(boolean value) {
         mAsserter.dumpLog("Waiting to check pref");
 
-        JSONObject data = null;
-        int requestId = -1;
+        mAsserter.isnot(prefWaiter, null, "Check pref waiter is not null");
+        prefWaiter.waitForFinish();
 
-        while (requestId != PREF_OBSERVE_REQUEST_ID) {
-            data = new JSONObject(mExpecter.blockForEventData());
-            if (!mExpecter.eventReceived()) {
-                mAsserter.ok(false, "Checking pref is correct value", "Didn't receive pref");
-                return;
-            }
-            requestId = data.getInt("requestId");
-        }
-
-        JSONObject pref = data.getJSONArray("preferences").getJSONObject(0);
-        mAsserter.is(pref.getString("name"), PREF_TEST_PREF, "Pref name is correct");
-        mAsserter.is(pref.getString("type"), "bool", "Pref type is correct");
-        mAsserter.is(pref.getBoolean("value"), value, "Pref value is correct");
+        mAsserter.is(prefValue, value, "Check correct pref value");
     }
 
-    public void verifyDisconnect() throws JSONException {
+    public void verifyDisconnect() {
         mAsserter.dumpLog("Checking pref observer is removed");
 
-        JSONObject pref = null;
-        int requestId = -1;
-
-        while (requestId != PREF_OBSERVE_REQUEST_ID) {
-            String data = mExpecter.blockForEventDataWithTimeout(PREF_TIMEOUT);
-            if (data == null) {
-                mAsserter.ok(true, "Verifying pref is unobserved", "Didn't get unobserved pref");
-                return;
-            }
-            pref = new JSONObject(data);
-            requestId = pref.getInt("requestId");
-        }
-
-        mAsserter.ok(false, "Received unobserved pref change", "");
+        final boolean newValue = !prefValue;
+        setPreferenceAndWaitForChange(PREF_TEST_PREF, newValue);
+        mAsserter.isnot(prefValue, newValue, "Check pref value did not change");
     }
 
-    public void observePref() throws JSONException {
+    public void observePref() {
         mAsserter.dumpLog("Setting up pref observer");
 
         // Setup the pref observer
-        mExpecter = mActions.expectGeckoEvent("Preferences:Data");
-        mActions.sendPreferencesObserveEvent(PREF_OBSERVE_REQUEST_ID, new String[] { PREF_TEST_PREF });
+        mAsserter.is(prefWaiter, null, "Check pref waiter is null");
+        prefWaiter = mActions.addPrefsObserver(
+                new String[] { PREF_TEST_PREF }, new Actions.PrefHandlerBase() {
+            @Override // Actions.PrefHandlerBase
+            public void prefValue(String pref, boolean value) {
+                mAsserter.is(pref, PREF_TEST_PREF, "Check correct pref name");
+                prefValue = value;
+            }
+        });
     }
 
     public void removePrefObserver() {
         mAsserter.dumpLog("Removing pref observer");
 
-        mActions.sendPreferencesRemoveObserversEvent(PREF_OBSERVE_REQUEST_ID);
+        mActions.removePrefsObserver(prefWaiter);
     }
 
     public void testPrefsObserver() {
         blockForGeckoReady();
 
-        try {
-            setPref(false);
-            observePref();
-            waitAndCheckPref(false);
-
-            setPref(true);
-            waitAndCheckPref(true);
+        setPref(false);
+        observePref();
+        waitAndCheckPref(false);
 
-            removePrefObserver();
-            setPref(false);
-            verifyDisconnect();
-        } catch (Exception ex) {
-            mAsserter.ok(false, "exception in testPrefsObserver", ex.toString());
-        } finally {
-            // Make sure we remove the observer - if it's already removed, this
-            // will do nothing.
-            removePrefObserver();
-        }
-        mExpecter.unregisterListener();
+        setPref(true);
+        waitAndCheckPref(true);
+
+        removePrefObserver();
+        verifyDisconnect();
+
+        // Removing again should be a no-op.
+        removePrefObserver();
     }
 }