Bug 1288106 - Move history (highlights) into main recyclerview, and eliminated horizontal highlights r=sebastian
authorAndrzej Hunt <ahunt@mozilla.com>
Tue, 16 Aug 2016 13:38:19 -0700
changeset 351868 08e9ded26ada149385af32e1c1d89f30e3d8279c
parent 351867 c499af51f167880b1a51458a2d7d25e35e2f4b57
child 351869 b2947c489cc7d1536ce1094609c4bd19d4aea1be
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssebastian
bugs1288106
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1288106 - Move history (highlights) into main recyclerview, and eliminated horizontal highlights r=sebastian Moving all vertically listed items into one RecylerView avoids the layouting issues encountered with nested RVs. This results in a slightly more complex adapter, however overall this still seems simpler than having to hack around the RecyclerView height measurements. (This also makes the layout itself simpler, which hopefully means better performance too.) MozReview-Commit-ID: HFS9q5JNYpY
mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/HighlightRecyclerAdapter.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/HistoryRecyclerAdapter.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/MainRecyclerAdapter.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/MainRecyclerLayout.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/moz.build
mobile/android/base/resources/layout/activity_stream_card_highlights_item.xml
mobile/android/base/resources/layout/activity_stream_card_history_item.xml
mobile/android/base/resources/layout/activity_stream_card_top_sites_item.xml
mobile/android/base/resources/layout/activity_stream_category.xml
mobile/android/base/resources/layout/activity_stream_main_bottompanel.xml
mobile/android/base/resources/layout/activity_stream_main_toppanel.xml
mobile/android/base/strings.xml.in
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java
@@ -1,30 +1,35 @@
 /* -*- 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;
 
 import android.content.Context;
+import android.database.Cursor;
 import android.os.Bundle;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.widget.FrameLayout;
 
+import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.home.HomeBanner;
 import org.mozilla.gecko.home.HomeFragment;
 import org.mozilla.gecko.home.HomeScreen;
+import org.mozilla.gecko.home.SimpleCursorLoader;
 
 public class ActivityStream extends FrameLayout implements HomeScreen {
+    private StreamRecyclerAdapter adapter;
 
     public ActivityStream(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         inflate(context, R.layout.as_content, this);
     }
 
     @Override
@@ -60,18 +65,57 @@ public class ActivityStream extends Fram
         // TODO: we should probably implement this to show snippets.
     }
 
     @Override
     public void load(LoaderManager lm, FragmentManager fm, String panelId, Bundle restoreData,
                      PropertyAnimator animator) {
         // Signal to load data from storage as needed, compare with HomePager
         RecyclerView rv = (RecyclerView) findViewById(R.id.activity_stream_main_recyclerview);
-        rv.setAdapter(new MainRecyclerAdapter(getContext()));
+
+        adapter = new StreamRecyclerAdapter();
+        rv.setAdapter(adapter);
         rv.setLayoutManager(new LinearLayoutManager(getContext()));
         rv.setHasFixedSize(true);
+
+        lm.initLoader(0, null, new CursorLoaderCallbacks());
     }
 
     @Override
     public void unload() {
         // Signal to clear data that has been loaded, compare with HomePager
     }
+
+    /**
+     * This is a temporary cursor loader. We'll probably need a completely new query for AS,
+     * at that time we can switch to the new CursorLoader, as opposed to using our outdated
+     * SimpleCursorLoader.
+     */
+    private static class HistoryLoader extends SimpleCursorLoader {
+        public HistoryLoader(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected Cursor loadCursor() {
+            final Context context = getContext();
+            return GeckoProfile.get(context).getDB()
+                    .getRecentHistory(context.getContentResolver(), 10);
+        }
+    }
+
+    private class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
+        @Override
+        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+            return new HistoryLoader(getContext());
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+            adapter.swapCursor(data);
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Cursor> loader) {
+            adapter.swapCursor(null);
+        }
+    }
 }
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/HighlightRecyclerAdapter.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package org.mozilla.gecko.home.activitystream;
-
-
-import android.content.Context;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-
-class HighlightRecyclerAdapter extends RecyclerView.Adapter<HighlightRecyclerAdapter.ViewHolder> {
-
-    private final Context context;
-    private final String[] items = {
-            "What Do The Reviews Have To Say About the New Ghostbusters",
-            "The Dark Secrets Of This Now-Empty Island in Maine",
-            "How to Prototype a Game in Under 7 Days (2005)",
-            "Fuchsia, a new operating system"
-    };
-    private final String[] items_time = {
-            "3h",
-            "3h",
-            "3h",
-            "3h"
-    };
-
-    HighlightRecyclerAdapter(Context context) {
-        this.context = context;
-    }
-
-    @Override
-    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        View v = LayoutInflater
-                .from(context)
-                .inflate(R.layout.activity_stream_card_highlights_item, parent, false);
-        return new ViewHolder(v);
-    }
-
-    @Override
-    public void onBindViewHolder(ViewHolder holder, int position) {
-        holder.vLabel.setText(items[position]);
-        holder.vTimeSince.setText(items_time[position]);
-        int res = context.getResources()
-                .getIdentifier("thumb_" + (position + 1), "drawable", context.getPackageName());
-        holder.vThumbnail.setImageResource(res);
-    }
-
-    @Override
-    public int getItemCount() {
-        return items.length;
-    }
-
-    static class ViewHolder extends RecyclerView.ViewHolder {
-        TextView vLabel;
-        TextView vTimeSince;
-        ImageView vThumbnail;
-
-        public ViewHolder(View itemView) {
-            super(itemView);
-            vLabel = (TextView) itemView.findViewById(R.id.card_highlights_label);
-            vTimeSince = (TextView) itemView.findViewById(R.id.card_highlights_time_since);
-            vThumbnail = (ImageView) itemView.findViewById(R.id.card_highlights_thumbnail);
-        }
-    }
-}
-
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/HistoryRecyclerAdapter.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.mozilla.gecko.home.activitystream;
-
-import android.content.Context;
-import android.support.v7.widget.CardView;
-import android.support.v7.widget.RecyclerView;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-
-class HistoryRecyclerAdapter extends RecyclerView.Adapter<HistoryRecyclerAdapter.ViewHolder> {
-
-    private final Context context;
-    private final String[] items = {
-            "What Do The Reviews Have To Say About the New Ghostbusters",
-            "New “Leaf” Is More Efficient Than Natural Photosynthesis",
-            "Zero-cost futures in Rust",
-            "Indie Hackers: Learn how developers are making money",
-            "The fight to cheat death is heating up"
-    };
-
-    HistoryRecyclerAdapter(Context context) {
-        this.context = context;
-    }
-
-    @Override
-    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        View v = LayoutInflater
-                .from(context)
-                .inflate(R.layout.activity_stream_card_history_item, parent, false);
-        return new ViewHolder(v);
-    }
-
-    @Override
-    public void onBindViewHolder(ViewHolder holder, int position) {
-        holder.vLabel.setText(items[position]);
-    }
-
-    @Override
-    public int getItemCount() {
-        return items.length;
-    }
-
-    static class ViewHolder extends RecyclerView.ViewHolder {
-        TextView vLabel;
-        ViewHolder(View itemView) {
-            super(itemView);
-            vLabel = (TextView) itemView.findViewById(R.id.card_history_label);
-        }
-    }
-}
-
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/MainRecyclerLayout.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.mozilla.gecko.home.activitystream;
-
-import android.content.Context;
-import android.support.annotation.Nullable;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-
-import org.mozilla.gecko.R;
-
-public class MainRecyclerLayout extends LinearLayout {
-    public MainRecyclerLayout(Context context) {
-        super(context);
-        RecyclerView rv = (RecyclerView) findViewById(R.id.activity_stream_main_recyclerview);
-        rv.setAdapter(new MainRecyclerAdapter(context));
-        rv.setLayoutManager(new LinearLayoutManager(context));
-    }
-
-    public MainRecyclerLayout(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public MainRecyclerLayout(Context context, @Nullable AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
@@ -0,0 +1,88 @@
+/* -*- 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;
+
+import android.database.Cursor;
+import android.support.v7.widget.RecyclerView;
+import android.text.format.DateUtils;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract;
+
+public abstract class StreamItem extends RecyclerView.ViewHolder {
+    public StreamItem(View itemView) {
+        super(itemView);
+    }
+
+    public void bind(Cursor cursor) {
+        throw new IllegalStateException("Cannot bind " + this.getClass().getSimpleName());
+    }
+
+    public static class TopPanel extends StreamItem {
+        public static final int LAYOUT_ID = R.layout.activity_stream_main_toppanel;
+
+        public TopPanel(View itemView) {
+            super(itemView);
+        }
+    }
+
+    public static class BottomPanel extends StreamItem {
+        public static final int LAYOUT_ID = R.layout.activity_stream_main_bottompanel;
+
+        public BottomPanel(View itemView) {
+            super(itemView);
+        }
+    }
+
+    public static class CompactItem extends StreamItem {
+        public static final int LAYOUT_ID = R.layout.activity_stream_card_history_item;
+
+        final TextView vLabel;
+        final TextView vTimeSince;
+
+        public CompactItem(View itemView) {
+            super(itemView);
+            vLabel = (TextView) itemView.findViewById(R.id.card_history_label);
+            vTimeSince = (TextView) itemView.findViewById(R.id.card_history_time_since);
+        }
+
+        @Override
+        public void bind(Cursor cursor) {
+            vLabel.setText(cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE)));
+
+            final long timeVisited = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.History.DATE_LAST_VISITED));
+            final String ago = DateUtils.getRelativeTimeSpanString(timeVisited, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
+            vTimeSince.setText(ago);
+        }
+    }
+
+    public static class HighlightItem extends StreamItem {
+        public static final int LAYOUT_ID = R.layout.activity_stream_card_highlights_item;
+
+        final TextView vLabel;
+        final TextView vTimeSince;
+        final ImageView vThumbnail;
+
+        public HighlightItem(View itemView) {
+            super(itemView);
+            vLabel = (TextView) itemView.findViewById(R.id.card_highlights_label);
+            vTimeSince = (TextView) itemView.findViewById(R.id.card_highlights_time_since);
+            vThumbnail = (ImageView) itemView.findViewById(R.id.card_highlights_thumbnail);
+        }
+
+        @Override
+        public void bind(Cursor cursor) {
+            vLabel.setText(cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE)));
+
+            final long timeVisited = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.History.DATE_LAST_VISITED));
+            final String ago = DateUtils.getRelativeTimeSpanString(timeVisited, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
+            vTimeSince.setText(ago);
+        }
+    }
+
+}
rename from mobile/android/base/java/org/mozilla/gecko/home/activitystream/MainRecyclerAdapter.java
rename to mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/MainRecyclerAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
@@ -1,140 +1,94 @@
+/* -*- 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;
 
-
-import android.content.Context;
-import android.support.v7.widget.LinearLayoutManager;
+import android.database.Cursor;
 import android.support.v7.widget.RecyclerView;
-import android.util.TypedValue;
 import android.view.LayoutInflater;
-import android.view.View;
 import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
 
-import java.util.Arrays;
-import java.util.List;
+import org.mozilla.gecko.home.activitystream.StreamItem.BottomPanel;
+import org.mozilla.gecko.home.activitystream.StreamItem.CompactItem;
+import org.mozilla.gecko.home.activitystream.StreamItem.HighlightItem;
+import org.mozilla.gecko.home.activitystream.StreamItem.TopPanel;
 
-
-class MainRecyclerAdapter extends RecyclerView.Adapter<MainRecyclerAdapter.ViewHolder> {
+public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamItem> {
+    private Cursor highlightsCursor;
 
-    private final Context context;
-
-    private static final int VIEW_TYPE_TOP_SITES = 0;
-    private static final int VIEW_TYPE_HIGHLIGHTS = 1;
-    private static final int VIEW_TYPE_HISTORY = 2;
-
-    private static final int VIEW_SIZE_TOP_SITES = 115;
-    private static final int VIEW_SIZE_HIGHLIGHTS = 220;
-    private static final int VIEW_SIZE_HISTORY = 80;
-
-    private final List<Item> categories = Arrays.asList(
-            new Item("Top Sites"),
-            new Item("Highlights"),
-            new Item("History")
-    );
-
-    MainRecyclerAdapter(Context context) {
-        this.context = context;
+    @Override
+    public int getItemViewType(int position) {
+        if (position == 0) {
+            return TopPanel.LAYOUT_ID;
+        } else if (position == getItemCount() - 1) {
+            return BottomPanel.LAYOUT_ID;
+        } else {
+            // TODO: in future we'll want to create different items for some results, tbc?
+            // For now let's show a detailed view for these two positions...
+            if (position == 2 || position == 6) {
+                return HighlightItem.LAYOUT_ID;
+            }
+            return CompactItem.LAYOUT_ID;
+        }
     }
 
     @Override
-    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        View itemView = LayoutInflater
-                .from(context)
-                .inflate(R.layout.activity_stream_category, parent, false);
-        return new ViewHolder(itemView);
+    public StreamItem onCreateViewHolder(ViewGroup parent, final int type) {
+        final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+
+        if (type == TopPanel.LAYOUT_ID) {
+            return new TopPanel(inflater.inflate(type, parent, false));
+        } else if (type == BottomPanel.LAYOUT_ID) {
+                return new BottomPanel(inflater.inflate(type, parent, false));
+        } else if (type == CompactItem.LAYOUT_ID) {
+            return new CompactItem(inflater.inflate(type, parent, false));
+        } else if (type == HighlightItem.LAYOUT_ID) {
+            return new HighlightItem(inflater.inflate(type, parent, false));
+        } else {
+            throw new IllegalStateException("Missing inflation for ViewType " + type);
+        }
+    }
+
+    private int translatePositionToCursor(int position) {
+        if (position == 0 ||
+            position == getItemCount() - 1) {
+            throw new IllegalArgumentException("Requested cursor position for invalid item");
+        }
+
+        // We have one blank panel at the top, hence remove that to obtain the cursor position
+        return position - 1;
     }
 
     @Override
-    public void onBindViewHolder(ViewHolder holder, int position) {
-        LinearLayoutManager llm = new LinearLayoutManager(context);
-        if (position == VIEW_TYPE_TOP_SITES) {
-            holder.vCategoryRv.setAdapter(new TopSitesRecyclerAdapter(context));
-            setCorrectedLayoutOrientation(llm, LinearLayoutManager.HORIZONTAL);
-            setCorrectedLayoutHeight(context, VIEW_SIZE_TOP_SITES, holder, llm);
-        } else if (position == VIEW_TYPE_HIGHLIGHTS) {
-            holder.vCategoryRv.setAdapter(new HighlightRecyclerAdapter(context));
-            setCorrectedLayoutOrientation(llm, LinearLayoutManager.HORIZONTAL);
-            setCorrectedLayoutHeight(context, VIEW_SIZE_HIGHLIGHTS, holder, llm);
-        } else if (position == VIEW_TYPE_HISTORY) {
-            holder.vCategoryRv.setAdapter(new HistoryRecyclerAdapter(context));
-            setCorrectedLayoutOrientation(llm, LinearLayoutManager.VERTICAL);
-            setCorrectedLayoutHeight(context, VIEW_SIZE_HISTORY, holder, llm);
-        }
+    public void onBindViewHolder(StreamItem holder, int position) {
+        int type = getItemViewType(position);
 
-        holder.vCategoryRv.setLayoutManager(llm);
+        if (type == CompactItem.LAYOUT_ID ||
+            type == HighlightItem.LAYOUT_ID) {
 
-        if (position != VIEW_TYPE_HISTORY) {
-            holder.vTitle.setText(categories.get(position).getLabel());
-            holder.vMore.setVisibility(View.VISIBLE);
+            final int cursorPosition = translatePositionToCursor(position);
+
+            highlightsCursor.moveToPosition(cursorPosition);
+            holder.bind(highlightsCursor);
         }
     }
 
     @Override
     public int getItemCount() {
-        return categories.size();
-    }
-
-    private void setCorrectedLayoutHeight(final Context context,
-                                          final int height,
-                                          final ViewHolder holder,
-                                          final LinearLayoutManager llm) {
-        int correctedHeight = height;
-        if (isHistoryView(llm)) {
-            int holderItemCount;
-            holderItemCount = holder.vCategoryRv.getAdapter().getItemCount();
-            correctedHeight = height * holderItemCount;
+        final int highlightsCount;
+        if (highlightsCursor != null) {
+            highlightsCount = highlightsCursor.getCount();
+        } else {
+            highlightsCount = 0;
         }
-        setHolderRecyclerViewHeight(holder,
-                getActualPixelValue(context, correctedHeight));
-    }
 
-    private static int getActualPixelValue(Context context, int height) {
-        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height,
-                context.getResources().getDisplayMetrics());
-    }
-
-    private static void setHolderRecyclerViewHeight(ViewHolder holder, int height) {
-        holder.vCategoryRv.setLayoutParams(new LinearLayout.LayoutParams(
-                holder.vCategoryRv.getLayoutParams().width, height));
-    }
-
-    private static boolean isHistoryView(LinearLayoutManager llm) {
-        return llm.getOrientation() == LinearLayoutManager.VERTICAL;
+        return 2 + highlightsCount;
     }
 
-    private void setCorrectedLayoutOrientation(final LinearLayoutManager lm,
-                                               final int orientation) {
-        lm.setOrientation(orientation);
-    }
-
-    static class ViewHolder extends RecyclerView.ViewHolder {
-        TextView vTitle;
-        TextView vMore;
-        RecyclerView vCategoryRv;
-        ViewHolder(View itemView) {
-            super(itemView);
-            vTitle = (TextView) itemView.findViewById(R.id.category_title);
-            vMore = (TextView) itemView.findViewById(R.id.category_more_link);
-            vCategoryRv = (RecyclerView) itemView.findViewById(R.id.recycler_category);
-        }
-    }
+    public void swapCursor(Cursor cursor) {
+        highlightsCursor = cursor;
 
-    static class Item {
-        String label;
-
-        Item(String label) {
-            setLabel(label);
-        }
-
-        public void setLabel(String label) {
-            this.label = label;
-        }
-
-        public String getLabel() {
-            return label;
-        }
+        notifyDataSetChanged();
     }
-}
\ No newline at end of file
+}
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -826,8 +826,13 @@ just addresses the organization to follo
 <!ENTITY helper_first_offline_bookmark_title "Read offline">
 <!ENTITY helper_first_offline_bookmark_message "Find your Reader View items in Bookmarks, even offline.">
 <!ENTITY helper_first_offline_bookmark_button "Go to Bookmarks">
 
 <!ENTITY helper_triple_readerview_open_title "Available offline">
 <!ENTITY helper_triple_readerview_open_message "Bookmark Reader View items to read them offline.">
 <!ENTITY helper_triple_readerview_open_button "Add to Bookmarks">
 
+<!ENTITY activity_stream_topsites "Top Sites">
+<!ENTITY activity_stream_highlights "Highlights">
+<!-- LOCALIZATION NOTE (activity_stream_more): Link that opens the detailed bookmark/history lists
+     when pressed. -->
+<!ENTITY activity_stream_more "More">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -425,20 +425,18 @@ gbjar.sources += ['java/org/mozilla/geck
     'GeckoProfilesProvider.java',
     'GeckoUpdateReceiver.java',
     'GlobalHistory.java',
     'GuestSession.java',
     'health/HealthRecorder.java',
     'health/SessionInformation.java',
     'health/StubbedHealthRecorder.java',
     'home/activitystream/ActivityStream.java',
-    'home/activitystream/HighlightRecyclerAdapter.java',
-    'home/activitystream/HistoryRecyclerAdapter.java',
-    'home/activitystream/MainRecyclerAdapter.java',
-    'home/activitystream/MainRecyclerLayout.java',
+    'home/activitystream/StreamItem.java',
+    'home/activitystream/StreamRecyclerAdapter.java',
     'home/activitystream/TopSitesRecyclerAdapter.java',
     'home/BookmarkFolderView.java',
     'home/BookmarkScreenshotRow.java',
     'home/BookmarksListAdapter.java',
     'home/BookmarksListView.java',
     'home/BookmarksPanel.java',
     'home/BrowserSearch.java',
     'home/ClientsAdapter.java',
--- a/mobile/android/base/resources/layout/activity_stream_card_highlights_item.xml
+++ b/mobile/android/base/resources/layout/activity_stream_card_highlights_item.xml
@@ -1,22 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 <android.support.v7.widget.CardView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:orientation="vertical"
     android:layout_marginRight="5dp"
     android:layout_marginEnd="5dp"
     android:layout_marginTop="10dp"
     android:layout_marginBottom="10dp"
-    android:layout_width="180dp"
-    android:layout_height="match_parent">
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
 
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:baselineAligned="false"
         android:orientation="vertical">
 
         <FrameLayout
             android:background="@color/disabled_grey"
             android:layout_width="match_parent"
             android:layout_height="140dp">
 
@@ -25,65 +26,53 @@
                 android:id="@+id/card_highlights_thumbnail"
                 android:src="@drawable/favicon_globe"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"/>
         </FrameLayout>
         <RelativeLayout
             android:padding="3dp"
             android:layout_width="match_parent"
-            android:layout_height="match_parent">
+            android:layout_height="wrap_content">
 
             <TextView
                 android:id="@+id/card_highlights_label"
                 android:textSize="10sp"
                 android:maxLines="3"
                 android:ellipsize="end"
+                tools:text="FooBar"
                 android:textColor="@android:color/black"
                 android:layout_marginLeft="4dp"
                 android:layout_marginStart="4dp"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_alignParentLeft="true"
                 android:layout_alignParentStart="true"
                 android:layout_toStartOf="@+id/card_highlights_time_since"
                 android:layout_toLeftOf="@+id/card_highlights_time_since"
                 android:layout_alignParentTop="true"
-                android:layout_above="@+id/linearLayout3"/>
+                android:layout_alignBottom="@+id/card_highlights_time_since"/>
 
             <TextView
                 android:id="@+id/card_highlights_time_since"
-                android:text="2h"
+                tools:text="2h"
                 android:textSize="10sp"
                 android:layout_marginEnd="4dp"
                 android:layout_marginRight="4dp"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_alignParentTop="true"
                 android:layout_alignParentRight="true"
                 android:layout_alignParentEnd="true"/>
 
-            <LinearLayout
-                android:layout_marginTop="5dp"
-                android:orientation="horizontal"
+            <TextView
+                tools:text="Bookmarked"
+                android:textSize="10sp"
+                android:drawableLeft="@drawable/search_icon_active"
+                android:gravity="center_vertical"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_alignParentBottom="true"
-                android:layout_alignParentStart="true"
+                android:layout_below="@+id/card_highlights_label"
                 android:layout_alignParentLeft="true"
-                android:id="@+id/linearLayout3">
-
-                <ImageView
-                    android:src="@drawable/search_icon_active"
-                    android:alpha="0.5"
-                    android:layout_width="18dp"
-                    android:layout_height="18dp"/>
-
-                <TextView
-                    android:text="Bookmarked"
-                    android:textSize="10sp"
-                    android:gravity="center_vertical"
-                    android:layout_width="wrap_content"
-                    android:layout_height="match_parent"/>
-            </LinearLayout>
+                android:layout_alignParentStart="true"/>
         </RelativeLayout>
     </LinearLayout>
 </android.support.v7.widget.CardView>
\ No newline at end of file
--- a/mobile/android/base/resources/layout/activity_stream_card_history_item.xml
+++ b/mobile/android/base/resources/layout/activity_stream_card_history_item.xml
@@ -1,11 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <android.support.v7.widget.CardView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:orientation="vertical"
     android:layout_marginRight="5dp"
     android:layout_marginEnd="5dp"
     android:layout_marginTop="10dp"
     android:layout_marginBottom="0dp"
     android:layout_width="match_parent"
     android:layout_height="60dp">
 
@@ -26,61 +27,51 @@
                 android:src="@drawable/favicon_globe"
                 android:scaleType="fitCenter"
                 android:layout_gravity="center"
                 android:layout_width="30dp"
                 android:layout_height="30dp"/>
         </FrameLayout>
 
         <FrameLayout
-            android:id="@+id/frameLayout2"
-            android:layout_toRightOf="@id/frameLayout1"
-            android:layout_toEndOf="@id/frameLayout1"
-            android:layout_width="50dp"
-            android:layout_height="match_parent">
-
-            <ImageView
-                android:src="@drawable/tab_new"
-                android:alpha="0.5"
-                android:scaleType="fitCenter"
-                android:layout_gravity="center"
-                android:layout_width="15dp"
-                android:layout_height="15dp"/>
-        </FrameLayout>
-
-        <FrameLayout
             android:id="@+id/frameLayout3"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:layout_toRightOf="@id/frameLayout2"
-            android:layout_toEndOf="@id/frameLayout2">
+            android:layout_toRightOf="@+id/imageView"
+            android:layout_toLeftOf="@+id/card_history_time_since">
 
             <TextView
                 android:id="@+id/card_history_label"
+                tools:text="Descriptive title of a page..."
                 android:gravity="center_vertical"
                 android:maxLines="2"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"/>
 
         </FrameLayout>
 
-        <LinearLayout
-            android:id="@+id/linearLayout5"
-            android:orientation="vertical"
+        <TextView
+            android:id="@+id/card_history_time_since"
+            tools:text="20m"
+            android:textSize="12sp"
+            android:gravity="bottom|right"
+            android:paddingRight="4dp"
+            android:paddingEnd="4dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
             android:layout_alignParentRight="true"
             android:layout_alignParentEnd="true"
-            android:layout_width="40dp"
-            android:layout_height="match_parent">
+            android:layout_alignParentTop="false"/>
 
-            <TextView
-                android:id="@+id/card_history_time_since"
-                android:text="20m"
-                android:textSize="12sp"
-                android:gravity="bottom|right"
-                android:paddingRight="4dp"
-                android:paddingEnd="4dp"
-                android:layout_margin="4dp"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"/>
-
-        </LinearLayout>
+        <ImageView
+            android:src="@drawable/tab_new"
+            android:alpha="0.5"
+            android:scaleType="fitCenter"
+            android:layout_gravity="center"
+            android:layout_width="50dp"
+            android:layout_height="15dp"
+            android:layout_centerVertical="true"
+            android:layout_toRightOf="@+id/frameLayout1"
+            android:layout_toEndOf="@+id/frameLayout1"
+            android:id="@+id/imageView"/>
     </RelativeLayout>
 </android.support.v7.widget.CardView>
\ No newline at end of file
--- a/mobile/android/base/resources/layout/activity_stream_card_top_sites_item.xml
+++ b/mobile/android/base/resources/layout/activity_stream_card_top_sites_item.xml
@@ -1,11 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <android.support.v7.widget.CardView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_marginRight="5dp"
     android:layout_marginEnd="5dp"
     android:layout_marginTop="10dp"
     android:layout_marginBottom="10dp"
     android:orientation="vertical"
     android:layout_width="90dp"
     android:layout_height="match_parent">
 
@@ -29,17 +30,17 @@
         </FrameLayout>
 
         <FrameLayout
             android:layout_width="match_parent"
             android:layout_height="match_parent">
 
             <TextView
                 android:id="@+id/card_row_label"
-                android:text="Firefox"
+                tools:text="Firefox"
                 android:textSize="10sp"
                 android:textStyle="bold"
                 android:layout_gravity="center"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"/>
         </FrameLayout>
     </LinearLayout>
 </android.support.v7.widget.CardView>
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/base/resources/layout/activity_stream_category.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:orientation="vertical"
-              android:layout_width="wrap_content"
-              android:layout_height="match_parent">
-
-        <RelativeLayout
-            android:orientation="horizontal"
-            android:layout_marginRight="12dp"
-            android:layout_marginEnd="12dp"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content">
-
-            <TextView
-                android:id="@+id/category_title"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignParentLeft="true"
-                android:layout_alignParentStart="true"/>
-
-            <TextView
-                android:id="@+id/category_more_link"
-                android:text="More"
-                android:visibility="invisible"
-                android:textAllCaps="true"
-                android:textSize="14sp"
-                android:textColor="@android:color/holo_orange_dark"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignParentRight="true"
-                android:layout_alignParentEnd="true"/>
-
-        </RelativeLayout>
-
-        <android.support.v7.widget.RecyclerView
-            android:id="@+id/recycler_category"
-            android:layout_marginTop="5dp"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-
-</LinearLayout>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/activity_stream_main_bottompanel.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+</LinearLayout>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/activity_stream_main_toppanel.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                xmlns:tools="http://schemas.android.com/tools"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+
+    <TextView
+        android:id="@+id/title_topsites"
+        android:text="@string/activity_stream_topsites"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentStart="true"
+        android:textStyle="bold"
+        android:layout_alignParentTop="true"
+        android:layout_toLeftOf="@+id/more_topsites"
+        android:layout_toStartOf="@+id/more_topsites"/>
+
+    <TextView
+        android:id="@+id/more_topsites"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentRight="true"
+        android:textAllCaps="true"
+        android:textColor="@android:color/holo_orange_dark"
+        android:textSize="14sp"
+        android:text="@string/activity_stream_more"
+        tools:text="More"
+        android:layout_alignBottom="@+id/title_topsites"/>
+
+    <android.support.v7.widget.RecyclerView
+        android:layout_width="match_parent"
+        android:layout_height="115dp"
+        android:id="@+id/android.support.v7.widget.RecyclerView"
+        android:layout_below="@+id/title_topsites"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentStart="true"/>
+
+    <TextView
+        android:id="@+id/title_highlights"
+        android:text="@string/activity_stream_highlights"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textStyle="bold"
+        android:layout_below="@+id/android.support.v7.widget.RecyclerView"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentStart="true"
+        android:layout_toLeftOf="@+id/more_highlights"
+        android:layout_toStartOf="@+id/more_highlights"/>
+
+    <TextView
+        android:id="@+id/more_highlights"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAllCaps="true"
+        android:textColor="@android:color/holo_orange_dark"
+        android:textSize="14sp"
+        android:text="@string/activity_stream_more"
+        android:layout_alignTop="@+id/title_highlights"
+        android:layout_alignLeft="@+id/more_topsites"
+        android:layout_alignStart="@+id/more_topsites"/>
+
+
+</RelativeLayout>
\ No newline at end of file
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -629,9 +629,12 @@
   <string name="helper_first_offline_bookmark_title">&helper_first_offline_bookmark_title;</string>
   <string name="helper_first_offline_bookmark_message">&helper_first_offline_bookmark_message;</string>
   <string name="helper_first_offline_bookmark_button">&helper_first_offline_bookmark_button;</string>
 
   <string name="helper_triple_readerview_open_title">&helper_triple_readerview_open_title;</string>
   <string name="helper_triple_readerview_open_message">&helper_triple_readerview_open_message;</string>
   <string name="helper_triple_readerview_open_button">&helper_triple_readerview_open_button;</string>
 
+  <string name="activity_stream_topsites">&activity_stream_topsites;</string>
+  <string name="activity_stream_highlights">&activity_stream_highlights;</string>
+  <string name="activity_stream_more">&activity_stream_more;</string>
 </resources>