Bug 1393699: Add Pocket referrer URI to AS top story clicks. r=liuche draft
authorMichael Comella <michael.l.comella@gmail.com>
Wed, 30 Aug 2017 15:33:56 -0700
changeset 656301 68d17e59231fb5e7fc4232d4256b925e6e4fce7e
parent 656300 52c616720fb1735e593f02330d1dd02db45f501f
child 656311 c7134775819873cbb492ee59fdb84ab2d0e3b70b
push id77157
push usermichael.l.comella@gmail.com
push dateThu, 31 Aug 2017 00:16:18 +0000
reviewersliuche
bugs1393699
milestone57.0a1
Bug 1393699: Add Pocket referrer URI to AS top story clicks. r=liuche I tested this by replacing the placeholder top story URLs with [1], clicking on the links, and verifying clicks from top stories included the referrer but clicks from highlights and top sites did not. [1]: https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending MozReview-Commit-ID: CcOxkvNgOHY
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/StreamRecyclerAdapter.java
mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/menu/ActivityStreamContextMenu.java
mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/topstories/PocketStoriesLoader.java
mobile/android/base/java/org/mozilla/gecko/home/HomePager.java
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -6,16 +6,17 @@
 package org.mozilla.gecko;
 
 import android.Manifest;
 import android.annotation.TargetApi;
 import android.app.DownloadManager;
 import android.content.ContentProviderClient;
 import android.os.Environment;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.UiThread;
 
 import org.mozilla.gecko.activitystream.ActivityStream;
 import org.mozilla.gecko.adjust.AdjustBrowserAppDelegate;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.Tabs.TabEvents;
@@ -2447,33 +2448,38 @@ public class BrowserApp extends GeckoApp
         tabs.selectTab(tab.getId());
 
         mBrowserToolbar.cancelEdit();
 
         return true;
     }
 
     public void openUrlAndStopEditing(String url) {
-        openUrlAndStopEditing(url, null, false);
+        openUrlAndStopEditing(url, null, null, false);
+    }
+
+    private void openUrlAndStopEditingWithReferrer(final String url, final String referrerUri) {
+        openUrlAndStopEditing(url, null, referrerUri, false);
     }
 
     private void openUrlAndStopEditing(String url, String searchEngine) {
-        openUrlAndStopEditing(url, searchEngine, false);
+        openUrlAndStopEditing(url, searchEngine, null, false);
     }
 
-    private void openUrlAndStopEditing(String url, String searchEngine, boolean newTab) {
+    private void openUrlAndStopEditing(String url, String searchEngine, @Nullable final String referrerUri,
+            boolean newTab) {
         int flags = Tabs.LOADURL_NONE;
         if (newTab) {
             flags |= Tabs.LOADURL_NEW_TAB;
             if (Tabs.getInstance().getSelectedTab().isPrivate()) {
                 flags |= Tabs.LOADURL_PRIVATE;
             }
         }
 
-        Tabs.getInstance().loadUrl(url, searchEngine, -1, flags);
+        Tabs.getInstance().loadUrl(url, searchEngine, referrerUri, Tabs.INVALID_TAB_ID, null, flags);
 
         mBrowserToolbar.cancelEdit();
     }
 
     private boolean isHomePagerVisible() {
         return (mHomeScreen != null && mHomeScreen.isVisible()
                 && mHomeScreenContainer != null && mHomeScreenContainer.getVisibility() == View.VISIBLE);
     }
@@ -4124,16 +4130,22 @@ public class BrowserApp extends GeckoApp
                 }
             }
         });
     }
 
     // HomePager.OnUrlOpenListener
     @Override
     public void onUrlOpen(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
+        onUrlOpenWithReferrer(url, null, flags);
+    }
+
+    @Override
+    public void onUrlOpenWithReferrer(final String url, @Nullable final String referrerUri,
+            final EnumSet<OnUrlOpenListener.Flags> flags) {
         if (flags.contains(OnUrlOpenListener.Flags.OPEN_WITH_INTENT)) {
             Intent intent = new Intent(Intent.ACTION_VIEW);
             intent.setData(Uri.parse(url));
             startActivity(intent);
         } else {
             // By default this listener is used for lists where the offline reader-view icon
             // is shown - hence we need to redirect to the reader-view page by default.
             // However there are some cases where we might not want to use this, e.g.
@@ -4141,25 +4153,31 @@ public class BrowserApp extends GeckoApp
             final String pageURL;
             if (!flags.contains(OnUrlOpenListener.Flags.NO_READER_VIEW)) {
                 pageURL = SavedReaderViewHelper.getReaderURLIfCached(this, url);
             } else {
                 pageURL = url;
             }
 
             if (!maybeSwitchToTab(pageURL, flags)) {
-                openUrlAndStopEditing(pageURL);
+                openUrlAndStopEditingWithReferrer(pageURL, referrerUri);
                 clearSelectedTabApplicationId();
             }
         }
     }
 
     // HomePager.OnUrlOpenInBackgroundListener
     @Override
     public void onUrlOpenInBackground(final String url, EnumSet<OnUrlOpenInBackgroundListener.Flags> flags) {
+        onUrlOpenInBackgroundWithReferrer(url, null, flags);
+    }
+
+    @Override
+    public void onUrlOpenInBackgroundWithReferrer(final String url, @Nullable final String referrerUri,
+            final EnumSet<OnUrlOpenInBackgroundListener.Flags> flags) {
         if (url == null) {
             throw new IllegalArgumentException("url must not be null");
         }
         if (flags == null) {
             throw new IllegalArgumentException("flags must not be null");
         }
 
         // We only use onUrlOpenInBackgroundListener for the homepanel context menus, hence
@@ -4168,17 +4186,17 @@ public class BrowserApp extends GeckoApp
 
         final boolean isPrivate = flags.contains(OnUrlOpenInBackgroundListener.Flags.PRIVATE);
 
         int loadFlags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND;
         if (isPrivate) {
             loadFlags |= Tabs.LOADURL_PRIVATE;
         }
 
-        final Tab newTab = Tabs.getInstance().loadUrl(pageURL, loadFlags);
+        final Tab newTab = Tabs.getInstance().loadUrl(pageURL, null, referrerUri, Tabs.INVALID_TAB_ID, null, loadFlags);
 
         // We switch to the desired tab by unique ID, which closes any window
         // for a race between opening the tab and closing it, and switching to
         // it. We could also switch to the Tab explicitly, but we don't want to
         // hold a reference to the Tab itself in the anonymous listener class.
         final int newTabId = newTab.getId();
 
         final SnackbarBuilder.SnackbarCallback callback = new SnackbarBuilder.SnackbarCallback() {
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/StreamRecyclerAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/StreamRecyclerAdapter.java
@@ -17,16 +17,17 @@ import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.activitystream.ActivityStreamTelemetry;
 import org.mozilla.gecko.activitystream.homepanel.menu.ActivityStreamContextMenu;
 import org.mozilla.gecko.activitystream.homepanel.model.RowModel;
 import org.mozilla.gecko.activitystream.homepanel.model.WebpageRowModel;
 import org.mozilla.gecko.activitystream.homepanel.stream.TopPanelRow;
 import org.mozilla.gecko.activitystream.homepanel.model.TopStory;
+import org.mozilla.gecko.activitystream.homepanel.topstories.PocketStoriesLoader;
 import org.mozilla.gecko.home.HomePager;
 import org.mozilla.gecko.activitystream.homepanel.model.Highlight;
 import org.mozilla.gecko.activitystream.homepanel.stream.WebpageItemRow;
 import org.mozilla.gecko.activitystream.homepanel.stream.StreamTitleRow;
 import org.mozilla.gecko.activitystream.homepanel.stream.StreamViewHolder;
 import org.mozilla.gecko.activitystream.homepanel.stream.WelcomePanelRow;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.widget.RecyclerViewClickSupport;
@@ -181,26 +182,29 @@ public class StreamRecyclerAdapter exten
             return;
         }
 
         final WebpageRowModel model = (WebpageRowModel) recyclerViewModel.get(position);
 
         final String sourceType;
         final int actionPosition;
         final int size;
+        final String referrerUri;
         final int viewType = getItemViewType(position);
 
         if (viewType == RowItemType.HIGHLIGHT_ITEM.getViewType()) {
             sourceType = ActivityStreamTelemetry.Contract.TYPE_HIGHLIGHTS;
             actionPosition = getHighlightsIndexFromAdapterPosition(position);
             size = getNumOfTypeShown(RowItemType.HIGHLIGHT_ITEM);
+            referrerUri = null;
         } else {
             sourceType = ActivityStreamTelemetry.Contract.TYPE_POCKET;
             actionPosition = getTopStoriesIndexFromAdapterPosition(position);
             size = getNumOfTypeShown(RowItemType.TOP_STORIES_ITEM);
+            referrerUri = PocketStoriesLoader.POCKET_REFERRER_URI;
         }
 
         ActivityStreamTelemetry.Extras.Builder extras = ActivityStreamTelemetry.Extras.builder()
                 .forHighlightSource(model.getSource())
                 .set(ActivityStreamTelemetry.Contract.SOURCE_TYPE, sourceType)
                 .set(ActivityStreamTelemetry.Contract.ACTION_POSITION, actionPosition)
                 .set(ActivityStreamTelemetry.Contract.COUNT, size);
 
@@ -208,17 +212,18 @@ public class StreamRecyclerAdapter exten
                 TelemetryContract.Event.LOAD_URL,
                 TelemetryContract.Method.LIST_ITEM,
                 extras.build()
         );
 
         // NB: This is hacky. We need to process telemetry data first, otherwise we run a risk of
         // not having a cursor to work with once url is opened and BrowserApp closes A-S home screen
         // and clears its resources (read: cursors). See Bug 1326018.
-        onUrlOpenListener.onUrlOpen(model.getUrl(), EnumSet.of(HomePager.OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
+        onUrlOpenListener.onUrlOpenWithReferrer(model.getUrl(), referrerUri,
+                EnumSet.of(HomePager.OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
     }
 
     @Override
     public boolean onItemLongClicked(final RecyclerView recyclerView, final int position, final View v) {
         if (!onItemClickIsValidRowItem(position)) {
             return false;
         }
 
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/menu/ActivityStreamContextMenu.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/menu/ActivityStreamContextMenu.java
@@ -13,16 +13,17 @@ import android.view.View;
 
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.IntentHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.activitystream.ActivityStreamTelemetry;
 import org.mozilla.gecko.activitystream.homepanel.model.WebpageModel;
+import org.mozilla.gecko.activitystream.homepanel.topstories.PocketStoriesLoader;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.home.HomePager;
 import org.mozilla.gecko.reader.SavedReaderViewHelper;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UIAsyncTask;
@@ -176,16 +177,18 @@ public abstract class ActivityStreamCont
     public boolean onNavigationItemSelected(MenuItem menuItem) {
         final int menuItemId = menuItem.getItemId();
 
         // Sets extra telemetry which doesn't require additional state information.
         // Pin and bookmark items are handled separately below, since they do require state
         // information to handle correctly.
         telemetryExtraBuilder.fromMenuItemId(menuItemId);
 
+        final String referrerUri = mode == MenuMode.TOPSTORY ? PocketStoriesLoader.POCKET_REFERRER_URI : null;
+
         switch (menuItem.getItemId()) {
             case R.id.share:
                 // NB: Generic menu item action event will be sent at the end of this function.
                 // We have a seemingly duplicate telemetry event here because we want to emit
                 // a concrete event in case it is used by other queries to estimate feature usage.
                 Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "as_contextmenu");
 
                 IntentHelper.openUriExternal(item.getUrl(), "text/plain", "", "", Intent.ACTION_SEND, item.getTitle(), false);
@@ -262,21 +265,23 @@ public abstract class ActivityStreamCont
                 Clipboard.setText(item.getUrl());
                 break;
 
             case R.id.add_homescreen:
                 GeckoApplication.createShortcut(item.getTitle(), item.getUrl());
                 break;
 
             case R.id.open_new_tab:
-                onUrlOpenInBackgroundListener.onUrlOpenInBackground(item.getUrl(), EnumSet.noneOf(HomePager.OnUrlOpenInBackgroundListener.Flags.class));
+                onUrlOpenInBackgroundListener.onUrlOpenInBackgroundWithReferrer(item.getUrl(), referrerUri,
+                        EnumSet.noneOf(HomePager.OnUrlOpenInBackgroundListener.Flags.class));
                 break;
 
             case R.id.open_new_private_tab:
-                onUrlOpenInBackgroundListener.onUrlOpenInBackground(item.getUrl(), EnumSet.of(HomePager.OnUrlOpenInBackgroundListener.Flags.PRIVATE));
+                onUrlOpenInBackgroundListener.onUrlOpenInBackgroundWithReferrer(item.getUrl(), referrerUri,
+                        EnumSet.of(HomePager.OnUrlOpenInBackgroundListener.Flags.PRIVATE));
                 break;
 
             case R.id.dismiss:
                 ThreadUtils.postToBackgroundThread(new Runnable() {
                     @Override
                     public void run() {
                         BrowserDB.from(context)
                                 .blockActivityStreamSite(context.getContentResolver(), item.getUrl());
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/topstories/PocketStoriesLoader.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/topstories/PocketStoriesLoader.java
@@ -45,16 +45,18 @@ import java.util.concurrent.TimeUnit;
  *   ac_add_options --with-pocket-api-keyfile=$topsrcdir/mobile/android/base/pocket-api-sandbox.token
  *
  * and include the Pocket API token in the token file.
  */
 
 public class PocketStoriesLoader extends AsyncTaskLoader<List<TopStory>> {
     public static String LOGTAG = "PocketStoriesLoader";
 
+    public static final String POCKET_REFERRER_URI = "https://getpocket.com/recommendations";
+
     // Pocket SharedPreferences keys
     private static final String POCKET_PREFS_FILE = "PocketStories";
     private static final String CACHE_TIMESTAMP_MILLIS_PREFIX = "timestampMillis-";
     private static final String STORIES_CACHE_PREFIX = "storiesCache-";
 
     // Pocket API params and defaults
     private static final String GLOBAL_ENDPOINT = "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs";
     private static final String PARAM_APIKEY = "consumer_key";
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomePager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomePager.java
@@ -89,17 +89,18 @@ public class HomePager extends RtlViewPa
             OPEN_WITH_INTENT,
             /**
              * Ensure that the raw URL is opened. If not set, then the reader view version of the page
              * might be opened if the URL is stored as an offline reader-view bookmark.
              */
             NO_READER_VIEW
         }
 
-        public void onUrlOpen(String url, EnumSet<Flags> flags);
+        void onUrlOpen(String url, EnumSet<Flags> flags);
+        void onUrlOpenWithReferrer(String url, String referrerUri, EnumSet<Flags> flags);
     }
 
     /**
      * Interface for requesting a new tab be opened in the background.
      * <p>
      * This is the <code>HomeFragment</code> equivalent of opening a new tab by
      * long clicking a link and selecting the "Open new [private] tab" context
      * menu option.
@@ -111,16 +112,17 @@ public class HomePager extends RtlViewPa
 
         /**
          * Open a new tab with the given URL
          *
          * @param url to open.
          * @param flags to open new tab with.
          */
         public void onUrlOpenInBackground(String url, EnumSet<Flags> flags);
+        void onUrlOpenInBackgroundWithReferrer(String url, String referrerUri, EnumSet<Flags> flags);
     }
 
     /**
      * Special type of child views that could be added as pager decorations by default.
      */
     public interface Decor {
         void onAddPagerView(String title);
         void removeAllPagerViews();