Bug 1325303 - A-S Telemetry: track positions of Top Site items for tile and menu interactions r=sebastian
MozReview-Commit-ID: B4XpqpwfjMi
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStreamTelemetry.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStreamTelemetry.java
@@ -7,31 +7,33 @@ package org.mozilla.gecko.activitystream
import android.support.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.home.activitystream.model.TopSite;
import java.util.HashMap;
/**
* Telemetry constants and an 'extras' builder specific to Activity Stream.
*/
public class ActivityStreamTelemetry {
public static class Contract {
// Keys
public final static String FX_ACCOUNT_PRESENT = "fx_account_present";
public final static String ITEM = "item";
public final static String SOURCE_TYPE = "source_type";
public final static String SOURCE_SUBTYPE = "source_subtype";
public final static String ACTION_POSITION = "action_position";
public final static String COUNT = "count";
+ public final static String PAGE_NUMBER = "page_number";
// Values
public final static String TYPE_TOPSITES = "topsites";
public final static String TYPE_HIGHLIGHTS = "highlights";
public final static String SUBTYPE_PINNED = "pinned";
public final static String SUBTYPE_SUGGESTED = "suggested";
public final static String SUBTYPE_TOP = "top";
public final static String SUBTYPE_VISITED = "visited";
@@ -139,32 +141,38 @@ public class ActivityStreamTelemetry {
this.set(Contract.SOURCE_SUBTYPE, Contract.SUBTYPE_BOOKMARKED);
break;
default:
throw new IllegalStateException("Unknown highlight source: " + source);
}
return this;
}
- public Builder forTopSiteType(@BrowserContract.TopSites.TopSiteType int type) {
- switch (type) {
+ public Builder forTopSite(final TopSite topSite) {
+ this.set(
+ ActivityStreamTelemetry.Contract.SOURCE_TYPE,
+ ActivityStreamTelemetry.Contract.TYPE_TOPSITES
+ );
+
+ switch (topSite.getType()) {
case BrowserContract.TopSites.TYPE_PINNED:
this.set(Contract.SOURCE_SUBTYPE, Contract.SUBTYPE_PINNED);
break;
case BrowserContract.TopSites.TYPE_SUGGESTED:
this.set(Contract.SOURCE_SUBTYPE, Contract.SUBTYPE_SUGGESTED);
break;
case BrowserContract.TopSites.TYPE_TOP:
this.set(Contract.SOURCE_SUBTYPE, Contract.SUBTYPE_TOP);
break;
// While we also have a "blank" type, it is not used by Activity Stream.
case BrowserContract.TopSites.TYPE_BLANK:
default:
- throw new IllegalStateException("Unknown top site type: " + type);
+ throw new IllegalStateException("Unknown top site type: " + topSite.getType());
}
+
return this;
}
public String build() {
return data.toString();
}
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java
@@ -1,77 +1,95 @@
/* -*- 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.home.activitystream.topsites;
import android.graphics.Color;
-import android.support.annotation.Nullable;
import android.support.v4.widget.TextViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.activitystream.ActivityStream;
import org.mozilla.gecko.activitystream.ActivityStreamTelemetry;
-import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.home.HomePager;
import org.mozilla.gecko.home.activitystream.menu.ActivityStreamContextMenu;
import org.mozilla.gecko.home.activitystream.model.TopSite;
import org.mozilla.gecko.icons.IconCallback;
import org.mozilla.gecko.icons.IconResponse;
import org.mozilla.gecko.icons.Icons;
import org.mozilla.gecko.util.DrawableUtil;
import org.mozilla.gecko.util.ViewUtil;
import org.mozilla.gecko.util.TouchTargetUtil;
import org.mozilla.gecko.widget.FaviconView;
-import java.util.EnumSet;
import java.util.concurrent.Future;
-class TopSitesCard extends RecyclerView.ViewHolder
- implements IconCallback, View.OnClickListener {
+/* package-local */ class TopSitesCard extends RecyclerView.ViewHolder
+ implements IconCallback {
private final FaviconView faviconView;
private final TextView title;
private final ImageView menuButton;
private Future<IconResponse> ongoingIconLoad;
private TopSite topSite;
+ private int absolutePosition;
private final HomePager.OnUrlOpenListener onUrlOpenListener;
private final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
- public TopSitesCard(FrameLayout card, final HomePager.OnUrlOpenListener onUrlOpenListener, final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
+ /* package-local */ TopSitesCard(FrameLayout card, final HomePager.OnUrlOpenListener onUrlOpenListener, final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
super(card);
faviconView = (FaviconView) card.findViewById(R.id.favicon);
title = (TextView) card.findViewById(R.id.title);
menuButton = (ImageView) card.findViewById(R.id.menu);
this.onUrlOpenListener = onUrlOpenListener;
this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
- card.setOnClickListener(this);
+ TouchTargetUtil.ensureTargetHitArea(menuButton, card);
+ menuButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ActivityStreamTelemetry.Extras.Builder extras = ActivityStreamTelemetry.Extras.builder()
+ .forTopSite(topSite)
+ .set(ActivityStreamTelemetry.Contract.ACTION_POSITION, absolutePosition);
- TouchTargetUtil.ensureTargetHitArea(menuButton, card);
- menuButton.setOnClickListener(this);
+ ActivityStreamContextMenu.show(v.getContext(),
+ menuButton,
+ extras,
+ ActivityStreamContextMenu.MenuMode.TOPSITE,
+ topSite,
+ onUrlOpenListener, onUrlOpenInBackgroundListener,
+ faviconView.getWidth(), faviconView.getHeight());
+
+ Telemetry.sendUIEvent(
+ TelemetryContract.Event.SHOW,
+ TelemetryContract.Method.CONTEXT_MENU,
+ extras.build()
+ );
+ }
+ });
ViewUtil.enableTouchRipple(menuButton);
}
- void bind(final TopSite topSite) {
+ void bind(final TopSite topSite, final int absolutePosition) {
this.topSite = topSite;
+ this.absolutePosition = absolutePosition;
ActivityStream.extractLabel(itemView.getContext(), topSite.getUrl(), true, new ActivityStream.LabelCallback() {
@Override
public void onLabelExtracted(String label) {
title.setText(label);
}
});
@@ -93,40 +111,9 @@ class TopSitesCard extends RecyclerView.
public void onIconResponse(IconResponse response) {
faviconView.updateImage(response);
final int tintColor = !response.hasColor() || response.getColor() == Color.WHITE ? Color.LTGRAY : Color.WHITE;
menuButton.setImageDrawable(
DrawableUtil.tintDrawable(menuButton.getContext(), R.drawable.menu, tintColor));
}
-
- @Override
- public void onClick(View clickedView) {
- ActivityStreamTelemetry.Extras.Builder extras = ActivityStreamTelemetry.Extras.builder()
- .set(ActivityStreamTelemetry.Contract.SOURCE_TYPE, ActivityStreamTelemetry.Contract.TYPE_TOPSITES)
- .forTopSiteType(topSite.getType());
-
- if (clickedView == itemView) {
- onUrlOpenListener.onUrlOpen(topSite.getUrl(), EnumSet.noneOf(HomePager.OnUrlOpenListener.Flags.class));
-
- Telemetry.sendUIEvent(
- TelemetryContract.Event.LOAD_URL,
- TelemetryContract.Method.LIST_ITEM,
- extras.build()
- );
- } else if (clickedView == menuButton) {
- ActivityStreamContextMenu.show(clickedView.getContext(),
- menuButton,
- extras,
- ActivityStreamContextMenu.MenuMode.TOPSITE,
- topSite,
- onUrlOpenListener, onUrlOpenInBackgroundListener,
- faviconView.getWidth(), faviconView.getHeight());
-
- Telemetry.sendUIEvent(
- TelemetryContract.Event.SHOW,
- TelemetryContract.Method.CONTEXT_MENU,
- extras.build()
- );
- }
- }
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPageAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPageAdapter.java
@@ -1,47 +1,52 @@
/* -*- 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.home.activitystream.topsites;
import android.content.Context;
import android.database.Cursor;
-import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.activitystream.ActivityStreamTelemetry;
import org.mozilla.gecko.home.HomePager;
import org.mozilla.gecko.home.activitystream.model.TopSite;
+import org.mozilla.gecko.widget.RecyclerViewClickSupport;
import java.util.ArrayList;
+import java.util.EnumSet;
import java.util.List;
-public class TopSitesPageAdapter extends RecyclerView.Adapter<TopSitesCard> {
+/* package-local */ class TopSitesPageAdapter extends RecyclerView.Adapter<TopSitesCard> implements RecyclerViewClickSupport.OnItemClickListener {
private List<TopSite> topSites;
+ private final int pageNumber;
private int tiles;
private int tilesWidth;
private int tilesHeight;
private int textHeight;
private final HomePager.OnUrlOpenListener onUrlOpenListener;
private final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
- public TopSitesPageAdapter(Context context, int tiles, int tilesWidth, int tilesHeight,
+ /* package-local */ TopSitesPageAdapter(Context context, int pageNumber, int tiles, int tilesWidth, int tilesHeight,
HomePager.OnUrlOpenListener onUrlOpenListener, HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
setHasStableIds(true);
this.topSites = new ArrayList<>();
+ this.pageNumber = pageNumber;
this.tiles = tiles;
this.tilesWidth = tilesWidth;
this.tilesHeight = tilesHeight;
this.textHeight = context.getResources().getDimensionPixelSize(R.dimen.activity_stream_top_sites_text_height);
this.onUrlOpenListener = onUrlOpenListener;
this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
}
@@ -62,18 +67,37 @@ public class TopSitesPageAdapter extends
topSites.add(TopSite.fromCursor(cursor));
}
notifyDataSetChanged();
}
@Override
+ public void onItemClicked(RecyclerView recyclerView, int position, View v) {
+ final TopSite topSite = topSites.get(position);
+ final int absolutePosition = getTopSiteAbsolutePosition(position);
+
+ ActivityStreamTelemetry.Extras.Builder extras = ActivityStreamTelemetry.Extras.builder()
+ .forTopSite(topSite)
+ .set(ActivityStreamTelemetry.Contract.PAGE_NUMBER, pageNumber)
+ .set(ActivityStreamTelemetry.Contract.ACTION_POSITION, absolutePosition);
+
+ Telemetry.sendUIEvent(
+ TelemetryContract.Event.LOAD_URL,
+ TelemetryContract.Method.LIST_ITEM,
+ extras.build()
+ );
+
+ onUrlOpenListener.onUrlOpen(topSite.getUrl(), EnumSet.noneOf(HomePager.OnUrlOpenListener.Flags.class));
+ }
+
+ @Override
public void onBindViewHolder(TopSitesCard holder, int position) {
- holder.bind(topSites.get(position));
+ holder.bind(topSites.get(position), getTopSiteAbsolutePosition(position));
}
@Override
public TopSitesCard onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final FrameLayout card = (FrameLayout) inflater.inflate(R.layout.activity_stream_topsites_card, parent, false);
final View content = card.findViewById(R.id.content);
@@ -91,9 +115,20 @@ public class TopSitesPageAdapter extends
return topSites.size();
}
@Override
@UiThread
public long getItemId(int position) {
return topSites.get(position).getId();
}
+
+ /**
+ * This assumes that every TopSite page up to the current one has the same number of tiles.
+ * relativePosition must range from 0 to {number of tiles on the current page}.
+ */
+ private int getTopSiteAbsolutePosition(int relativePosition) {
+ if (relativePosition < 0 || relativePosition > tiles) {
+ throw new IllegalArgumentException("Illegal relative top site position encountered");
+ }
+ return relativePosition + pageNumber * tiles;
+ }
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java
@@ -8,16 +8,17 @@ import android.content.Context;
import android.database.Cursor;
import android.support.v4.view.PagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.mozilla.gecko.R;
import org.mozilla.gecko.home.HomePager;
+import org.mozilla.gecko.widget.RecyclerViewClickSupport;
import java.util.LinkedList;
/**
* The primary / top-level TopSites adapter: it handles the ViewPager, and also handles
* all lower-level Adapters that populate the individual topsite items.
*/
public class TopSitesPagerAdapter extends PagerAdapter {
@@ -91,19 +92,21 @@ public class TopSitesPagerAdapter extend
final int pageDelta = count;
if (pageDelta > 0) {
final LayoutInflater inflater = LayoutInflater.from(context);
for (int i = 0; i < pageDelta; i++) {
final TopSitesPage page = (TopSitesPage) inflater.inflate(R.layout.activity_stream_topsites_page, null, false);
page.setTiles(tiles);
- final TopSitesPageAdapter adapter = new TopSitesPageAdapter(context, tiles, tilesWidth, tilesHeight,
+ final TopSitesPageAdapter adapter = new TopSitesPageAdapter(
+ context, i, tiles, tilesWidth, tilesHeight,
onUrlOpenListener, onUrlOpenInBackgroundListener);
page.setAdapter(adapter);
+ RecyclerViewClickSupport.addTo(page).setOnItemClickListener(adapter);
pages.add(page);
}
} else if (pageDelta < 0) {
for (int i = 0; i > pageDelta; i--) {
final TopSitesPage page = pages.getLast();
// Ensure the page doesn't use the old/invalid cursor anymore
page.getAdapter().swapCursor(null, 0);