Bug 1288106 - Move history (highlights) into main recyclerview, and eliminated horizontal highlights r?jonalmeida draft
authorAndrzej Hunt <ahunt@mozilla.com>
Tue, 16 Aug 2016 13:38:19 -0700
changeset 401731 57b9bae04eeb1b09a54c57fd08f341c9c4dadc89
parent 401730 4c5a1b92038c27db0dc5c968fff723e7788818fe
child 528596 6fd034bec63c7204059f4a782bba36b973478aee
push id26575
push userahunt@mozilla.com
push dateWed, 17 Aug 2016 17:11:16 +0000
reviewersjonalmeida
bugs1288106
milestone51.0a1
Bug 1288106 - Move history (highlights) into main recyclerview, and eliminated horizontal highlights r?jonalmeida 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,31 +1,37 @@
 /* -*- 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 {
 
+    StreamRecyclerAdapter adapter;
+
     public ActivityStream(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         inflate(context, R.layout.as_content, this);
     }
 
     @Override
     public boolean isVisible() {
@@ -60,18 +66,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,91 @@
+/* -*- 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 abstract void bind(Cursor cursor);
+
+    public static class TopPanel extends StreamItem {
+
+        public TopPanel(View itemView) {
+            super(itemView);
+        }
+
+        @Override
+        public void bind(Cursor cursor) {
+            throw new IllegalStateException("Cannot bind TopPanel");
+        }
+    }
+
+    public static class BottomPanel extends StreamItem {
+
+        public BottomPanel(View itemView) {
+            super(itemView);
+        }
+
+        @Override
+        public void bind(Cursor cursor) {
+            throw new IllegalStateException("Cannot bind TopPanel");
+        }
+    }
+
+    public static class CompactItem extends StreamItem {
+        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 {
+        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,107 @@
+/* -*- 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;
-
-
-class MainRecyclerAdapter extends RecyclerView.Adapter<MainRecyclerAdapter.ViewHolder> {
+public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamItem> {
 
-    private final Context context;
+    private enum ViewType {
+        TopPanel,
+        BottomPanel,
+        CompactItem,
+        HighlightItem;
 
-    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;
+        public static ViewType fromInt(int viewType) {
+            return ViewType.values()[viewType];
+        }
+    }
+
+    private Cursor highlightsCursor;
 
-    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 ViewType getViewType(int position) {
+        if (position == 0) {
+            return ViewType.TopPanel;
+        } else if (position == getItemCount() - 1) {
+            return ViewType.BottomPanel;
+        } 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 ViewType.HighlightItem;
+            }
+            return ViewType.CompactItem;
+        }
+    }
 
-    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) {
+        return getViewType(position).ordinal();
     }
 
     @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, int type) {
+        final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+
+        final ViewType viewType = ViewType.fromInt(type);
+        switch (viewType) {
+            case TopPanel:
+                return new StreamItem.TopPanel(inflater.inflate(R.layout.activity_stream_main_toppanel, parent, false));
+            case BottomPanel:
+                return new StreamItem.BottomPanel(inflater.inflate(R.layout.activity_stream_main_bottompanel, parent, false));
+            case CompactItem:
+                return new StreamItem.CompactItem(inflater.inflate(R.layout.activity_stream_card_history_item, parent, false));
+            case HighlightItem:
+                return new StreamItem.HighlightItem(inflater.inflate(R.layout.activity_stream_card_highlights_item, parent, false));
+
+            default:
+                throw new IllegalStateException("Missing inflation for ViewType " + viewType.name());
+        }
+    }
+
+    private int translatePositionToCursor(int 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) {
+        switch (getViewType(position)) {
+            case TopPanel:
+            case BottomPanel:
+                // No need to bind - these panels handle themselves, and we don't pass data to them.
+                break;
+            default:
+                final int cursorPosition = translatePositionToCursor(position);
 
-        holder.vCategoryRv.setLayoutManager(llm);
+                highlightsCursor.moveToPosition(cursorPosition);
+                holder.bind(highlightsCursor);
 
-        if (position != VIEW_TYPE_HISTORY) {
-            holder.vTitle.setText(categories.get(position).getLabel());
-            holder.vMore.setVisibility(View.VISIBLE);
+                break;
         }
     }
 
     @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,11 @@ 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">
+<!ENTITY activity_stream_more "More">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -434,20 +434,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>