Bug 1269734 - Include adjust campaign ID with core ping r=mcomella
authorJonathan Almeida (:jonalmeida) <jonalmeida942@gmail.com>
Tue, 19 Jul 2016 13:58:28 -0700
changeset 336335 ed793a550dd602631e25febe9fc39f3bab41e80a
parent 336334 3e537d8eb88c440fd0b7aa88deec56aa688715b0
child 336336 fec37519e65fe5d4b2e68a7f0c76bca60253a0ed
push id10033
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:50:26 +0000
treeherdermozilla-aurora@5dddbefdf759 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcomella
bugs1269734
milestone51.0a1
Bug 1269734 - Include adjust campaign ID with core ping r=mcomella MozReview-Commit-ID: KZJKYzBfRfK
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelper.java
mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelperInterface.java
mobile/android/base/java/org/mozilla/gecko/adjust/AttributionHelperListener.java
mobile/android/base/java/org/mozilla/gecko/adjust/StubAdjustHelper.java
mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryCorePingDelegate.java
mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/CampaignIdMeasurements.java
mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryCorePingBuilder.java
mobile/android/base/moz.build
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -12,16 +12,17 @@ import android.support.annotation.CheckR
 import android.support.annotation.NonNull;
 
 import android.widget.VideoView;
 import android.graphics.Rect;
 
 import org.json.JSONArray;
 import org.mozilla.gecko.activitystream.ActivityStream;
 import org.mozilla.gecko.adjust.AdjustHelperInterface;
+import org.mozilla.gecko.adjust.AttributionHelperListener;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.Tabs.TabEvents;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.cleanup.FileCleanupController;
 import org.mozilla.gecko.db.BrowserContract;
@@ -308,24 +309,26 @@ public class BrowserApp extends GeckoApp
     // The animator used to toggle HomePager visibility has a race where if the HomePager is shown
     // (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 TelemetryCorePingDelegate mTelemetryCorePingDelegate = new TelemetryCorePingDelegate();
+
     private final List<BrowserAppDelegate> delegates = Collections.unmodifiableList(Arrays.asList(
             (BrowserAppDelegate) new AddToHomeScreenPromotion(),
             (BrowserAppDelegate) new ScreenshotDelegate(),
             (BrowserAppDelegate) new BookmarkStateChangeDelegate(),
             (BrowserAppDelegate) new ReaderViewBookmarkPromotion(),
             (BrowserAppDelegate) new ContentNotificationsDelegate(),
             (BrowserAppDelegate) new PostUpdateHandler(),
-            new TelemetryCorePingDelegate(),
+            mTelemetryCorePingDelegate,
             new OfflineTabStatusDelegate()
     ));
 
     @NonNull
     private SearchEngineManager mSearchEngineManager; // Contains reference to Context - DO NOT LEAK!
 
     private boolean mHasResumed;
 
@@ -711,17 +714,17 @@ public class BrowserApp extends GeckoApp
         final BrowserDB db = profile.getDB();
         db.setSuggestedSites(suggestedSites);
 
         JavaAddonManager.getInstance().init(appContext);
         mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
         mReadingListHelper = new ReadingListHelper(appContext, profile);
         mAccountsHelper = new AccountsHelper(appContext, profile);
 
-        initAdjustSDK(this, isInAutomation);
+        initAdjustSDK(this, isInAutomation, mTelemetryCorePingDelegate);
 
         if (AppConstants.MOZ_ANDROID_BEAM) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
             if (nfc != null) {
                 nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
                     @Override
                     public NdefMessage createNdefMessage(NfcEvent event) {
                         Tab tab = Tabs.getInstance().getSelectedTab();
@@ -809,19 +812,19 @@ public class BrowserApp extends GeckoApp
         final String serverUrl = TextUtils.isEmpty(serverExtra) ? SWITCHBOARD_SERVER : serverExtra;
         new AsyncConfigLoader(context, serverUrl).execute();
     }
 
     private static void initTelemetryUploader(final boolean isInAutomation) {
         TelemetryUploadService.setDisabled(isInAutomation);
     }
 
-    private static void initAdjustSDK(final Context context, final boolean isInAutomation) {
+    private static void initAdjustSDK(final Context context, final boolean isInAutomation, final AttributionHelperListener listener) {
         final AdjustHelperInterface adjustHelper = AdjustConstants.getAdjustHelper();
-        adjustHelper.onCreate(context, AdjustConstants.MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN);
+        adjustHelper.onCreate(context, AdjustConstants.MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN, listener);
 
         // Adjust stores enabled state so this is only necessary because users may have set
         // their data preferences before this feature was implemented and we need to respect
         // those before upload can occur in Adjust.onResume.
         final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
         final boolean enabled = !isInAutomation &&
                 prefs.getBoolean(GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true);
         adjustHelper.setEnabled(enabled);
--- a/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelper.java
@@ -2,41 +2,50 @@
  * 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.adjust;
 
 import android.content.Context;
 import android.content.Intent;
+import android.util.Log;
 
 import com.adjust.sdk.Adjust;
+import com.adjust.sdk.AdjustAttribution;
 import com.adjust.sdk.AdjustConfig;
 import com.adjust.sdk.AdjustReferrerReceiver;
 import com.adjust.sdk.LogLevel;
+import com.adjust.sdk.OnAttributionChangedListener;
 
 import org.mozilla.gecko.AppConstants;
 
-public class AdjustHelper implements AdjustHelperInterface {
-    public void onCreate(final Context context, final String maybeAppToken) {
+public class AdjustHelper implements AdjustHelperInterface, OnAttributionChangedListener {
+
+    private static final String LOGTAG = AdjustHelper.class.getSimpleName();
+    private AttributionHelperListener attributionListener;
+
+    public void onCreate(final Context context, final String maybeAppToken, final AttributionHelperListener listener) {
         final String environment;
         final LogLevel logLevel;
         if (AppConstants.MOZILLA_OFFICIAL) {
             environment = AdjustConfig.ENVIRONMENT_PRODUCTION;
             logLevel = LogLevel.WARN;
         } else {
             environment = AdjustConfig.ENVIRONMENT_SANDBOX;
             logLevel = LogLevel.VERBOSE;
         }
         if (maybeAppToken == null) {
             // We've got install tracking turned on -- we better have a token!
             throw new IllegalArgumentException("maybeAppToken must not be null");
         }
+        attributionListener = listener;
         AdjustConfig config = new AdjustConfig(context, maybeAppToken, environment);
         config.setLogLevel(logLevel);
+        config.setOnAttributionChangedListener(this);
         Adjust.onCreate(config);
     }
 
     public void onPause() {
         Adjust.onPause();
     }
 
     public void onResume() {
@@ -45,9 +54,22 @@ public class AdjustHelper implements Adj
 
     public void setEnabled(final boolean isEnabled) {
         Adjust.setEnabled(isEnabled);
     }
 
     public void onReceive(final Context context, final Intent intent) {
         new AdjustReferrerReceiver().onReceive(context, intent);
     }
+
+    @Override
+    public void onAttributionChanged(AdjustAttribution attribution) {
+        if (attributionListener == null) {
+            throw new IllegalStateException("Expected non-null attribution listener.");
+        }
+
+        if (attribution == null) {
+            Log.e(LOGTAG, "Adjust attribution is null; skipping campaign id retrieval.");
+            return;
+        }
+        attributionListener.onCampaignIdChanged(attribution.campaign);
+    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelperInterface.java
+++ b/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelperInterface.java
@@ -8,15 +8,15 @@ package org.mozilla.gecko.adjust;
 import android.content.Context;
 import android.content.Intent;
 
 public interface AdjustHelperInterface {
     /**
      * Register the Application with the Adjust SDK.
      * @param appToken the (secret!) Adjust SDK per-application token to register with; may be null.
      */
-    void onCreate(final Context context, final String appToken);
+    void onCreate(final Context context, final String appToken, final AttributionHelperListener listener);
     void onPause();
     void onResume();
 
     void setEnabled(final boolean isEnabled);
     void onReceive(final Context context, final Intent intent);
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/adjust/AttributionHelperListener.java
@@ -0,0 +1,17 @@
+/* -*- 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.adjust;
+
+/**
+ * Because of how our build module dependencies are structured, we aren't able to use
+ * the {@link com.adjust.sdk.OnAttributionChangedListener} directly outside of {@link AdjustHelper}.
+ * If the Adjust SDK is enabled, this listener should be notified when {@link com.adjust.sdk.OnAttributionChangedListener}
+ * is fired (i.e. this listener would be daisy-chained to the Adjust one). The listener also
+ * inherits thread-safety from GeckoSharedPrefs which is used to store the campaign ID.
+ */
+public interface AttributionHelperListener {
+    void onCampaignIdChanged(String campaignId);
+}
--- a/mobile/android/base/java/org/mozilla/gecko/adjust/StubAdjustHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/adjust/StubAdjustHelper.java
@@ -4,17 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.adjust;
 
 import android.content.Context;
 import android.content.Intent;
 
 public class StubAdjustHelper implements AdjustHelperInterface {
-    public void onCreate(final Context context, final String appToken) {
+    public void onCreate(final Context context, final String appToken, final AttributionHelperListener listener) {
         // Do nothing.
     }
 
     public void onPause() {
         // Do nothing.
     }
 
     public void onResume() {
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryCorePingDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryCorePingDelegate.java
@@ -1,40 +1,43 @@
 /*
  * 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.content.SharedPreferences;
 import android.support.annotation.Nullable;
 import android.support.annotation.WorkerThread;
 import android.util.Log;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.adjust.AttributionHelperListener;
+import org.mozilla.gecko.telemetry.measurements.CampaignIdMeasurements;
 import org.mozilla.gecko.delegates.BrowserAppDelegateWithReference;
 import org.mozilla.gecko.distribution.DistributionStoreCallback;
 import org.mozilla.gecko.search.SearchEngineManager;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.telemetry.measurements.SearchCountMeasurements;
 import org.mozilla.gecko.telemetry.measurements.SessionMeasurements;
 import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCorePingBuilder;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import java.io.IOException;
 
 /**
  * An activity-lifecycle delegate for uploading the core ping.
  */
 public class TelemetryCorePingDelegate extends BrowserAppDelegateWithReference
-        implements SearchEngineManager.SearchEngineCallback {
+        implements SearchEngineManager.SearchEngineCallback, AttributionHelperListener {
     private static final String LOGTAG = StringUtils.safeSubstring(
             "Gecko" + TelemetryCorePingDelegate.class.getSimpleName(), 0, 23);
 
     private static final String PREF_IS_FIRST_RUN = "telemetry-isFirstRun";
 
     private TelemetryDispatcher telemetryDispatcher; // lazy
     private final SessionMeasurements sessionMeasurements = new SessionMeasurements();
 
@@ -148,27 +151,38 @@ public class TelemetryCorePingDelegate e
                         sessionMeasurements.getAndResetSessionMeasurements(activity);
                 final TelemetryCorePingBuilder pingBuilder = new TelemetryCorePingBuilder(activity)
                         .setClientID(clientID)
                         .setDefaultSearchEngine(TelemetryCorePingBuilder.getEngineIdentifier(engine))
                         .setProfileCreationDate(TelemetryCorePingBuilder.getProfileCreationDate(activity, profile))
                         .setSequenceNumber(TelemetryCorePingBuilder.getAndIncrementSequenceNumber(sharedPrefs))
                         .setSessionCount(sessionMeasurementsContainer.sessionCount)
                         .setSessionDuration(sessionMeasurementsContainer.elapsedSeconds);
-                maybeSetOptionalMeasurements(sharedPrefs, pingBuilder);
+                maybeSetOptionalMeasurements(activity, sharedPrefs, pingBuilder);
 
                 getTelemetryDispatcher(activity).queuePingForUpload(activity, pingBuilder);
             }
         });
     }
 
-    private void maybeSetOptionalMeasurements(final SharedPreferences sharedPrefs, final TelemetryCorePingBuilder pingBuilder) {
+    private void maybeSetOptionalMeasurements(final Context context, final SharedPreferences sharedPrefs,
+                                              final TelemetryCorePingBuilder pingBuilder) {
         final String distributionId = sharedPrefs.getString(DistributionStoreCallback.PREF_DISTRIBUTION_ID, null);
         if (distributionId != null) {
             pingBuilder.setOptDistributionID(distributionId);
         }
 
         final ExtendedJSONObject searchCounts = SearchCountMeasurements.getAndZeroSearch(sharedPrefs);
         if (searchCounts.size() > 0) {
             pingBuilder.setOptSearchCounts(searchCounts);
         }
+
+        final String campaignId = CampaignIdMeasurements.getCampaignIdFromPrefs(context);
+        if (campaignId != null) {
+            pingBuilder.setOptCampaignId(campaignId);
+        }
+    }
+
+    @Override
+    public void onCampaignIdChanged(String campaignId) {
+        CampaignIdMeasurements.updateCampaignIdPref(getBrowserApp(), campaignId);
     }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/CampaignIdMeasurements.java
@@ -0,0 +1,37 @@
+/*
+ * 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.measurements;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.adjust.AttributionHelperListener;
+
+/**
+ * A class to retrieve and store the campaign Id pref that is used when the Adjust SDK gives us
+ * new attribution from the {@link AttributionHelperListener}.
+ */
+public class CampaignIdMeasurements {
+    private static final String PREF_CAMPAIGN_ID = "measurements-campaignId";
+
+    public static String getCampaignIdFromPrefs(@NonNull final Context context) {
+        return GeckoSharedPrefs.forProfile(context)
+                .getString(PREF_CAMPAIGN_ID, null);
+    }
+
+    public static void updateCampaignIdPref(@NonNull final Context context, @NonNull final String campaignId) {
+        if (TextUtils.isEmpty(campaignId)) {
+            return;
+        }
+        GeckoSharedPrefs.forProfile(context)
+                .edit()
+                .putString(PREF_CAMPAIGN_ID, campaignId)
+                .apply();
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryCorePingBuilder.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryCorePingBuilder.java
@@ -43,16 +43,17 @@ public class TelemetryCorePingBuilder ex
     // For legacy reasons, this preference key is not namespaced with "core".
     private static final String PREF_SEQ_COUNT = "telemetry-seqCount";
 
     private static final String NAME = "core";
     private static final int VERSION_VALUE = 7; // For version history, see toolkit/components/telemetry/docs/core-ping.rst
     private static final String OS_VALUE = "Android";
 
     private static final String ARCHITECTURE = "arch";
+    private static final String CAMPAIGN_ID = "campaignId";
     private static final String CLIENT_ID = "clientId";
     private static final String DEFAULT_SEARCH_ENGINE = "defaultSearch";
     private static final String DEVICE = "device";
     private static final String DISTRIBUTION_ID = "distributionId";
     private static final String EXPERIMENTS = "experiments";
     private static final String LOCALE = "locale";
     private static final String OS_ATTR = "os";
     private static final String OS_VERSION = "osversion";
@@ -150,16 +151,24 @@ public class TelemetryCorePingBuilder ex
         } else if (searchCounts.size() == 0) {
             throw new IllegalStateException("Expected non-empty search counts");
         }
 
         payload.put(SEARCH_COUNTS, searchCounts);
         return this;
     }
 
+    public TelemetryCorePingBuilder setOptCampaignId(final String campaignId) {
+        if (campaignId == null) {
+            throw new IllegalStateException("Expected non-null campaign ID.");
+        }
+        payload.put(CAMPAIGN_ID, campaignId);
+        return this;
+    }
+
     /**
      * @param date The profile creation date in days to the unix epoch (not millis!), or null if there is an error.
      */
     public TelemetryCorePingBuilder setProfileCreationDate(@Nullable final Long date) {
         if (date != null && date < 0) {
             throw new IllegalArgumentException("Expect positive date value. Received: " + date);
         }
         payload.put(PROFILE_CREATION_DATE, date);
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -11,16 +11,17 @@ CONFIGURE_SUBST_FILES += ['adjust_sdk_ap
 include('android-services.mozbuild')
 
 geckoview_source_dir = TOPSRCDIR + '/mobile/android/geckoview/src/main/'
 thirdparty_source_dir = TOPSRCDIR + '/mobile/android/thirdparty/'
 
 constants_jar = add_java_jar('constants')
 constants_jar.sources = ['java/org/mozilla/gecko/' + x for x in [
     'adjust/AdjustHelperInterface.java',
+    'adjust/AttributionHelperListener.java',
     'annotation/JNITarget.java',
     'annotation/ReflectionTarget.java',
     'annotation/RobocopTarget.java',
     'annotation/WebRTCJNITarget.java',
     'annotation/WrapForJNI.java',
     'db/BrowserContract.java',
     'LocaleManager.java',
     'Locales.java',
@@ -601,16 +602,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'tabs/TabPanelBackButton.java',
     'tabs/TabsGridLayout.java',
     'tabs/TabsLayoutAdapter.java',
     'tabs/TabsLayoutItemView.java',
     'tabs/TabsListLayout.java',
     'tabs/TabsPanel.java',
     'tabs/TabsPanelThumbnailView.java',
     'Telemetry.java',
+    'telemetry/measurements/CampaignIdMeasurements.java',
     'telemetry/measurements/SearchCountMeasurements.java',
     'telemetry/measurements/SessionMeasurements.java',
     'telemetry/pingbuilders/TelemetryCorePingBuilder.java',
     'telemetry/pingbuilders/TelemetryPingBuilder.java',
     'telemetry/schedulers/TelemetryUploadAllPingsImmediatelyScheduler.java',
     'telemetry/schedulers/TelemetryUploadScheduler.java',
     'telemetry/stores/TelemetryJSONFilePingStore.java',
     'telemetry/stores/TelemetryPingStore.java',