Bug 1116415 - 6. Make the tabs list grid view a RecyclerView. r?sebastian draft
authorTom Klein <twointofive@gmail.com>
Mon, 12 Sep 2016 11:21:51 -0500
changeset 424672 1c6a92e7614c2b0981db9ccfbc1d673656c88daf
parent 424671 e7fce024e563867f791d174d4b4fcd878acc8cbf
child 424673 8873a9ba7282bf454fd6cff4bb652ea0319be09b
push id32215
push userbmo:twointofive@gmail.com
push dateThu, 13 Oct 2016 05:22:21 +0000
reviewerssebastian
bugs1116415
milestone52.0a1
Bug 1116415 - 6. Make the tabs list grid view a RecyclerView. r?sebastian Our previous GridLayout settings gave extra horizontal space to the padding between items, but GridLayoutManager by default simply left aligns fixed width items in their column, so the item's width has been changed to fill_parent and the item title has been switched to fixed width (since otherwise it looks broken when it expands to an item width larger than the thumbnail width). The drawback is that clicking on the extra width part of an item activates the tab, even though it would seem from what's being displayed that the item should end at the vertical edge of the thumbnail - that will be fixed in a future commit. Both the list and grid tabs panel views are now RecyclerViews, so move TabsLayoutRecyclerAdapter.java to TabsLayoutAdapter.java. MozReview-Commit-ID: CBrxw1HfRcP
mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java
mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutAdapter.java
mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutRecyclerAdapter.java
mobile/android/base/moz.build
mobile/android/base/resources/layout/tabs_layout_item_view.xml
mobile/android/base/resources/values-land/dimens.xml
mobile/android/base/resources/values-sw240dp/dimens.xml
mobile/android/base/resources/values-sw360dp/dimens.xml
mobile/android/base/resources/values-sw400dp/dimens.xml
mobile/android/base/resources/values-v11/themes.xml
mobile/android/base/resources/values-v21/themes.xml
mobile/android/base/resources/values-xlarge-land-v11/dimens.xml
mobile/android/base/resources/values-xlarge-v11/dimens.xml
mobile/android/base/resources/values/attrs.xml
mobile/android/base/resources/values/dimens.xml
mobile/android/base/resources/values/styles.xml
mobile/android/base/resources/values/themes.xml
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseTest.java
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java
@@ -1,711 +1,64 @@
 /* -*- 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.tabs;
 
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.tabs.TabsPanel.TabsLayout;
-import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
+import org.mozilla.gecko.widget.SpacingDecoration;
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.Build;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.GridLayoutManager;
 import android.util.AttributeSet;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.AbsListView;
-import android.widget.AdapterView;
-import android.widget.Button;
-import android.widget.GridView;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A tabs layout implementation for the tablet redesign (bug 1014156) and later ported to mobile (bug 1193745).
- */
-
-class TabsGridLayout extends GridView
-                     implements TabsLayout,
-                                Tabs.OnTabsChangedListener {
-
-    private static final String LOGTAG = "Gecko" + TabsGridLayout.class.getSimpleName();
 
-    public static final int ANIM_DELAY_MULTIPLE_MS = 20;
-    private static final int ANIM_TIME_MS = 200;
-    private static final DecelerateInterpolator ANIM_INTERPOLATOR = new DecelerateInterpolator();
-
-    private final SparseArray<PointF> tabLocations = new SparseArray<PointF>();
-    private final boolean isPrivate;
-    private final TabsLayoutAdapter tabsAdapter;
-    private final int columnWidth;
-    private TabsPanel tabsPanel;
-    private int lastSelectedTabId;
-
-    public TabsGridLayout(final Context context, final AttributeSet attrs) {
-        super(context, attrs, R.attr.tabGridLayoutViewStyle);
-
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
-        isPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
-        a.recycle();
+public class TabsGridLayout extends TabsLayout {
 
-        tabsAdapter = new TabsGridLayoutAdapter(context);
-        setAdapter(tabsAdapter);
+    public TabsGridLayout(Context context, AttributeSet attrs) {
+        super(context, attrs, R.layout.tabs_layout_item_view);
 
-        setRecyclerListener(new RecyclerListener() {
-            @Override
-            public void onMovedToScrapHeap(View view) {
-                TabsLayoutItemView item = (TabsLayoutItemView) view;
-                item.setThumbnail(null);
-            }
-        });
-
-        // The clipToPadding setting in the styles.xml doesn't seem to be working (bug 1101784)
-        // so lets set it manually in code for the moment as it's needed for the padding animation
-        setClipToPadding(false);
-
-        setVerticalFadingEdgeEnabled(false);
-
-        final Resources resources = getResources();
-        columnWidth = resources.getDimensionPixelSize(R.dimen.tab_panel_column_width);
+        final Resources resources = context.getResources();
 
-        final int padding = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_padding);
-        final int paddingTop = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_padding_top);
-
-        // Lets set double the top padding on the bottom so that the last row shows up properly!
-        // Your demise, GridView, cannot come fast enough.
-        final int paddingBottom = paddingTop * 2;
-
-        setPadding(padding, paddingTop, padding, paddingBottom);
-
-        setOnItemClickListener(new OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                final TabsLayoutItemView tab = (TabsLayoutItemView) view;
-                final int tabId = tab.getTabId();
-                Tabs.getInstance().selectTab(tabId);
-                autoHidePanel();
-                Tabs.getInstance().notifyListeners(
-                        Tabs.getInstance().getTab(tabId), Tabs.TabEvents.OPENED_FROM_TABS_TRAY
-                );
-            }
-        });
-
-        TabSwipeGestureListener mSwipeListener = new TabSwipeGestureListener();
-        setOnTouchListener(mSwipeListener);
-        setOnScrollListener(mSwipeListener.makeScrollListener());
-    }
-
-    private void populateTabLocations(final Tab removedTab) {
-        tabLocations.clear();
-
-        final int firstPosition = getFirstVisiblePosition();
-        final int lastPosition = getLastVisiblePosition();
-        final int numberOfColumns = getNumColumns();
-        final int childCount = getChildCount();
-        final int removedPosition = tabsAdapter.getPositionForTab(removedTab);
+        setLayoutManager(new GridLayoutManager(context, 1));
+        setAutoFit(true);
 
-        for (int x = 1, i = (removedPosition - firstPosition) + 1; i < childCount; i++, x++) {
-            final View child = getChildAt(i);
-            if (child != null) {
-                // Reset the transformations here in case the user is swiping tabs away fast and they swipe a tab
-                // before the last animation has finished (bug 1179195).
-                resetTransforms(child);
-
-                tabLocations.append(x, new PointF(child.getX(), child.getY()));
-            }
-        }
-
-        final boolean firstChildOffScreen = ((firstPosition > 0) || getChildAt(0).getY() < 0);
-        final boolean lastChildVisible = (lastPosition - childCount == firstPosition - 1);
-        final boolean oneItemOnLastRow = (lastPosition % numberOfColumns == 0);
-        if (firstChildOffScreen && lastChildVisible && oneItemOnLastRow) {
-            // We need to set the view's bottom padding to prevent a sudden jump as the
-            // last item in the row is being removed. We then need to remove the padding
-            // via a sweet animation
+        final int itemWidth = resources.getDimensionPixelSize(R.dimen.tab_panel_item_width);
+        final int horizontalItemPadding = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_item_hpadding);
+        final int verticalItemPadding = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_item_vpadding);
+        final int viewPaddingHorizontal = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_hpadding);
+        final int viewPaddingVertical = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_vpadding);
 
-            final int removedHeight = getChildAt(0).getMeasuredHeight();
-            final int verticalSpacing =
-                    getResources().getDimensionPixelOffset(R.dimen.tab_panel_grid_vspacing);
-
-            ValueAnimator paddingAnimator = ValueAnimator.ofInt(getPaddingBottom() + removedHeight + verticalSpacing, getPaddingBottom());
-            paddingAnimator.setDuration(ANIM_TIME_MS * 2);
-
-            paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (Integer) animation.getAnimatedValue());
-                }
-            });
-            paddingAnimator.start();
-        }
-    }
-
-    @Override
-    public void setTabsPanel(TabsPanel panel) {
-        tabsPanel = panel;
-    }
+        setDesiredColumnWidth(itemWidth + 2 * horizontalItemPadding);
 
-    @Override
-    public void show() {
-        setVisibility(View.VISIBLE);
-        Tabs.getInstance().refreshThumbnails();
-        Tabs.registerOnTabsChangedListener(this);
-        refreshTabsData();
-
-        final Tab currentlySelectedTab = Tabs.getInstance().getSelectedTab();
-        final int position =  currentlySelectedTab != null ? tabsAdapter.getPositionForTab(currentlySelectedTab) : -1;
-        if (position != -1) {
-            final boolean selectionChanged = lastSelectedTabId != currentlySelectedTab.getId();
-            final boolean positionIsVisible = position >= getFirstVisiblePosition() && position <= getLastVisiblePosition();
-
-            if (selectionChanged || !positionIsVisible) {
-                smoothScrollToPosition(position);
-            }
-        }
-    }
-
-    @Override
-    public void hide() {
-        lastSelectedTabId = Tabs.getInstance().getSelectedTab().getId();
-        setVisibility(View.GONE);
-        Tabs.unregisterOnTabsChangedListener(this);
-        GeckoAppShell.notifyObservers("Tab:Screenshot:Cancel", "");
-        tabsAdapter.clear();
-    }
-
-    @Override
-    public boolean shouldExpand() {
-        return true;
-    }
-
-    private void autoHidePanel() {
-        tabsPanel.autoHidePanel();
-    }
-
-    @Override
-    public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
-        switch (msg) {
-            case ADDED:
-                // Refresh only if panel is shown. show() will call refreshTabsData() later again.
-                if (tabsPanel.isShown()) {
-                    // Refresh the list to make sure the new tab is added in the right position.
-                    refreshTabsData();
-                }
-                break;
-
-            case CLOSED:
-
-                // This is limited to >= ICS as animations on GB devices are generally pants
-                if (Build.VERSION.SDK_INT >= 11 && tabsAdapter.getCount() > 0) {
-                    animateRemoveTab(tab);
-                }
-
-                final Tabs tabsInstance = Tabs.getInstance();
+        // TODO We'll need to be more specific about how much space the ItemDecoration should fill
+        // in - otherwise we're left with empty item space that can be clicked even though it
+        // appears to the user that there should be no content there.
+        // (The issue is that GridLayoutManager left aligns its content in a given column, so we
+        // need to make content full width, and then (until we support setting thumbnail widths on
+        // the fly in the tabs layout) to keep the portion of the content outside the fixed-size
+        // thumbnail/title region unclickable, we need to fill in that region with ItemDecoration,
+        // which is what remains to be done.)
+        addItemDecoration(new SpacingDecoration(horizontalItemPadding, verticalItemPadding));
 
-                if (tabsAdapter.removeTab(tab)) {
-                    if (tab.isPrivate() == isPrivate && tabsAdapter.getCount() > 0) {
-                        int selected = tabsAdapter.getPositionForTab(tabsInstance.getSelectedTab());
-                        updateSelectedStyle(selected);
-                    }
-                    if (!tab.isPrivate()) {
-                        // Make sure we always have at least one normal tab
-                        final Iterable<Tab> tabs = tabsInstance.getTabsInOrder();
-                        boolean removedTabIsLastNormalTab = true;
-                        for (Tab singleTab : tabs) {
-                            if (!singleTab.isPrivate()) {
-                                removedTabIsLastNormalTab = false;
-                                break;
-                            }
-                        }
-                        if (removedTabIsLastNormalTab) {
-                            tabsInstance.addTab();
-                        }
-                    }
-                }
-                break;
-
-            case SELECTED:
-                // Update the selected position, then fall through...
-                updateSelectedPosition();
-            case UNSELECTED:
-                // We just need to update the style for the unselected tab...
-            case THUMBNAIL:
-            case TITLE:
-            case RECORDING_CHANGE:
-            case AUDIO_PLAYING_CHANGE:
-                View view = getChildAt(tabsAdapter.getPositionForTab(tab) - getFirstVisiblePosition());
-                if (view == null)
-                    return;
-
-                ((TabsLayoutItemView) view).assignValues(tab);
-                break;
-        }
-    }
-
-    // Updates the selected position in the list so that it will be scrolled to the right place.
-    private void updateSelectedPosition() {
-        int selected = tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
-        updateSelectedStyle(selected);
+        setPadding(viewPaddingHorizontal, viewPaddingVertical, viewPaddingHorizontal, viewPaddingVertical);
+        setClipToPadding(false);
+        setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY);
 
-        if (selected != -1) {
-            setSelection(selected);
-        }
-    }
-
-    /**
-     * Updates the selected/unselected style for the tabs.
-     *
-     * @param selected position of the selected tab
-     */
-    private void updateSelectedStyle(final int selected) {
-        post(new Runnable() {
-            @Override
-            public void run() {
-                final int displayCount = tabsAdapter.getCount();
-
-                for (int i = 0; i < displayCount; i++) {
-                    final Tab tab = tabsAdapter.getItem(i);
-                    final boolean checked = displayCount == 1 || i == selected;
-                    final View tabView = getViewForTab(tab);
-                    if (tabView != null) {
-                        ((TabsLayoutItemView) tabView).setChecked(checked);
-                    }
-                    // setItemChecked doesn't exist until API 11, despite what the API docs say!
-                    setItemChecked(i, checked);
-                }
-            }
-        });
-    }
-
-    private void refreshTabsData() {
-        // Store a different copy of the tabs, so that we don't have to worry about
-        // accidentally updating it on the wrong thread.
-        ArrayList<Tab> tabData = new ArrayList<>();
-
-        Iterable<Tab> allTabs = Tabs.getInstance().getTabsInOrder();
-        for (Tab tab : allTabs) {
-            if (tab.isPrivate() == isPrivate)
-                tabData.add(tab);
-        }
-
-        tabsAdapter.setTabs(tabData);
-        updateSelectedPosition();
-    }
-
-    private void resetTransforms(View view) {
-        view.setAlpha(1);
-        view.setTranslationX(0);
-        view.setTranslationY(0);
-
-        ((TabsLayoutItemView) view).setCloseVisible(true);
+        final DefaultItemAnimator animator = new DefaultItemAnimator();
+        // On close we only animate the moves, not the remove.
+        animator.setRemoveDuration(0);
+        // A fade in/out each time the title/thumbnail/etc. gets updated isn't helpful, so disable
+        // the change animation.
+        animator.setSupportsChangeAnimations(false);
+        setItemAnimator(animator);
     }
 
     @Override
     public void closeAll() {
-
         autoHidePanel();
 
-        if (getChildCount() == 0) {
-            return;
-        }
-
-        final Iterable<Tab> tabs = Tabs.getInstance().getTabsInOrder();
-        for (Tab tab : tabs) {
-            // In the normal panel we want to close all tabs (both private and normal),
-            // but in the private panel we only want to close private tabs.
-            if (!isPrivate || tab.isPrivate()) {
-                Tabs.getInstance().closeTab(tab, false);
-            }
-        }
-    }
-
-    private View getViewForTab(Tab tab) {
-        final int position = tabsAdapter.getPositionForTab(tab);
-        return getChildAt(position - getFirstVisiblePosition());
-    }
-
-    void closeTab(View v) {
-        if (tabsAdapter.getCount() == 1) {
-            autoHidePanel();
-        }
-
-        TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
-        Tab tab = Tabs.getInstance().getTab(itemView.getTabId());
-
-        Tabs.getInstance().closeTab(tab, true);
-    }
-
-    private void animateRemoveTab(final Tab removedTab) {
-        final int removedPosition = tabsAdapter.getPositionForTab(removedTab);
-
-        final View removedView = getViewForTab(removedTab);
-
-        // The removed position might not have a matching child view
-        // when it's not within the visible range of positions in the strip.
-        if (removedView == null) {
-            return;
-        }
-        final int removedHeight = removedView.getMeasuredHeight();
-
-        populateTabLocations(removedTab);
-
-        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                getViewTreeObserver().removeOnPreDrawListener(this);
-                // We don't animate the removed child view (it just disappears)
-                // but we still need its size to animate all affected children
-                // within the visible viewport.
-                final int childCount = getChildCount();
-                final int firstPosition = getFirstVisiblePosition();
-                final int numberOfColumns = getNumColumns();
-
-                final List<Animator> childAnimators = new ArrayList<>();
-
-                PropertyValuesHolder translateX, translateY;
-                for (int x = 0, i = removedPosition - firstPosition; i < childCount; i++, x++) {
-                    final View child = getChildAt(i);
-                    ObjectAnimator animator;
-
-                    if (i % numberOfColumns == numberOfColumns - 1) {
-                        // Animate X & Y
-                        translateX = PropertyValuesHolder.ofFloat("translationX", -(columnWidth * numberOfColumns), 0);
-                        translateY = PropertyValuesHolder.ofFloat("translationY", removedHeight, 0);
-                        animator = ObjectAnimator.ofPropertyValuesHolder(child, translateX, translateY);
-                    } else {
-                        // Just animate X
-                        translateX = PropertyValuesHolder.ofFloat("translationX", columnWidth, 0);
-                        animator = ObjectAnimator.ofPropertyValuesHolder(child, translateX);
-                    }
-                    animator.setStartDelay(x * ANIM_DELAY_MULTIPLE_MS);
-                    childAnimators.add(animator);
-                }
-
-                final AnimatorSet animatorSet = new AnimatorSet();
-                animatorSet.playTogether(childAnimators);
-                animatorSet.setDuration(ANIM_TIME_MS);
-                animatorSet.setInterpolator(ANIM_INTERPOLATOR);
-                animatorSet.start();
-
-                // Set the starting position of the child views - because we are delaying the start
-                // of the animation, we need to prevent the items being drawn in their final position
-                // prior to the animation starting
-                for (int x = 1, i = (removedPosition - firstPosition) + 1; i < childCount; i++, x++) {
-                    final View child = getChildAt(i);
-
-                    final PointF targetLocation = tabLocations.get(x + 1);
-                    if (targetLocation == null) {
-                        continue;
-                    }
-
-                    child.setX(targetLocation.x);
-                    child.setY(targetLocation.y);
-                }
-
-                return true;
-            }
-        });
-    }
-
-
-    private void animateCancel(final View view) {
-        PropertyAnimator animator = new PropertyAnimator(ANIM_TIME_MS);
-        animator.attach(view, PropertyAnimator.Property.ALPHA, 1);
-        animator.attach(view, PropertyAnimator.Property.TRANSLATION_X, 0);
-
-        animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
-            @Override
-            public void onPropertyAnimationStart() {
-            }
-
-            @Override
-            public void onPropertyAnimationEnd() {
-                TabsLayoutItemView tab = (TabsLayoutItemView) view;
-                tab.setCloseVisible(true);
-            }
-        });
-
-        animator.start();
-    }
-
-    private class TabsGridLayoutAdapter extends TabsLayoutAdapter {
-
-        final private Button.OnClickListener mCloseClickListener;
-
-        public TabsGridLayoutAdapter(Context context) {
-            super(context, R.layout.tabs_layout_item_view);
-
-            mCloseClickListener = new Button.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    closeTab(v);
-                }
-            };
-        }
-
-        @Override
-        TabsLayoutItemView newView(int position, ViewGroup parent) {
-            final TabsLayoutItemView item = super.newView(position, parent);
-
-            item.setCloseOnClickListener(mCloseClickListener);
-            ((ThemedRelativeLayout) item.findViewById(R.id.wrapper)).setPrivateMode(isPrivate);
-
-            return item;
-        }
-
-        @Override
-        public void bindView(TabsLayoutItemView view, Tab tab) {
-            super.bindView(view, tab);
-
-            // If we're recycling this view, there's a chance it was transformed during
-            // the close animation. Remove any of those properties.
-            resetTransforms(view);
-        }
-    }
-
-    private class TabSwipeGestureListener implements View.OnTouchListener {
-        // same value the stock browser uses for after drag animation velocity in pixels/sec
-        // http://androidxref.com/4.0.4/xref/packages/apps/Browser/src/com/android/browser/NavTabScroller.java#61
-        private static final float MIN_VELOCITY = 750;
-
-        private final int mSwipeThreshold;
-        private final int mMinFlingVelocity;
-
-        private final int mMaxFlingVelocity;
-        private VelocityTracker mVelocityTracker;
-
-        private int mTabWidth = 1;
-
-        private View mSwipeView;
-        private Runnable mPendingCheckForTap;
-
-        private float mSwipeStartX;
-        private boolean mSwiping;
-        private boolean mEnabled;
-
-        public TabSwipeGestureListener() {
-            mEnabled = true;
-
-            ViewConfiguration vc = ViewConfiguration.get(TabsGridLayout.this.getContext());
-            mSwipeThreshold = vc.getScaledTouchSlop();
-            mMinFlingVelocity = (int) (TabsGridLayout.this.getContext().getResources().getDisplayMetrics().density * MIN_VELOCITY);
-            mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
-        }
-
-        public void setEnabled(boolean enabled) {
-            mEnabled = enabled;
-        }
-
-        public OnScrollListener makeScrollListener() {
-            return new OnScrollListener() {
-                @Override
-                public void onScrollStateChanged(AbsListView view, int scrollState) {
-                    setEnabled(scrollState != GridView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
-                }
-
-                @Override
-                public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
-
-                }
-            };
-        }
-
-        @Override
-        public boolean onTouch(View view, MotionEvent e) {
-            if (!mEnabled) {
-                return false;
-            }
-
-            switch (e.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN: {
-                    // Check if we should set pressed state on the
-                    // touched view after a standard delay.
-                    triggerCheckForTap();
-
-                    final float x = e.getRawX();
-                    final float y = e.getRawY();
-
-                    // Find out which view is being touched
-                    mSwipeView = findViewAt(x, y);
-
-                    if (mSwipeView != null) {
-                        if (mTabWidth < 2) {
-                            mTabWidth = mSwipeView.getWidth();
-                        }
-
-                        mSwipeStartX = e.getRawX();
-
-                        mVelocityTracker = VelocityTracker.obtain();
-                        mVelocityTracker.addMovement(e);
-                    }
-
-                    view.onTouchEvent(e);
-                    return true;
-                }
-
-                case MotionEvent.ACTION_UP: {
-                    if (mSwipeView == null) {
-                        break;
-                    }
-
-                    cancelCheckForTap();
-                    mSwipeView.setPressed(false);
-
-                    if (!mSwiping) {
-                        final TabsLayoutItemView item = (TabsLayoutItemView) mSwipeView;
-                        final int tabId = item.getTabId();
-                        Tabs.getInstance().selectTab(tabId);
-                        autoHidePanel();
-                        Tabs.getInstance().notifyListeners(
-                                Tabs.getInstance().getTab(tabId), Tabs.TabEvents.OPENED_FROM_TABS_TRAY
-                        );
-
-                        mVelocityTracker.recycle();
-                        mVelocityTracker = null;
-                        break;
-                    }
-
-                    mVelocityTracker.addMovement(e);
-                    mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
-
-                    float velocityX = Math.abs(mVelocityTracker.getXVelocity());
-
-                    boolean dismiss = false;
-
-                    float deltaX = mSwipeView.getTranslationX();
-
-                    if (Math.abs(deltaX) > mTabWidth / 2) {
-                        dismiss = true;
-                    } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity) {
-                        dismiss = mSwiping && (deltaX * mVelocityTracker.getYVelocity() > 0);
-                    }
-                    if (dismiss) {
-                        closeTab(mSwipeView.findViewById(R.id.close));
-                    } else {
-                        animateCancel(mSwipeView);
-                    }
-                    mVelocityTracker.recycle();
-                    mVelocityTracker = null;
-                    mSwipeView = null;
-
-                    mSwipeStartX = 0;
-                    mSwiping = false;
-                }
-
-                case MotionEvent.ACTION_MOVE: {
-                    if (mSwipeView == null || mVelocityTracker == null) {
-                        break;
-                    }
-
-                    mVelocityTracker.addMovement(e);
-
-                    float delta = e.getRawX() - mSwipeStartX;
-
-                    boolean isScrollingX = Math.abs(delta) > mSwipeThreshold;
-                    boolean isSwipingToClose = isScrollingX;
-
-                    // If we're actually swiping, make sure we don't
-                    // set pressed state on the swiped view.
-                    if (isScrollingX) {
-                        cancelCheckForTap();
-                    }
-
-                    if (isSwipingToClose) {
-                        mSwiping = true;
-                        TabsGridLayout.this.requestDisallowInterceptTouchEvent(true);
-
-                        ((TabsLayoutItemView) mSwipeView).setCloseVisible(false);
-
-                        // Stops listview from highlighting the touched item
-                        // in the list when swiping.
-                        MotionEvent cancelEvent = MotionEvent.obtain(e);
-                        cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
-                                (e.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
-                        TabsGridLayout.this.onTouchEvent(cancelEvent);
-                        cancelEvent.recycle();
-                    }
-
-                    if (mSwiping) {
-                        mSwipeView.setTranslationX(delta);
-
-                        mSwipeView.setAlpha(Math.min(1f, 1f - 2f * Math.abs(delta) / mTabWidth));
-
-                        return true;
-                    }
-
-                    break;
-                }
-            }
-            return false;
-        }
-
-        private View findViewAt(float rawX, float rawY) {
-            Rect rect = new Rect();
-
-            int[] listViewCoords = new int[2];
-            TabsGridLayout.this.getLocationOnScreen(listViewCoords);
-
-            int x = (int) rawX - listViewCoords[0];
-            int y = (int) rawY - listViewCoords[1];
-
-            for (int i = 0; i < TabsGridLayout.this.getChildCount(); i++) {
-                View child = TabsGridLayout.this.getChildAt(i);
-                child.getHitRect(rect);
-
-                if (rect.contains(x, y)) {
-                    return child;
-                }
-            }
-
-            return null;
-        }
-
-        private void triggerCheckForTap() {
-            if (mPendingCheckForTap == null) {
-                mPendingCheckForTap = new CheckForTap();
-            }
-
-            TabsGridLayout.this.postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
-        }
-
-        private void cancelCheckForTap() {
-            if (mPendingCheckForTap == null) {
-                return;
-            }
-
-            TabsGridLayout.this.removeCallbacks(mPendingCheckForTap);
-        }
-
-        private class CheckForTap implements Runnable {
-            @Override
-            public void run() {
-                if (!mSwiping && mSwipeView != null && mEnabled) {
-                    mSwipeView.setPressed(true);
-                }
-            }
-        }
+        closeAllTabs();
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
@@ -5,46 +5,47 @@
 
 package org.mozilla.gecko.tabs;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.widget.RecyclerViewClickSupport;
+import org.mozilla.gecko.widget.AutoFitRecyclerView;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.Button;
 
 import java.util.ArrayList;
 
-public abstract class TabsLayout extends RecyclerView
+public abstract class TabsLayout extends AutoFitRecyclerView
         implements TabsPanel.TabsLayout,
         Tabs.OnTabsChangedListener,
         RecyclerViewClickSupport.OnItemClickListener,
         TabsTouchHelperCallback.DismissListener {
 
     private static final String LOGTAG = "Gecko" + TabsLayout.class.getSimpleName();
 
     private final boolean isPrivate;
     private TabsPanel tabsPanel;
-    private final TabsLayoutRecyclerAdapter tabsAdapter;
+    private final TabsLayoutAdapter tabsAdapter;
 
     public TabsLayout(Context context, AttributeSet attrs, int itemViewLayoutResId) {
         super(context, attrs);
 
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
         isPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
         a.recycle();
 
-        tabsAdapter = new TabsLayoutRecyclerAdapter(context, itemViewLayoutResId, isPrivate,
+        tabsAdapter = new TabsLayoutAdapter(context, itemViewLayoutResId, isPrivate,
                 /* close on click listener */
                 new Button.OnClickListener() {
                     @Override
                     public void onClick(View v) {
                         // The view here is the close button, which has a reference
                         // to the parent TabsLayoutItemView in its tag, hence the getTag() call.
                         TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
                         closeTab(itemView);
@@ -114,29 +115,33 @@ public abstract class TabsLayout extends
             case TITLE:
             case RECORDING_CHANGE:
             case AUDIO_PLAYING_CHANGE:
                 tabsAdapter.notifyTabChanged(tab);
                 break;
         }
     }
 
+    protected int getSelectedAdapterPosition() {
+        return tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
+    }
+
     @Override
     public void onItemClicked(RecyclerView recyclerView, int position, View v) {
         final TabsLayoutItemView item = (TabsLayoutItemView) v;
         final int tabId = item.getTabId();
         final Tabs tabs = Tabs.getInstance();
         tabs.selectTab(tabId);
         autoHidePanel();
         tabs.notifyListeners(tabs.getTab(tabId), Tabs.TabEvents.OPENED_FROM_TABS_TRAY);
     }
 
     // Updates the selected position in the list so that it will be scrolled to the right place.
     private void updateSelectedPosition() {
-        final int selected = tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
+        final int selected = getSelectedAdapterPosition();
         if (selected != NO_POSITION) {
             scrollToPosition(selected);
         }
     }
 
     private void refreshTabsData() {
         // Store a different copy of the tabs, so that we don't have to worry about
         // accidentally updating it on the wrong thread.
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutAdapter.java
@@ -1,100 +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.tabs;
 
-import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 
 import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.BaseAdapter;
+import android.widget.Button;
 
 import java.util.ArrayList;
 
-// Adapter to bind tabs into a list
-public class TabsLayoutAdapter extends BaseAdapter {
-    public static final String LOGTAG = "Gecko" + TabsLayoutAdapter.class.getSimpleName();
+public class TabsLayoutAdapter
+        extends RecyclerView.Adapter<TabsLayoutAdapter.TabsListViewHolder> {
+
+    private final int tabLayoutId;
+    private @NonNull ArrayList<Tab> tabs;
+    private final LayoutInflater inflater;
+    private final boolean isPrivate;
+    // Click listener for the close button on itemViews.
+    private final Button.OnClickListener closeOnClickListener;
 
-    private final Context mContext;
-    private final int mTabLayoutId;
-    private ArrayList<Tab> mTabs;
-    private final LayoutInflater mInflater;
+    // The TabsLayoutItemView takes care of caching its own Views, so we don't need to do anything
+    // here except not be abstract.
+    public static class TabsListViewHolder extends RecyclerView.ViewHolder {
+        public TabsListViewHolder(View itemView) {
+            super(itemView);
+        }
+    }
 
-    public TabsLayoutAdapter (Context context, int tabLayoutId) {
-        mContext = context;
-        mInflater = LayoutInflater.from(mContext);
-        mTabLayoutId = tabLayoutId;
+    public TabsLayoutAdapter(Context context, int tabLayoutId, boolean isPrivate,
+                             Button.OnClickListener closeOnClickListener) {
+        inflater = LayoutInflater.from(context);
+        this.tabLayoutId = tabLayoutId;
+        this.isPrivate = isPrivate;
+        this.closeOnClickListener = closeOnClickListener;
+        tabs = new ArrayList<>(0);
     }
 
-    final void setTabs (ArrayList<Tab> tabs) {
-        mTabs = tabs;
-        notifyDataSetChanged(); // Be sure to call this whenever mTabs changes.
+    /* package */ final void setTabs(@NonNull ArrayList<Tab> tabs) {
+        this.tabs = tabs;
+        notifyDataSetChanged();
+    }
+
+    /* package */ final void clear() {
+        tabs = new ArrayList<>(0);
+        notifyDataSetChanged();
     }
 
-    final boolean removeTab (Tab tab) {
-        boolean tabRemoved = mTabs.remove(tab);
-        if (tabRemoved) {
-            notifyDataSetChanged(); // Be sure to call this whenever mTabs changes.
+    /* package */ final boolean removeTab(Tab tab) {
+        final int position = getPositionForTab(tab);
+        if (position == -1) {
+            return false;
         }
-        return tabRemoved;
+        tabs.remove(position);
+        notifyItemRemoved(position);
+        return true;
     }
 
-    final void clear() {
-        mTabs = null;
+    /* package */ final int getPositionForTab(Tab tab) {
+        if (tab == null) {
+            return -1;
+        }
 
-        notifyDataSetChanged(); // Be sure to call this whenever mTabs changes.
+        return tabs.indexOf(tab);
+    }
+
+    /* package */ void notifyTabChanged(Tab tab) {
+        notifyItemChanged(getPositionForTab(tab));
     }
 
     @Override
-    public int getCount() {
-        return (mTabs == null ? 0 : mTabs.size());
-    }
-
-    @Override
-    public Tab getItem(int position) {
-        return mTabs.get(position);
+    public int getItemCount() {
+        return tabs.size();
     }
 
-    @Override
-    public long getItemId(int position) {
-        return position;
-    }
-
-    final int getPositionForTab(Tab tab) {
-        if (mTabs == null || tab == null)
-            return -1;
-
-        return mTabs.indexOf(tab);
+    private Tab getItem(int position) {
+        return tabs.get(position);
     }
 
     @Override
-    public boolean isEnabled(int position) {
-        return true;
+    public void onBindViewHolder(TabsListViewHolder viewHolder, int position) {
+        final Tab tab = getItem(position);
+        final TabsLayoutItemView itemView = (TabsLayoutItemView) viewHolder.itemView;
+        itemView.assignValues(tab);
+        // Make sure we didn't miss any resets after animations and swipes:
+        itemView.setAlpha(1);
+        itemView.setTranslationX(0);
+        itemView.setTranslationY(0);
     }
 
     @Override
-    final public TabsLayoutItemView getView(int position, View convertView, ViewGroup parent) {
-        final TabsLayoutItemView view;
-        if (convertView == null) {
-            view = newView(position, parent);
-        } else {
-            view = (TabsLayoutItemView) convertView;
-        }
-        final Tab tab = mTabs.get(position);
-        bindView(view, tab);
-        return view;
+    public TabsListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        final TabsLayoutItemView viewItem = (TabsLayoutItemView) inflater.inflate(tabLayoutId, parent, false);
+        viewItem.setPrivateMode(isPrivate);
+        viewItem.setCloseOnClickListener(closeOnClickListener);
+
+        return new TabsListViewHolder(viewItem);
     }
-
-    TabsLayoutItemView newView(int position, ViewGroup parent) {
-        return (TabsLayoutItemView) mInflater.inflate(mTabLayoutId, parent, false);
-    }
-
-    void bindView(TabsLayoutItemView view, Tab tab) {
-        view.assignValues(tab);
-    }
-}
\ No newline at end of file
+}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutRecyclerAdapter.java
+++ /dev/null
@@ -1,107 +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.tabs;
-
-import org.mozilla.gecko.Tab;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-
-import java.util.ArrayList;
-
-public class TabsLayoutRecyclerAdapter
-        extends RecyclerView.Adapter<TabsLayoutRecyclerAdapter.TabsListViewHolder> {
-
-    private final int tabLayoutId;
-    private @NonNull ArrayList<Tab> tabs;
-    private final LayoutInflater inflater;
-    private final boolean isPrivate;
-    // Click listener for the close button on itemViews.
-    private final Button.OnClickListener closeOnClickListener;
-
-    // The TabsLayoutItemView takes care of caching its own Views, so we don't need to do anything
-    // here except not be abstract.
-    public static class TabsListViewHolder extends RecyclerView.ViewHolder {
-        public TabsListViewHolder(View itemView) {
-            super(itemView);
-        }
-    }
-
-    public TabsLayoutRecyclerAdapter(Context context, int tabLayoutId, boolean isPrivate,
-                                     Button.OnClickListener closeOnClickListener) {
-        inflater = LayoutInflater.from(context);
-        this.tabLayoutId = tabLayoutId;
-        this.isPrivate = isPrivate;
-        this.closeOnClickListener = closeOnClickListener;
-        tabs = new ArrayList<>(0);
-    }
-
-    /* package */ final void setTabs(@NonNull ArrayList<Tab> tabs) {
-        this.tabs = tabs;
-        notifyDataSetChanged();
-    }
-
-    /* package */ final void clear() {
-        tabs = new ArrayList<>(0);
-        notifyDataSetChanged();
-    }
-
-    /* package */ final boolean removeTab(Tab tab) {
-        final int position = getPositionForTab(tab);
-        if (position == -1) {
-            return false;
-        }
-        tabs.remove(position);
-        notifyItemRemoved(position);
-        return true;
-    }
-
-    /* package */ final int getPositionForTab(Tab tab) {
-        if (tab == null) {
-            return -1;
-        }
-
-        return tabs.indexOf(tab);
-    }
-
-    /* package */ void notifyTabChanged(Tab tab) {
-        notifyItemChanged(getPositionForTab(tab));
-    }
-
-    @Override
-    public int getItemCount() {
-        return tabs.size();
-    }
-
-    private Tab getItem(int position) {
-        return tabs.get(position);
-    }
-
-    @Override
-    public void onBindViewHolder(TabsListViewHolder viewHolder, int position) {
-        final Tab tab = getItem(position);
-        final TabsLayoutItemView itemView = (TabsLayoutItemView) viewHolder.itemView;
-        itemView.assignValues(tab);
-        // Make sure we didn't miss any resets after animations and swipes:
-        itemView.setAlpha(1);
-        itemView.setTranslationX(0);
-        itemView.setTranslationY(0);
-    }
-
-    @Override
-    public TabsListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        final TabsLayoutItemView viewItem = (TabsLayoutItemView) inflater.inflate(tabLayoutId, parent, false);
-        viewItem.setPrivateMode(isPrivate);
-        viewItem.setCloseOnClickListener(closeOnClickListener);
-
-        return new TabsListViewHolder(viewItem);
-    }
-}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -661,17 +661,16 @@ gbjar.sources += ['java/org/mozilla/geck
     'tabs/TabHistoryFragment.java',
     'tabs/TabHistoryItemRow.java',
     'tabs/TabHistoryPage.java',
     'tabs/TabPanelBackButton.java',
     'tabs/TabsGridLayout.java',
     'tabs/TabsLayout.java',
     'tabs/TabsLayoutAdapter.java',
     'tabs/TabsLayoutItemView.java',
-    'tabs/TabsLayoutRecyclerAdapter.java',
     'tabs/TabsListLayout.java',
     'tabs/TabsListLayoutAnimator.java',
     'tabs/TabsPanel.java',
     'tabs/TabsPanelThumbnailView.java',
     'tabs/TabsTouchHelperCallback.java',
     'Telemetry.java',
     'telemetry/measurements/CampaignIdMeasurements.java',
     'telemetry/measurements/SearchCountMeasurements.java',
--- a/mobile/android/base/resources/layout/tabs_layout_item_view.xml
+++ b/mobile/android/base/resources/layout/tabs_layout_item_view.xml
@@ -2,27 +2,25 @@
 <!-- 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/. -->
 
 <org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
                                            xmlns:gecko="http://schemas.android.com/apk/res-auto"
                                            style="@style/TabsItem"
                                            android:id="@+id/info"
-                                           android:layout_width="wrap_content"
+                                           android:layout_width="fill_parent"
                                            android:layout_height="wrap_content"
                                            android:gravity="center"
                                            android:orientation="vertical">
 
-    <LinearLayout android:layout_width="fill_parent"
+    <LinearLayout android:layout_width="@dimen/tab_thumbnail_width"
                   android:layout_height="wrap_content"
                   android:orientation="horizontal"
                   android:duplicateParentState="true"
-                  android:paddingLeft="@dimen/tab_highlight_stroke_width"
-                  android:paddingRight="@dimen/tab_highlight_stroke_width"
                   android:paddingBottom="@dimen/tab_highlight_stroke_width">
 
        <org.mozilla.gecko.widget.FadedSingleColorTextView
                android:id="@+id/title"
                android:layout_width="0dip"
                android:layout_height="wrap_content"
                android:layout_weight="1.0"
                style="@style/TabLayoutItemTextAppearance"
--- a/mobile/android/base/resources/values-land/dimens.xml
+++ b/mobile/android/base/resources/values-land/dimens.xml
@@ -2,10 +2,10 @@
 <!-- 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/. -->
 
 <resources>
 
     <!-- Remote Tabs static view top padding. Less in landscape on phones. -->
     <dimen name="home_remote_tabs_top_padding">16dp</dimen>
-    <dimen name="tab_panel_grid_padding">48dp</dimen>
+    <dimen name="tab_panel_grid_hpadding">48dp</dimen>
 </resources>
--- a/mobile/android/base/resources/values-sw240dp/dimens.xml
+++ b/mobile/android/base/resources/values-sw240dp/dimens.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <resources>
-    <dimen name="tab_panel_column_width">143dip</dimen>
+    <dimen name="tab_panel_item_width">143dip</dimen>
     <dimen name="tab_thumbnail_height">100dip</dimen>
     <dimen name="tab_thumbnail_width">135dip</dimen>
 </resources>
--- a/mobile/android/base/resources/values-sw360dp/dimens.xml
+++ b/mobile/android/base/resources/values-sw360dp/dimens.xml
@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <resources>
-    <dimen name="tab_panel_column_width">156dip</dimen>
+    <dimen name="tab_panel_item_width">156dip</dimen>
     <dimen name="tab_thumbnail_height">110dip</dimen>
     <dimen name="tab_thumbnail_width">148dip</dimen>
 
     <dimen name="firstrun_background_height">180dp</dimen>
     <dimen name="firstrun_min_height">180dp</dimen>
 </resources>
--- a/mobile/android/base/resources/values-sw400dp/dimens.xml
+++ b/mobile/android/base/resources/values-sw400dp/dimens.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <resources>
-    <dimen name="tab_panel_column_width">176dip</dimen>
+    <dimen name="tab_panel_item_width">176dip</dimen>
     <dimen name="tab_thumbnail_height">120dip</dimen>
     <dimen name="tab_thumbnail_width">168dip</dimen>
 </resources>
--- a/mobile/android/base/resources/values-v11/themes.xml
+++ b/mobile/android/base/resources/values-v11/themes.xml
@@ -34,12 +34,11 @@
         <item name="android:actionModeCutDrawable">@drawable/ab_cut</item>
         <item name="android:actionModePasteDrawable">@drawable/ab_paste</item>
         <item name="android:listViewStyle">@style/Widget.ListView</item>
         <item name="android:spinnerDropDownItemStyle">@style/Widget.DropDownItem.Spinner</item>
         <item name="android:spinnerItemStyle">@style/Widget.TextView.SpinnerItem</item>
         <item name="menuItemSwitcherLayoutStyle">@style/Widget.MenuItemSwitcherLayout</item>
         <item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
         <item name="menuItemSecondaryActionBarStyle">@style/Widget.MenuItemSecondaryActionBar</item>
-        <item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>
     </style>
 
 </resources>
--- a/mobile/android/base/resources/values-v21/themes.xml
+++ b/mobile/android/base/resources/values-v21/themes.xml
@@ -29,12 +29,11 @@
     <style name="GeckoAppBase" parent="Gecko">
         <item name="android:actionButtonStyle">@style/GeckoActionBar.Button</item>
         <item name="android:listViewStyle">@style/Widget.ListView</item>
         <item name="android:spinnerDropDownItemStyle">@style/Widget.DropDownItem.Spinner</item>
         <item name="android:spinnerItemStyle">@style/Widget.TextView.SpinnerItem</item>
         <item name="menuItemSwitcherLayoutStyle">@style/Widget.MenuItemSwitcherLayout</item>
         <item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
         <item name="menuItemSecondaryActionBarStyle">@style/Widget.MenuItemSecondaryActionBar</item>
-        <item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>
     </style>
 
 </resources>
--- a/mobile/android/base/resources/values-xlarge-land-v11/dimens.xml
+++ b/mobile/android/base/resources/values-xlarge-land-v11/dimens.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <resources>
 
-    <dimen name="tab_panel_grid_padding">64dp</dimen>
+    <dimen name="tab_panel_grid_hpadding">64dp</dimen>
 
 </resources>
--- a/mobile/android/base/resources/values-xlarge-v11/dimens.xml
+++ b/mobile/android/base/resources/values-xlarge-v11/dimens.xml
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <resources>
 
     <dimen name="panel_grid_view_column_width">250dp</dimen>
-    <dimen name="tab_panel_grid_padding">48dp</dimen>
+    <dimen name="tab_panel_grid_hpadding">48dp</dimen>
 
 </resources>
--- a/mobile/android/base/resources/values/attrs.xml
+++ b/mobile/android/base/resources/values/attrs.xml
@@ -29,19 +29,16 @@
         <attr name="bookmarksListViewStyle" format="reference" />
 
         <!-- Default style for the TopSitesGridItemView -->
         <attr name="topSitesGridItemViewStyle" format="reference" />
 
         <!-- Styles for dynamic panel grid views -->
         <attr name="panelIconViewStyle" format="reference" />
 
-        <!-- Style for the TabsGridLayout -->
-        <attr name="tabGridLayoutViewStyle" format="reference" />
-
         <!-- Default style for the TopSitesGridView -->
         <attr name="topSitesGridViewStyle" format="reference" />
 
         <!-- Default style for the TopSitesThumbnailView -->
         <attr name="topSitesThumbnailViewStyle" format="reference" />
 
         <!-- Default style for the HomeListView -->
         <attr name="homeListViewStyle" format="reference" />
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -142,20 +142,21 @@
     <dimen name="text_selection_handle_width">47dp</dimen>
     <dimen name="text_selection_handle_height">58dp</dimen>
     <dimen name="text_selection_handle_shadow">11dp</dimen>
     <dimen name="validation_message_height">50dp</dimen>
     <dimen name="validation_message_margin_top">6dp</dimen>
 
     <dimen name="tab_thumbnail_width">121dp</dimen>
     <dimen name="tab_thumbnail_height">90dp</dimen>
-    <dimen name="tab_panel_column_width">129dp</dimen>
-    <dimen name="tab_panel_grid_padding">20dp</dimen>
-    <dimen name="tab_panel_grid_vspacing">20dp</dimen>
-    <dimen name="tab_panel_grid_padding_top">19dp</dimen>
+    <dimen name="tab_panel_item_width">129dp</dimen>
+    <dimen name="tab_panel_grid_hpadding">20dp</dimen>
+    <dimen name="tab_panel_grid_vpadding">19dp</dimen>
+    <dimen name="tab_panel_grid_item_hpadding">1dp</dimen>
+    <dimen name="tab_panel_grid_item_vpadding">10dp</dimen>
 
     <dimen name="tab_highlight_stroke_width">4dp</dimen>
 
     <!-- PageActionButtons dimensions -->
     <dimen name="page_action_button_width">32dp</dimen>
 
     <!-- Banner -->
     <dimen name="home_banner_height">72dp</dimen>
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -175,31 +175,16 @@
 
     <style name="Widget.TopSitesGridItemView">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">match_parent</item>
         <item name="android:padding">5dip</item>
         <item name="android:orientation">vertical</item>
     </style>
 
-    <style name="Widget.TabsGridLayout" parent="Widget.GridView">
-        <item name="android:layout_width">match_parent</item>
-        <item name="android:layout_height">match_parent</item>
-        <item name="android:paddingTop">0dp</item>
-        <item name="android:stretchMode">spacingWidth</item>
-        <item name="android:scrollbarStyle">outsideOverlay</item>
-        <item name="android:gravity">center</item>
-        <item name="android:numColumns">auto_fit</item>
-        <item name="android:columnWidth">@dimen/tab_panel_column_width</item>
-        <item name="android:horizontalSpacing">2dp</item>
-        <item name="android:verticalSpacing">@dimen/tab_panel_grid_vspacing</item>
-        <item name="android:drawSelectorOnTop">true</item>
-        <item name="android:clipToPadding">false</item>
-    </style>
-
     <style name="Widget.BookmarkItemView" parent="Widget.TwoLinePageRow"/>
 
     <style name="Widget.BookmarksListView" parent="Widget.HomeListView"/>
 
     <style name="Widget.TopSitesThumbnailView">
       <item name="android:padding">0dip</item>
       <item name="android:scaleType">centerCrop</item>
     </style>
--- a/mobile/android/base/resources/values/themes.xml
+++ b/mobile/android/base/resources/values/themes.xml
@@ -90,17 +90,16 @@
     </style>
 
     <!-- All customizations that are NOT specific to a particular API-level can go here. -->
     <style name="Gecko.App" parent="GeckoAppBase">
         <item name="android:gridViewStyle">@style/Widget.GridView</item>
         <item name="android:spinnerStyle">@style/Widget.Spinner</item>
         <item name="android:windowBackground">@android:color/white</item>
         <item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
-        <item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>
         <item name="geckoMenuListViewStyle">@style/Widget.GeckoMenuListView</item>
         <item name="homeListViewStyle">@style/Widget.HomeListView</item>
         <item name="menuItemActionBarStyle">@style/Widget.MenuItemActionBar</item>
         <item name="menuItemActionModeStyle">@style/GeckoActionBar.Button</item>
         <item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
         <item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
         <item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>
     </style>
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseTest.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseTest.java
@@ -608,89 +608,52 @@ abstract class BaseTest extends BaseRobo
     }
 
     public void closeAddedTabs() {
         for(int tabID : mKnownTabIDs) {
             closeTab(tabID);
         }
     }
 
-    // A temporary tabs list/grid holder while the list and grid views are being transitioned to
-    // RecyclerViews.
-    private static class TabsView {
-        private AdapterView<ListAdapter> gridView;
-        private RecyclerView listView;
-
-        public TabsView(View view) {
-            if (view instanceof RecyclerView) {
-                listView = (RecyclerView) view;
-            } else {
-                gridView = (AdapterView<ListAdapter>) view;
-            }
-        }
-
-        public void bringPositionIntoView(int index) {
-            if (gridView != null) {
-                gridView.setSelection(index);
-            } else {
-                listView.scrollToPosition(index);
-            }
-        }
-
-        public View getViewAtIndex(int index) {
-            if (gridView != null) {
-                return gridView.getChildAt(index - gridView.getFirstVisiblePosition());
-            } else {
-                final RecyclerView.ViewHolder itemViewHolder = listView.findViewHolderForLayoutPosition(index);
-                return itemViewHolder == null ? null : itemViewHolder.itemView;
-            }
-        }
-
-        public void post(Runnable runnable) {
-            if (gridView != null) {
-                gridView.post(runnable);
-            } else {
-                listView.post(runnable);
-            }
-        }
-    }
     /**
-     * Gets the AdapterView of the tabs list.
+     * Gets the RecyclerView of the tabs list.
      *
      * @return List view in the tabs panel
      */
-    private final TabsView getTabsLayout() {
+    private final RecyclerView getTabsLayout() {
         Element tabs = mDriver.findElement(getActivity(), R.id.tabs);
         tabs.click();
-        return new TabsView(getActivity().findViewById(R.id.normal_tabs));
+        return (RecyclerView) getActivity().findViewById(R.id.normal_tabs);
     }
 
     /**
      * Gets the view in the tabs panel at the specified index.
      *
      * @return View at index
      */
     private View getTabViewAt(final int index) {
         final View[] childView = { null };
 
-        final TabsView view = getTabsLayout();
+        final RecyclerView view = getTabsLayout();
 
         runOnUiThreadSync(new Runnable() {
             @Override
             public void run() {
-                view.bringPositionIntoView(index);
+                view.scrollToPosition(index);
 
                 // The selection isn't updated synchronously; posting a
                 // runnable to the view's queue guarantees we'll run after the
                 // layout pass.
                 view.post(new Runnable() {
                     @Override
                     public void run() {
                         // Index is relative to all views in the list.
-                        childView[0] = view.getViewAtIndex(index);
+                        final RecyclerView.ViewHolder itemViewHolder =
+                                view.findViewHolderForLayoutPosition(index);
+                        childView[0] = itemViewHolder == null ? null : itemViewHolder.itemView;
                     }
                 });
             }
         });
 
         boolean result = waitForCondition(new Condition() {
             @Override
             public boolean isSatisfied() {