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
--- 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();