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