Bug 1249288 - Add default search engine to core ping. r=rnewman draft
authorMichael Comella <michael.l.comella@gmail.com>
Thu, 10 Mar 2016 16:00:15 -0800
changeset 344466 7be9fdf01e57b5eba21842707a42662307dc5bee
parent 344465 0a4cd7c64ecfbb5270fa2811924b7d22a87741cb
child 344467 bc54f8eda9a985843358ebd2075edfeedd99b302
push id13832
push usermichael.l.comella@gmail.com
push dateThu, 24 Mar 2016 18:19:18 +0000
reviewersrnewman
bugs1249288
milestone48.0a1
Bug 1249288 - Add default search engine to core ping. r=rnewman The default search engine attribute may be null in the core ping if we haven't been able to retrieve the value yet. It's unclear when this might be, but the possibility is in the javadoc of `SearchEngineManager.getEngine`. MozReview-Commit-ID: IrJB6GyjyTO
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryConstants.java
mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPingGenerator.java
mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -49,16 +49,17 @@ import org.mozilla.gecko.permissions.Per
 import org.mozilla.gecko.preferences.ClearOnShutdownPref;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.prompts.Prompt;
 import org.mozilla.gecko.prompts.PromptListItem;
 import org.mozilla.gecko.reader.ReaderModeUtils;
 import org.mozilla.gecko.reader.ReadingListHelper;
 import org.mozilla.gecko.restrictions.Restrictable;
 import org.mozilla.gecko.restrictions.RestrictedProfileConfiguration;
+import org.mozilla.gecko.search.SearchEngineManager;
 import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
 import org.mozilla.gecko.tabqueue.TabQueueHelper;
 import org.mozilla.gecko.tabqueue.TabQueuePrompt;
 import org.mozilla.gecko.tabs.TabHistoryController;
 import org.mozilla.gecko.tabs.TabHistoryController.OnShowTabHistory;
 import org.mozilla.gecko.tabs.TabHistoryFragment;
 import org.mozilla.gecko.tabs.TabHistoryPage;
 import org.mozilla.gecko.tabs.TabsPanel;
@@ -146,16 +147,17 @@ import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.Method;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLEncoder;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
@@ -277,16 +279,18 @@ public class BrowserApp extends GeckoApp
     // (starting the animation), the HomePager is hidden, and the HomePager animation completes,
     // both the web content and the HomePager will be hidden. This flag is used to prevent the
     // race by determining if the web content should be hidden at the animation's end.
     private boolean mHideWebContentOnAnimationEnd;
 
     private final DynamicToolbar mDynamicToolbar = new DynamicToolbar();
     private final ScreenshotObserver mScreenshotObserver = new ScreenshotObserver();
 
+    private SearchEngineManager searchEngineManager;
+
     @Override
     public View onCreateView(final String name, final Context context, final AttributeSet attrs) {
         final View view;
         if (BrowserToolbar.class.getName().equals(name)) {
             view = BrowserToolbar.create(context, attrs);
         } else if (TabsPanel.TabsLayout.class.getName().equals(name)) {
             view = TabsPanel.createTabsLayout(context, attrs);
         } else {
@@ -687,17 +691,20 @@ public class BrowserApp extends GeckoApp
             "Menu:Add",
             "Menu:Remove",
             "Sanitize:ClearHistory",
             "Sanitize:ClearSyncedTabs",
             "Settings:Show",
             "Telemetry:Gather",
             "Updater:Launch");
 
+        // We want to upload the telemetry core ping as soon after startup as possible. It relies on the
+        // Distribution being initialized. If you move this initialization, ensure it plays well with telemetry.
         Distribution distribution = Distribution.init(this);
+        searchEngineManager = new SearchEngineManager(this, distribution);
 
         // Init suggested sites engine in BrowserDB.
         final SuggestedSites suggestedSites = new SuggestedSites(appContext, distribution);
         final BrowserDB db = getProfile().getDB();
         db.setSuggestedSites(suggestedSites);
 
         JavaAddonManager.getInstance().init(appContext);
         mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
@@ -1402,16 +1409,19 @@ public class BrowserApp extends GeckoApp
             mAccountsHelper.uninit();
             mAccountsHelper = null;
         }
 
         if (mZoomedView != null) {
             mZoomedView.destroy();
         }
 
+        searchEngineManager.destroy();
+        searchEngineManager = null;
+
         EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) this,
             "Gecko:DelayedStartup",
             "Menu:Open",
             "Menu:Update",
             "LightweightTheme:Update",
             "Search:Keyword",
             "Prompt:ShowTop");
 
@@ -3945,23 +3955,48 @@ public class BrowserApp extends GeckoApp
         }
 
         final SharedPreferences sharedPrefs = GeckoSharedPrefs.forProfileName(this, profile.getName());
         final int seq = sharedPrefs.getInt(TelemetryConstants.PREF_SEQ_COUNT, 1);
 
         // We store synchronously before sending the Intent to ensure this sequence number will not be re-used.
         sharedPrefs.edit().putInt(TelemetryConstants.PREF_SEQ_COUNT, seq + 1).commit();
 
-        final Intent i = new Intent(TelemetryConstants.ACTION_UPLOAD_CORE);
-        i.setClass(this, TelemetryUploadService.class);
-        i.putExtra(TelemetryConstants.EXTRA_DOC_ID, UUID.randomUUID().toString());
-        i.putExtra(TelemetryConstants.EXTRA_PROFILE_NAME, profile.getName());
-        i.putExtra(TelemetryConstants.EXTRA_PROFILE_PATH, profile.getDir().toString());
-        i.putExtra(TelemetryConstants.EXTRA_SEQ, seq);
-        startService(i);
+        searchEngineManager.getEngine(new UploadTelemetryCallback(getContext(), seq, profile.getName(), profile.getDir()));
+    }
+
+    private static class UploadTelemetryCallback implements SearchEngineManager.SearchEngineCallback {
+        private final WeakReference<Context> contextWeakReference;
+        private final int seq;
+        private final String profileName;
+        private final String profileDirPath;
+
+        public UploadTelemetryCallback(final Context context, final int seq, final String profileName, final File profileDir) {
+            this.contextWeakReference = new WeakReference<>(context);
+            this.seq = seq;
+            this.profileName = profileName;
+            this.profileDirPath = profileDir.toString();
+        }
+
+        @Override
+        public void execute(final org.mozilla.gecko.search.SearchEngine engine) {
+            final Context context = this.contextWeakReference.get();
+            if (context == null) {
+                return;
+            }
+
+            final Intent i = new Intent(TelemetryConstants.ACTION_UPLOAD_CORE);
+            i.setClass(context, TelemetryUploadService.class);
+            i.putExtra(TelemetryConstants.EXTRA_DEFAULT_SEARCH_ENGINE, engine.getIdentifier());
+            i.putExtra(TelemetryConstants.EXTRA_DOC_ID, UUID.randomUUID().toString());
+            i.putExtra(TelemetryConstants.EXTRA_PROFILE_NAME, this.profileName);
+            i.putExtra(TelemetryConstants.EXTRA_PROFILE_PATH, this.profileDirPath);
+            i.putExtra(TelemetryConstants.EXTRA_SEQ, this.seq);
+            context.startService(i);
+        }
     }
 
     public static interface TabStripInterface {
         public void refresh();
         void setOnTabChangedListener(OnTabAddedOrRemovedListener listener);
         interface OnTabAddedOrRemovedListener {
             void onTabChanged();
         }
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryConstants.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryConstants.java
@@ -11,33 +11,35 @@ public class TelemetryConstants {
     // Change these two values to enable upload in developer builds.
     public static final boolean UPLOAD_ENABLED = AppConstants.MOZILLA_OFFICIAL; // Disabled for developer builds.
     public static final String DEFAULT_SERVER_URL = "https://incoming.telemetry.mozilla.org";
 
     public static final String USER_AGENT =
             "Firefox-Android-Telemetry/" + AppConstants.MOZ_APP_VERSION + " (" + AppConstants.MOZ_APP_UA_NAME + ")";
 
     public static final String ACTION_UPLOAD_CORE = "uploadCore";
+    public static final String EXTRA_DEFAULT_SEARCH_ENGINE = "defaultSearchEngine";
     public static final String EXTRA_DOC_ID = "docId";
     public static final String EXTRA_PROFILE_NAME = "geckoProfileName";
     public static final String EXTRA_PROFILE_PATH = "geckoProfilePath";
     public static final String EXTRA_SEQ = "seq";
 
     public static final String PREF_SERVER_URL = "telemetry-serverUrl";
     public static final String PREF_SEQ_COUNT = "telemetry-seqCount";
 
     public static class CorePing {
         private CorePing() { /* To prevent instantiation */ }
 
         public static final String NAME = "core";
-        public static final int VERSION_VALUE = 1;
+        public static final int VERSION_VALUE = 2; // For version history, see toolkit/components/telemetry/docs/core-ping.rst
         public static final String OS_VALUE = "Android";
 
         public static final String ARCHITECTURE = "arch";
         public static final String CLIENT_ID = "clientId";
+        public static final String DEFAULT_SEARCH_ENGINE = "defaultSearch";
         public static final String DEVICE = "device";
         public static final String EXPERIMENTS = "experiments";
         public static final String LOCALE = "locale";
         public static final String OS_ATTR = "os";
         public static final String OS_VERSION = "osversion";
         public static final String PROFILE_CREATION_DATE = "profileDate";
         public static final String SEQ = "seq";
         public static final String VERSION_ATTR = "v";
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPingGenerator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPingGenerator.java
@@ -2,16 +2,18 @@
  * 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.telemetry;
 
 import android.content.Context;
 import android.os.Build;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.telemetry.TelemetryConstants.CorePing;
 import org.mozilla.gecko.util.Experiments;
 import org.mozilla.gecko.util.StringUtils;
 
@@ -57,36 +59,39 @@ public class TelemetryPingGenerator {
     /**
      * @param docId A unique document ID for the ping associated with the upload to this server
      * @param clientId The client ID of this profile (from Gecko)
      * @param serverURLSchemeHostPort The server url with the scheme, host, and port (e.g. "http://mozilla.org:80")
      * @param profileCreationDateDays The profile creation date in days to the UNIX epoch, NOT MILLIS.
      * @throws IOException when client ID could not be created
      */
     public static TelemetryPing createCorePing(final Context context, final String docId, final String clientId,
-            final String serverURLSchemeHostPort, final int seq, final long profileCreationDateDays) {
+            final String serverURLSchemeHostPort, final int seq, final long profileCreationDateDays,
+            @Nullable final String defaultSearchEngine) {
         final String serverURL = getTelemetryServerURL(docId, serverURLSchemeHostPort, CorePing.NAME);
-        final ExtendedJSONObject payload = createCorePingPayload(context, clientId, seq, profileCreationDateDays);
+        final ExtendedJSONObject payload =
+                createCorePingPayload(context, clientId, seq, profileCreationDateDays, defaultSearchEngine);
         return new TelemetryPing(serverURL, payload);
     }
 
     private static ExtendedJSONObject createCorePingPayload(final Context context, final String clientId,
-            final int seq, final long profileCreationDate) {
+            final int seq, final long profileCreationDate, @Nullable final String defaultSearchEngine) {
         final ExtendedJSONObject ping = new ExtendedJSONObject();
         ping.put(CorePing.VERSION_ATTR, CorePing.VERSION_VALUE);
         ping.put(CorePing.OS_ATTR, CorePing.OS_VALUE);
 
         // We limit the device descriptor to 32 characters because it can get long. We give fewer characters to the
         // manufacturer because we're less likely to have manufacturers with similar names than we are for a
         // manufacturer to have two devices with the similar names (e.g. Galaxy S6 vs. Galaxy Note 6).
         final String deviceDescriptor =
                 StringUtils.safeSubstring(Build.MANUFACTURER, 0, 12) + '-' + StringUtils.safeSubstring(Build.MODEL, 0, 19);
 
         ping.put(CorePing.ARCHITECTURE, AppConstants.ANDROID_CPU_ARCH);
         ping.put(CorePing.CLIENT_ID, clientId);
+        ping.put(CorePing.DEFAULT_SEARCH_ENGINE, TextUtils.isEmpty(defaultSearchEngine) ? null : defaultSearchEngine);
         ping.put(CorePing.DEVICE, deviceDescriptor);
         ping.put(CorePing.LOCALE, Locales.getLanguageTag(Locale.getDefault()));
         ping.put(CorePing.OS_VERSION, Integer.toString(Build.VERSION.SDK_INT)); // A String for cross-platform reasons.
         ping.put(CorePing.SEQ, seq);
         ping.putArray(CorePing.EXPERIMENTS, Experiments.getActiveExperiments(context));
         // TODO (bug 1246816): Remove this "optional" parameter work-around when
         // GeckoProfile.getAndPersistProfileCreationDateFromFilesystem is implemented. That method returns -1
         // while it's not implemented so we don't include the parameter in the ping if that's the case.
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.telemetry;
 
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.WorkerThread;
 import android.util.Log;
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.client.ClientProtocolException;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.background.BackgroundService;
 import org.mozilla.gecko.preferences.GeckoPreferences;
@@ -76,23 +77,24 @@ public class TelemetryUploadService exte
             return;
         }
 
         if (!TelemetryConstants.ACTION_UPLOAD_CORE.equals(intent.getAction())) {
             Log.w(LOGTAG, "Unknown action: " + intent.getAction() + ". Returning");
             return;
         }
 
+        final String defaultSearchEngine = intent.getStringExtra(TelemetryConstants.EXTRA_DEFAULT_SEARCH_ENGINE);
         final String docId = intent.getStringExtra(TelemetryConstants.EXTRA_DOC_ID);
         final int seq = intent.getIntExtra(TelemetryConstants.EXTRA_SEQ, -1);
 
         final String profileName = intent.getStringExtra(TelemetryConstants.EXTRA_PROFILE_NAME);
         final String profilePath = intent.getStringExtra(TelemetryConstants.EXTRA_PROFILE_PATH);
 
-        uploadCorePing(docId, seq, profileName, profilePath);
+        uploadCorePing(docId, seq, profileName, profilePath, defaultSearchEngine);
     }
 
     /**
      * Determines if the telemetry upload feature is enabled via the application configuration. Prefer to use
      * {@link #isUploadEnabledByProfileConfig(Context, GeckoProfile)} if the profile is available as it takes into
      * account more information.
      *
      * Note that this method logs debug statements when upload is disabled.
@@ -160,17 +162,17 @@ public class TelemetryUploadService exte
             return false;
         }
 
         return true;
     }
 
     @WorkerThread
     private void uploadCorePing(@NonNull final String docId, final int seq, @NonNull final String profileName,
-                @NonNull final String profilePath) {
+                @NonNull final String profilePath, @Nullable final String defaultSearchEngine) {
         final GeckoProfile profile = GeckoProfile.get(this, profileName, profilePath);
         final long profileCreationDate = getProfileCreationDate(profile);
         final String clientId;
         try {
             clientId = profile.getClientId();
         } catch (final IOException e) {
             Log.w(LOGTAG, "Unable to get client ID to generate core ping: returning.", e);
             return;
@@ -178,17 +180,17 @@ public class TelemetryUploadService exte
 
         // Each profile can have different telemetry data so we intentionally grab the shared prefs for the profile.
         final SharedPreferences sharedPrefs = GeckoSharedPrefs.forProfileName(this, profileName);
         // TODO (bug 1241685): Sync this preference with the gecko preference.
         final String serverURLSchemeHostPort =
                 sharedPrefs.getString(TelemetryConstants.PREF_SERVER_URL, TelemetryConstants.DEFAULT_SERVER_URL);
 
         final TelemetryPing corePing = TelemetryPingGenerator.createCorePing(this, docId, clientId,
-                serverURLSchemeHostPort, seq, profileCreationDate);
+                serverURLSchemeHostPort, seq, profileCreationDate, defaultSearchEngine);
         final CorePingResultDelegate resultDelegate = new CorePingResultDelegate();
         uploadPing(corePing, resultDelegate);
     }
 
     private void uploadPing(final TelemetryPing ping, final ResultDelegate delegate) {
         final BaseResource resource;
         try {
             resource = new BaseResource(ping.getURL());