Bug 1328937 - StreamItem: Move inner classes to separate files. r?grisha draft
authorSebastian Kaspari <s.kaspari@gmail.com>
Thu, 05 Jan 2017 19:29:38 +0100
changeset 457649 0fe1a8aba3ece1d3a7a9918deb1bbed0d0c9ab78
parent 456417 b548da4e16f067e5b69349376e37b2db97983cf7
child 457650 a2a052006d1e8e64b92a5057293fd58143e47d6d
push id40848
push users.kaspari@gmail.com
push dateMon, 09 Jan 2017 13:43:11 +0000
reviewersgrisha
bugs1328937
milestone53.0a1
Bug 1328937 - StreamItem: Move inner classes to separate files. r?grisha MozReview-Commit-ID: K6q39diAjms
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/java/org/mozilla/gecko/home/activitystream/stream/HighlightItem.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/stream/HighlightsTitle.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/stream/StreamItem.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/stream/TopPanel.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/stream/WelcomePanel.java
mobile/android/base/moz.build
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
+++ /dev/null
@@ -1,317 +0,0 @@
-/* -*- 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.content.SharedPreferences;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Color;
-import android.support.annotation.Nullable;
-import android.support.v4.view.ViewPager;
-import android.support.v7.widget.RecyclerView;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.activitystream.Utils;
-import org.mozilla.gecko.activitystream.ActivityStream.LabelCallback;
-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.topsites.CirclePageIndicator;
-import org.mozilla.gecko.home.activitystream.topsites.TopSitesPagerAdapter;
-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.concurrent.Future;
-
-import static org.mozilla.gecko.activitystream.ActivityStream.extractLabel;
-
-public abstract class StreamItem extends RecyclerView.ViewHolder {
-    private static final String LOGTAG = "GeckoStreamItem";
-
-    public StreamItem(View itemView) {
-        super(itemView);
-    }
-
-    public static class HighlightsTitle extends StreamItem {
-        public static final int LAYOUT_ID = R.layout.activity_stream_main_highlightstitle;
-
-        public HighlightsTitle(final View itemView) {
-            super(itemView);
-        }
-    }
-
-    public static class WelcomePanel
-            extends StreamItem
-            implements View.OnClickListener {
-        public static final int LAYOUT_ID = R.layout.activity_stream_main_welcomepanel;
-
-        public static final String PREF_WELCOME_DISMISSED = "activitystream.welcome_dismissed";
-
-        private final RecyclerView.Adapter<StreamItem> adapter;
-        private final Context context;
-
-        public WelcomePanel(final View itemView, final RecyclerView.Adapter<StreamItem> adapter) {
-            super(itemView);
-
-            this.adapter = adapter;
-            this.context = itemView.getContext();
-
-            final SharedPreferences sharedPrefs = GeckoSharedPrefs.forApp(itemView.getContext());
-
-            if (!sharedPrefs.getBoolean(PREF_WELCOME_DISMISSED, false)) {
-                final ViewStub welcomePanelStub = (ViewStub) itemView.findViewById(R.id.welcomepanel_stub);
-
-                welcomePanelStub.inflate();
-
-                final Button dismissButton = (Button) itemView.findViewById(R.id.dismiss_welcomepanel);
-
-                dismissButton.setOnClickListener(this);
-            }
-        }
-
-        @Override
-        public void onClick(View v) {
-            // To animate between item changes, RecyclerView keeps around the old version of the view,
-            // and creates a new equivalent item (which is bound using the new data) - followed by
-            // animating between those two versions. Hence we just need to make sure that
-            // any future calls to onCreateViewHolder create a version of the Header Item
-            // with the welcome panel hidden (i.e. we don't need to care about animations ourselves).
-            // We communicate this state change via the pref.
-
-            final SharedPreferences sharedPrefs = GeckoSharedPrefs.forApp(context);
-
-            sharedPrefs.edit()
-                    .putBoolean(WelcomePanel.PREF_WELCOME_DISMISSED, true)
-                    .apply();
-
-            adapter.notifyItemChanged(getAdapterPosition());
-        }
-    }
-
-    public static class TopPanel extends StreamItem {
-        public static final int LAYOUT_ID = R.layout.activity_stream_main_toppanel;
-
-        private final ViewPager topSitesPager;
-
-        public TopPanel(View itemView, HomePager.OnUrlOpenListener onUrlOpenListener, HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
-            super(itemView);
-
-            topSitesPager = (ViewPager) itemView.findViewById(R.id.topsites_pager);
-            topSitesPager.setAdapter(new TopSitesPagerAdapter(itemView.getContext(), onUrlOpenListener, onUrlOpenInBackgroundListener));
-
-            CirclePageIndicator indicator = (CirclePageIndicator) itemView.findViewById(R.id.topsites_indicator);
-            indicator.setViewPager(topSitesPager);
-        }
-
-        public void bind(Cursor cursor, int tiles, int tilesWidth, int tilesHeight) {
-            final TopSitesPagerAdapter adapter = (TopSitesPagerAdapter) topSitesPager.getAdapter();
-            adapter.setTilesSize(tiles, tilesWidth, tilesHeight);
-            adapter.swapCursor(cursor);
-
-            final Resources resources = itemView.getResources();
-            final int tilesMargin = resources.getDimensionPixelSize(R.dimen.activity_stream_base_margin);
-            final int textHeight = resources.getDimensionPixelSize(R.dimen.activity_stream_top_sites_text_height);
-
-            ViewGroup.LayoutParams layoutParams = topSitesPager.getLayoutParams();
-            layoutParams.height = tilesHeight + tilesMargin + textHeight;
-            topSitesPager.setLayoutParams(layoutParams);
-        }
-    }
-
-    public static class HighlightItem extends StreamItem implements IconCallback {
-        public static final int LAYOUT_ID = R.layout.activity_stream_card_history_item;
-
-        String title;
-        String url;
-        JSONObject metadata;
-
-        @Nullable Boolean isPinned;
-        @Nullable Boolean isBookmarked;
-
-        Utils.HighlightSource source;
-
-        final FaviconView vIconView;
-        final TextView vLabel;
-        final TextView vTimeSince;
-        final TextView vSourceView;
-        final TextView vPageView;
-        final ImageView vSourceIconView;
-
-        private Future<IconResponse> ongoingIconLoad;
-        private int tilesMargin;
-
-        public HighlightItem(final View itemView,
-                             final HomePager.OnUrlOpenListener onUrlOpenListener,
-                             final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
-            super(itemView);
-
-            tilesMargin = itemView.getResources().getDimensionPixelSize(R.dimen.activity_stream_base_margin);
-
-            vLabel = (TextView) itemView.findViewById(R.id.card_history_label);
-            vTimeSince = (TextView) itemView.findViewById(R.id.card_history_time_since);
-            vIconView = (FaviconView) itemView.findViewById(R.id.icon);
-            vSourceView = (TextView) itemView.findViewById(R.id.card_history_source);
-            vPageView = (TextView) itemView.findViewById(R.id.page);
-            vSourceIconView = (ImageView) itemView.findViewById(R.id.source_icon);
-
-            final ImageView menuButton = (ImageView) itemView.findViewById(R.id.menu);
-
-            menuButton.setImageDrawable(
-                    DrawableUtil.tintDrawable(menuButton.getContext(), R.drawable.menu, Color.LTGRAY));
-
-            TouchTargetUtil.ensureTargetHitArea(menuButton, itemView);
-
-            menuButton.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    ActivityStreamTelemetry.Extras.Builder extras = ActivityStreamTelemetry.Extras.builder()
-                        .set(ActivityStreamTelemetry.Contract.SOURCE_TYPE, ActivityStreamTelemetry.Contract.TYPE_HIGHLIGHTS)
-                        .forHighlightSource(source);
-
-                    ActivityStreamContextMenu.show(v.getContext(),
-                            menuButton,
-                            extras,
-                            ActivityStreamContextMenu.MenuMode.HIGHLIGHT,
-                            title, url, isBookmarked, isPinned,
-                            onUrlOpenListener, onUrlOpenInBackgroundListener,
-                            vIconView.getWidth(), vIconView.getHeight());
-
-                    Telemetry.sendUIEvent(
-                            TelemetryContract.Event.SHOW,
-                            TelemetryContract.Method.CONTEXT_MENU,
-                            extras.build()
-                    );
-                }
-            });
-
-            ViewUtil.enableTouchRipple(menuButton);
-        }
-
-        public void bind(Cursor cursor, int tilesWidth, int tilesHeight) {
-            final long time = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Highlights.DATE));
-            final String ago = DateUtils.getRelativeTimeSpanString(time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
-
-            title = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE));
-            url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
-            source = Utils.highlightSource(cursor);
-
-            try {
-                final String rawMetadata = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Highlights.METADATA));
-                if (rawMetadata != null) {
-                    metadata = new JSONObject(rawMetadata);
-                }
-            } catch (JSONException e) {
-                Log.w(LOGTAG, "JSONException while parsing page metadata", e);
-            }
-
-            vLabel.setText(title);
-            vTimeSince.setText(ago);
-
-            ViewGroup.LayoutParams layoutParams = vIconView.getLayoutParams();
-            layoutParams.width = tilesWidth - tilesMargin;
-            layoutParams.height = tilesHeight;
-            vIconView.setLayoutParams(layoutParams);
-
-            updateStateForSource(source);
-            updateUiForSource(source);
-            updatePage(metadata, url);
-
-            if (ongoingIconLoad != null) {
-                ongoingIconLoad.cancel(true);
-            }
-
-            ongoingIconLoad = Icons.with(itemView.getContext())
-                    .pageUrl(url)
-                    .skipNetwork()
-                    .build()
-                    .execute(this);
-        }
-
-        private void updateStateForSource(Utils.HighlightSource source) {
-            // We can only be certain of bookmark state if an item is a bookmark item.
-            // Otherwise, due to the underlying highlights query, we have to look up states when
-            // menus are displayed.
-            switch (source) {
-                case BOOKMARKED:
-                    isBookmarked = true;
-                    isPinned = null;
-                    break;
-                case VISITED:
-                    isBookmarked = null;
-                    isPinned = null;
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unknown source: " + source);
-            }
-        }
-
-        private void updateUiForSource(Utils.HighlightSource source) {
-            switch (source) {
-                case BOOKMARKED:
-                    vSourceView.setText(R.string.activity_stream_highlight_label_bookmarked);
-                    vSourceView.setVisibility(View.VISIBLE);
-                    vSourceIconView.setImageResource(R.drawable.ic_as_bookmarked);
-                    break;
-                case VISITED:
-                    vSourceView.setText(R.string.activity_stream_highlight_label_visited);
-                    vSourceView.setVisibility(View.VISIBLE);
-                    vSourceIconView.setImageResource(R.drawable.ic_as_visited);
-                    break;
-                default:
-                    vSourceView.setVisibility(View.INVISIBLE);
-                    vSourceIconView.setImageResource(0);
-                    break;
-            }
-        }
-
-        private void updatePage(final JSONObject metadata, final String url) {
-            // First try to set the provider name from the page's metadata.
-
-            try {
-                if (metadata != null && metadata.has("provider")) {
-                    vPageView.setText(metadata.getString("provider"));
-                    return;
-                }
-            } catch (JSONException e) {
-                // Broken JSON? Continue with fallback.
-            }
-
-            // If there's no provider name available then let's try to extract one from the URL.
-
-            extractLabel(itemView.getContext(), url, false, new LabelCallback() {
-                @Override
-                public void onLabelExtracted(String label) {
-                    vPageView.setText(TextUtils.isEmpty(label) ? url : label);
-                }
-            });
-        }
-
-        @Override
-        public void onIconResponse(IconResponse response) {
-            vIconView.updateImage(response);
-        }
-    }
-}
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
@@ -12,20 +12,21 @@ import android.view.View;
 import android.view.ViewGroup;
 
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.activitystream.ActivityStreamTelemetry;
 import org.mozilla.gecko.activitystream.Utils;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.home.activitystream.StreamItem.HighlightItem;
-import org.mozilla.gecko.home.activitystream.StreamItem.WelcomePanel;
-import org.mozilla.gecko.home.activitystream.StreamItem.HighlightsTitle;
-import org.mozilla.gecko.home.activitystream.StreamItem.TopPanel;
+import org.mozilla.gecko.home.activitystream.stream.HighlightItem;
+import org.mozilla.gecko.home.activitystream.stream.HighlightsTitle;
+import org.mozilla.gecko.home.activitystream.stream.StreamItem;
+import org.mozilla.gecko.home.activitystream.stream.TopPanel;
+import org.mozilla.gecko.home.activitystream.stream.WelcomePanel;
 import org.mozilla.gecko.widget.RecyclerViewClickSupport;
 
 import java.util.EnumSet;
 
 public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamItem> implements RecyclerViewClickSupport.OnItemClickListener {
     private Cursor highlightsCursor;
     private Cursor topSitesCursor;
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/stream/HighlightItem.java
@@ -0,0 +1,217 @@
+/* -*- 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.stream;
+
+import android.database.Cursor;
+import android.graphics.Color;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+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.activitystream.Utils;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.home.HomePager;
+import org.mozilla.gecko.home.activitystream.menu.ActivityStreamContextMenu;
+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.TouchTargetUtil;
+import org.mozilla.gecko.util.ViewUtil;
+import org.mozilla.gecko.widget.FaviconView;
+
+import java.util.concurrent.Future;
+
+import static org.mozilla.gecko.activitystream.ActivityStream.extractLabel;
+
+public class HighlightItem extends StreamItem implements IconCallback {
+    private static final String LOGTAG = "GeckoHighlightItem";
+
+    public static final int LAYOUT_ID = R.layout.activity_stream_card_history_item;
+
+    String title;
+    String url;
+    JSONObject metadata;
+
+    @Nullable
+    Boolean isPinned;
+    @Nullable Boolean isBookmarked;
+
+    Utils.HighlightSource source;
+
+    final FaviconView vIconView;
+    final TextView vLabel;
+    final TextView vTimeSince;
+    final TextView vSourceView;
+    final TextView vPageView;
+    final ImageView vSourceIconView;
+
+    private Future<IconResponse> ongoingIconLoad;
+    private int tilesMargin;
+
+    public HighlightItem(final View itemView,
+                         final HomePager.OnUrlOpenListener onUrlOpenListener,
+                         final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
+        super(itemView);
+
+        tilesMargin = itemView.getResources().getDimensionPixelSize(R.dimen.activity_stream_base_margin);
+
+        vLabel = (TextView) itemView.findViewById(R.id.card_history_label);
+        vTimeSince = (TextView) itemView.findViewById(R.id.card_history_time_since);
+        vIconView = (FaviconView) itemView.findViewById(R.id.icon);
+        vSourceView = (TextView) itemView.findViewById(R.id.card_history_source);
+        vPageView = (TextView) itemView.findViewById(R.id.page);
+        vSourceIconView = (ImageView) itemView.findViewById(R.id.source_icon);
+
+        final ImageView menuButton = (ImageView) itemView.findViewById(R.id.menu);
+
+        menuButton.setImageDrawable(
+                DrawableUtil.tintDrawable(menuButton.getContext(), R.drawable.menu, Color.LTGRAY));
+
+        TouchTargetUtil.ensureTargetHitArea(menuButton, itemView);
+
+        menuButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                ActivityStreamTelemetry.Extras.Builder extras = ActivityStreamTelemetry.Extras.builder()
+                        .set(ActivityStreamTelemetry.Contract.SOURCE_TYPE, ActivityStreamTelemetry.Contract.TYPE_HIGHLIGHTS)
+                        .forHighlightSource(source);
+
+                ActivityStreamContextMenu.show(v.getContext(),
+                        menuButton,
+                        extras,
+                        ActivityStreamContextMenu.MenuMode.HIGHLIGHT,
+                        title, url, isBookmarked, isPinned,
+                        onUrlOpenListener, onUrlOpenInBackgroundListener,
+                        vIconView.getWidth(), vIconView.getHeight());
+
+                Telemetry.sendUIEvent(
+                        TelemetryContract.Event.SHOW,
+                        TelemetryContract.Method.CONTEXT_MENU,
+                        extras.build()
+                );
+            }
+        });
+
+        ViewUtil.enableTouchRipple(menuButton);
+    }
+
+    public void bind(Cursor cursor, int tilesWidth, int tilesHeight) {
+        final long time = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Highlights.DATE));
+        final String ago = DateUtils.getRelativeTimeSpanString(time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
+
+        title = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE));
+        url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
+        source = Utils.highlightSource(cursor);
+
+        try {
+            final String rawMetadata = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Highlights.METADATA));
+            if (rawMetadata != null) {
+                metadata = new JSONObject(rawMetadata);
+            }
+        } catch (JSONException e) {
+            Log.w(LOGTAG, "JSONException while parsing page metadata", e);
+        }
+
+        vLabel.setText(title);
+        vTimeSince.setText(ago);
+
+        ViewGroup.LayoutParams layoutParams = vIconView.getLayoutParams();
+        layoutParams.width = tilesWidth - tilesMargin;
+        layoutParams.height = tilesHeight;
+        vIconView.setLayoutParams(layoutParams);
+
+        updateStateForSource(source);
+        updateUiForSource(source);
+        updatePage(metadata, url);
+
+        if (ongoingIconLoad != null) {
+            ongoingIconLoad.cancel(true);
+        }
+
+        ongoingIconLoad = Icons.with(itemView.getContext())
+                .pageUrl(url)
+                .skipNetwork()
+                .build()
+                .execute(this);
+    }
+
+    private void updateStateForSource(Utils.HighlightSource source) {
+        // We can only be certain of bookmark state if an item is a bookmark item.
+        // Otherwise, due to the underlying highlights query, we have to look up states when
+        // menus are displayed.
+        switch (source) {
+            case BOOKMARKED:
+                isBookmarked = true;
+                isPinned = null;
+                break;
+            case VISITED:
+                isBookmarked = null;
+                isPinned = null;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown source: " + source);
+        }
+    }
+
+    private void updateUiForSource(Utils.HighlightSource source) {
+        switch (source) {
+            case BOOKMARKED:
+                vSourceView.setText(R.string.activity_stream_highlight_label_bookmarked);
+                vSourceView.setVisibility(View.VISIBLE);
+                vSourceIconView.setImageResource(R.drawable.ic_as_bookmarked);
+                break;
+            case VISITED:
+                vSourceView.setText(R.string.activity_stream_highlight_label_visited);
+                vSourceView.setVisibility(View.VISIBLE);
+                vSourceIconView.setImageResource(R.drawable.ic_as_visited);
+                break;
+            default:
+                vSourceView.setVisibility(View.INVISIBLE);
+                vSourceIconView.setImageResource(0);
+                break;
+        }
+    }
+
+    private void updatePage(final JSONObject metadata, final String url) {
+        // First try to set the provider name from the page's metadata.
+
+        try {
+            if (metadata != null && metadata.has("provider")) {
+                vPageView.setText(metadata.getString("provider"));
+                return;
+            }
+        } catch (JSONException e) {
+            // Broken JSON? Continue with fallback.
+        }
+
+        // If there's no provider name available then let's try to extract one from the URL.
+
+        extractLabel(itemView.getContext(), url, false, new ActivityStream.LabelCallback() {
+            @Override
+            public void onLabelExtracted(String label) {
+                vPageView.setText(TextUtils.isEmpty(label) ? url : label);
+            }
+        });
+    }
+
+    @Override
+    public void onIconResponse(IconResponse response) {
+        vIconView.updateImage(response);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/stream/HighlightsTitle.java
@@ -0,0 +1,19 @@
+/* -*- 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.stream;
+
+import android.view.View;
+
+import org.mozilla.gecko.R;
+
+public class HighlightsTitle extends StreamItem {
+    public static final int LAYOUT_ID = R.layout.activity_stream_main_highlightstitle;
+
+    public HighlightsTitle(final View itemView) {
+        super(itemView);
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/stream/StreamItem.java
@@ -0,0 +1,15 @@
+/* -*- 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.stream;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+public abstract class StreamItem extends RecyclerView.ViewHolder {
+    public StreamItem(View itemView) {
+        super(itemView);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/stream/TopPanel.java
@@ -0,0 +1,47 @@
+/* -*- 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.stream;
+
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.support.v4.view.ViewPager;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.home.HomePager;
+import org.mozilla.gecko.home.activitystream.topsites.CirclePageIndicator;
+import org.mozilla.gecko.home.activitystream.topsites.TopSitesPagerAdapter;
+
+public class TopPanel extends StreamItem {
+    public static final int LAYOUT_ID = R.layout.activity_stream_main_toppanel;
+
+    private final ViewPager topSitesPager;
+
+    public TopPanel(View itemView, HomePager.OnUrlOpenListener onUrlOpenListener, HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
+        super(itemView);
+
+        topSitesPager = (ViewPager) itemView.findViewById(R.id.topsites_pager);
+        topSitesPager.setAdapter(new TopSitesPagerAdapter(itemView.getContext(), onUrlOpenListener, onUrlOpenInBackgroundListener));
+
+        CirclePageIndicator indicator = (CirclePageIndicator) itemView.findViewById(R.id.topsites_indicator);
+        indicator.setViewPager(topSitesPager);
+    }
+
+    public void bind(Cursor cursor, int tiles, int tilesWidth, int tilesHeight) {
+        final TopSitesPagerAdapter adapter = (TopSitesPagerAdapter) topSitesPager.getAdapter();
+        adapter.setTilesSize(tiles, tilesWidth, tilesHeight);
+        adapter.swapCursor(cursor);
+
+        final Resources resources = itemView.getResources();
+        final int tilesMargin = resources.getDimensionPixelSize(R.dimen.activity_stream_base_margin);
+        final int textHeight = resources.getDimensionPixelSize(R.dimen.activity_stream_top_sites_text_height);
+
+        ViewGroup.LayoutParams layoutParams = topSitesPager.getLayoutParams();
+        layoutParams.height = tilesHeight + tilesMargin + textHeight;
+        topSitesPager.setLayoutParams(layoutParams);
+    }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/stream/WelcomePanel.java
@@ -0,0 +1,62 @@
+/* -*- 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.stream;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.Button;
+
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.R;
+
+public class WelcomePanel extends StreamItem implements View.OnClickListener {
+    public static final int LAYOUT_ID = R.layout.activity_stream_main_welcomepanel;
+
+    public static final String PREF_WELCOME_DISMISSED = "activitystream.welcome_dismissed";
+
+    private final RecyclerView.Adapter<StreamItem> adapter;
+    private final Context context;
+
+    public WelcomePanel(final View itemView, final RecyclerView.Adapter<StreamItem> adapter) {
+        super(itemView);
+
+        this.adapter = adapter;
+        this.context = itemView.getContext();
+
+        final SharedPreferences sharedPrefs = GeckoSharedPrefs.forApp(itemView.getContext());
+
+        if (!sharedPrefs.getBoolean(PREF_WELCOME_DISMISSED, false)) {
+            final ViewStub welcomePanelStub = (ViewStub) itemView.findViewById(R.id.welcomepanel_stub);
+
+            welcomePanelStub.inflate();
+
+            final Button dismissButton = (Button) itemView.findViewById(R.id.dismiss_welcomepanel);
+
+            dismissButton.setOnClickListener(this);
+        }
+    }
+
+    @Override
+    public void onClick(View v) {
+        // To animate between item changes, RecyclerView keeps around the old version of the view,
+        // and creates a new equivalent item (which is bound using the new data) - followed by
+        // animating between those two versions. Hence we just need to make sure that
+        // any future calls to onCreateViewHolder create a version of the Header Item
+        // with the welcome panel hidden (i.e. we don't need to care about animations ourselves).
+        // We communicate this state change via the pref.
+
+        final SharedPreferences sharedPrefs = GeckoSharedPrefs.forApp(context);
+
+        sharedPrefs.edit()
+                .putBoolean(WelcomePanel.PREF_WELCOME_DISMISSED, true)
+                .apply();
+
+        adapter.notifyItemChanged(getAdapterPosition());
+    }
+}
\ No newline at end of file
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -465,17 +465,21 @@ gbjar.sources += ['java/org/mozilla/geck
     'health/SessionInformation.java',
     'health/StubbedHealthRecorder.java',
     'home/activitystream/ActivityStream.java',
     'home/activitystream/ActivityStreamHomeFragment.java',
     'home/activitystream/ActivityStreamHomeScreen.java',
     'home/activitystream/menu/ActivityStreamContextMenu.java',
     'home/activitystream/menu/BottomSheetContextMenu.java',
     'home/activitystream/menu/PopupContextMenu.java',
-    'home/activitystream/StreamItem.java',
+    'home/activitystream/stream/HighlightItem.java',
+    'home/activitystream/stream/HighlightsTitle.java',
+    'home/activitystream/stream/StreamItem.java',
+    'home/activitystream/stream/TopPanel.java',
+    'home/activitystream/stream/WelcomePanel.java',
     'home/activitystream/StreamRecyclerAdapter.java',
     'home/activitystream/topsites/CirclePageIndicator.java',
     'home/activitystream/topsites/TopSitesCard.java',
     'home/activitystream/topsites/TopSitesPage.java',
     'home/activitystream/topsites/TopSitesPageAdapter.java',
     'home/activitystream/topsites/TopSitesPagerAdapter.java',
     'home/BookmarkFolderView.java',
     'home/BookmarkScreenshotRow.java',