Bug 922694 - Part 4: grab Accept-Locale pref in FHR. r=mcomella
☠☠ backed out by 63b7d4bbaab6 ☠ ☠
authorRichard Newman <rnewman@mozilla.com>
Tue, 15 Oct 2013 16:53:45 -0700
changeset 164739 3d0582ab04175c06e6f4f5601606ed40e3c41497
parent 164738 d333b85c805de705b0303be5a3c0b44cdd3768c7
child 164740 51185a26a7b7660550d6ab5e857459542d34b222
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcomella
bugs922694
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 922694 - Part 4: grab Accept-Locale pref in FHR. r=mcomella
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/health/BrowserHealthRecorder.java
mobile/android/chrome/content/browser.js
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -112,16 +112,17 @@ import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 abstract public class GeckoApp
                 extends GeckoActivity 
     implements GeckoEventListener, SensorEventListener, LocationListener,
@@ -1286,17 +1287,26 @@ abstract public class GeckoApp
                 editor.commit();
 
                 // The lifecycle of mHealthRecorder is "shortly after onCreate"
                 // through "onDestroy" -- essentially the same as the lifecycle
                 // of the activity itself.
                 final String profilePath = getProfile().getDir().getAbsolutePath();
                 final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
                 Log.i(LOGTAG, "Creating BrowserHealthRecorder.");
-                mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this, profilePath, dispatcher,
+                final String osLocale = Locale.getDefault().toString();
+                Log.d(LOGTAG, "Locale is " + osLocale);
+
+                // Replace the duplicate `osLocale` argument when we support switchable
+                // application locales.
+                mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this,
+                                                            profilePath,
+                                                            dispatcher,
+                                                            osLocale,
+                                                            osLocale,    // Placeholder.
                                                             previousSession);
             }
         });
 
         GeckoAppShell.setNotificationClient(makeNotificationClient());
     }
 
     protected void initializeChrome() {
@@ -1550,18 +1560,25 @@ abstract public class GeckoApp
 
                 // Kick off our background services. We do this by invoking the broadcast
                 // receiver, which uses the system alarm infrastructure to perform tasks at
                 // intervals.
                 GeckoPreferences.broadcastAnnouncementsPref(context);
                 GeckoPreferences.broadcastHealthReportUploadPref(context);
 
                 /*
-                  XXXX see bug 635342
-                   We want to disable this code if possible.  It is about 145ms in runtime
+                XXXX see Bug 635342.
+                We want to disable this code if possible.  It is about 145ms in runtime.
+
+                If this code ever becomes live again, you'll need to chain the
+                new locale into BrowserHealthRecorder correctly. See
+                GeckoAppShell.setSelectedLocale.
+                We pass the OS locale into the BHR constructor: we need to grab
+                that *before* we modify the current locale!
+
                 SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
                 String localeCode = settings.getString(getPackageName() + ".locale", "");
                 if (localeCode != null && localeCode.length() > 0)
                     GeckoAppShell.setSelectedLocale(localeCode);
                 */
 
                 if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) {
                     return;
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -1520,16 +1520,22 @@ public class GeckoAppShell
     @GeneratableAndroidBridgeTarget
     public static void setSelectedLocale(String localeCode) {
         /* Bug 713464: This method is still called from Gecko side.
            Earlier we had an option to run Firefox in a language other than system's language.
            However, this is not supported as of now.
            Gecko resets the locale to en-US by calling this function with an empty string.
            This affects GeckoPreferences activity in multi-locale builds.
 
+        N.B., if this code ever becomes live again, you need to hook it up to locale
+        recording in BrowserHealthRecorder: we track the current app and OS locales
+        as part of the recorded environment.
+
+        See similar note in GeckoApp.java for the startup path.
+
         //We're not using this, not need to save it (see bug 635342)
         SharedPreferences settings =
             getContext().getPreferences(Activity.MODE_PRIVATE);
         settings.edit().putString(getContext().getPackageName() + ".locale",
                                   localeCode).commit();
         Locale locale;
         int index;
         if ((index = localeCode.indexOf('-')) != -1 ||
--- a/mobile/android/base/health/BrowserHealthRecorder.java
+++ b/mobile/android/base/health/BrowserHealthRecorder.java
@@ -11,18 +11,16 @@ import android.content.Context;
 import android.content.ContentProviderClient;
 import android.content.SharedPreferences;
 import android.util.Log;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.PrefsHelper.PrefHandler;
 
 import org.mozilla.gecko.background.healthreport.EnvironmentBuilder;
 import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage;
 import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field;
 import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields;
 import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields.FieldSpec;
 import org.mozilla.gecko.background.healthreport.ProfileInformationCache;
 
@@ -33,40 +31,41 @@ import org.mozilla.gecko.util.ThreadUtil
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.OutputStreamWriter;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.Scanner;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * BrowserHealthRecorder is the browser's interface to the Firefox Health
  * Report storage system. It manages environments (a collection of attributes
  * that are tracked longitudinally) on the browser's behalf, exposing a simpler
  * interface for recording changes.
  *
  * Keep an instance of this class around.
  *
  * Tell it when an environment attribute has changed: call {@link
- * #onBlocklistPrefChanged(boolean)} or {@link
- * #onTelemetryPrefChanged(boolean)}, followed by {@link
+ * #onAppLocaleChanged(String)} followed by {@link
  * #onEnvironmentChanged()}.
  *
  * Use it to record events: {@link #recordSearch(String, String)}.
  *
  * Shut it down when you're done being a browser: {@link #close()}.
  */
 public class BrowserHealthRecorder implements GeckoEventListener {
     private static final String LOG_TAG = "GeckoHealthRec";
+    private static final String PREF_ACCEPT_LANG = "intl.accept_languages";
     private static final String PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
-    private static final String EVENT_ADDONS_ALL = "Addons:All";
+    private static final String EVENT_SNAPSHOT = "HealthReport:Snapshot";
     private static final String EVENT_ADDONS_CHANGE = "Addons:Change";
     private static final String EVENT_ADDONS_UNINSTALLING = "Addons:Uninstalling";
     private static final String EVENT_PREF_CHANGE = "Pref:Change";
 
     // This is raised from Gecko and signifies a search via the URL bar (not a bookmarks keyword
     // search). Using this event (rather than passing the invocation location as an arg) avoids
     // browser.js having to know about the invocation location.
     public static final String EVENT_KEYWORD_SEARCH = "Search:Keyword";
@@ -237,18 +236,25 @@ public class BrowserHealthRecorder imple
      * This changes in certain circumstances; be sure to use the current value when recording data.
      */
     private void setHealthEnvironment(final int env) {
         this.env = env;
     }
 
     /**
      * This constructor does IO. Run it on a background thread.
+     *
+     * appLocale can be null, which indicates that it will be provided later.
      */
-    public BrowserHealthRecorder(final Context context, final String profilePath, final EventDispatcher dispatcher, SessionInformation previousSession) {
+    public BrowserHealthRecorder(final Context context,
+                                 final String profilePath,
+                                 final EventDispatcher dispatcher,
+                                 final String osLocale,
+                                 final String appLocale,
+                                 SessionInformation previousSession) {
         Log.d(LOG_TAG, "Initializing. Dispatcher is " + dispatcher);
         this.dispatcher = dispatcher;
         this.previousSession = previousSession;
 
         this.client = EnvironmentBuilder.getContentProviderClient(context);
         if (this.client == null) {
             throw new IllegalStateException("Could not fetch Health Report content provider.");
         }
@@ -258,19 +264,22 @@ public class BrowserHealthRecorder imple
             // Stick around even if we don't have storage: eventually we'll
             // want to report total failures of FHR storage itself, and this
             // way callers don't need to worry about whether their health
             // recorder didn't initialize.
             this.client.release();
             this.client = null;
         }
 
+        // Note that the PIC is not necessarily fully initialized at this point:
+        // we haven't set the app locale. This must be done before an environment
+        // is recorded.
         this.profileCache = new ProfileInformationCache(profilePath);
         try {
-            this.initialize(context, profilePath);
+            this.initialize(context, profilePath, osLocale, appLocale);
         } catch (Exception e) {
             Log.e(LOG_TAG, "Exception initializing.", e);
         }
     }
 
     /**
      * Shut down database connections, unregister event listeners, and perform
      * provider-specific uninitialization.
@@ -294,32 +303,27 @@ public class BrowserHealthRecorder imple
         this.storage = null;
         if (this.client != null) {
             this.client.release();
             this.client = null;
         }
     }
 
     private void unregisterEventListeners() {
-        this.dispatcher.unregisterEventListener(EVENT_ADDONS_ALL, this);
+        this.dispatcher.unregisterEventListener(EVENT_SNAPSHOT, this);
         this.dispatcher.unregisterEventListener(EVENT_ADDONS_CHANGE, this);
         this.dispatcher.unregisterEventListener(EVENT_ADDONS_UNINSTALLING, this);
         this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this);
         this.dispatcher.unregisterEventListener(EVENT_KEYWORD_SEARCH, this);
         this.dispatcher.unregisterEventListener(EVENT_SEARCH, this);
     }
 
-    public void onBlocklistPrefChanged(boolean to) {
+    public void onAppLocaleChanged(String to) {
         this.profileCache.beginInitialization();
-        this.profileCache.setBlocklistEnabled(to);
-    }
-
-    public void onTelemetryPrefChanged(boolean to) {
-        this.profileCache.beginInitialization();
-        this.profileCache.setTelemetryEnabled(to);
+        this.profileCache.setAppLocale(to);
     }
 
     public void onAddonChanged(String id, JSONObject json) {
         this.profileCache.beginInitialization();
         try {
             this.profileCache.updateJSONForAddon(id, json);
         } catch (IllegalStateException e) {
             Log.w(LOG_TAG, "Attempted to update add-on cache prior to full init.", e);
@@ -335,18 +339,17 @@ public class BrowserHealthRecorder imple
         }
     }
 
     /**
      * Call this when a material change might have occurred in the running
      * environment, such that a new environment should be computed and prepared
      * for use in future events.
      *
-     * Invoke this method after calls that mutate the environment, such as
-     * {@link #onBlocklistPrefChanged(boolean)}.
+     * Invoke this method after calls that mutate the environment.
      *
      * If this change resulted in a transition between two environments, {@link
      * #onEnvironmentTransition(int, int)} will be invoked on the background
      * thread.
      */
     public synchronized void onEnvironmentChanged() {
         final int previousEnv = this.env;
         this.env = -1;
@@ -486,24 +489,46 @@ public class BrowserHealthRecorder imple
                 Log.w(LOG_TAG, "Couldn't write times.json.", e);
             }
         }
 
         Log.d(LOG_TAG, "Incorporating environment: profile creation = " + time);
         return time;
     }
 
-    private void handlePrefValue(final String pref, final boolean value) {
-        Log.d(LOG_TAG, "Incorporating environment: " + pref + " = " + value);
-        if (AppConstants.TELEMETRY_PREF_NAME.equals(pref)) {
-            profileCache.setTelemetryEnabled(value);
+    private void onPrefMessage(final String pref, final JSONObject message) {
+        Log.d(LOG_TAG, "Incorporating environment: " + pref);
+        if (PREF_ACCEPT_LANG.equals(pref)) {
+            // We only record whether this is user-set.
+            try {
+                this.profileCache.beginInitialization();
+                this.profileCache.setAcceptLangUserSet(message.getBoolean("isUserSet"));
+            } catch (JSONException ex) {
+                Log.w(LOG_TAG, "Unexpected JSONException fetching isUserSet for " + pref);
+            }
             return;
         }
-        if (PREF_BLOCKLIST_ENABLED.equals(pref)) {
-            profileCache.setBlocklistEnabled(value);
+
+        // (We only handle boolean prefs right now.)
+        try {
+            boolean value = message.getBoolean("value");
+
+            if (AppConstants.TELEMETRY_PREF_NAME.equals(pref)) {
+                this.profileCache.beginInitialization();
+                this.profileCache.setTelemetryEnabled(value);
+                return;
+            }
+
+            if (PREF_BLOCKLIST_ENABLED.equals(pref)) {
+                this.profileCache.beginInitialization();
+                this.profileCache.setBlocklistEnabled(value);
+                return;
+            }
+        } catch (JSONException ex) {
+            Log.w(LOG_TAG, "Unexpected JSONException fetching boolean value for " + pref);
             return;
         }
         Log.w(LOG_TAG, "Unexpected pref: " + pref);
     }
 
     /**
      * Background init helper.
      */
@@ -566,57 +591,43 @@ public class BrowserHealthRecorder imple
             }
         });
     }
 
     /**
      * Add provider-specific initialization in this method.
      */
     private synchronized void initialize(final Context context,
-                                         final String profilePath)
+                                         final String profilePath,
+                                         final String osLocale,
+                                         final String appLocale)
         throws java.io.IOException {
 
         Log.d(LOG_TAG, "Initializing profile cache.");
         this.state = State.INITIALIZING;
 
         // If we can restore state from last time, great.
         if (this.profileCache.restoreUnlessInitialized()) {
+            this.profileCache.updateLocales(osLocale, appLocale);
+            this.profileCache.completeInitialization();
+
             Log.d(LOG_TAG, "Successfully restored state. Initializing storage.");
             initializeStorage();
             return;
         }
 
         // Otherwise, let's initialize it from scratch.
         this.profileCache.beginInitialization();
         this.profileCache.setProfileCreationTime(getAndPersistProfileInitTime(context, profilePath));
-
-        final BrowserHealthRecorder self = this;
-
-        PrefHandler handler = new PrefsHelper.PrefHandlerBase() {
-            @Override
-            public void prefValue(String pref, boolean value) {
-                handlePrefValue(pref, value);
-            }
+        this.profileCache.setOSLocale(osLocale);
+        this.profileCache.setAppLocale(appLocale);
 
-            @Override
-            public void finish() {
-                Log.d(LOG_TAG, "Requesting all add-ons from Gecko.");
-                dispatcher.registerEventListener(EVENT_ADDONS_ALL, self);
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Addons:FetchAll", null));
-                // Wait for the broadcast event which completes our initialization.
-            }
-        };
-
-        // Oh, singletons.
-        PrefsHelper.getPrefs(new String[] {
-                                 AppConstants.TELEMETRY_PREF_NAME,
-                                 PREF_BLOCKLIST_ENABLED
-                             },
-                             handler);
-        Log.d(LOG_TAG, "Requested prefs.");
+        Log.d(LOG_TAG, "Requesting all add-ons and FHR prefs from Gecko.");
+        dispatcher.registerEventListener(EVENT_SNAPSHOT, this);
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HealthReport:RequestSnapshot", null));
     }
 
     /**
      * Invoked in the background whenever the environment transitions between
      * two valid values.
      */
     protected void onEnvironmentTransition(int prev, int env) {
         if (this.state != State.INITIALIZED) {
@@ -633,22 +644,32 @@ public class BrowserHealthRecorder imple
         setCurrentSession(newSession);
         newSession.recordBegin(editor);
         editor.commit();
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
-            if (EVENT_ADDONS_ALL.equals(event)) {
-                Log.d(LOG_TAG, "Got all add-ons.");
+            if (EVENT_SNAPSHOT.equals(event)) {
+                Log.d(LOG_TAG, "Got all add-ons and prefs.");
                 try {
-                    JSONObject addons = message.getJSONObject("json");
+                    JSONObject json = message.getJSONObject("json");
+                    JSONObject addons = json.getJSONObject("addons");
                     Log.i(LOG_TAG, "Persisting " + addons.length() + " add-ons.");
                     profileCache.setJSONForAddons(addons);
+
+                    JSONObject prefs = json.getJSONObject("prefs");
+                    Log.i(LOG_TAG, "Persisting prefs.");
+                    Iterator<?> keys = prefs.keys();
+                    while (keys.hasNext()) {
+                        String pref = (String) keys.next();
+                        this.onPrefMessage(pref, prefs.getJSONObject(pref));
+                    }
+
                     profileCache.completeInitialization();
                 } catch (java.io.IOException e) {
                     Log.e(LOG_TAG, "Error completing profile cache initialization.", e);
                     state = State.INITIALIZATION_FAILED;
                     return;
                 }
 
                 if (state == State.INITIALIZING) {
@@ -670,17 +691,17 @@ public class BrowserHealthRecorder imple
                 this.onAddonChanged(message.getString("id"), message.getJSONObject("json"));
                 this.onEnvironmentChanged();
                 return;
             }
 
             if (EVENT_PREF_CHANGE.equals(event)) {
                 final String pref = message.getString("pref");
                 Log.d(LOG_TAG, "Pref changed: " + pref);
-                handlePrefValue(pref, message.getBoolean("value"));
+                this.onPrefMessage(pref, message);
                 this.onEnvironmentChanged();
                 return;
             }
 
             // Searches.
             if (EVENT_KEYWORD_SEARCH.equals(event)) {
                 // A search via the URL bar. Since we eliminate all other search possibilities
                 // (e.g. bookmarks keyword, search suggestion) when we initially process the
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -5306,17 +5306,20 @@ var FormAssistant = {
   }
 };
 
 /**
  * An object to watch for Gecko status changes -- add-on installs, pref changes
  * -- and reflect them back to Java.
  */
 let HealthReportStatusListener = {
-  TELEMETRY_PREF: 
+  PREF_ACCEPT_LANG: "intl.accept_languages",
+  PREF_BLOCKLIST_ENABLED: "extensions.blocklist.enabled",
+
+  PREF_TELEMETRY_ENABLED:
 #ifdef MOZ_TELEMETRY_REPORTING
     // Telemetry pref differs based on build.
 #ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
     "toolkit.telemetry.enabledPreRelease",
 #else
     "toolkit.telemetry.enabled",
 #endif
 #else
@@ -5325,40 +5328,62 @@ let HealthReportStatusListener = {
 
   init: function () {
     try {
       AddonManager.addAddonListener(this);
     } catch (ex) {
       console.log("Failed to initialize add-on status listener. FHR cannot report add-on state. " + ex);
     }
 
-    Services.obs.addObserver(this, "Addons:FetchAll", false);
-    Services.prefs.addObserver("extensions.blocklist.enabled", this, false);
-    if (this.TELEMETRY_PREF) {
-      Services.prefs.addObserver(this.TELEMETRY_PREF, this, false);
+    console.log("Adding HealthReport:RequestSnapshot observer.");
+    Services.obs.addObserver(this, "HealthReport:RequestSnapshot", false);
+    Services.prefs.addObserver(this.PREF_ACCEPT_LANG, this, false);
+    Services.prefs.addObserver(this.PREF_BLOCKLIST_ENABLED, this, false);
+    if (this.PREF_TELEMETRY_ENABLED) {
+      Services.prefs.addObserver(this.PREF_TELEMETRY_ENABLED, this, false);
     }
   },
 
   uninit: function () {
-    Services.obs.removeObserver(this, "Addons:FetchAll");
-    Services.prefs.removeObserver("extensions.blocklist.enabled", this);
-    if (this.TELEMETRY_PREF) {
-      Services.prefs.removeObserver(this.TELEMETRY_PREF, this);
+    Services.obs.removeObserver(this, "HealthReport:RequestSnapshot");
+    Services.prefs.removeObserver(this.PREF_ACCEPT_LANG, this);
+    Services.prefs.removeObserver(this.PREF_BLOCKLIST_ENABLED, this);
+    if (this.PREF_TELEMETRY_ENABLED) {
+      Services.prefs.removeObserver(this.PREF_TELEMETRY_ENABLED, this);
     }
 
     AddonManager.removeAddonListener(this);
   },
 
   observe: function (aSubject, aTopic, aData) {
     switch (aTopic) {
-      case "Addons:FetchAll":
-        HealthReportStatusListener.sendAllAddonsToJava();
+      case "HealthReport:RequestSnapshot":
+        HealthReportStatusListener.sendSnapshotToJava();
         break;
       case "nsPref:changed":
-        sendMessageToJava({ type: "Pref:Change", pref: aData, value: Services.prefs.getBoolPref(aData) });
+        let response = {
+          type: "Pref:Change",
+          pref: aData,
+          isUserSet: Services.prefs.prefHasUserValue(aData),
+        };
+
+        switch (aData) {
+          case this.PREF_ACCEPT_LANG:
+            response.value = Services.prefs.getCharPref(aData);
+            break;
+          case this.PREF_TELEMETRY_ENABLED:
+          case this.PREF_BLOCKLIST_ENABLED:
+            response.value = Services.prefs.getBoolPref(aData);
+            break;
+          default:
+            console.log("Unexpected pref in HealthReportStatusListener: " + aData);
+            return;
+        }
+
+        sendMessageToJava(response);
         break;
     }
   },
 
   MILLISECONDS_PER_DAY: 24 * 60 * 60 * 1000,
 
   COPY_FIELDS: [
     "blocklistState",
@@ -5430,35 +5455,64 @@ let HealthReportStatusListener = {
   },
   onPropertyChanged: function (aAddon, aProperties) {
     this.notifyJava(aAddon);
   },
   onOperationCancelled: function (aAddon) {
     this.notifyJava(aAddon);
   },
 
-  sendAllAddonsToJava: function () {
+  sendSnapshotToJava: function () {
     AddonManager.getAllAddons(function (aAddons) {
-        let json = {};
+        let jsonA = {};
         if (aAddons) {
           for (let i = 0; i < aAddons.length; ++i) {
             let addon = aAddons[i];
             try {
               let addonJSON = HealthReportStatusListener.jsonForAddon(addon);
               if (HealthReportStatusListener._shouldIgnore(addon)) {
                 addonJSON.ignore = true;
               }
-              json[addon.id] = addonJSON;
+              jsonA[addon.id] = addonJSON;
             } catch (e) {
               // Just skip this add-on.
             }
           }
         }
-        sendMessageToJava({ type: "Addons:All", json: json });
-      });
+
+        // Now add prefs.
+        let jsonP = {};
+        for (let pref of [this.PREF_BLOCKLIST_ENABLED, this.PREF_TELEMETRY_ENABLED]) {
+          if (!pref) {
+            // This will be the case for PREF_TELEMETRY_ENABLED in developer builds.
+            continue;
+          }
+          jsonP[pref] = {
+            pref: pref,
+            value: Services.prefs.getBoolPref(pref),
+            isUserSet: Services.prefs.prefHasUserValue(pref),
+          };
+        }
+        for (let pref of [this.PREF_ACCEPT_LANG]) {
+          jsonP[pref] = {
+            pref: pref,
+            value: Services.prefs.getCharPref(pref),
+            isUserSet: Services.prefs.prefHasUserValue(pref),
+          };
+        }
+
+        console.log("Sending snapshot message.");
+        sendMessageToJava({
+          type: "HealthReport:Snapshot",
+          json: {
+            addons: jsonA,
+            prefs: jsonP,
+          },
+        });
+      }.bind(this));
   },
 };
 
 var XPInstallObserver = {
   init: function xpi_init() {
     Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false);
     Services.obs.addObserver(XPInstallObserver, "addon-install-started", false);