Backed out 5 changesets (bug 922694) for Android rc3 orange
authorWes Kocher <wkocher@mozilla.com>
Tue, 15 Oct 2013 18:32:31 -0700
changeset 165694 63b7d4bbaab6aeafa152ffc7176f35e826595a51
parent 165693 97f6836ede99ce794564abd206471cd1c31a4d36
child 165695 9988599193391944b60d3d2a600b2e3fc71d1b0f
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs922694
milestone27.0a1
backs out51185a26a7b7660550d6ab5e857459542d34b222
3d0582ab04175c06e6f4f5601606ed40e3c41497
d333b85c805de705b0303be5a3c0b44cdd3768c7
a78a707ba721edc15353b4d8c7fa21886468689c
34e48f8bac5236fd987cd8e776dae36b95b12f50
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
Backed out 5 changesets (bug 922694) for Android rc3 orange Backed out changeset 51185a26a7b7 (bug 922694) Backed out changeset 3d0582ab0417 (bug 922694) Backed out changeset d333b85c805d (bug 922694) Backed out changeset a78a707ba721 (bug 922694) Backed out changeset 34e48f8bac52 (bug 922694)
mobile/android/base/Distribution.java
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/android-services-files.mk
mobile/android/base/background/healthreport/Environment.java
mobile/android/base/background/healthreport/EnvironmentBuilder.java
mobile/android/base/background/healthreport/EnvironmentV1.java
mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java
mobile/android/base/background/healthreport/HealthReportGenerator.java
mobile/android/base/background/healthreport/ProfileInformationCache.java
mobile/android/base/health/BrowserHealthRecorder.java
mobile/android/chrome/content/browser.js
mobile/android/services/java-sources.mn
mobile/android/tests/background/junit3/src/healthreport/MockDatabaseEnvironment.java
mobile/android/tests/background/junit3/src/healthreport/TestHealthReportGenerator.java
mobile/android/tests/background/junit3/src/healthreport/TestHealthReportProvider.java
--- a/mobile/android/base/Distribution.java
+++ b/mobile/android/base/Distribution.java
@@ -1,359 +1,208 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ *
  * 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/. */
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONArray;
 import org.json.JSONException;
-import org.json.JSONObject;
 
 import android.app.Activity;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.util.Log;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
-import java.util.Collections;
 import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Scanner;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 public final class Distribution {
     private static final String LOGTAG = "GeckoDistribution";
 
     private static final int STATE_UNKNOWN = 0;
     private static final int STATE_NONE = 1;
     private static final int STATE_SET = 2;
 
-    public static class DistributionDescriptor {
-        public final boolean valid;
-        public final String id;
-        public final String version;    // Example uses a float, but that's a crazy idea.
-
-        // Default UI-visible description of the distribution.
-        public final String about;
-
-        // Each distribution file can include multiple localized versions of
-        // the 'about' string. These are represented as, e.g., "about.en-US"
-        // keys in the Global object.
-        // Here we map locale to description.
-        public final Map<String, String> localizedAbout;
-
-        @SuppressWarnings("unchecked")
-        public DistributionDescriptor(JSONObject obj) {
-            this.id = obj.optString("id");
-            this.version = obj.optString("version");
-            this.about = obj.optString("about");
-            Map<String, String> loc = new HashMap<String, String>();
-            try {
-                Iterator<String> keys = obj.keys();
-                while (keys.hasNext()) {
-                    String key = keys.next();
-                    if (key.startsWith("about.")) {
-                        String locale = key.substring(6);
-                        if (!obj.isNull(locale)) {
-                            loc.put(locale, obj.getString(key));
-                        }
-                    }
-                }
-            } catch (JSONException ex) {
-                Log.w(LOGTAG, "Unable to completely process distribution JSON.", ex);
-            }
-
-            this.localizedAbout = Collections.unmodifiableMap(loc);
-            this.valid = (null != this.id) &&
-                         (null != this.version) &&
-                         (null != this.about);
-        }
-    }
-
     /**
-     * Initializes distribution if it hasn't already been initalized. Sends
-     * messages to Gecko as appropriate.
+     * Initializes distribution if it hasn't already been initalized.
      *
-     * @param packagePath where to look for the distribution directory.
+     * @param packagePath specifies where to look for the distribution directory.
      */
     public static void init(final Context context, final String packagePath) {
         // Read/write preferences and files on the background thread.
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
-                Distribution dist = new Distribution(context, packagePath);
-                boolean distributionSet = dist.doInit();
+                // Bail if we've already initialized the distribution.
+                SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
+                String keyName = context.getPackageName() + ".distribution_state";
+                int state = settings.getInt(keyName, STATE_UNKNOWN);
+                if (state == STATE_NONE) {
+                    return;
+                }
+
+                // Send a message to Gecko if we've set a distribution.
+                if (state == STATE_SET) {
+                    GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Distribution:Set", ""));
+                    return;
+                }
+
+                boolean distributionSet = false;
+                try {
+                    // First, try copying distribution files out of the APK.
+                    distributionSet = copyFiles(context, packagePath);
+                } catch (IOException e) {
+                    Log.e(LOGTAG, "Error copying distribution files", e);
+                }
+
+                if (!distributionSet) {
+                    // If there aren't any distribution files in the APK, look in the /system directory.
+                    File distDir = new File("/system/" + context.getPackageName() + "/distribution");
+                    if (distDir.exists()) {
+                        distributionSet = true;
+                    }
+                }
+
                 if (distributionSet) {
                     GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Distribution:Set", ""));
+                    settings.edit().putInt(keyName, STATE_SET).commit();
+                } else {
+                    settings.edit().putInt(keyName, STATE_NONE).commit();
                 }
             }
         });
     }
 
     /**
-     * Use <code>Context.getPackageResourcePath</code> to find an implicit
-     * package path.
-     */
-    public static void init(final Context context) {
-        Distribution.init(context, context.getPackageResourcePath());
-    }
-
-    /**
-     * Returns parsed contents of bookmarks.json.
-     * This method should only be called from a background thread.
-     */
-    public static JSONArray getBookmarks(final Context context) {
-        Distribution dist = new Distribution(context);
-        return dist.getBookmarks();
-    }
-
-    private final String packagePath;
-    private final Context context;
-
-    private int state = STATE_UNKNOWN;
-    private File distributionDir = null;
-
-    /**
-     * @param packagePath where to look for the distribution directory.
-     */
-    public Distribution(final Context context, final String packagePath) {
-        this.context = context;
-        this.packagePath = packagePath;
-    }
-
-    public Distribution(final Context context) {
-        this(context, context.getPackageResourcePath());
-    }
-
-    /**
-     * Don't call from the main thread.
-     *
-     * @return true if we've set a distribution.
-     */
-    private boolean doInit() {
-        // Bail if we've already tried to initialize the distribution, and
-        // there wasn't one.
-        SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
-        String keyName = context.getPackageName() + ".distribution_state";
-        this.state = settings.getInt(keyName, STATE_UNKNOWN);
-        if (this.state == STATE_NONE) {
-            return false;
-        }
-
-        // We've done the work once; don't do it again.
-        if (this.state == STATE_SET) {
-            // Note that we don't compute the distribution directory.
-            // Call `ensureDistributionDir` if you need it.
-            return true;
-        }
-
-        boolean distributionSet = false;
-        try {
-            // First, try copying distribution files out of the APK.
-            distributionSet = copyFiles();
-            if (distributionSet) {
-                // We always copy to the data dir, and we only copy files from
-                // a 'distribution' subdirectory. Track our dist dir now that
-                // we know it.
-                this.distributionDir = new File(getDataDir(), "distribution/");
-            }
-        } catch (IOException e) {
-            Log.e(LOGTAG, "Error copying distribution files", e);
-        }
-
-        if (!distributionSet) {
-            // If there aren't any distribution files in the APK, look in the /system directory.
-            File distDir = getSystemDistributionDir();
-            if (distDir.exists()) {
-                distributionSet = true;
-                this.distributionDir = distDir;
-            }
-        }
-
-        this.state = distributionSet ? STATE_SET : STATE_NONE;
-        settings.edit().putInt(keyName, this.state).commit();
-        return distributionSet;
-    }
-
-    /**
      * Copies the /distribution folder out of the APK and into the app's data directory.
      * Returns true if distribution files were found and copied.
      */
-    private boolean copyFiles() throws IOException {
+    private static boolean copyFiles(Context context, String packagePath) throws IOException {
         File applicationPackage = new File(packagePath);
         ZipFile zip = new ZipFile(applicationPackage);
 
         boolean distributionSet = false;
         Enumeration<? extends ZipEntry> zipEntries = zip.entries();
-
-        byte[] buffer = new byte[1024];
         while (zipEntries.hasMoreElements()) {
             ZipEntry fileEntry = zipEntries.nextElement();
             String name = fileEntry.getName();
 
-            if (!name.startsWith("distribution/")) {
+            if (!name.startsWith("distribution/"))
                 continue;
-            }
 
             distributionSet = true;
 
-            File outFile = new File(getDataDir(), name);
-            File dir = outFile.getParentFile();
+            File dataDir = new File(context.getApplicationInfo().dataDir);
+            File outFile = new File(dataDir, name);
 
-            if (!dir.exists()) {
-                if (!dir.mkdirs()) {
-                    Log.e(LOGTAG, "Unable to create directories: " + dir.getAbsolutePath());
-                    continue;
-                }
-            }
+            File dir = outFile.getParentFile();
+            if (!dir.exists())
+                dir.mkdirs();
 
             InputStream fileStream = zip.getInputStream(fileEntry);
             OutputStream outStream = new FileOutputStream(outFile);
 
-            int count;
-            while ((count = fileStream.read(buffer)) != -1) {
-                outStream.write(buffer, 0, count);
-            }
+            int b;
+            while ((b = fileStream.read()) != -1)
+                outStream.write(b);
 
             fileStream.close();
             outStream.close();
             outFile.setLastModified(fileEntry.getTime());
         }
 
         zip.close();
 
         return distributionSet;
     }
 
     /**
-     * After calling this method, either <code>distributionDir</code>
-     * will be set, or there is no distribution in use.
-     *
-     * Only call after init.
+     * Returns parsed contents of bookmarks.json.
+     * This method should only be called from a background thread.
      */
-    private File ensureDistributionDir() {
-        if (this.distributionDir != null) {
-            return this.distributionDir;
-        }
-
-        if (this.state != STATE_SET) {
+    public static JSONArray getBookmarks(Context context) {
+        SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
+        String keyName = context.getPackageName() + ".distribution_state";
+        int state = settings.getInt(keyName, STATE_UNKNOWN);
+        if (state == STATE_NONE) {
             return null;
         }
 
-        // After init, we know that either we've copied a distribution out of
-        // the APK, or it exists in /system/.
-        // Look in each location in turn.
-        // (This could be optimized by caching the path in shared prefs.)
-        File copied = new File(getDataDir(), "distribution/");
-        if (copied.exists()) {
-            return this.distributionDir = copied;
-        }
-        File system = getSystemDistributionDir();
-        if (system.exists()) {
-            return this.distributionDir = system;
-        }
-        return null;
-    }
+        ZipFile zip = null;
+        InputStream inputStream = null;
+        try {
+            if (state == STATE_UNKNOWN) {
+                // If the distribution hasn't been set yet, first look for bookmarks.json in the APK.
+                File applicationPackage = new File(context.getPackageResourcePath());
+                zip = new ZipFile(applicationPackage);
+                ZipEntry zipEntry = zip.getEntry("distribution/bookmarks.json");
+                if (zipEntry != null) {
+                    inputStream = zip.getInputStream(zipEntry);
+                } else {
+                    // If there's no bookmarks.json in the APK, but there is a preferences.json,
+                    // don't create any distribution bookmarks.
+                    zipEntry = zip.getEntry("distribution/preferences.json");
+                    if (zipEntry != null) {
+                        return null;
+                    }
+                    // Otherwise, look for bookmarks.json in the /system directory.
+                    File systemFile = new File("/system/" + context.getPackageName() + "/distribution/bookmarks.json");
+                    if (!systemFile.exists()) {
+                        return null;
+                    }
+                    inputStream = new FileInputStream(systemFile);
+                }
+            } else {
+                // Otherwise, first look for the distribution in the data directory.
+                File distDir = new File(context.getApplicationInfo().dataDir, "distribution");
+                if (!distDir.exists()) {
+                    // If that doesn't exist, then we must be using a distribution from the system directory.
+                    distDir = new File("/system/" + context.getPackageName() + "/distribution");
+                }
 
-    /**
-     * Helper to grab a file in the distribution directory.
-     *
-     * Returns null if there is no distribution directory or the file
-     * doesn't exist. Ensures init first.
-     */
-    private File getDistributionFile(String name) {
-        Log.i(LOGTAG, "Getting file from distribution.");
-        if (this.state == STATE_UNKNOWN) {
-            if (!this.doInit()) {
-                return null;
-            }
-        }
-
-        File dist = ensureDistributionDir();
-        if (dist == null) {
-            return null;
-        }
-
-        File descFile = new File(dist, name);
-        if (!descFile.exists()) {
-            Log.e(LOGTAG, "Distribution directory exists, but no file named " + name);
-            return null;
-        }
-
-        return descFile;
-    }
-
-    public DistributionDescriptor getDescriptor() {
-        File descFile = getDistributionFile("preferences.json");
-        if (descFile == null) {
-            // Logging and existence checks are handled in getDistributionFile.
-            return null;
-        }
-
-        try {
-            JSONObject all = new JSONObject(getFileContents(descFile));
-
-            if (!all.has("Global")) {
-                Log.e(LOGTAG, "Distribution preferences.json has no Global entry!");
-                return null;
+                File file = new File(distDir, "bookmarks.json");
+                inputStream = new FileInputStream(file);
             }
 
-            return new DistributionDescriptor(all.getJSONObject("Global"));
-
-        } catch (IOException e) {
-            Log.e(LOGTAG, "Error getting distribution descriptor file.", e);
-            return null;
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "Error parsing preferences.json", e);
-            return null;
-        }
-    }
-
-    public JSONArray getBookmarks() {
-        File bookmarks = getDistributionFile("bookmarks.json");
-        if (bookmarks == null) {
-            // Logging and existence checks are handled in getDistributionFile.
-            return null;
-        }
-
-        try {
-            return new JSONArray(getFileContents(bookmarks));
+            // Convert input stream to JSONArray
+            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+            StringBuilder stringBuilder = new StringBuilder();
+            String s;
+            while ((s = reader.readLine()) != null) {
+                stringBuilder.append(s);
+            }
+            return new JSONArray(stringBuilder.toString());
         } catch (IOException e) {
             Log.e(LOGTAG, "Error getting bookmarks", e);
         } catch (JSONException e) {
             Log.e(LOGTAG, "Error parsing bookmarks.json", e);
+        } finally {
+            try {
+                if (zip != null) {
+                    zip.close();
+                }
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+            } catch (IOException e) {
+                Log.e(LOGTAG, "Error closing streams", e);
+            } 
         }
-
         return null;
     }
-
-    // Shortcut to slurp a file without messing around with streams.
-    private String getFileContents(File file) throws IOException {
-        Scanner scanner = null;
-        try {
-            scanner = new Scanner(file, "UTF-8");
-            return scanner.useDelimiter("\\A").next();
-        } finally {
-            if (scanner != null) {
-                scanner.close();
-            }
-        }
-    }
-
-    private String getDataDir() {
-        return context.getApplicationInfo().dataDir;
-    }
-
-    private File getSystemDistributionDir() {
-        return new File("/system/" + context.getPackageName() + "/distribution");
-    }
 }
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -112,17 +112,16 @@ 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,
@@ -1287,26 +1286,17 @@ 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.");
-                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.
+                mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this, profilePath, dispatcher,
                                                             previousSession);
             }
         });
 
         GeckoAppShell.setNotificationClient(makeNotificationClient());
     }
 
     protected void initializeChrome() {
@@ -1560,25 +1550,18 @@ 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.
-
-                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!
-
+                  XXXX see bug 635342
+                   We want to disable this code if possible.  It is about 145ms in runtime
                 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,22 +1520,16 @@ 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/android-services-files.mk
+++ b/mobile/android/base/android-services-files.mk
@@ -35,17 +35,16 @@ SYNC_JAVA_FILES := \
   background/common/log/writers/StringLogWriter.java \
   background/common/log/writers/TagLogWriter.java \
   background/common/log/writers/ThreadLocalTagLogWriter.java \
   background/datareporting/TelemetryRecorder.java \
   background/db/CursorDumper.java \
   background/db/Tab.java \
   background/healthreport/Environment.java \
   background/healthreport/EnvironmentBuilder.java \
-  background/healthreport/EnvironmentV1.java \
   background/healthreport/HealthReportBroadcastReceiver.java \
   background/healthreport/HealthReportBroadcastService.java \
   background/healthreport/HealthReportDatabases.java \
   background/healthreport/HealthReportDatabaseStorage.java \
   background/healthreport/HealthReportGenerator.java \
   background/healthreport/HealthReportProvider.java \
   background/healthreport/HealthReportStorage.java \
   background/healthreport/HealthReportUtils.java \
--- a/mobile/android/base/background/healthreport/Environment.java
+++ b/mobile/android/base/background/healthreport/Environment.java
@@ -1,49 +1,274 @@
 /* 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.background.healthreport;
 
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Iterator;
+import java.util.SortedSet;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.apache.commons.codec.binary.Base64;
+import org.mozilla.gecko.background.common.log.Logger;
+
 /**
  * This captures all of the details that define an 'environment' for FHR's purposes.
  * Whenever this format changes, it'll be changing with a build ID, so no migration
  * of values is needed.
  *
  * Unless you remove the build descriptors from the set, of course.
  *
  * Or store these in a database.
  *
  * Instances of this class should be considered "effectively immutable": control their
  * scope such that clear creation/sharing boundaries exist. Once you've populated and
  * registered an <code>Environment</code>, don't do so again; start from scratch.
  *
  */
-public abstract class Environment extends EnvironmentV1 {
-  // Version 2 adds osLocale, appLocale, acceptLangSet, and distribution.
-  public static final int CURRENT_VERSION = 2;
+public abstract class Environment {
+  private static final String LOG_TAG = "GeckoEnvironment";
+
+  public static int VERSION = 1;
+
+  protected final Class<? extends EnvironmentAppender> appenderClass;
+
+  protected volatile String hash = null;
+  protected volatile int id = -1;
+
+  // org.mozilla.profile.age.
+  public int profileCreation;
+
+  // org.mozilla.sysinfo.sysinfo.
+  public int cpuCount;
+  public int memoryMB;
+  public String architecture;
+  public String sysName;
+  public String sysVersion;      // Kernel.
 
-  public String osLocale;                // The Android OS "Locale" value.
-  public String appLocale;
-  public int acceptLangSet;
-  public String distribution;            // ID + version. Typically empty.
+  // geckoAppInfo. Not sure if we can/should provide this on Android.
+  public String vendor;
+  public String appName;
+  public String appID;
+  public String appVersion;
+  public String appBuildID;
+  public String platformVersion;
+  public String platformBuildID;
+  public String os;
+  public String xpcomabi;
+  public String updateChannel;
+
+  // appInfo.
+  public int isBlocklistEnabled;
+  public int isTelemetryEnabled;
+  // public int isDefaultBrowser;        // This is meaningless on Android.
+
+  // org.mozilla.addons.active.
+  public JSONObject addons = null;
+
+  // org.mozilla.addons.counts.
+  public int extensionCount;
+  public int pluginCount;
+  public int themeCount;
 
   public Environment() {
     this(Environment.HashAppender.class);
   }
 
   public Environment(Class<? extends EnvironmentAppender> appenderClass) {
-    super(appenderClass);
-    version = CURRENT_VERSION;
+    this.appenderClass = appenderClass;
+  }
+
+  public JSONObject getNonIgnoredAddons() {
+    if (addons == null) {
+      return null;
+    }
+    JSONObject out = new JSONObject();
+    @SuppressWarnings("unchecked")
+    Iterator<String> keys = addons.keys();
+    while (keys.hasNext()) {
+      try {
+        final String key = keys.next();
+        final Object obj = addons.get(key);
+        if (obj != null && obj instanceof JSONObject && ((JSONObject) obj).optBoolean("ignore", false)) {
+          continue;
+        }
+        out.put(key, obj);
+      } catch (JSONException ex) {
+        // Do nothing.
+      }
+    }
+    return out;
+  }
+
+  /**
+   * We break out this interface in order to allow for testing -- pass in your
+   * own appender that just records strings, for example.
+   */
+  public static abstract class EnvironmentAppender {
+    public abstract void append(String s);
+    public abstract void append(int v);
+  }
+
+  public static class HashAppender extends EnvironmentAppender {
+    final MessageDigest hasher;
+
+    public HashAppender() throws NoSuchAlgorithmException {
+      // Note to the security minded reader: we deliberately use SHA-1 here, not
+      // a stronger hash. These identifiers don't strictly need a cryptographic
+      // hash function, because there is negligible value in attacking the hash.
+      // We use SHA-1 because it's *shorter* -- the exact same reason that Git
+      // chose SHA-1.
+      hasher = MessageDigest.getInstance("SHA-1");
+    }
+
+    @Override
+    public void append(String s) {
+      try {
+        hasher.update(((s == null) ? "null" : s).getBytes("UTF-8"));
+      } catch (UnsupportedEncodingException e) {
+        // This can never occur. Thanks, Java.
+      }
+    }
+
+    @Override
+    public void append(int profileCreation) {
+      append(Integer.toString(profileCreation, 10));
+    }
+
+    @Override
+    public String toString() {
+      // We *could* use ASCII85… but the savings would be negated by the
+      // inclusion of JSON-unsafe characters like double-quote.
+      return new Base64(-1, null, false).encodeAsString(hasher.digest());
+    }
   }
 
-  @Override
-  protected void appendHash(EnvironmentAppender appender) {
-    super.appendHash(appender);
+  /**
+   * Compute the stable hash of the configured environment.
+   *
+   * @return the hash in base34, or null if there was a problem.
+   */
+  public String getHash() {
+    // It's never unset, so we only care about partial reads. volatile is enough.
+    if (hash != null) {
+      return hash;
+    }
+
+    EnvironmentAppender appender;
+    try {
+      appender = appenderClass.newInstance();
+    } catch (InstantiationException ex) {
+      // Should never happen, but...
+      Logger.warn(LOG_TAG,  "Could not compute hash.", ex);
+      return null;
+    } catch (IllegalAccessException ex) {
+      // Should never happen, but...
+      Logger.warn(LOG_TAG,  "Could not compute hash.", ex);
+      return null;
+    }
+
+    appender.append(profileCreation);
+    appender.append(cpuCount);
+    appender.append(memoryMB);
+    appender.append(architecture);
+    appender.append(sysName);
+    appender.append(sysVersion);
+    appender.append(vendor);
+    appender.append(appName);
+    appender.append(appID);
+    appender.append(appVersion);
+    appender.append(appBuildID);
+    appender.append(platformVersion);
+    appender.append(platformBuildID);
+    appender.append(os);
+    appender.append(xpcomabi);
+    appender.append(updateChannel);
+    appender.append(isBlocklistEnabled);
+    appender.append(isTelemetryEnabled);
+    appender.append(extensionCount);
+    appender.append(pluginCount);
+    appender.append(themeCount);
+
+    // We need sorted values.
+    if (addons != null) {
+      appendSortedAddons(getNonIgnoredAddons(), appender);
+    }
+
+    return hash = appender.toString();
+  }
+
+  /**
+   * Take a collection of add-on descriptors, appending a consistent string
+   * to the provided builder.
+   */
+  public static void appendSortedAddons(JSONObject addons,
+                                        final EnvironmentAppender builder) {
+    final SortedSet<String> keys = HealthReportUtils.sortedKeySet(addons);
 
-    // v2.
-    appender.append(osLocale);
-    appender.append(appLocale);
-    appender.append(acceptLangSet);
-    appender.append(distribution);
+    // For each add-on, produce a consistent, sorted mapping of its descriptor.
+    for (String key : keys) {
+      try {
+        JSONObject addon = addons.getJSONObject(key);
+
+        // Now produce the output for this add-on.
+        builder.append(key);
+        builder.append("={");
+
+        for (String addonKey : HealthReportUtils.sortedKeySet(addon)) {
+          builder.append(addonKey);
+          builder.append("==");
+          try {
+            builder.append(addon.get(addonKey).toString());
+          } catch (JSONException e) {
+            builder.append("_e_");
+          }
+        }
+
+        builder.append("}");
+      } catch (Exception e) {
+        // Muffle.
+        Logger.warn(LOG_TAG, "Invalid add-on for ID " + key);
+      }
+    }
+  }
+
+  public void setJSONForAddons(byte[] json) throws Exception {
+    setJSONForAddons(new String(json, "UTF-8"));
   }
+
+  public void setJSONForAddons(String json) throws Exception {
+    if (json == null || "null".equals(json)) {
+      addons = null;
+      return;
+    }
+    addons = new JSONObject(json);
+  }
+
+  public void setJSONForAddons(JSONObject json) {
+    addons = json;
+  }
+
+  /**
+   * Includes ignored add-ons.
+   */
+  public String getNormalizedAddonsJSON() {
+    // We trust that our input will already be normalized. If that assumption
+    // is invalidated, then we'll be sorry.
+    return (addons == null) ? "null" : addons.toString();
+  }
+
+  /**
+   * Ensure that the {@link Environment} has been registered with its
+   * storage layer, and can be used to annotate events.
+   *
+   * It's safe to call this method more than once, and each time you'll
+   * get the same ID.
+   *
+   * @return the integer ID to use in subsequent DB insertions.
+   */
+  public abstract int register();
 }
--- a/mobile/android/base/background/healthreport/EnvironmentBuilder.java
+++ b/mobile/android/base/background/healthreport/EnvironmentBuilder.java
@@ -53,23 +53,17 @@ public class EnvironmentBuilder {
       Logger.error(LOG_TAG, "ContentProvider not a HealthReportProvider!", ex);
       throw ex;
     }
   }
 
   public static interface ProfileInformationProvider {
     public boolean isBlocklistEnabled();
     public boolean isTelemetryEnabled();
-    public boolean isAcceptLangUserSet();
     public long getProfileCreationTime();
-
-    public String getDistributionString();
-    public String getOSLocale();
-    public String getAppLocale();
-
     public JSONObject getAddonsJSON();
   }
 
   protected static void populateEnvironment(Environment e,
                                             ProfileInformationProvider info) {
     e.cpuCount = SysInfo.getCPUCount();
     e.memoryMB = SysInfo.getMemSize();
 
@@ -125,22 +119,16 @@ public class EnvironmentBuilder {
           Logger.debug(LOG_TAG, "Unknown add-on type: " + type);
         }
       } catch (Exception ex) {
         Logger.warn(LOG_TAG, "Failed to process add-on " + key, ex);
       }
     }
 
     e.addons = addons;
-
-    // v2 environment fields.
-    e.distribution = info.getDistributionString();
-    e.osLocale = info.getOSLocale();
-    e.appLocale = info.getAppLocale();
-    e.acceptLangSet = info.isAcceptLangUserSet() ? 1 : 0;
   }
 
   /**
    * Returns an {@link Environment} not linked to a storage instance, but
    * populated with current field values.
    *
    * @param info a source of profile data
    * @return the new {@link Environment}
deleted file mode 100644
--- a/mobile/android/base/background/healthreport/EnvironmentV1.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/* 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.background.healthreport;
-
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Iterator;
-import java.util.SortedSet;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.background.common.log.Logger;
-
-public abstract class EnvironmentV1 {
-  private static final String LOG_TAG = "GeckoEnvironment";
-  private static final int VERSION = 1;
-
-  protected final Class<? extends EnvironmentAppender> appenderClass;
-
-  protected volatile String hash = null;
-  protected volatile int id = -1;
-
-  public int version = VERSION;
-
-  // org.mozilla.profile.age.
-  public int profileCreation;
-
-  // org.mozilla.sysinfo.sysinfo.
-  public int cpuCount;
-  public int memoryMB;
-  public String architecture;
-  public String sysName;
-  public String sysVersion;       // Kernel.
-
-  // geckoAppInfo.
-  public String vendor;
-  public String appName;
-  public String appID;
-  public String appVersion;
-  public String appBuildID;
-  public String platformVersion;
-  public String platformBuildID;
-  public String os;
-  public String xpcomabi;
-  public String updateChannel;
-
-  // appinfo.
-  public int isBlocklistEnabled;
-  public int isTelemetryEnabled;
-
-  // org.mozilla.addons.active.
-  public JSONObject addons = null;
-
-  // org.mozilla.addons.counts.
-  public int extensionCount;
-  public int pluginCount;
-  public int themeCount;
-
-  /**
-   * We break out this interface in order to allow for testing -- pass in your
-   * own appender that just records strings, for example.
-   */
-  public static abstract class EnvironmentAppender {
-    public abstract void append(String s);
-    public abstract void append(int v);
-  }
-
-  public static class HashAppender extends EnvironmentAppender {
-    final MessageDigest hasher;
-
-    public HashAppender() throws NoSuchAlgorithmException {
-      // Note to the security-minded reader: we deliberately use SHA-1 here, not
-      // a stronger hash. These identifiers don't strictly need a cryptographic
-      // hash function, because there is negligible value in attacking the hash.
-      // We use SHA-1 because it's *shorter* -- the exact same reason that Git
-      // chose SHA-1.
-      hasher = MessageDigest.getInstance("SHA-1");
-    }
-
-    @Override
-    public void append(String s) {
-      try {
-        hasher.update(((s == null) ? "null" : s).getBytes("UTF-8"));
-      } catch (UnsupportedEncodingException e) {
-        // This can never occur. Thanks, Java.
-      }
-    }
-
-    @Override
-    public void append(int profileCreation) {
-      append(Integer.toString(profileCreation, 10));
-    }
-
-    @Override
-    public String toString() {
-      // We *could* use ASCII85… but the savings would be negated by the
-      // inclusion of JSON-unsafe characters like double-quote.
-      return new Base64(-1, null, false).encodeAsString(hasher.digest());
-    }
-  }
-
-  /**
-   * Ensure that the {@link Environment} has been registered with its
-   * storage layer, and can be used to annotate events.
-   *
-   * It's safe to call this method more than once, and each time you'll
-   * get the same ID.
-   *
-   * @return the integer ID to use in subsequent DB insertions.
-   */
-  public abstract int register();
-
-  protected EnvironmentAppender getAppender() {
-    EnvironmentAppender appender = null;
-    try {
-      appender = appenderClass.newInstance();
-    } catch (InstantiationException ex) {
-      // Should never happen, but...
-      Logger.warn(LOG_TAG,  "Could not compute hash.", ex);
-    } catch (IllegalAccessException ex) {
-      // Should never happen, but...
-      Logger.warn(LOG_TAG,  "Could not compute hash.", ex);
-    }
-    return appender;
-  }
-
-  protected void appendHash(EnvironmentAppender appender) {
-    appender.append(profileCreation);
-    appender.append(cpuCount);
-    appender.append(memoryMB);
-    appender.append(architecture);
-    appender.append(sysName);
-    appender.append(sysVersion);
-    appender.append(vendor);
-    appender.append(appName);
-    appender.append(appID);
-    appender.append(appVersion);
-    appender.append(appBuildID);
-    appender.append(platformVersion);
-    appender.append(platformBuildID);
-    appender.append(os);
-    appender.append(xpcomabi);
-    appender.append(updateChannel);
-    appender.append(isBlocklistEnabled);
-    appender.append(isTelemetryEnabled);
-    appender.append(extensionCount);
-    appender.append(pluginCount);
-    appender.append(themeCount);
-
-    // We need sorted values.
-    if (addons != null) {
-      appendSortedAddons(getNonIgnoredAddons(), appender);
-    }
-  }
-
-  /**
-   * Compute the stable hash of the configured environment.
-   *
-   * @return the hash in base34, or null if there was a problem.
-   */
-  public String getHash() {
-    // It's never unset, so we only care about partial reads. volatile is enough.
-    if (hash != null) {
-      return hash;
-    }
-
-    EnvironmentAppender appender = getAppender();
-    if (appender == null) {
-      return null;
-    }
-
-    appendHash(appender);
-    return hash = appender.toString();
-  }
-
-  public EnvironmentV1(Class<? extends EnvironmentAppender> appenderClass) {
-    super();
-    this.appenderClass = appenderClass;
-  }
-
-  public JSONObject getNonIgnoredAddons() {
-    if (addons == null) {
-      return null;
-    }
-    JSONObject out = new JSONObject();
-    @SuppressWarnings("unchecked")
-    Iterator<String> keys = addons.keys();
-    while (keys.hasNext()) {
-      try {
-        final String key = keys.next();
-        final Object obj = addons.get(key);
-        if (obj != null &&
-            obj instanceof JSONObject &&
-            ((JSONObject) obj).optBoolean("ignore", false)) {
-          continue;
-        }
-        out.put(key, obj);
-      } catch (JSONException ex) {
-        // Do nothing.
-      }
-    }
-    return out;
-  }
-
-  /**
-   * Take a collection of add-on descriptors, appending a consistent string
-   * to the provided builder.
-   */
-  public static void appendSortedAddons(JSONObject addons, final EnvironmentAppender builder) {
-    final SortedSet<String> keys = HealthReportUtils.sortedKeySet(addons);
-
-    // For each add-on, produce a consistent, sorted mapping of its descriptor.
-    for (String key : keys) {
-      try {
-        JSONObject addon = addons.getJSONObject(key);
-
-        // Now produce the output for this add-on.
-        builder.append(key);
-        builder.append("={");
-
-        for (String addonKey : HealthReportUtils.sortedKeySet(addon)) {
-          builder.append(addonKey);
-          builder.append("==");
-          try {
-            builder.append(addon.get(addonKey).toString());
-          } catch (JSONException e) {
-            builder.append("_e_");
-          }
-        }
-
-        builder.append("}");
-      } catch (Exception e) {
-        // Muffle.
-        Logger.warn(LOG_TAG, "Invalid add-on for ID " + key);
-      }
-    }
-  }
-
-  public void setJSONForAddons(byte[] json) throws Exception {
-    setJSONForAddons(new String(json, "UTF-8"));
-  }
-
-  public void setJSONForAddons(String json) throws Exception {
-    if (json == null || "null".equals(json)) {
-      addons = null;
-      return;
-    }
-    addons = new JSONObject(json);
-  }
-
-  public void setJSONForAddons(JSONObject json) {
-    addons = json;
-  }
-
-  /**
-   * Includes ignored add-ons.
-   */
-  public String getNormalizedAddonsJSON() {
-    // We trust that our input will already be normalized. If that assumption
-    // is invalidated, then we'll be sorry.
-    return (addons == null) ? "null" : addons.toString();
-  }
-}
--- a/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java
+++ b/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java
@@ -123,28 +123,26 @@ public class HealthReportDatabaseStorage
   public static final String[] COLUMNS_HASH = new String[] {"hash"};
   public static final String[] COLUMNS_DATE_ENV_FIELD_VALUE = new String[] {"date", "env", "field", "value"};
   public static final String[] COLUMNS_DATE_ENVSTR_M_MV_F_VALUE = new String[] {
     "date", "environment", "measurement_name", "measurement_version",
     "field_name", "field_flags", "value"
   };
 
   private static final String[] COLUMNS_ENVIRONMENT_DETAILS = new String[] {
-      "id", "version", "hash",
+      "id", "hash",
       "profileCreation", "cpuCount", "memoryMB",
 
       "isBlocklistEnabled", "isTelemetryEnabled", "extensionCount",
       "pluginCount", "themeCount",
 
       "architecture", "sysName", "sysVersion", "vendor", "appName", "appID",
       "appVersion", "appBuildID", "platformVersion", "platformBuildID", "os",
       "xpcomabi", "updateChannel",
 
-      "distribution", "osLocale", "appLocale", "acceptLangSet",
-
       // Joined to the add-ons table.
       "addonsBody"
   };
 
   public static final String[] COLUMNS_MEASUREMENT_DETAILS = new String[] {"id", "name", "version"};
   public static final String[] COLUMNS_MEASUREMENT_AND_FIELD_DETAILS =
       new String[] {"measurement_name", "measurement_id", "measurement_version",
                     "field_name", "field_id", "field_flags"};
@@ -185,17 +183,17 @@ public class HealthReportDatabaseStorage
     this.fields.clear();
     this.envs.clear();
     this.measurementVersions.clear();
   }
 
   protected final HealthReportSQLiteOpenHelper helper;
 
   public static class HealthReportSQLiteOpenHelper extends SQLiteOpenHelper {
-    public static final int CURRENT_VERSION = 6;
+    public static final int CURRENT_VERSION = 5;
     public static final String LOG_TAG = "HealthReportSQL";
 
     /**
      * A little helper to avoid SQLiteOpenHelper misbehaving on Android 2.1.
      * Partly cribbed from
      * <http://stackoverflow.com/questions/5332328/sqliteopenhelper-problem-with-fully-qualified-db-path-name>.
      */
     public static class AbsolutePathContext extends ContextWrapper {
@@ -249,20 +247,17 @@ public class HealthReportDatabaseStorage
 
     @Override
     public void onCreate(SQLiteDatabase db) {
       db.execSQL("CREATE TABLE addons (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                  "                     body TEXT, " +
                  "                     UNIQUE (body) " +
                  ")");
 
-      // N.B., hash collisions can occur across versions. In that case, the system
-      // is likely to persist the original environment version.
       db.execSQL("CREATE TABLE environments (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
-                 "                           version INTEGER, " +
                  "                           hash TEXT, " +
                  "                           profileCreation INTEGER, " +
                  "                           cpuCount        INTEGER, " +
                  "                           memoryMB        INTEGER, " +
                  "                           isBlocklistEnabled INTEGER, " +
                  "                           isTelemetryEnabled INTEGER, " +
                  "                           extensionCount     INTEGER, " +
                  "                           pluginCount        INTEGER, " +
@@ -275,22 +270,16 @@ public class HealthReportDatabaseStorage
                  "                           appID           TEXT, " +
                  "                           appVersion      TEXT, " +
                  "                           appBuildID      TEXT, " +
                  "                           platformVersion TEXT, " +
                  "                           platformBuildID TEXT, " +
                  "                           os              TEXT, " +
                  "                           xpcomabi        TEXT, " +
                  "                           updateChannel   TEXT, " +
-
-                 "                           distribution    TEXT, " +
-                 "                           osLocale        TEXT, " +
-                 "                           appLocale       TEXT, " +
-                 "                           acceptLangSet   INTEGER, " +
-
                  "                           addonsID        INTEGER, " +
                  "                           FOREIGN KEY (addonsID) REFERENCES addons(id) ON DELETE RESTRICT, " +
                  "                           UNIQUE (hash) " +
                  ")");
 
       db.execSQL("CREATE TABLE measurements (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                  "                           name TEXT, " +
                  "                           version INTEGER, " +
@@ -363,17 +352,16 @@ public class HealthReportDatabaseStorage
       if (!db.isReadOnly()) {
         db.execSQL("PRAGMA foreign_keys=ON;");
       }
     }
 
     private void createAddonsEnvironmentsView(SQLiteDatabase db) {
       db.execSQL("CREATE VIEW environments_with_addons AS " +
           "SELECT e.id AS id, " +
-          "       e.version AS version, " +
           "       e.hash AS hash, " +
           "       e.profileCreation AS profileCreation, " +
           "       e.cpuCount AS cpuCount, " +
           "       e.memoryMB AS memoryMB, " +
           "       e.isBlocklistEnabled AS isBlocklistEnabled, " +
           "       e.isTelemetryEnabled AS isTelemetryEnabled, " +
           "       e.extensionCount AS extensionCount, " +
           "       e.pluginCount AS pluginCount, " +
@@ -386,20 +374,16 @@ public class HealthReportDatabaseStorage
           "       e.appID AS appID, " +
           "       e.appVersion AS appVersion, " +
           "       e.appBuildID AS appBuildID, " +
           "       e.platformVersion AS platformVersion, " +
           "       e.platformBuildID AS platformBuildID, " +
           "       e.os AS os, " +
           "       e.xpcomabi AS xpcomabi, " +
           "       e.updateChannel AS updateChannel, " +
-          "       e.distribution AS distribution, " +
-          "       e.osLocale AS osLocale, " +
-          "       e.appLocale AS appLocale, " +
-          "       e.acceptLangSet AS acceptLangSet, " +
           "       addons.body AS addonsBody " +
           "FROM environments AS e, addons " +
           "WHERE e.addonsID = addons.id");
     }
 
     private void upgradeDatabaseFrom2To3(SQLiteDatabase db) {
       db.execSQL("CREATE TABLE addons (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                  "                     body TEXT, " +
@@ -428,49 +412,31 @@ public class HealthReportDatabaseStorage
       db.delete("fields", "measurement NOT IN (SELECT id FROM measurements)", null);
       db.delete("environments", "addonsID NOT IN (SELECT id from addons)", null);
       db.delete(EVENTS_INTEGER, "env NOT IN (SELECT id FROM environments)", null);
       db.delete(EVENTS_TEXTUAL, "env NOT IN (SELECT id FROM environments)", null);
       db.delete(EVENTS_INTEGER, "field NOT IN (SELECT id FROM fields)", null);
       db.delete(EVENTS_TEXTUAL, "field NOT IN (SELECT id FROM fields)", null);
     }
 
-    private void upgradeDatabaseFrom5to6(SQLiteDatabase db) {
-      db.execSQL("DROP VIEW environments_with_addons");
-
-      // Add version to environment (default to 1).
-      db.execSQL("ALTER TABLE environments ADD COLUMN version INTEGER DEFAULT 1");
-
-      // Add fields to environment (default to empty string).
-      db.execSQL("ALTER TABLE environments ADD COLUMN distribution TEXT DEFAULT ''");
-      db.execSQL("ALTER TABLE environments ADD COLUMN osLocale TEXT DEFAULT ''");
-      db.execSQL("ALTER TABLE environments ADD COLUMN appLocale TEXT DEFAULT ''");
-      db.execSQL("ALTER TABLE environments ADD COLUMN acceptLangSet INTEGER DEFAULT 0");
-
-      // Recreate view.
-      createAddonsEnvironmentsView(db);
-    }
-
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
       if (oldVersion >= newVersion) {
         return;
       }
 
       Logger.info(LOG_TAG, "onUpgrade: from " + oldVersion + " to " + newVersion + ".");
       try {
         switch (oldVersion) {
         case 2:
           upgradeDatabaseFrom2To3(db);
         case 3:
           upgradeDatabaseFrom3To4(db);
         case 4:
           upgradeDatabaseFrom4to5(db);
-        case 5:
-          upgradeDatabaseFrom5to6(db);
         }
       } catch (Exception e) {
         Logger.error(LOG_TAG, "Failure in onUpgrade.", e);
         throw new RuntimeException(e);
       }
    }
 
     public void deleteEverything() {
@@ -565,17 +531,16 @@ public class HealthReportDatabaseStorage
       final String h = getHash();
       if (storage.envs.containsKey(h)) {
         this.id = storage.envs.get(h);
         return this.id;
       }
 
       // Otherwise, add data and hash to the DB.
       ContentValues v = new ContentValues();
-      v.put("version", version);
       v.put("hash", h);
       v.put("profileCreation", profileCreation);
       v.put("cpuCount", cpuCount);
       v.put("memoryMB", memoryMB);
       v.put("isBlocklistEnabled", isBlocklistEnabled);
       v.put("isTelemetryEnabled", isTelemetryEnabled);
       v.put("extensionCount", extensionCount);
       v.put("pluginCount", pluginCount);
@@ -588,20 +553,16 @@ public class HealthReportDatabaseStorage
       v.put("appID", appID);
       v.put("appVersion", appVersion);
       v.put("appBuildID", appBuildID);
       v.put("platformVersion", platformVersion);
       v.put("platformBuildID", platformBuildID);
       v.put("os", os);
       v.put("xpcomabi", xpcomabi);
       v.put("updateChannel", updateChannel);
-      v.put("distribution", distribution);
-      v.put("osLocale", osLocale);
-      v.put("appLocale", appLocale);
-      v.put("acceptLangSet", acceptLangSet);
 
       final SQLiteDatabase db = storage.helper.getWritableDatabase();
 
       // If we're not already, we want all of our inserts to be in a transaction.
       boolean newTransaction = !db.inTransaction();
 
       // Insert, with a little error handling to populate the cache in case of
       // omission and consequent collision.
@@ -677,17 +638,16 @@ public class HealthReportDatabaseStorage
         values.put("body", json);
         return (int) db.insert("addons", null, values);
       } finally {
         c.close();
       }
     }
 
     public void init(ContentValues v) {
-      version         = v.containsKey("version") ? v.getAsInteger("version") : Environment.CURRENT_VERSION;
       profileCreation = v.getAsInteger("profileCreation");
       cpuCount        = v.getAsInteger("cpuCount");
       memoryMB        = v.getAsInteger("memoryMB");
 
       isBlocklistEnabled = v.getAsInteger("isBlocklistEnabled");
       isTelemetryEnabled = v.getAsInteger("isTelemetryEnabled");
       extensionCount     = v.getAsInteger("extensionCount");
       pluginCount        = v.getAsInteger("pluginCount");
@@ -702,21 +662,16 @@ public class HealthReportDatabaseStorage
       appVersion      = v.getAsString("appVersion");
       appBuildID      = v.getAsString("appBuildID");
       platformVersion = v.getAsString("platformVersion");
       platformBuildID = v.getAsString("platformBuildID");
       os              = v.getAsString("os");
       xpcomabi        = v.getAsString("xpcomabi");
       updateChannel   = v.getAsString("updateChannel");
 
-      distribution    = v.getAsString("distribution");
-      osLocale        = v.getAsString("osLocale");
-      appLocale       = v.getAsString("appLocale");
-      acceptLangSet   = v.getAsInteger("acceptLangSet");
-
       try {
         setJSONForAddons(v.getAsString("addonsBody"));
       } catch (Exception e) {
         // Nothing we can do.
       }
 
       this.hash = null;
       this.id = -1;
@@ -726,17 +681,16 @@ public class HealthReportDatabaseStorage
      * Fill ourselves with data from the DB, then advance the cursor.
      *
      * @param cursor a {@link Cursor} pointing at a record to load.
      * @return true if the cursor was successfully advanced.
      */
     public boolean init(Cursor cursor) {
       int i = 0;
       this.id         = cursor.getInt(i++);
-      this.version    = cursor.getInt(i++);
       this.hash       = cursor.getString(i++);
 
       profileCreation = cursor.getInt(i++);
       cpuCount        = cursor.getInt(i++);
       memoryMB        = cursor.getInt(i++);
 
       isBlocklistEnabled = cursor.getInt(i++);
       isTelemetryEnabled = cursor.getInt(i++);
@@ -753,21 +707,16 @@ public class HealthReportDatabaseStorage
       appVersion      = cursor.getString(i++);
       appBuildID      = cursor.getString(i++);
       platformVersion = cursor.getString(i++);
       platformBuildID = cursor.getString(i++);
       os              = cursor.getString(i++);
       xpcomabi        = cursor.getString(i++);
       updateChannel   = cursor.getString(i++);
 
-      distribution    = cursor.getString(i++);
-      osLocale        = cursor.getString(i++);
-      appLocale       = cursor.getString(i++);
-      acceptLangSet   = cursor.getInt(i++);
-
       try {
         setJSONForAddons(cursor.getBlob(i++));
       } catch (Exception e) {
         // Nothing we can do.
       }
 
       return cursor.moveToNext();
     }
@@ -1385,32 +1334,30 @@ public class HealthReportDatabaseStorage
    * Deletes environments not referenced by any events except for the given current environment.
    */
   protected int deleteOrphanedEnv(final int curEnv) {
     final SQLiteDatabase db = this.helper.getWritableDatabase();
     return deleteOrphanedEnv(db, curEnv);
   }
 
   // Called internally only to ensure the same db instance is used.
-  @SuppressWarnings("static-method")
   protected int deleteOrphanedEnv(final SQLiteDatabase db, final int curEnv) {
     final String whereClause =
         "id != ? AND " +
         "id NOT IN (SELECT env FROM events)";
     final String[] whereArgs = new String[] {Integer.toString(curEnv)};
     return db.delete("environments", whereClause, whereArgs);
   }
 
   protected int deleteEventsBefore(final String dayString) {
     final SQLiteDatabase db = this.helper.getWritableDatabase();
     return deleteEventsBefore(db, dayString);
   }
 
   // Called internally only to ensure the same db instance is used.
-  @SuppressWarnings("static-method")
   protected int deleteEventsBefore(final SQLiteDatabase db, final String dayString) {
     final String whereClause = "date < ?";
     final String[] whereArgs = new String[] {dayString};
     int numEventsDeleted = 0;
     db.beginTransaction();
     try {
       numEventsDeleted += db.delete("events_integer", whereClause, whereArgs);
       numEventsDeleted += db.delete("events_textual", whereClause, whereArgs);
@@ -1425,17 +1372,16 @@ public class HealthReportDatabaseStorage
    * Deletes addons not referenced by any environments.
    */
   protected int deleteOrphanedAddons() {
     final SQLiteDatabase db = this.helper.getWritableDatabase();
     return deleteOrphanedAddons(db);
   }
 
   // Called internally only to ensure the same db instance is used.
-  @SuppressWarnings("static-method")
   protected int deleteOrphanedAddons(final SQLiteDatabase db) {
     final String whereClause = "id NOT IN (SELECT addonsID FROM environments)";
     return db.delete("addons", whereClause, null);
   }
 
   /**
    * Retrieve a mapping from a table. Keys should be unique; only one key-value
    * pair will be returned for each key.
--- a/mobile/android/base/background/healthreport/HealthReportGenerator.java
+++ b/mobile/android/base/background/healthreport/HealthReportGenerator.java
@@ -383,127 +383,34 @@ public class HealthReportGenerator {
     }
     if (current != null && changes == 0) {
       return null;
     }
     gecko.put("_v", 1);
     return gecko;
   }
 
-  // Null-safe string comparison.
-  private static boolean stringsDiffer(final String a, final String b) {
-    if (a == null) {
-      return b != null;
-    }
-    return !a.equals(b);
-  }
-
   private static JSONObject getAppInfo(Environment e, Environment current) throws JSONException {
     JSONObject appinfo = new JSONObject();
-
-    Logger.debug(LOG_TAG, "Generating appinfo for v" + e.version + " env " + e.hash);
-
-    // Is the environment in question newer than the diff target, or is
-    // there no diff target?
-    final boolean outdated = current == null ||
-                             e.version > current.version;
-
-    // Is the environment in question a different version (lower or higher),
-    // or is there no diff target?
-    final boolean differ = outdated || current.version > e.version;
-
-    // Always produce an output object if there's a version mismatch or this
-    // isn't a diff. Otherwise, track as we go if there's any difference.
-    boolean changed = differ;
-
-    switch (e.version) {
-    // There's a straightforward correspondence between environment versions
-    // and appinfo versions.
-    case 2:
-      appinfo.put("_v", 3);
-      break;
-    case 1:
-      appinfo.put("_v", 2);
-      break;
-    default:
-      Logger.warn(LOG_TAG, "Unknown environment version: " + e.version);
-      return appinfo;
+    int changes = 0;
+    if (current == null || current.isBlocklistEnabled != e.isBlocklistEnabled) {
+      appinfo.put("isBlocklistEnabled", e.isBlocklistEnabled);
+      changes++;
     }
-
-    switch (e.version) {
-    case 2:
-      if (populateAppInfoV2(appinfo, e, current, outdated)) {
-        changed = true;
-      }
-      // Fall through.
-
-    case 1:
-      // There is no older version than v1, so don't check outdated.
-      if (populateAppInfoV1(e, current, appinfo)) {
-        changed = true;
-      }
+    if (current == null || current.isTelemetryEnabled != e.isTelemetryEnabled) {
+      appinfo.put("isTelemetryEnabled", e.isTelemetryEnabled);
+      changes++;
     }
-
-    if (!changed) {
+    if (current != null && changes == 0) {
       return null;
     }
-
+    appinfo.put("_v", 2);
     return appinfo;
   }
 
-  private static boolean populateAppInfoV1(Environment e,
-                                           Environment current,
-                                           JSONObject appinfo)
-    throws JSONException {
-    boolean changes = false;
-    if (current == null || current.isBlocklistEnabled != e.isBlocklistEnabled) {
-      appinfo.put("isBlocklistEnabled", e.isBlocklistEnabled);
-      changes = true;
-    }
-
-    if (current == null || current.isTelemetryEnabled != e.isTelemetryEnabled) {
-      appinfo.put("isTelemetryEnabled", e.isTelemetryEnabled);
-      changes = true;
-    }
-
-    return changes;
-  }
-
-  private static boolean populateAppInfoV2(JSONObject appinfo,
-                                           Environment e,
-                                           Environment current,
-                                           final boolean outdated)
-    throws JSONException {
-    boolean changes = false;
-    if (outdated ||
-        stringsDiffer(current.osLocale, e.osLocale)) {
-      appinfo.put("osLocale", e.osLocale);
-      changes = true;
-    }
-
-    if (outdated ||
-        stringsDiffer(current.appLocale, e.appLocale)) {
-      appinfo.put("appLocale", e.appLocale);
-      changes = true;
-    }
-
-    if (outdated ||
-        stringsDiffer(current.distribution, e.distribution)) {
-      appinfo.put("distribution", e.distribution);
-      changes = true;
-    }
-
-    if (outdated ||
-        current.acceptLangSet != e.acceptLangSet) {
-      appinfo.put("acceptLangIsUserSet", e.acceptLangSet);
-      changes = true;
-    }
-    return changes;
-  }
-
   private static JSONObject getAddonCounts(Environment e, Environment current) throws JSONException {
     JSONObject counts = new JSONObject();
     int changes = 0;
     if (current == null || current.extensionCount != e.extensionCount) {
       counts.put("extension", e.extensionCount);
       changes++;
     }
     if (current == null || current.pluginCount != e.pluginCount) {
--- a/mobile/android/base/background/healthreport/ProfileInformationCache.java
+++ b/mobile/android/base/background/healthreport/ProfileInformationCache.java
@@ -5,17 +5,16 @@
 package org.mozilla.gecko.background.healthreport;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.nio.charset.Charset;
-import java.util.Locale;
 import java.util.Scanner;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.healthreport.EnvironmentBuilder.ProfileInformationProvider;
 
 /**
@@ -28,50 +27,27 @@ public class ProfileInformationCache imp
   private static final String LOG_TAG = "GeckoProfileInfo";
   private static final String CACHE_FILE = "profile_info_cache.json";
 
   /*
    * FORMAT_VERSION history:
    *   -: No version number; implicit v1.
    *   1: Add versioning (Bug 878670).
    *   2: Bump to regenerate add-on set after landing Bug 900694 (Bug 901622).
-   *   3: Add distribution, osLocale, appLocale.
    */
-  public static final int FORMAT_VERSION = 3;
+  public static final int FORMAT_VERSION = 2;
 
   protected boolean initialized = false;
   protected boolean needsWrite = false;
 
   protected final File file;
 
   private volatile boolean blocklistEnabled = true;
   private volatile boolean telemetryEnabled = false;
-  private volatile boolean isAcceptLangUserSet = false;
-
   private volatile long profileCreationTime = 0;
-  private volatile String distribution = "";
-
-  // There are really four kinds of locale in play:
-  //
-  // * The OS
-  // * The Android environment of the app (setDefault)
-  // * The Gecko locale
-  // * The requested content locale (Accept-Language).
-  //
-  // We track only the first two, assuming that the Gecko locale will typically
-  // be the same as the app locale.
-  //
-  // The app locale is fetched from the PIC because it can be modified at
-  // runtime -- it won't necessarily be what Locale.getDefaultLocale() returns
-  // in a fresh non-browser profile.
-  //
-  // We also track the OS locale here for the same reason -- we need to store
-  // the default (OS) value before the locale-switching code takes effect!
-  private volatile String osLocale = "";
-  private volatile String appLocale = "";
 
   private volatile JSONObject addons = null;
 
   public ProfileInformationCache(String profilePath) {
     file = new File(profilePath + File.separator + CACHE_FILE);
     Logger.pii(LOG_TAG, "Using " + file.getAbsolutePath() + " for profile information cache.");
   }
 
@@ -81,21 +57,17 @@ public class ProfileInformationCache imp
   }
 
   public JSONObject toJSON() {
     JSONObject object = new JSONObject();
     try {
       object.put("version", FORMAT_VERSION);
       object.put("blocklist", blocklistEnabled);
       object.put("telemetry", telemetryEnabled);
-      object.put("isAcceptLangUserSet", isAcceptLangUserSet);
       object.put("profileCreated", profileCreationTime);
-      object.put("osLocale", osLocale);
-      object.put("appLocale", appLocale);
-      object.put("distribution", distribution);
       object.put("addons", addons);
     } catch (JSONException e) {
       // There isn't much we can do about this.
       // Let's just quietly muffle.
       return null;
     }
     return object;
   }
@@ -109,22 +81,18 @@ public class ProfileInformationCache imp
    * @return false if there's a version mismatch or an error, true on success.
    */
   private boolean fromJSON(JSONObject object) throws JSONException {
     int version = object.optInt("version", 1);
     switch (version) {
     case FORMAT_VERSION:
       blocklistEnabled = object.getBoolean("blocklist");
       telemetryEnabled = object.getBoolean("telemetry");
-      isAcceptLangUserSet = object.getBoolean("isAcceptLangUserSet");
       profileCreationTime = object.getLong("profileCreated");
       addons = object.getJSONObject("addons");
-      distribution = object.getString("distribution");
-      osLocale = object.getString("osLocale");
-      appLocale = object.getString("appLocale");
       return true;
     default:
       Logger.warn(LOG_TAG, "Unable to restore from version " + version + " PIC file: expecting " + FORMAT_VERSION);
       return false;
     }
   }
 
   protected JSONObject readFromFile() throws FileNotFoundException, JSONException {
@@ -234,141 +202,60 @@ public class ProfileInformationCache imp
 
   public void setTelemetryEnabled(boolean value) {
     Logger.debug(LOG_TAG, "Setting telemetry enabled: " + value);
     telemetryEnabled = value;
     needsWrite = true;
   }
 
   @Override
-  public boolean isAcceptLangUserSet() {
-    ensureInitialized();
-    return isAcceptLangUserSet;
-  }
-
-  public void setAcceptLangUserSet(boolean value) {
-    Logger.debug(LOG_TAG, "Setting accept-lang as user-set: " + value);
-    isAcceptLangUserSet = value;
-    needsWrite = true;
-  }
-
-  @Override
   public long getProfileCreationTime() {
     ensureInitialized();
     return profileCreationTime;
   }
 
   public void setProfileCreationTime(long value) {
     Logger.debug(LOG_TAG, "Setting profile creation time: " + value);
     profileCreationTime = value;
     needsWrite = true;
   }
 
   @Override
-  public String getDistributionString() {
-    ensureInitialized();
-    return distribution;
-  }
-
-  /**
-   * Ensure that your arguments are non-null.
-   */
-  public void setDistributionString(String distributionID, String distributionVersion) {
-    Logger.debug(LOG_TAG, "Setting distribution: " + distributionID + ", " + distributionVersion);
-    distribution = distributionID + ":" + distributionVersion;
-    needsWrite = true;
-  }
-
-  @Override
-  public String getAppLocale() {
-    ensureInitialized();
-    return appLocale;
-  }
-
-  public void setAppLocale(String value) {
-    if (value.equalsIgnoreCase(appLocale)) {
-      return;
-    }
-    Logger.debug(LOG_TAG, "Setting app locale: " + value);
-    appLocale = value.toLowerCase(Locale.US);
-    needsWrite = true;
-  }
-
-  @Override
-  public String getOSLocale() {
-    ensureInitialized();
-    return osLocale;
-  }
-
-  public void setOSLocale(String value) {
-    if (value.equalsIgnoreCase(osLocale)) {
-      return;
-    }
-    Logger.debug(LOG_TAG, "Setting OS locale: " + value);
-    osLocale = value.toLowerCase(Locale.US);
-    needsWrite = true;
-  }
-
-  /**
-   * Update the PIC, if necessary, to match the current locale environment.
-   *
-   * @return true if the PIC needed to be updated.
-   */
-  public boolean updateLocales(String osLocale, String appLocale) {
-    if (this.osLocale.equalsIgnoreCase(osLocale) &&
-        (appLocale == null || this.appLocale.equalsIgnoreCase(appLocale))) {
-      return false;
-    }
-    this.setOSLocale(osLocale);
-    if (appLocale != null) {
-      this.setAppLocale(appLocale);
-    }
-    return true;
-  }
-
-  @Override
   public JSONObject getAddonsJSON() {
-    ensureInitialized();
     return addons;
   }
 
   public void updateJSONForAddon(String id, String json) throws Exception {
     addons.put(id, new JSONObject(json));
-    needsWrite = true;
   }
 
   public void removeAddon(String id) {
-    if (null != addons.remove(id)) {
-      needsWrite = true;
-    }
+    addons.remove(id);
   }
 
   /**
    * Will throw if you haven't done a full update at least once.
    */
   public void updateJSONForAddon(String id, JSONObject json) {
     if (addons == null) {
       throw new IllegalStateException("Cannot incrementally update add-ons without first initializing.");
     }
     try {
       addons.put(id, json);
-      needsWrite = true;
     } catch (Exception e) {
       // Why would this happen?
       Logger.warn(LOG_TAG, "Unexpected failure updating JSON for add-on.", e);
     }
   }
 
   /**
    * Update the cached set of add-ons. Throws on invalid input.
    *
    * @param json a valid add-ons JSON string.
    */
   public void setJSONForAddons(String json) throws Exception {
     addons = new JSONObject(json);
-    needsWrite = true;
   }
 
   public void setJSONForAddons(JSONObject json) {
     addons = json;
-    needsWrite = true;
   }
 }
--- a/mobile/android/base/health/BrowserHealthRecorder.java
+++ b/mobile/android/base/health/BrowserHealthRecorder.java
@@ -8,21 +8,21 @@ package org.mozilla.gecko.health;
 import java.util.ArrayList;
 
 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.Distribution;
-import org.mozilla.gecko.Distribution.DistributionDescriptor;
 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,41 +33,40 @@ 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
- * #onAppLocaleChanged(String)} followed by {@link
+ * #onBlocklistPrefChanged(boolean)} or {@link
+ * #onTelemetryPrefChanged(boolean)}, 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_SNAPSHOT = "HealthReport:Snapshot";
+    private static final String EVENT_ADDONS_ALL = "Addons:All";
     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";
@@ -238,25 +237,18 @@ 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,
-                                 final String osLocale,
-                                 final String appLocale,
-                                 SessionInformation previousSession) {
+    public BrowserHealthRecorder(final Context context, final String profilePath, final EventDispatcher dispatcher, 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.");
         }
@@ -266,22 +258,19 @@ 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, osLocale, appLocale);
+            this.initialize(context, profilePath);
         } catch (Exception e) {
             Log.e(LOG_TAG, "Exception initializing.", e);
         }
     }
 
     /**
      * Shut down database connections, unregister event listeners, and perform
      * provider-specific uninitialization.
@@ -305,27 +294,32 @@ public class BrowserHealthRecorder imple
         this.storage = null;
         if (this.client != null) {
             this.client.release();
             this.client = null;
         }
     }
 
     private void unregisterEventListeners() {
-        this.dispatcher.unregisterEventListener(EVENT_SNAPSHOT, this);
+        this.dispatcher.unregisterEventListener(EVENT_ADDONS_ALL, 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 onAppLocaleChanged(String to) {
+    public void onBlocklistPrefChanged(boolean to) {
         this.profileCache.beginInitialization();
-        this.profileCache.setAppLocale(to);
+        this.profileCache.setBlocklistEnabled(to);
+    }
+
+    public void onTelemetryPrefChanged(boolean to) {
+        this.profileCache.beginInitialization();
+        this.profileCache.setTelemetryEnabled(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);
@@ -341,17 +335,18 @@ 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.
+     * Invoke this method after calls that mutate the environment, such as
+     * {@link #onBlocklistPrefChanged(boolean)}.
      *
      * 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;
@@ -491,46 +486,24 @@ 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 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);
-            }
+    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);
             return;
         }
-
-        // (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);
+        if (PREF_BLOCKLIST_ENABLED.equals(pref)) {
+            profileCache.setBlocklistEnabled(value);
             return;
         }
         Log.w(LOG_TAG, "Unexpected pref: " + pref);
     }
 
     /**
      * Background init helper.
      */
@@ -593,55 +566,57 @@ public class BrowserHealthRecorder imple
             }
         });
     }
 
     /**
      * Add provider-specific initialization in this method.
      */
     private synchronized void initialize(final Context context,
-                                         final String profilePath,
-                                         final String osLocale,
-                                         final String appLocale)
+                                         final String profilePath)
         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));
-        this.profileCache.setOSLocale(osLocale);
-        this.profileCache.setAppLocale(appLocale);
+
+        final BrowserHealthRecorder self = this;
 
-        // Because the distribution lookup can take some time, do it at the end of
-        // our background startup work, along with the Gecko snapshot fetch.
-        final GeckoEventListener self = this;
-        ThreadUtils.postToBackgroundThread(new Runnable() {
+        PrefHandler handler = new PrefsHelper.PrefHandlerBase() {
+            @Override
+            public void prefValue(String pref, boolean value) {
+                handlePrefValue(pref, value);
+            }
+
             @Override
-            public void run() {
-                final DistributionDescriptor desc = new Distribution(context).getDescriptor();
-                if (desc != null && desc.valid) {
-                    profileCache.setDistributionString(desc.id, desc.version);
-                }
-                Log.d(LOG_TAG, "Requesting all add-ons and FHR prefs from Gecko.");
-                dispatcher.registerEventListener(EVENT_SNAPSHOT, self);
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HealthReport:RequestSnapshot", null));
+            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.");
     }
 
     /**
      * Invoked in the background whenever the environment transitions between
      * two valid values.
      */
     protected void onEnvironmentTransition(int prev, int env) {
         if (this.state != State.INITIALIZED) {
@@ -658,32 +633,22 @@ public class BrowserHealthRecorder imple
         setCurrentSession(newSession);
         newSession.recordBegin(editor);
         editor.commit();
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
-            if (EVENT_SNAPSHOT.equals(event)) {
-                Log.d(LOG_TAG, "Got all add-ons and prefs.");
+            if (EVENT_ADDONS_ALL.equals(event)) {
+                Log.d(LOG_TAG, "Got all add-ons.");
                 try {
-                    JSONObject json = message.getJSONObject("json");
-                    JSONObject addons = json.getJSONObject("addons");
+                    JSONObject addons = message.getJSONObject("json");
                     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) {
@@ -705,17 +670,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);
-                this.onPrefMessage(pref, message);
+                handlePrefValue(pref, message.getBoolean("value"));
                 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,20 +5306,17 @@ var FormAssistant = {
   }
 };
 
 /**
  * An object to watch for Gecko status changes -- add-on installs, pref changes
  * -- and reflect them back to Java.
  */
 let HealthReportStatusListener = {
-  PREF_ACCEPT_LANG: "intl.accept_languages",
-  PREF_BLOCKLIST_ENABLED: "extensions.blocklist.enabled",
-
-  PREF_TELEMETRY_ENABLED:
+  TELEMETRY_PREF: 
 #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
@@ -5328,62 +5325,40 @@ 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);
     }
 
-    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);
+    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);
     }
   },
 
   uninit: function () {
-    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);
+    Services.obs.removeObserver(this, "Addons:FetchAll");
+    Services.prefs.removeObserver("extensions.blocklist.enabled", this);
+    if (this.TELEMETRY_PREF) {
+      Services.prefs.removeObserver(this.TELEMETRY_PREF, this);
     }
 
     AddonManager.removeAddonListener(this);
   },
 
   observe: function (aSubject, aTopic, aData) {
     switch (aTopic) {
-      case "HealthReport:RequestSnapshot":
-        HealthReportStatusListener.sendSnapshotToJava();
+      case "Addons:FetchAll":
+        HealthReportStatusListener.sendAllAddonsToJava();
         break;
       case "nsPref:changed":
-        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);
+        sendMessageToJava({ type: "Pref:Change", pref: aData, value: Services.prefs.getBoolPref(aData) });
         break;
     }
   },
 
   MILLISECONDS_PER_DAY: 24 * 60 * 60 * 1000,
 
   COPY_FIELDS: [
     "blocklistState",
@@ -5455,64 +5430,35 @@ let HealthReportStatusListener = {
   },
   onPropertyChanged: function (aAddon, aProperties) {
     this.notifyJava(aAddon);
   },
   onOperationCancelled: function (aAddon) {
     this.notifyJava(aAddon);
   },
 
-  sendSnapshotToJava: function () {
+  sendAllAddonsToJava: function () {
     AddonManager.getAllAddons(function (aAddons) {
-        let jsonA = {};
+        let json = {};
         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;
               }
-              jsonA[addon.id] = addonJSON;
+              json[addon.id] = addonJSON;
             } catch (e) {
               // Just skip this add-on.
             }
           }
         }
-
-        // 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));
+        sendMessageToJava({ type: "Addons:All", json: json });
+      });
   },
 };
 
 var XPInstallObserver = {
   init: function xpi_init() {
     Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false);
     Services.obs.addObserver(XPInstallObserver, "addon-install-started", false);
 
--- a/mobile/android/services/java-sources.mn
+++ b/mobile/android/services/java-sources.mn
@@ -22,17 +22,16 @@ background/common/log/writers/SimpleTagL
 background/common/log/writers/StringLogWriter.java
 background/common/log/writers/TagLogWriter.java
 background/common/log/writers/ThreadLocalTagLogWriter.java
 background/datareporting/TelemetryRecorder.java
 background/db/CursorDumper.java
 background/db/Tab.java
 background/healthreport/Environment.java
 background/healthreport/EnvironmentBuilder.java
-background/healthreport/EnvironmentV1.java
 background/healthreport/HealthReportBroadcastReceiver.java
 background/healthreport/HealthReportBroadcastService.java
 background/healthreport/HealthReportDatabases.java
 background/healthreport/HealthReportDatabaseStorage.java
 background/healthreport/HealthReportGenerator.java
 background/healthreport/HealthReportProvider.java
 background/healthreport/HealthReportStorage.java
 background/healthreport/HealthReportUtils.java
--- a/mobile/android/tests/background/junit3/src/healthreport/MockDatabaseEnvironment.java
+++ b/mobile/android/tests/background/junit3/src/healthreport/MockDatabaseEnvironment.java
@@ -33,44 +33,36 @@ public class MockDatabaseEnvironment ext
     }
 
     @Override
     public String toString() {
       return appended.toString();
     }
   }
 
-  public MockDatabaseEnvironment mockInit(String appVersion) {
+  public MockDatabaseEnvironment mockInit(String version) {
     profileCreation = 1234;
     cpuCount        = 2;
     memoryMB        = 512;
 
     isBlocklistEnabled = 1;
     isTelemetryEnabled = 1;
     extensionCount     = 0;
     pluginCount        = 0;
     themeCount         = 0;
 
     architecture    = "";
     sysName         = "";
     sysVersion      = "";
     vendor          = "";
     appName         = "";
     appID           = "";
-    this.appVersion = appVersion;
+    appVersion      = version;
     appBuildID      = "";
     platformVersion = "";
     platformBuildID = "";
     os              = "";
     xpcomabi        = "";
     updateChannel   = "";
 
-    // v2 fields.
-    distribution  = "";
-    appLocale     = "";
-    osLocale      = "";
-    acceptLangSet = 0;
-
-    version       = Environment.CURRENT_VERSION;
-
     return this;
   }
 }
--- a/mobile/android/tests/background/junit3/src/healthreport/TestHealthReportGenerator.java
+++ b/mobile/android/tests/background/junit3/src/healthreport/TestHealthReportGenerator.java
@@ -10,20 +10,16 @@ import java.util.Iterator;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.background.common.DateUtils;
 import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field;
 import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields;
 import org.mozilla.gecko.background.helpers.FakeProfileTestCase;
 
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.util.SparseArray;
-
 public class TestHealthReportGenerator extends FakeProfileTestCase {
   @SuppressWarnings("static-method")
   public void testOptObject() throws JSONException {
     JSONObject o = new JSONObject();
     o.put("foo", JSONObject.NULL);
     assertEquals(null, o.optJSONObject("foo"));
   }
 
@@ -56,24 +52,19 @@ public class TestHealthReportGenerator e
     assertEquals(1, foo.getInt("b"));
     assertEquals(1, foo.getInt("c"));
     assertFalse(foo.has("d"));
     assertEquals(1, bar.getInt("a"));
     assertEquals(1, bar.getInt("d"));
     assertFalse(bar.has("b"));
   }
 
-  // We don't initialize the env in testHashing, so these are just the default
-  // values for the Java types, in order.
   private static final String EXPECTED_MOCK_BASE_HASH = "000nullnullnullnullnullnullnull"
                                                         + "nullnullnullnullnullnull00000";
 
-  // v2 fields.
-  private static final String EXPECTED_MOCK_BASE_HASH_SUFFIX = "null" + "null" + 0 + "null";
-
   public void testHashing() throws JSONException {
     MockHealthReportDatabaseStorage storage = new MockHealthReportDatabaseStorage(context, fakeProfileDirectory);
     MockDatabaseEnvironment env = new MockDatabaseEnvironment(storage, MockDatabaseEnvironment.MockEnvironmentAppender.class);
     env.addons = new JSONObject();
 
     String addonAHash = "{addonA}={appDisabled==falseforeignInstall==false"
         + "hasBinaryComponents==falseinstallDay==15269scope==1"
         + "type==extensionupdateDay==15602userDisabled==false"
@@ -100,20 +91,20 @@ public class TestHealthReportGenerator e
         "\"type\": \"extension\", " +
         "\"scope\": 1, " +
         "\"appDisabled\": false, " +
         "\"version\": \"1.10\", " +
         "\"updateDay\": 15602 " +
     "}");
     env.addons.put("{addonA}", addonA1);
 
-    assertEquals(EXPECTED_MOCK_BASE_HASH + addonAHash + EXPECTED_MOCK_BASE_HASH_SUFFIX, env.getHash());
+    assertEquals(EXPECTED_MOCK_BASE_HASH + addonAHash, env.getHash());
 
     env.addons.put("{addonA}", addonA1rev);
-    assertEquals(EXPECTED_MOCK_BASE_HASH + addonAHash + EXPECTED_MOCK_BASE_HASH_SUFFIX, env.getHash());
+    assertEquals(EXPECTED_MOCK_BASE_HASH + addonAHash, env.getHash());
   }
 
   private void assertJSONDiff(JSONObject source, JSONObject diff) throws JSONException {
     assertEquals(source.get("a"), diff.get("a"));
     assertFalse(diff.has("b"));
     assertEquals(source.get("c"), diff.get("c"));
     JSONObject diffD = diff.getJSONObject("d");
     assertFalse(diffD.has("aa"));
@@ -410,108 +401,9 @@ public class TestHealthReportGenerator e
     assertEquals(JSONObject.NULL, discreteJSON.get(1));
     assertEquals("bar", discreteJSON.getJSONObject(2).getString("foo"));
   }
 
   @Override
   protected String getCacheSuffix() {
     return File.separator + "health-" + System.currentTimeMillis() + ".profile";
   }
-
-
-  public void testEnvironmentDiffing() throws JSONException {
-    // Manually insert a v1 environment.
-    final MockHealthReportDatabaseStorage storage = new MockHealthReportDatabaseStorage(context, fakeProfileDirectory);
-    final SQLiteDatabase db = storage.getDB();
-    storage.deleteEverything();
-    final MockDatabaseEnvironment v1env = storage.getEnvironment();
-    v1env.mockInit("27.0a1");
-    v1env.version = 1;
-    v1env.appLocale = "";
-    v1env.osLocale  = "";
-    v1env.distribution = "";
-    v1env.acceptLangSet = 0;
-    final int v1ID = v1env.register();
-
-    // Verify.
-    final String[] cols = new String[] {
-      "id", "version", "hash",
-      "osLocale", "acceptLangSet", "appLocale", "distribution"
-    };
-
-    final Cursor c1 = db.query("environments", cols, "id = " + v1ID, null, null, null, null);
-    String v1envHash;
-    try {
-      assertTrue(c1.moveToFirst());
-      assertEquals(1, c1.getCount());
-
-      assertEquals(v1ID, c1.getInt(0));
-      assertEquals(1,    c1.getInt(1));
-
-      v1envHash = c1.getString(2);
-      assertNotNull(v1envHash);
-      assertEquals("", c1.getString(3));
-      assertEquals(0,  c1.getInt(4));
-      assertEquals("", c1.getString(5));
-      assertEquals("", c1.getString(6));
-    } finally {
-      c1.close();
-    }
-
-    // Insert a v2 environment.
-    final MockDatabaseEnvironment v2env = storage.getEnvironment();
-    v2env.mockInit("27.0a1");
-    v2env.appLocale = v2env.osLocale = "en_us";
-    v2env.acceptLangSet = 1;
-
-    final int v2ID = v2env.register();
-    assertFalse(v1ID == v2ID);
-    final Cursor c2 = db.query("environments", cols, "id = " + v2ID, null, null, null, null);
-    String v2envHash;
-    try {
-      assertTrue(c2.moveToFirst());
-      assertEquals(1, c2.getCount());
-
-      assertEquals(v2ID, c2.getInt(0));
-      assertEquals(2,    c2.getInt(1));
-
-      v2envHash = c2.getString(2);
-      assertNotNull(v2envHash);
-      assertEquals("en_us", c2.getString(3));
-      assertEquals(1,       c2.getInt(4));
-      assertEquals("en_us", c2.getString(5));
-      assertEquals("",      c2.getString(6));
-    } finally {
-      c2.close();
-    }
-
-    assertFalse(v1envHash.equals(v2envHash));
-
-    // Now let's diff based on DB contents.
-    SparseArray<Environment> envs = storage.getEnvironmentRecordsByID();
-
-    JSONObject oldEnv = HealthReportGenerator.jsonify(envs.get(v1ID), null).getJSONObject("org.mozilla.appInfo.appinfo");
-    JSONObject newEnv = HealthReportGenerator.jsonify(envs.get(v2ID), null).getJSONObject("org.mozilla.appInfo.appinfo");
-
-    // Generate the new env as if the old were the current. This should rarely happen in practice.
-    // Fields supported by the new env but not the old will appear, even if the 'default' for the
-    // old implementation is equal to the new env's value.
-    JSONObject newVsOld = HealthReportGenerator.jsonify(envs.get(v2ID), envs.get(v1ID)).getJSONObject("org.mozilla.appInfo.appinfo");
-
-    // Generate the old env as if the new were the current. This is normal. Fields not supported by the old
-    // environment version should not appear in the output.
-    JSONObject oldVsNew = HealthReportGenerator.jsonify(envs.get(v1ID), envs.get(v2ID)).getJSONObject("org.mozilla.appInfo.appinfo");
-    assertEquals(2, oldEnv.getInt("_v"));
-    assertEquals(3, newEnv.getInt("_v"));
-    assertEquals(2, oldVsNew.getInt("_v"));
-    assertEquals(3, newVsOld.getInt("_v"));
-
-    assertFalse(oldVsNew.has("osLocale"));
-    assertFalse(oldVsNew.has("appLocale"));
-    assertFalse(oldVsNew.has("distribution"));
-    assertFalse(oldVsNew.has("acceptLangIsUserSet"));
-
-    assertTrue(newVsOld.has("osLocale"));
-    assertTrue(newVsOld.has("appLocale"));
-    assertTrue(newVsOld.has("distribution"));
-    assertTrue(newVsOld.has("acceptLangIsUserSet"));
-  }
 }
--- a/mobile/android/tests/background/junit3/src/healthreport/TestHealthReportProvider.java
+++ b/mobile/android/tests/background/junit3/src/healthreport/TestHealthReportProvider.java
@@ -141,17 +141,17 @@ public class TestHealthReportProvider ex
     ensureMeasurementCount(1);
     ensureFieldCount(4);
 
     final Uri envURI = resolver.insert(getCompleteUri("/environments/"), getTestEnvContentValues());
     String envHash = null;
     Cursor envCursor = resolver.query(envURI, null, null, null, null);
     try {
       assertTrue(envCursor.moveToFirst());
-      envHash = envCursor.getString(2);      // id, version, hash, ...
+      envHash = envCursor.getString(1);
     } finally {
       envCursor.close();
     }
 
     final Uri eventURI = HealthReportUtils.getEventURI(envURI);
 
     Uri discrete1 = eventURI.buildUpon().appendEncodedPath("testm1/1/discrete1").build();
     Uri counter1 = eventURI.buildUpon().appendEncodedPath("testm1/1/counter1/counter").build();
@@ -244,18 +244,11 @@ public class TestHealthReportProvider ex
     v.put("appID", "");
     v.put("appVersion", "");
     v.put("appBuildID", "");
     v.put("platformVersion", "");
     v.put("platformBuildID", "");
     v.put("os", "");
     v.put("xpcomabi", "");
     v.put("updateChannel", "");
-
-    // v2.
-    v.put("distribution", "");
-    v.put("osLocale", "en_us");
-    v.put("appLocale", "en_us");
-    v.put("acceptLangSet", 0);
-
     return v;
   }
 }