Merge "Fix TabLayout when used in a HorizontalScrollView" into lmp-mr1-ub-dev
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
index eb58dc5..411f17c 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
@@ -39,6 +39,7 @@
     final Context mContext;
     final Window mWindow;
     final Window.Callback mOriginalWindowCallback;
+    final Window.Callback mAppCompatWindowCallback;
     final AppCompatCallback mAppCompatCallback;
 
     ActionBar mActionBar;
@@ -69,8 +70,9 @@
             throw new IllegalStateException(
                     "AppCompat has already installed itself into the Window");
         }
+        mAppCompatWindowCallback = wrapWindowCallback(mOriginalWindowCallback);
         // Now install the new callback
-        mWindow.setCallback(wrapWindowCallback(mOriginalWindowCallback));
+        mWindow.setCallback(mAppCompatWindowCallback);
     }
 
     abstract void initWindowDecorActionBar();
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
index c777f57..2f0d5af 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
@@ -200,7 +200,7 @@
         }
 
         ToolbarActionBar tbab = new ToolbarActionBar(toolbar, ((Activity) mContext).getTitle(),
-                mWindow);
+                mAppCompatWindowCallback);
         setSupportActionBar(tbab);
         mWindow.setCallback(tbab.getWrappedWindowCallback());
         tbab.invalidateOptionsMenu();
@@ -1835,7 +1835,8 @@
 
         @Override
         public boolean dispatchKeyEvent(KeyEvent event) {
-            return AppCompatDelegateImplV7.this.dispatchKeyEvent(event);
+            return AppCompatDelegateImplV7.this.dispatchKeyEvent(event)
+                    || super.dispatchKeyEvent(event);
         }
 
         @Override
diff --git a/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java b/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
index 46bebe6..1781015 100644
--- a/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
+++ b/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
@@ -24,8 +24,8 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.WindowCompat;
 import android.support.v7.app.ActionBar;
-import android.support.v7.internal.view.WindowCallbackWrapper;
 import android.support.v7.appcompat.R;
+import android.support.v7.internal.view.WindowCallbackWrapper;
 import android.support.v7.internal.view.menu.ListMenuPresenter;
 import android.support.v7.internal.view.menu.MenuBuilder;
 import android.support.v7.internal.view.menu.MenuPresenter;
@@ -54,10 +54,8 @@
     private boolean mMenuCallbackSet;
 
     private boolean mLastMenuVisibility;
-    private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
-            new ArrayList<OnMenuVisibilityListener>();
+    private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners = new ArrayList<>();
 
-    private Window mWindow;
     private ListMenuPresenter mListMenuPresenter;
 
     private final Runnable mMenuInvalidator = new Runnable() {
@@ -75,14 +73,12 @@
                 }
             };
 
-    public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window window) {
+    public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback callback) {
         mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
-        mWindowCallback = new ToolbarCallbackWrapper(window.getCallback());
+        mWindowCallback = new ToolbarCallbackWrapper(callback);
         mDecorToolbar.setWindowCallback(mWindowCallback);
         toolbar.setOnMenuItemClickListener(mMenuClicker);
         mDecorToolbar.setWindowTitle(title);
-
-        mWindow = window;
     }
 
     public Window.Callback getWrappedWindowCallback() {
diff --git a/v7/recyclerview/api/current.txt b/v7/recyclerview/api/current.txt
index f48e8fe..a7becb4 100644
--- a/v7/recyclerview/api/current.txt
+++ b/v7/recyclerview/api/current.txt
@@ -625,7 +625,7 @@
     method public int getChildPosition(android.view.View);
     method public android.support.v7.widget.RecyclerView.LayoutManager getLayoutManager();
     method public int getTargetPosition();
-    method public void instantScrollToPosition(int);
+    method public deprecated void instantScrollToPosition(int);
     method public boolean isPendingInitialRun();
     method public boolean isRunning();
     method protected void normalize(android.graphics.PointF);
@@ -646,6 +646,7 @@
     method public int getDx();
     method public int getDy();
     method public android.view.animation.Interpolator getInterpolator();
+    method public void jumpTo(int);
     method public void setDuration(int);
     method public void setDx(int);
     method public void setDy(int);
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index c1e312c..f842ed5 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -29,6 +29,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
+import android.support.v7.widget.RecyclerView.LayoutParams;
 
 import java.util.List;
 
@@ -175,8 +176,8 @@
      * {@inheritDoc}
      */
     @Override
-    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
-        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+    public LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                 ViewGroup.LayoutParams.WRAP_CONTENT);
     }
 
@@ -1377,7 +1378,7 @@
             result.mFinished = true;
             return;
         }
-        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
+        LayoutParams params = (LayoutParams) view.getLayoutParams();
         if (layoutState.mScrapList == null) {
             if (mShouldReverseLayout == (layoutState.mLayoutDirection
                     == LayoutState.LAYOUT_START)) {
@@ -1587,7 +1588,7 @@
             final View view = getChildAt(i);
             final int position = getPosition(view);
             if (position >= 0 && position < itemCount) {
-                if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
+                if (((LayoutParams) view.getLayoutParams()).isItemRemoved()) {
                     if (invalidMatch == null) {
                         invalidMatch = view; // removed item, least preferred
                     }
@@ -1973,13 +1974,14 @@
         private View nextViewFromScrapList() {
             final int size = mScrapList.size();
             for (int i = 0; i < size; i++) {
-                final RecyclerView.ViewHolder viewHolder = mScrapList.get(i);
-                if (viewHolder.isRemoved()) {
+                final View view = mScrapList.get(i).itemView;
+                final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                if (lp.isItemRemoved()) {
                     continue;
                 }
-                if (mCurrentPosition == viewHolder.getLayoutPosition()) {
-                    assignPositionFromScrapList(viewHolder);
-                    return viewHolder.itemView;
+                if (mCurrentPosition == lp.getViewLayoutPosition()) {
+                    assignPositionFromScrapList(view);
+                    return view;
                 }
             }
             return null;
@@ -1989,31 +1991,36 @@
             assignPositionFromScrapList(null);
         }
 
-        public void assignPositionFromScrapList(RecyclerView.ViewHolder ignore) {
-            RecyclerView.ViewHolder closest = nextViewHolderInLimitedList(ignore);
-            mCurrentPosition = closest == null ? RecyclerView.NO_POSITION :
-                    closest.getLayoutPosition();
+        public void assignPositionFromScrapList(View ignore) {
+            final View closest = nextViewInLimitedList(ignore);
+            if (closest == null) {
+                mCurrentPosition = NO_POSITION;
+            } else {
+                mCurrentPosition = ((LayoutParams) closest.getLayoutParams())
+                        .getViewLayoutPosition();
+            }
         }
 
-        public RecyclerView.ViewHolder nextViewHolderInLimitedList(RecyclerView.ViewHolder ignore) {
+        public View nextViewInLimitedList(View ignore) {
             int size = mScrapList.size();
-            RecyclerView.ViewHolder closest = null;
+            View closest = null;
             int closestDistance = Integer.MAX_VALUE;
             if (DEBUG && mIsPreLayout) {
                 throw new IllegalStateException("Scrap list cannot be used in pre layout");
             }
             for (int i = 0; i < size; i++) {
-                RecyclerView.ViewHolder viewHolder = mScrapList.get(i);
-                if (viewHolder == ignore || viewHolder.isRemoved()) {
+                View view = mScrapList.get(i).itemView;
+                final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                if (view == ignore || lp.isItemRemoved()) {
                     continue;
                 }
-                final int distance = (viewHolder.getLayoutPosition() - mCurrentPosition) *
+                final int distance = (lp.getViewLayoutPosition() - mCurrentPosition) *
                         mItemDirection;
                 if (distance < 0) {
                     continue; // item is not in current direction
                 }
                 if (distance < closestDistance) {
-                    closest = viewHolder;
+                    closest = view;
                     closestDistance = distance;
                     if (distance == 0) {
                         break;
@@ -2120,7 +2127,7 @@
         }
 
         private boolean isViewValidAsAnchor(View child, RecyclerView.State state) {
-            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
             return !lp.isItemRemoved() && lp.getViewLayoutPosition() >= 0
                     && lp.getViewLayoutPosition() < state.getItemCount();
         }
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java b/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
index ed4c950..cda5cd1 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
@@ -229,8 +229,8 @@
                     + "LayoutManager#computeScrollVectorForPosition.\n"
                     + "Falling back to instant scroll");
             final int target = getTargetPosition();
+            action.jumpTo(target);
             stop();
-            instantScrollToPosition(target);
             return;
         }
         normalize(scrollVector);
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index 108e68e..d884caf 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -1271,6 +1271,14 @@
         awakenScrollBars();
     }
 
+    private void jumpToPositionForSmoothScroller(int position) {
+        if (mLayout == null) {
+            return;
+        }
+        mLayout.scrollToPosition(position);
+        awakenScrollBars();
+    }
+
     /**
      * Starts a smooth scroll to an adapter position.
      * <p>
@@ -3797,6 +3805,8 @@
                             }
                         }
                     }
+                    onExitLayoutOrScroll();
+                    resumeRequestLayout(false);
 
                     if (smoothScroller != null && !smoothScroller.isPendingInitialRun() &&
                             smoothScroller.isRunning()) {
@@ -3810,8 +3820,6 @@
                             smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
                         }
                     }
-                    onExitLayoutOrScroll();
-                    resumeRequestLayout(false);
                 }
                 if (!mItemDecorations.isEmpty()) {
                     invalidate();
@@ -3864,8 +3872,13 @@
                 }
             }
             // call this after the onAnimation is complete not to have inconsistent callbacks etc.
-            if (smoothScroller != null && smoothScroller.isPendingInitialRun()) {
-                smoothScroller.onAnimation(0, 0);
+            if (smoothScroller != null) {
+                if (smoothScroller.isPendingInitialRun()) {
+                    smoothScroller.onAnimation(0, 0);
+                }
+                if (!mReSchedulePostAnimationCallback) {
+                    smoothScroller.stop(); //stop if it does not trigger any scroll
+                }
             }
             enableRunOnAnimationRequests();
         }
@@ -8571,8 +8584,10 @@
         }
 
         /**
-         * @return The LayoutManager to which this SmoothScroller is attached
+         * @return The LayoutManager to which this SmoothScroller is attached. Will return
+         * <code>null</code> after the SmoothScroller is stopped.
          */
+        @Nullable
         public LayoutManager getLayoutManager() {
             return mLayoutManager;
         }
@@ -8630,15 +8645,16 @@
         }
 
         private void onAnimation(int dx, int dy) {
-            if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION) {
+            final RecyclerView recyclerView = mRecyclerView;
+            if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) {
                 stop();
             }
             mPendingInitialRun = false;
             if (mTargetView != null) {
                 // verify target position
                 if (getChildPosition(mTargetView) == mTargetPosition) {
-                    onTargetFound(mTargetView, mRecyclerView.mState, mRecyclingAction);
-                    mRecyclingAction.runIfNecessary(mRecyclerView);
+                    onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction);
+                    mRecyclingAction.runIfNecessary(recyclerView);
                     stop();
                 } else {
                     Log.e(TAG, "Passed over target position while smooth scrolling.");
@@ -8646,8 +8662,18 @@
                 }
             }
             if (mRunning) {
-                onSeekTargetStep(dx, dy, mRecyclerView.mState, mRecyclingAction);
-                mRecyclingAction.runIfNecessary(mRecyclerView);
+                onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction);
+                boolean hadJumpTarget = mRecyclingAction.hasJumpTarget();
+                mRecyclingAction.runIfNecessary(recyclerView);
+                if (hadJumpTarget) {
+                    // It is not stopped so needs to be restarted
+                    if (mRunning) {
+                        mPendingInitialRun = true;
+                        recyclerView.mViewFlinger.postOnAnimation();
+                    } else {
+                        stop(); // done
+                    }
+                }
             }
         }
 
@@ -8674,7 +8700,9 @@
 
         /**
          * @see RecyclerView#scrollToPosition(int)
+         * @deprecated Use {@link Action#jumpTo(int)}.
          */
+        @Deprecated
         public void instantScrollToPosition(int position) {
             mRecyclerView.scrollToPosition(position);
         }
@@ -8748,6 +8776,8 @@
 
             private int mDuration;
 
+            private int mJumpToPosition = NO_POSITION;
+
             private Interpolator mInterpolator;
 
             private boolean changed = false;
@@ -8786,7 +8816,38 @@
                 mDuration = duration;
                 mInterpolator = interpolator;
             }
+
+            /**
+             * Instead of specifying pixels to scroll, use the target position to jump using
+             * {@link RecyclerView#scrollToPosition(int)}.
+             * <p>
+             * You may prefer using this method if scroll target is really far away and you prefer
+             * to jump to a location and smooth scroll afterwards.
+             * <p>
+             * Note that calling this method takes priority over other update methods such as
+             * {@link #update(int, int, int, Interpolator)}, {@link #setX(float)},
+             * {@link #setY(float)} and #{@link #setInterpolator(Interpolator)}. If you call
+             * {@link #jumpTo(int)}, the other changes will not be considered for this animation
+             * frame.
+             *
+             * @param targetPosition The target item position to scroll to using instant scrolling.
+             */
+            public void jumpTo(int targetPosition) {
+                mJumpToPosition = targetPosition;
+            }
+
+            boolean hasJumpTarget() {
+                return mJumpToPosition >= 0;
+            }
+
             private void runIfNecessary(RecyclerView recyclerView) {
+                if (mJumpToPosition >= 0) {
+                    final int position = mJumpToPosition;
+                    mJumpToPosition = NO_POSITION;
+                    recyclerView.jumpToPositionForSmoothScroller(position);
+                    changed = false;
+                    return;
+                }
                 if (changed) {
                     validate();
                     if (mInterpolator == null) {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index 630c12d..519520a 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -48,9 +48,11 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING;
 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
+import static android.support.v7.widget.RecyclerView.getChildViewHolderInt;
 
 import android.support.test.runner.AndroidJUnit4;
 
@@ -291,7 +293,7 @@
 
     private void scrollInOtherOrientationTest(int flags)
             throws Throwable {
-        scrollInOtherOrientationTest(flags);
+        scrollInOtherOrientationTest(flags, flags);
     }
 
     private void scrollInOtherOrientationTest(final int flags, int expectedFlags) throws Throwable {
@@ -2792,6 +2794,152 @@
         assertEquals(addItemDecors ? -30 : -20, scrollDist.get());
     }
 
+    @Test
+    public void testUnimplementedSmoothScroll() throws Throwable {
+        final AtomicInteger receivedScrollToPosition = new AtomicInteger(-1);
+        final AtomicInteger receivedSmoothScrollToPosition = new AtomicInteger(-1);
+        final CountDownLatch cbLatch = new CountDownLatch(2);
+        TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                layoutRange(recycler, 0, 10);
+                layoutLatch.countDown();
+            }
+
+            @Override
+            public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
+                    int position) {
+                assertEquals(-1, receivedSmoothScrollToPosition.get());
+                receivedSmoothScrollToPosition.set(position);
+                RecyclerView.SmoothScroller ss =
+                        new LinearSmoothScroller(recyclerView.getContext()) {
+                            @Override
+                            public PointF computeScrollVectorForPosition(int targetPosition) {
+                                return null;
+                            }
+                        };
+                ss.setTargetPosition(position);
+                startSmoothScroll(ss);
+                cbLatch.countDown();
+            }
+
+            @Override
+            public void scrollToPosition(int position) {
+                assertEquals(-1, receivedScrollToPosition.get());
+                receivedScrollToPosition.set(position);
+                cbLatch.countDown();
+            }
+        };
+        RecyclerView rv = new RecyclerView(getActivity());
+        rv.setAdapter(new TestAdapter(100));
+        rv.setLayoutManager(tlm);
+        tlm.expectLayouts(1);
+        setRecyclerView(rv);
+        tlm.waitForLayout(2);
+        smoothScrollToPosition(35);
+        assertTrue("both scrolls should be called", cbLatch.await(3, TimeUnit.SECONDS));
+        checkForMainThreadException();
+        assertEquals(35, receivedSmoothScrollToPosition.get());
+        assertEquals(35, receivedScrollToPosition.get());
+    }
+
+    @Test
+    public void testJumpingJackSmoothScroller() throws Throwable {
+        jumpingJackSmoothScrollerTest(true);
+    }
+
+    @Test
+    public void testJumpingJackSmoothScrollerGoesIdle() throws Throwable {
+        jumpingJackSmoothScrollerTest(false);
+    }
+
+    private void jumpingJackSmoothScrollerTest(final boolean succeed) throws Throwable {
+        final List<Integer> receivedScrollToPositions = new ArrayList<>();
+        final TestAdapter testAdapter = new TestAdapter(200);
+        final AtomicBoolean mTargetFound = new AtomicBoolean(false);
+        TestLayoutManager tlm = new TestLayoutManager() {
+            int pendingScrollPosition = -1;
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                final int pos = pendingScrollPosition < 0 ? 0: pendingScrollPosition;
+                layoutRange(recycler, pos, pos + 10);
+                if (layoutLatch != null) {
+                    layoutLatch.countDown();
+                }
+            }
+
+            @Override
+            public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
+                    final int position) {
+                RecyclerView.SmoothScroller ss =
+                        new LinearSmoothScroller(recyclerView.getContext()) {
+                            @Override
+                            public PointF computeScrollVectorForPosition(int targetPosition) {
+                                return new PointF(0, 1);
+                            }
+
+                            @Override
+                            protected void onTargetFound(View targetView, RecyclerView.State state,
+                                                         Action action) {
+                                super.onTargetFound(targetView, state, action);
+                                mTargetFound.set(true);
+                            }
+
+                            @Override
+                            protected void updateActionForInterimTarget(Action action) {
+                                int limit = succeed ? getTargetPosition() : 100;
+                                if (pendingScrollPosition + 2 < limit) {
+                                    if (pendingScrollPosition != NO_POSITION) {
+                                        assertEquals(pendingScrollPosition,
+                                                getChildViewHolderInt(getChildAt(0))
+                                                        .getAdapterPosition());
+                                    }
+                                    action.jumpTo(pendingScrollPosition + 2);
+                                }
+                            }
+                        };
+                ss.setTargetPosition(position);
+                startSmoothScroll(ss);
+            }
+
+            @Override
+            public void scrollToPosition(int position) {
+                receivedScrollToPositions.add(position);
+                pendingScrollPosition = position;
+                requestLayout();
+            }
+        };
+        final RecyclerView rv = new RecyclerView(getActivity());
+        rv.setAdapter(testAdapter);
+        rv.setLayoutManager(tlm);
+
+        tlm.expectLayouts(1);
+        setRecyclerView(rv);
+        tlm.waitForLayout(2);
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                rv.smoothScrollToPosition(150);
+            }
+        });
+        int limit = 100;
+        while (rv.getLayoutManager().isSmoothScrolling() && --limit > 0) {
+            Thread.sleep(200);
+            checkForMainThreadException();
+        }
+        checkForMainThreadException();
+        assertTrue(limit > 0);
+        for (int i = 1; i < 100; i+=2) {
+            assertTrue("scroll positions must include " + i, receivedScrollToPositions.contains(i));
+        }
+
+        assertEquals(succeed, mTargetFound.get());
+
+    }
+
     private static class TestViewHolder2 extends RecyclerView.ViewHolder {
 
         Object mData;