Bug 1198009 - Add support to BrowserHealthRecorder for managing active experiment list into ProfileInformationCache r=rnewman
authorMark Finkle <mfinkle@mozilla.com>
Sat, 12 Sep 2015 19:26:45 -0400
changeset 294802 b07426112b4520bb263afdd9202ab81632eec262
parent 294801 3363996dc230ec3d8b876d46f11beea9ae513560
child 294803 13f2b1fad74b5b4f93ee0aaf529eb60681112fa3
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs1198009
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1198009 - Add support to BrowserHealthRecorder for managing active experiment list into ProfileInformationCache r=rnewman
mobile/android/base/GeckoApp.java
mobile/android/base/health/BrowserHealthRecorder.java
mobile/android/base/health/HealthRecorder.java
mobile/android/base/health/StubbedHealthRecorder.java
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -2109,17 +2109,17 @@ public abstract class GeckoApp
 
         final HealthRecorder rec = mHealthRecorder;
         mHealthRecorder = null;
         if (rec != null && rec.isEnabled()) {
             // Closing a BrowserHealthRecorder could incur a write.
             ThreadUtils.postToBackgroundThread(new Runnable() {
                 @Override
                 public void run() {
-                    rec.close();
+                    rec.close(GeckoApp.this);
                 }
             });
         }
 
         Favicons.close();
 
         super.onDestroy();
 
--- a/mobile/android/base/health/BrowserHealthRecorder.java
+++ b/mobile/android/base/health/BrowserHealthRecorder.java
@@ -3,16 +3,17 @@
  * 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.health;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.OutputStreamWriter;
+import java.lang.String;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Scanner;
@@ -33,19 +34,25 @@ import org.mozilla.gecko.background.heal
 import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field;
 import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields;
 import org.mozilla.gecko.background.healthreport.ProfileInformationCache;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.distribution.Distribution.DistributionDescriptor;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import com.keepsafe.switchboard.SwitchBoard;
+
+import android.content.BroadcastReceiver;
 import android.content.ContentProviderClient;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.support.v4.content.LocalBroadcastManager;
 import android.util.Log;
 
 /**
  * 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.
  *
@@ -54,17 +61,17 @@ import android.util.Log;
  * Tell it when an environment attribute has changed: call {@link
  * #onAppLocaleChanged(String)} followed by {@link
  * #onEnvironmentChanged()}.
  *
  * Use it to record events: {@link #recordSearch(String, String)}.
  *
  * Shut it down when you're done being a browser: {@link #close()}.
  */
-public class BrowserHealthRecorder implements HealthRecorder, GeckoEventListener {
+public class BrowserHealthRecorder extends BroadcastReceiver implements HealthRecorder, 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_CHANGE = "Addons:Change";
     private static final String EVENT_ADDONS_UNINSTALLING = "Addons:Uninstalling";
     private static final String EVENT_PREF_CHANGE = "Pref:Change";
 
@@ -172,30 +179,33 @@ public class BrowserHealthRecorder imple
         return true;
     }
 
     /**
      * Shut down database connections, unregister event listeners, and perform
      * provider-specific uninitialization.
      */
     @Override
-    public synchronized void close() {
+    public synchronized void close(final Context context) {
         switch (this.state) {
             case CLOSED:
                 Log.w(LOG_TAG, "Ignoring attempt to double-close closed BrowserHealthRecorder.");
                 return;
             case INITIALIZED:
                 Log.i(LOG_TAG, "Closing Health Report client.");
                 break;
             default:
                 Log.i(LOG_TAG, "Closing incompletely initialized BrowserHealthRecorder.");
         }
 
         this.state = State.CLOSED;
         this.unregisterEventListeners();
+        if (AppConstants.MOZ_SWITCHBOARD) {
+            LocalBroadcastManager.getInstance(context).unregisterReceiver(this);
+        }
 
         // Add any necessary provider uninitialization here.
         this.storage = null;
         if (this.client != null) {
             this.client.release();
             this.client = null;
         }
     }
@@ -518,16 +528,22 @@ public class BrowserHealthRecorder imple
         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();
 
+            // Listen for experiment changes.
+            if (AppConstants.MOZ_SWITCHBOARD) {
+                IntentFilter intentFilter = new IntentFilter(SwitchBoard.ACTION_CONFIG_FETCHED);
+                LocalBroadcastManager.getInstance(context).registerReceiver(this, intentFilter);
+            }
+
             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));
@@ -689,16 +705,81 @@ public class BrowserHealthRecorder imple
                 recordSearch(message.optString("identifier", null), message.getString("location"));
                 return;
             }
         } catch (Exception e) {
             Log.e(LOG_TAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        switch (action) {
+            case SwitchBoard.ACTION_CONFIG_FETCHED:
+                Log.d(LOG_TAG, "Handle the new experiments.");
+                // Get the list of active experiments.
+                List<String> experiments = SwitchBoard.getActiveExperiments(context);
+
+                // We need to figure out which ones are new and which ones were removed. Convert
+                // the active experiments to the fake add-on names for easier lookup.
+                ArrayList<String> addToProfile = new ArrayList<String>();
+                for (String experiment : experiments) {
+                    addToProfile.add(experiment + "@experiments.mozilla.org");
+                }
+
+                // Create a list of add-ons(experiments) we need to remove.
+                ArrayList<String> removeFromProfile = new ArrayList<String>();
+
+                // Loop over the current profile set of add-ons, and figure out
+                // which add-ons (experiments) are new and which need to be removed.
+                JSONObject addons = this.profileCache.getAddonsJSON();
+                Iterator<?> keys = addons.keys();
+                while (keys.hasNext()) {
+                    String addon = (String) keys.next();
+                    if (addon.endsWith("@experiments.mozilla.org")) {
+                        if (addToProfile.contains(addon)) {
+                            // This experiment is already in the profile. We don't need to add it again.
+                            addToProfile.remove(addon);
+                        } else {
+                            // The active set of experiments does not include this fake add-on.
+                            removeFromProfile.add(addon);
+                        }
+                    }
+                }
+
+                // If we don't have any changes, exit early.
+                if (addToProfile.isEmpty() && removeFromProfile.isEmpty()) {
+                    return;
+                }
+
+                // Add the newly active experiments into the profile.
+                for (String fakeName : addToProfile) {
+                    try {
+                        // Create a dummy JSON object for the experiment.
+                        JSONObject fakeAddon = new JSONObject();
+                        fakeAddon.put("type", "experiment");
+                        this.onAddonChanged(fakeName, fakeAddon);
+                        Log.d(LOG_TAG, "Add this experiment: " + fakeName);
+                    } catch (JSONException je) {
+                    }
+                }
+
+                // Remove experiments that are no longer active from the profile.
+                for (String fakeName : removeFromProfile) {
+                    this.onAddonUninstalling(fakeName);
+                    Log.d(LOG_TAG, "Remove this experiment: " + fakeName);
+                }
+
+                // Something changed, so update the environment.
+                this.onEnvironmentChanged();
+                break;
+        }
+    }
+
     /*
      * Searches.
      */
 
     public static final String MEASUREMENT_NAME_SEARCH_COUNTS = "org.mozilla.searches.counts";
     public static final int MEASUREMENT_VERSION_SEARCH_COUNTS = 6;
 
     public static final Set<String> SEARCH_LOCATIONS = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(new String[] {
--- a/mobile/android/base/health/HealthRecorder.java
+++ b/mobile/android/base/health/HealthRecorder.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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.health;
 
+import android.content.Context;
 import android.content.SharedPreferences;
 
 import org.json.JSONObject;
 
 /**
  * HealthRecorder is an interface into the Firefox Health Report storage system.
  */
 public interface HealthRecorder {
@@ -28,12 +29,12 @@ public interface HealthRecorder {
     public void recordSessionEnd(String reason, SharedPreferences.Editor editor, final int environment);
 
     public void onAppLocaleChanged(String to);
     public void onAddonChanged(String id, JSONObject json);
     public void onAddonUninstalling(String id);
     public void onEnvironmentChanged();
     public void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason);
 
-    public void close();
+    public void close(final Context context);
 
     public void processDelayed();
 }
--- a/mobile/android/base/health/StubbedHealthRecorder.java
+++ b/mobile/android/base/health/StubbedHealthRecorder.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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.health;
 
+import android.content.Context;
 import android.content.SharedPreferences;
 
 import org.json.JSONObject;
 
 /**
  * StubbedHealthRecorder is an implementation of HealthRecorder that does (you guessed it!)
  * nothing.
  */
@@ -40,13 +41,13 @@ public class StubbedHealthRecorder imple
     @Override
     public void onAddonUninstalling(String id) { }
     @Override
     public void onEnvironmentChanged() { }
     @Override
     public void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason) { }
 
     @Override
-    public void close() { }
+    public void close(final Context context) { }
 
     @Override
     public void processDelayed() { }
 }