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