1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v7.widget.helper;
18
19import static android.support.v7.widget.helper.ItemTouchHelper.*;
20
21import static org.junit.Assert.*;
22
23import android.os.Build;
24import android.support.test.filters.SdkSuppress;
25import android.support.test.runner.AndroidJUnit4;
26import android.support.v4.view.ViewCompat;
27import android.support.v7.util.TouchUtils;
28import android.support.v7.widget.BaseRecyclerViewInstrumentationTest;
29import android.support.v7.widget.RecyclerView;
30import android.support.v7.widget.WrappedRecyclerView;
31import android.test.suitebuilder.annotation.MediumTest;
32import android.view.Gravity;
33import android.view.View;
34
35import org.junit.Test;
36import org.junit.runner.RunWith;
37
38import java.util.ArrayList;
39import java.util.List;
40
41@MediumTest
42@RunWith(AndroidJUnit4.class)
43public class ItemTouchHelperTest extends BaseRecyclerViewInstrumentationTest {
44
45    TestAdapter mAdapter;
46
47    TestLayoutManager mLayoutManager;
48
49    private LoggingCalback mCalback;
50
51    private LoggingItemTouchHelper mItemTouchHelper;
52
53    private WrappedRecyclerView mWrappedRecyclerView;
54
55    private Boolean mSetupRTL;
56
57    public ItemTouchHelperTest() {
58        super(false);
59    }
60
61    private RecyclerView setup(int dragDirs, int swipeDirs) throws Throwable {
62        mWrappedRecyclerView = inflateWrappedRV();
63        mAdapter = new TestAdapter(10);
64        mLayoutManager = new TestLayoutManager() {
65            @Override
66            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
67                detachAndScrapAttachedViews(recycler);
68                layoutRange(recycler, 0, Math.min(5, state.getItemCount()));
69                layoutLatch.countDown();
70            }
71
72            @Override
73            public boolean canScrollHorizontally() {
74                return false;
75            }
76
77            @Override
78            public boolean supportsPredictiveItemAnimations() {
79                return false;
80            }
81        };
82        mWrappedRecyclerView.setFakeRTL(mSetupRTL);
83        mWrappedRecyclerView.setAdapter(mAdapter);
84        mWrappedRecyclerView.setLayoutManager(mLayoutManager);
85        mCalback = new LoggingCalback(dragDirs, swipeDirs);
86        mItemTouchHelper = new LoggingItemTouchHelper(mCalback);
87        runTestOnUiThread(new Runnable() {
88            @Override
89            public void run() {
90                mItemTouchHelper.attachToRecyclerView(mWrappedRecyclerView);
91            }
92        });
93
94        return mWrappedRecyclerView;
95    }
96
97    @Test
98    public void swipeLeft() throws Throwable {
99        basicSwipeTest(LEFT, LEFT | RIGHT, -getActivity().getWindow().getDecorView().getWidth());
100    }
101
102    @Test
103    public void swipeRight() throws Throwable {
104        basicSwipeTest(RIGHT, LEFT | RIGHT, getActivity().getWindow().getDecorView().getWidth());
105    }
106
107    @Test
108    public void swipeStart() throws Throwable {
109        basicSwipeTest(START, START | END, -getActivity().getWindow().getDecorView().getWidth());
110    }
111
112    @Test
113    public void swipeEnd() throws Throwable {
114        basicSwipeTest(END, START | END, getActivity().getWindow().getDecorView().getWidth());
115    }
116
117    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1)
118    @Test
119    public void swipeStartInRTL() throws Throwable {
120        mSetupRTL = true;
121        basicSwipeTest(START, START | END, getActivity().getWindow().getDecorView().getWidth());
122    }
123
124    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1)
125    @Test
126    public void swipeEndInRTL() throws Throwable {
127        mSetupRTL = true;
128        basicSwipeTest(END, START | END, -getActivity().getWindow().getDecorView().getWidth());
129    }
130
131    private void setLayoutDirection(final View view, final int layoutDir) throws Throwable {
132        runTestOnUiThread(new Runnable() {
133            @Override
134            public void run() {
135                ViewCompat.setLayoutDirection(view, layoutDir);
136            }
137        });
138    }
139
140    public void basicSwipeTest(int dir, int swipeDirs, int targetX) throws Throwable {
141        final RecyclerView recyclerView = setup(0, swipeDirs);
142        mLayoutManager.expectLayouts(1);
143        setRecyclerView(recyclerView);
144        mLayoutManager.waitForLayout(1);
145
146        final RecyclerView.ViewHolder target = mRecyclerView
147                .findViewHolderForAdapterPosition(1);
148        TouchUtils.dragViewToX(getInstrumentation(), target.itemView, Gravity.CENTER, targetX);
149        Thread.sleep(100); //wait for animation end
150        final SwipeRecord swipe = mCalback.getSwipe(target);
151        assertNotNull(swipe);
152        assertEquals(dir, swipe.dir);
153        assertEquals(1, mItemTouchHelper.mRecoverAnimations.size());
154        assertEquals(1, mItemTouchHelper.mPendingCleanup.size());
155        // get rid of the view
156        mLayoutManager.expectLayouts(1);
157        mAdapter.deleteAndNotify(1, 1);
158        mLayoutManager.waitForLayout(1);
159        waitForAnimations();
160        assertEquals(0, mItemTouchHelper.mRecoverAnimations.size());
161        assertEquals(0, mItemTouchHelper.mPendingCleanup.size());
162        assertTrue(mCalback.isCleared(target));
163    }
164
165    private void waitForAnimations() throws InterruptedException {
166        while (mRecyclerView.getItemAnimator().isRunning()) {
167            Thread.sleep(100);
168        }
169    }
170
171    private static class LoggingCalback extends SimpleCallback {
172
173        private List<MoveRecord> mMoveRecordList = new ArrayList<MoveRecord>();
174
175        private List<SwipeRecord> mSwipeRecords = new ArrayList<SwipeRecord>();
176
177        private List<RecyclerView.ViewHolder> mCleared = new ArrayList<RecyclerView.ViewHolder>();
178
179        LoggingCalback(int dragDirs, int swipeDirs) {
180            super(dragDirs, swipeDirs);
181        }
182
183        @Override
184        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
185                RecyclerView.ViewHolder target) {
186            mMoveRecordList.add(new MoveRecord(viewHolder, target));
187            return true;
188        }
189
190        @Override
191        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
192            mSwipeRecords.add(new SwipeRecord(viewHolder, direction));
193        }
194
195        public MoveRecord getMove(RecyclerView.ViewHolder vh) {
196            for (MoveRecord move : mMoveRecordList) {
197                if (move.from == vh) {
198                    return move;
199                }
200            }
201            return null;
202        }
203
204        @Override
205        public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
206            super.clearView(recyclerView, viewHolder);
207            mCleared.add(viewHolder);
208        }
209
210        public SwipeRecord getSwipe(RecyclerView.ViewHolder vh) {
211            for (SwipeRecord swipe : mSwipeRecords) {
212                if (swipe.viewHolder == vh) {
213                    return swipe;
214                }
215            }
216            return null;
217        }
218
219        public boolean isCleared(RecyclerView.ViewHolder vh) {
220            return mCleared.contains(vh);
221        }
222    }
223
224    private static class LoggingItemTouchHelper extends ItemTouchHelper {
225
226        public LoggingItemTouchHelper(Callback callback) {
227            super(callback);
228        }
229    }
230
231    private static class SwipeRecord {
232
233        RecyclerView.ViewHolder viewHolder;
234
235        int dir;
236
237        public SwipeRecord(RecyclerView.ViewHolder viewHolder, int dir) {
238            this.viewHolder = viewHolder;
239            this.dir = dir;
240        }
241    }
242
243    private static class MoveRecord {
244
245        final int fromPos, toPos;
246
247        RecyclerView.ViewHolder from, to;
248
249        MoveRecord(RecyclerView.ViewHolder from, RecyclerView.ViewHolder to) {
250            this.from = from;
251            this.to = to;
252            fromPos = from.getAdapterPosition();
253            toPos = to.getAdapterPosition();
254        }
255    }
256}
257