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