Bug 1116415 - 2. Make the linear tabs list a RecyclerView. r?sebastian draft
authorTom Klein <twointofive@gmail.com>
Mon, 12 Sep 2016 10:37:14 -0500
changeset 425500 4a2b54b24d24495875121d764f3e07f07be46f7b
parent 424886 22a3bca7516429880b97b8aee1f60fc2f111dfda
child 425501 2da7c360ca45b2d6dc11e69d0399c00f8467453b
push id32434
push userbmo:twointofive@gmail.com
push dateFri, 14 Oct 2016 20:21:22 +0000
reviewerssebastian
bugs1116415
milestone52.0a1
Bug 1116415 - 2. Make the linear tabs list a RecyclerView. r?sebastian Also make a temporary testing adjustment to handle the fact that TabsListLayout and TabsGridLayout don't currently share a common usable base. MozReview-Commit-ID: HPExAnehKRq
mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayout.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseTest.java
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayout.java
@@ -1,211 +1,39 @@
 /* -*- 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.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.animation.PropertyAnimator.Property;
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.tabs.TabsPanel.TabsLayout;
 import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.widget.TwoWayView;
-import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
 
 import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Rect;
+import android.support.v7.widget.LinearLayoutManager;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
 import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.Button;
 
-import java.util.ArrayList;
-import java.util.List;
-
-class TabsListLayout extends TwoWayView
-                     implements TabsLayout,
-                                Tabs.OnTabsChangedListener {
-
-    private static final String LOGTAG = "Gecko" + TabsListLayout.class.getSimpleName();
-
+public class TabsListLayout extends TabsLayout {
     // Time to animate non-flinged tabs of screen, in milliseconds
     private static final int ANIMATION_DURATION = 250;
 
     // Time between starting successive tab animations in closeAllTabs.
     private static final int ANIMATION_CASCADE_DELAY = 75;
 
-    private final boolean isPrivate;
-    private final TabsLayoutAdapter tabsAdapter;
-    private final List<View> pendingClosedTabs;
-    private TabsPanel tabsPanel;
-    private int closeAnimationCount;
     private int closeAllAnimationCount;
-    private int originalSize;
 
     public TabsListLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        pendingClosedTabs = new ArrayList<View>();
-
-        setItemsCanFocus(true);
-
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
-        isPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
-        a.recycle();
-
-        tabsAdapter = new TabsListLayoutAdapter(context);
-        setAdapter(tabsAdapter);
-
-        final TabSwipeGestureListener swipeListener = new TabSwipeGestureListener();
-        setOnTouchListener(swipeListener);
-        setOnScrollListener(swipeListener.makeScrollListener());
-        setRecyclerListener(new RecyclerListener() {
-            @Override
-            public void onMovedToScrapHeap(View view) {
-                TabsLayoutItemView item = (TabsLayoutItemView) view;
-                item.setThumbnail(null);
-                item.setCloseVisible(true);
-            }
-        });
-    }
-
-    @Override
-    public void setTabsPanel(TabsPanel panel) {
-        tabsPanel = panel;
-    }
-
-    @Override
-    public void show() {
-        setVisibility(View.VISIBLE);
-        Tabs.getInstance().refreshThumbnails();
-        Tabs.registerOnTabsChangedListener(this);
-        refreshTabsData();
-    }
-
-    @Override
-    public void hide() {
-        setVisibility(View.GONE);
-        Tabs.unregisterOnTabsChangedListener(this);
-        GeckoAppShell.notifyObservers("Tab:Screenshot:Cancel", "");
-        tabsAdapter.clear();
-    }
-
-    @Override
-    public boolean shouldExpand() {
-        return isVertical();
-    }
-
-    private void autoHidePanel() {
-        tabsPanel.autoHidePanel();
-    }
-
-    @Override
-    public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
-        switch (msg) {
-            case ADDED:
-                // Refresh the list to make sure the new tab is added in the right position.
-                refreshTabsData();
-                break;
-
-            case CLOSED:
-                if (tab.isPrivate() == isPrivate && tabsAdapter.getCount() > 0) {
-                    if (tabsAdapter.removeTab(tab)) {
-                        int selected = tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
-                        updateSelectedStyle(selected);
-                    }
-                }
-                break;
+        super(context, attrs, R.layout.tabs_list_item_view);
 
-            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 item = (TabsLayoutItemView) view;
-                item.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);
-        if (selected != -1) {
-            setSelection(selected);
-        }
-    }
+        setHasFixedSize(true);
 
-    /**
-     * Updates the selected/unselected style for the tabs.
-     *
-     * @param selected position of the selected tab
-     */
-    private void updateSelectedStyle(int selected) {
-        for (int i = 0; i < tabsAdapter.getCount(); i++) {
-            setItemChecked(i, (i == 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.
-        ArrayList<Tab> tabData = new ArrayList<Tab>();
-        Iterable<Tab> allTabs = Tabs.getInstance().getTabsInOrder();
-
-        for (Tab tab : allTabs) {
-            if (tab.isPrivate() == isPrivate) {
-                tabData.add(tab);
-            }
-        }
-
-        tabsAdapter.setTabs(tabData);
-        updateSelectedPosition();
-    }
-
-    public void resetTransforms(View view) {
-        ViewHelper.setAlpha(view, 1);
-
-        if (isVertical()) {
-            ViewHelper.setTranslationX(view, 0);
-        } else {
-            ViewHelper.setTranslationY(view, 0);
-        }
-
-        // We only need to reset the height or width after individual tab close animations.
-        if (originalSize != 0) {
-            if (isVertical()) {
-                ViewHelper.setHeight(view, originalSize);
-            } else {
-                ViewHelper.setWidth(view, originalSize);
-            }
-        }
-    }
-
-    private boolean isVertical() {
-        return (getOrientation().compareTo(TwoWayView.Orientation.VERTICAL) == 0);
+        setLayoutManager(new LinearLayoutManager(context));
     }
 
     @Override
     public void closeAll() {
         final int childCount = getChildCount();
 
         // Just close the panel if there are no tabs to close.
         if (childCount == 0) {
@@ -213,27 +41,27 @@ class TabsListLayout extends TwoWayView
             return;
         }
 
         // Disable the view so that gestures won't interfere wth the tab close animation.
         setEnabled(false);
 
         // Delay starting each successive animation to create a cascade effect.
         int cascadeDelay = 0;
+        closeAllAnimationCount = 0;
         for (int i = childCount - 1; i >= 0; i--) {
             final View view = getChildAt(i);
+            if (view == null) {
+                continue;
+            }
 
             final PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
-            animator.attach(view, Property.ALPHA, 0);
+            animator.attach(view, PropertyAnimator.Property.ALPHA, 0);
 
-            if (isVertical()) {
-                animator.attach(view, Property.TRANSLATION_X, view.getWidth());
-            } else {
-                animator.attach(view, Property.TRANSLATION_Y, view.getHeight());
-            }
+            animator.attach(view, PropertyAnimator.Property.TRANSLATION_X, view.getWidth());
 
             closeAllAnimationCount++;
 
             animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
                 @Override
                 public void onPropertyAnimationStart() {
                 }
 
@@ -246,415 +74,23 @@ class TabsListLayout extends TwoWayView
 
                     // Hide the panel after the animation is done.
                     autoHidePanel();
 
                     // Re-enable the view after the animation is done.
                     TabsListLayout.this.setEnabled(true);
 
                     // Then actually close all the tabs.
-                    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);
-                        }
-                    }
+                    closeAllTabs();
                 }
             });
 
-            ThreadUtils.getUiHandler().postDelayed(new Runnable() {
+            ThreadUtils.postDelayedToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     animator.start();
                 }
             }, cascadeDelay);
 
             cascadeDelay += ANIMATION_CASCADE_DELAY;
         }
     }
-
-    private void animateClose(final View view, int pos) {
-        PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
-        animator.attach(view, Property.ALPHA, 0);
-
-        if (isVertical()) {
-            animator.attach(view, Property.TRANSLATION_X, pos);
-        } else {
-            animator.attach(view, Property.TRANSLATION_Y, pos);
-        }
-
-        closeAnimationCount++;
-
-        pendingClosedTabs.add(view);
-
-        animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
-            @Override
-            public void onPropertyAnimationStart() {
-            }
-
-            @Override
-            public void onPropertyAnimationEnd() {
-                closeAnimationCount--;
-                if (closeAnimationCount > 0) {
-                    return;
-                }
-
-                for (View pendingView : pendingClosedTabs) {
-                    animateFinishClose(pendingView);
-                }
-                pendingClosedTabs.clear();
-            }
-        });
-
-        if (tabsAdapter.getCount() == 1) {
-            autoHidePanel();
-        }
-
-        animator.start();
-    }
-
-    private void animateFinishClose(final View view) {
-        final boolean isVertical = isVertical();
-
-        PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
-
-        if (isVertical) {
-            animator.attach(view, Property.HEIGHT, 1);
-        } else {
-            animator.attach(view, Property.WIDTH, 1);
-        }
-
-        final int tabId = ((TabsLayoutItemView) view).getTabId();
-
-        // Caching this assumes that all rows are the same height
-        if (originalSize == 0) {
-            originalSize = (isVertical ? view.getHeight() : view.getWidth());
-        }
-
-        animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
-            @Override
-            public void onPropertyAnimationStart() {
-            }
-
-            @Override
-            public void onPropertyAnimationEnd() {
-                Tabs tabs = Tabs.getInstance();
-                Tab tab = tabs.getTab(tabId);
-                tabs.closeTab(tab, true);
-            }
-        });
-
-        animator.start();
-    }
-
-    private void animateCancel(final View view) {
-        PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
-        animator.attach(view, Property.ALPHA, 1);
-
-        if (isVertical()) {
-            animator.attach(view, Property.TRANSLATION_X, 0);
-        } else {
-            animator.attach(view, Property.TRANSLATION_Y, 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 TabsListLayoutAdapter extends TabsLayoutAdapter {
-        private final Button.OnClickListener mCloseOnClickListener;
-
-        public TabsListLayoutAdapter(Context context) {
-            super(context, R.layout.tabs_list_item_view);
-            mCloseOnClickListener = 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 it's tag, hence the getTag() call
-                    TabsLayoutItemView item = (TabsLayoutItemView) v.getTag();
-                    final int pos = (isVertical() ? item.getWidth() : 0 - item.getHeight());
-                    animateClose(item, pos);
-                }
-            };
-        }
-
-        @Override
-        public TabsLayoutItemView newView(int position, ViewGroup parent) {
-            TabsLayoutItemView item = super.newView(position, parent);
-            item.setCloseOnClickListener(mCloseOnClickListener);
-            ((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 swipeThreshold;
-        private final int minFlingVelocity;
-        private final int maxFlingVelocity;
-        private VelocityTracker velocityTracker;
-        private int listWidth = 1;
-        private int listHeight = 1;
-        private View swipeView;
-        private Runnable pendingCheckForTap;
-        private float swipeStartX;
-        private float swipeStartY;
-        private boolean swiping;
-        private boolean enabled;
-
-        public TabSwipeGestureListener() {
-            enabled = true;
-
-            ViewConfiguration vc = ViewConfiguration.get(TabsListLayout.this.getContext());
-
-            swipeThreshold = vc.getScaledTouchSlop();
-            minFlingVelocity = (int) (getContext().getResources().getDisplayMetrics().density * MIN_VELOCITY);
-            maxFlingVelocity = vc.getScaledMaximumFlingVelocity();
-        }
-
-        public void setEnabled(boolean enabled) {
-            this.enabled = enabled;
-        }
-
-        public TwoWayView.OnScrollListener makeScrollListener() {
-            return new TwoWayView.OnScrollListener() {
-
-                @Override
-                public void onScrollStateChanged(TwoWayView twoWayView, int scrollState) {
-                    setEnabled(scrollState != TwoWayView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
-                }
-
-                @Override
-                public void onScroll(TwoWayView twoWayView, int i, int i1, int i2) {
-                }
-            };
-        }
-
-        @Override
-        public boolean onTouch(View view, MotionEvent e) {
-            if (!enabled) {
-                return false;
-            }
-
-            if (listWidth < 2 || listHeight < 2) {
-                listWidth = TabsListLayout.this.getWidth();
-                listHeight = TabsListLayout.this.getHeight();
-            }
-
-            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
-                    swipeView = findViewAt(x, y);
-
-                    if (swipeView != null) {
-                        swipeStartX = e.getRawX();
-                        swipeStartY = e.getRawY();
-                        velocityTracker = VelocityTracker.obtain();
-                        velocityTracker.addMovement(e);
-                    }
-
-                    view.onTouchEvent(e);
-                    return true;
-                }
-                case MotionEvent.ACTION_UP: {
-                    if (swipeView == null) {
-                        break;
-                    }
-
-                    cancelCheckForTap();
-                    swipeView.setPressed(false);
-
-                    if (!swiping) {
-                        TabsLayoutItemView item = (TabsLayoutItemView) swipeView;
-                        final int tabId = item.getTabId();
-                        Tabs.getInstance().selectTab(tabId);
-                        autoHidePanel();
-                        Tabs.getInstance().notifyListeners(
-                                Tabs.getInstance().getTab(tabId), Tabs.TabEvents.OPENED_FROM_TABS_TRAY
-                        );
-                        velocityTracker.recycle();
-                        velocityTracker = null;
-                        break;
-                    }
-
-                    velocityTracker.addMovement(e);
-                    velocityTracker.computeCurrentVelocity(1000, maxFlingVelocity);
-
-                    float velocityX = Math.abs(velocityTracker.getXVelocity());
-                    float velocityY = Math.abs(velocityTracker.getYVelocity());
-                    boolean dismiss = false;
-                    boolean dismissDirection = false;
-                    int dismissTranslation;
-
-                    if (isVertical()) {
-                        float deltaX = ViewHelper.getTranslationX(swipeView);
-
-                        if (Math.abs(deltaX) > listWidth / 2) {
-                            dismiss = true;
-                            dismissDirection = (deltaX > 0);
-                        } else if (minFlingVelocity <= velocityX && velocityX <= maxFlingVelocity
-                                                                 && velocityY < velocityX) {
-
-                            dismiss = swiping && (deltaX * velocityTracker.getXVelocity() > 0);
-                            dismissDirection = (velocityTracker.getXVelocity() > 0);
-                        }
-
-                        dismissTranslation = (dismissDirection ? listWidth : -listWidth);
-                    } else {
-                        float deltaY = ViewHelper.getTranslationY(swipeView);
-
-                        if (Math.abs(deltaY) > listHeight / 2) {
-                            dismiss = true;
-                            dismissDirection = (deltaY > 0);
-                        } else if (minFlingVelocity <= velocityY && velocityY <= maxFlingVelocity
-                                                                 && velocityX < velocityY) {
-
-                            dismiss = swiping && (deltaY * velocityTracker.getYVelocity() > 0);
-                            dismissDirection = (velocityTracker.getYVelocity() > 0);
-                        }
-
-                        dismissTranslation = (dismissDirection ? listHeight : -listHeight);
-                    }
-
-                    if (dismiss) {
-                        animateClose(swipeView, dismissTranslation);
-                    } else {
-                        animateCancel(swipeView);
-                    }
-
-                    velocityTracker.recycle();
-                    velocityTracker = null;
-                    swipeView = null;
-                    swipeStartX = 0;
-                    swipeStartY = 0;
-                    swiping = false;
-                    break;
-                }
-
-                case MotionEvent.ACTION_MOVE: {
-                    if (swipeView == null || velocityTracker == null) {
-                        break;
-                    }
-
-                    velocityTracker.addMovement(e);
-
-                    final boolean isVertical = isVertical();
-                    float deltaX = e.getRawX() - swipeStartX;
-                    float deltaY = e.getRawY() - swipeStartY;
-                    float delta = (isVertical ? deltaX : deltaY);
-                    boolean isScrollingX = Math.abs(deltaX) > swipeThreshold;
-                    boolean isScrollingY = Math.abs(deltaY) > swipeThreshold;
-                    boolean isSwipingToClose = (isVertical ? isScrollingX : isScrollingY);
-
-                    // If we're actually swiping, make sure we don't
-                    // set pressed state on the swiped view.
-                    if (isScrollingX || isScrollingY) {
-                        cancelCheckForTap();
-                    }
-
-                    if (isSwipingToClose) {
-                        swiping = true;
-                        TabsListLayout.this.requestDisallowInterceptTouchEvent(true);
-                        ((TabsLayoutItemView) swipeView).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));
-
-                        TabsListLayout.this.onTouchEvent(cancelEvent);
-                        cancelEvent.recycle();
-                    }
-                    if (swiping) {
-                        if (isVertical) {
-                            ViewHelper.setTranslationX(swipeView, delta);
-                        } else {
-                            ViewHelper.setTranslationY(swipeView, delta);
-                        }
-
-                        ViewHelper.setAlpha(swipeView, Math.max(0.1f, Math.min(1f,
-                                1f - 2f * Math.abs(delta) / (isVertical ? listWidth : listHeight))));
-
-                        return true;
-                    }
-                    break;
-                }
-            }
-            return false;
-        }
-
-        private View findViewAt(float rawX, float rawY) {
-            Rect rect = new Rect();
-
-            int[] listViewCoords = new int[2];
-            TabsListLayout.this.getLocationOnScreen(listViewCoords);
-
-            int x = (int) rawX - listViewCoords[0];
-            int y = (int) rawY - listViewCoords[1];
-
-            for (int i = 0; i < TabsListLayout.this.getChildCount(); i++) {
-                View child = TabsListLayout.this.getChildAt(i);
-                child.getHitRect(rect);
-
-                if (rect.contains(x, y)) {
-                    return child;
-                }
-            }
-            return null;
-        }
-
-        private void triggerCheckForTap() {
-            if (pendingCheckForTap == null) {
-                pendingCheckForTap = new CheckForTap();
-            }
-            TabsListLayout.this.postDelayed(pendingCheckForTap, ViewConfiguration.getTapTimeout());
-        }
-
-        private void cancelCheckForTap() {
-            if (pendingCheckForTap == null) {
-                return;
-            }
-            TabsListLayout.this.removeCallbacks(pendingCheckForTap);
-        }
-
-        private class CheckForTap implements Runnable {
-            @Override
-            public void run() {
-                if (!swiping && swipeView != null && enabled) {
-                    swipeView.setPressed(true);
-                }
-            }
-        }
-    }
 }
--- 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
@@ -27,16 +27,17 @@ import org.mozilla.gecko.Tabs;
 
 import android.content.ContentValues;
 import android.content.res.AssetManager;
 import android.database.Cursor;
 import android.os.Build;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentManager;
+import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.view.View;
 import android.widget.AdapterView;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ListAdapter;
 import android.widget.TextView;
@@ -606,53 +607,89 @@ 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 (bug 1116415 and bug 1310081).
+    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.
      *
      * @return List view in the tabs panel
      */
-    private final AdapterView<ListAdapter> getTabsLayout() {
+    private final TabsView getTabsLayout() {
         Element tabs = mDriver.findElement(getActivity(), R.id.tabs);
         tabs.click();
-        return (AdapterView<ListAdapter>) getActivity().findViewById(R.id.normal_tabs);
+        return new TabsView(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 AdapterView<ListAdapter> view = getTabsLayout();
+        final TabsView view = getTabsLayout();
 
         runOnUiThreadSync(new Runnable() {
             @Override
             public void run() {
-                view.setSelection(index);
+                view.bringPositionIntoView(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() {
-                        // getChildAt() is relative to the list of visible
-                        // views, but our index is relative to all views in the
-                        // list. Subtract the first visible list position for
-                        // the correct offset.
-                        childView[0] = view.getChildAt(index - view.getFirstVisiblePosition());
+                        // Index is relative to all views in the list.
+                        childView[0] = view.getViewAtIndex(index);
                     }
                 });
             }
         });
 
         boolean result = waitForCondition(new Condition() {
             @Override
             public boolean isSatisfied() {