1/* 2 * Copyright (C) 2014 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; 18 19import org.junit.After; 20import org.junit.Before; 21import org.junit.Test; 22import org.junit.runner.RunWith; 23 24import android.os.Looper; 25import android.support.test.runner.AndroidJUnit4; 26import android.test.ActivityInstrumentationTestCase2; 27import android.test.suitebuilder.annotation.MediumTest; 28import android.util.Log; 29import android.view.View; 30import android.view.ViewGroup; 31import android.widget.TextView; 32 33import java.util.ArrayList; 34import java.util.Arrays; 35import java.util.HashSet; 36import java.util.List; 37import java.util.Set; 38import java.util.concurrent.CountDownLatch; 39import java.util.concurrent.Semaphore; 40import java.util.concurrent.TimeUnit; 41import java.util.concurrent.locks.Lock; 42import java.util.concurrent.locks.ReentrantLock; 43import static org.junit.Assert.*; 44 45@MediumTest 46@RunWith(AndroidJUnit4.class) 47public class DefaultItemAnimatorTest extends BaseRecyclerViewInstrumentationTest { 48 49 private static final String TAG = "DefaultItemAnimatorTest"; 50 Throwable mainThreadException; 51 52 DefaultItemAnimator mAnimator; 53 Adapter mAdapter; 54 ViewGroup mDummyParent; 55 List<RecyclerView.ViewHolder> mExpectedItems = new ArrayList<RecyclerView.ViewHolder>(); 56 57 Set<RecyclerView.ViewHolder> mRemoveFinished = new HashSet<RecyclerView.ViewHolder>(); 58 Set<RecyclerView.ViewHolder> mAddFinished = new HashSet<RecyclerView.ViewHolder>(); 59 Set<RecyclerView.ViewHolder> mMoveFinished = new HashSet<RecyclerView.ViewHolder>(); 60 Set<RecyclerView.ViewHolder> mChangeFinished = new HashSet<RecyclerView.ViewHolder>(); 61 62 Semaphore mExpectedItemCount = new Semaphore(0); 63 64 @Before 65 public void setUp() throws Exception { 66 mAnimator = new DefaultItemAnimator() { 67 @Override 68 public void onRemoveFinished(RecyclerView.ViewHolder item) { 69 try { 70 assertTrue(mRemoveFinished.add(item)); 71 onFinished(item); 72 } catch (Throwable t) { 73 postExceptionToInstrumentation(t); 74 } 75 } 76 77 @Override 78 public void onAddFinished(RecyclerView.ViewHolder item) { 79 try { 80 assertTrue(mAddFinished.add(item)); 81 onFinished(item); 82 } catch (Throwable t) { 83 postExceptionToInstrumentation(t); 84 } 85 } 86 87 @Override 88 public void onMoveFinished(RecyclerView.ViewHolder item) { 89 try { 90 assertTrue(mMoveFinished.add(item)); 91 onFinished(item); 92 } catch (Throwable t) { 93 postExceptionToInstrumentation(t); 94 } 95 } 96 97 @Override 98 public void onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) { 99 try { 100 assertTrue(mChangeFinished.add(item)); 101 onFinished(item); 102 } catch (Throwable t) { 103 postExceptionToInstrumentation(t); 104 } 105 } 106 107 private void onFinished(RecyclerView.ViewHolder item) { 108 assertNotNull(mExpectedItems.remove(item)); 109 mExpectedItemCount.release(1); 110 } 111 }; 112 mAdapter = new Adapter(20); 113 mDummyParent = getActivity().getContainer(); 114 } 115 116 void checkForMainThreadException() throws Throwable { 117 if (mainThreadException != null) { 118 throw mainThreadException; 119 } 120 } 121 122 @Test 123 public void reUseWithPayload() { 124 RecyclerView.ViewHolder vh = new ViewHolder(new TextView(getActivity())); 125 assertFalse(mAnimator.canReuseUpdatedViewHolder(vh, new ArrayList<>())); 126 assertTrue(mAnimator.canReuseUpdatedViewHolder(vh, Arrays.asList((Object) "a"))); 127 } 128 129 void expectItems(RecyclerView.ViewHolder... viewHolders) { 130 mExpectedItems.addAll(Arrays.asList(viewHolders)); 131 } 132 133 void runAndWait(int itemCount, int seconds) throws Throwable { 134 runAndWait(itemCount, seconds, null); 135 } 136 137 void runAndWait(int itemCount, int seconds, final ThrowingRunnable postRun) throws Throwable { 138 runTestOnUiThread(new Runnable() { 139 @Override 140 public void run() { 141 mAnimator.runPendingAnimations(); 142 if (postRun != null) { 143 try { 144 postRun.run(); 145 } catch (Throwable e) { 146 throw new RuntimeException(e); 147 } 148 } 149 } 150 }); 151 waitForItems(itemCount, seconds); 152 checkForMainThreadException(); 153 } 154 155 void waitForItems(int itemCount, int seconds) throws InterruptedException { 156 assertTrue("all vh animations should end", 157 mExpectedItemCount.tryAcquire(itemCount, seconds, TimeUnit.SECONDS)); 158 assertEquals("all expected finish events should happen", 0, mExpectedItems.size()); 159 // wait one more second for unwanted 160 assertFalse("should not receive any more permits", 161 mExpectedItemCount.tryAcquire(1, 2, TimeUnit.SECONDS)); 162 } 163 164 @Test 165 public void animateAdd() throws Throwable { 166 ViewHolder vh = createViewHolder(1); 167 expectItems(vh); 168 assertTrue(animateAdd(vh)); 169 assertTrue(mAnimator.isRunning()); 170 runAndWait(1, 1); 171 } 172 173 @Test 174 public void animateRemove() throws Throwable { 175 ViewHolder vh = createViewHolder(1); 176 expectItems(vh); 177 assertTrue(animateRemove(vh)); 178 assertTrue(mAnimator.isRunning()); 179 runAndWait(1, 1); 180 } 181 182 @Test 183 public void animateMove() throws Throwable { 184 ViewHolder vh = createViewHolder(1); 185 expectItems(vh); 186 assertTrue(animateMove(vh, 0, 0, 100, 100)); 187 assertTrue(mAnimator.isRunning()); 188 runAndWait(1, 1); 189 } 190 191 @Test 192 public void animateChange() throws Throwable { 193 ViewHolder vh = createViewHolder(1); 194 ViewHolder vh2 = createViewHolder(2); 195 expectItems(vh, vh2); 196 assertTrue(animateChange(vh, vh2, 0, 0, 100, 100)); 197 assertTrue(mAnimator.isRunning()); 198 runAndWait(2, 1); 199 } 200 201 public void cancelBefore(int count, final RecyclerView.ViewHolder... toCancel) 202 throws Throwable { 203 cancelTest(true, count, toCancel); 204 } 205 206 public void cancelAfter(int count, final RecyclerView.ViewHolder... toCancel) 207 throws Throwable { 208 cancelTest(false, count, toCancel); 209 } 210 211 public void cancelTest(boolean before, int count, final RecyclerView.ViewHolder... toCancel) throws Throwable { 212 if (before) { 213 endAnimations(toCancel); 214 runAndWait(count, 1); 215 } else { 216 runAndWait(count, 1, new ThrowingRunnable() { 217 @Override 218 public void run() throws Throwable { 219 endAnimations(toCancel); 220 } 221 }); 222 } 223 } 224 225 @Test 226 public void cancelAddBefore() throws Throwable { 227 final ViewHolder vh = createViewHolder(1); 228 expectItems(vh); 229 assertTrue(animateAdd(vh)); 230 cancelBefore(1, vh); 231 } 232 233 @Test 234 public void cancelAddAfter() throws Throwable { 235 final ViewHolder vh = createViewHolder(1); 236 expectItems(vh); 237 assertTrue(animateAdd(vh)); 238 cancelAfter(1, vh); 239 } 240 241 @Test 242 public void cancelMoveBefore() throws Throwable { 243 ViewHolder vh = createViewHolder(1); 244 expectItems(vh); 245 assertTrue(animateMove(vh, 10, 10, 100, 100)); 246 cancelBefore(1, vh); 247 } 248 249 @Test 250 public void cancelMoveAfter() throws Throwable { 251 ViewHolder vh = createViewHolder(1); 252 expectItems(vh); 253 assertTrue(animateMove(vh, 10, 10, 100, 100)); 254 cancelAfter(1, vh); 255 } 256 257 @Test 258 public void cancelRemove() throws Throwable { 259 ViewHolder vh = createViewHolder(1); 260 expectItems(vh); 261 assertTrue(animateRemove(vh)); 262 endAnimations(vh); 263 runAndWait(1, 1); 264 } 265 266 @Test 267 public void cancelChangeOldBefore() throws Throwable { 268 cancelChangeOldTest(true); 269 } 270 @Test 271 public void cancelChangeOldAfter() throws Throwable { 272 cancelChangeOldTest(false); 273 } 274 275 public void cancelChangeOldTest(boolean before) throws Throwable { 276 ViewHolder vh = createViewHolder(1); 277 ViewHolder vh2 = createViewHolder(1); 278 expectItems(vh, vh2); 279 assertTrue(animateChange(vh, vh2, 20, 20, 100, 100)); 280 cancelTest(before, 2, vh); 281 } 282 283 @Test 284 public void cancelChangeNewBefore() throws Throwable { 285 cancelChangeNewTest(true); 286 } 287 288 @Test 289 public void cancelChangeNewAfter() throws Throwable { 290 cancelChangeNewTest(false); 291 } 292 293 public void cancelChangeNewTest(boolean before) throws Throwable { 294 ViewHolder vh = createViewHolder(1); 295 ViewHolder vh2 = createViewHolder(1); 296 expectItems(vh, vh2); 297 assertTrue(animateChange(vh, vh2, 20, 20, 100, 100)); 298 cancelTest(before, 2, vh2); 299 } 300 301 @Test 302 public void cancelChangeBothBefore() throws Throwable { 303 cancelChangeBothTest(true); 304 } 305 306 @Test 307 public void cancelChangeBothAfter() throws Throwable { 308 cancelChangeBothTest(false); 309 } 310 311 public void cancelChangeBothTest(boolean before) throws Throwable { 312 ViewHolder vh = createViewHolder(1); 313 ViewHolder vh2 = createViewHolder(1); 314 expectItems(vh, vh2); 315 assertTrue(animateChange(vh, vh2, 20, 20, 100, 100)); 316 cancelTest(before, 2, vh, vh2); 317 } 318 319 void endAnimations(final RecyclerView.ViewHolder... vhs) throws Throwable { 320 runTestOnUiThread(new Runnable() { 321 @Override 322 public void run() { 323 for (RecyclerView.ViewHolder vh : vhs) { 324 mAnimator.endAnimation(vh); 325 } 326 } 327 }); 328 } 329 330 boolean animateAdd(final RecyclerView.ViewHolder vh) throws Throwable { 331 final boolean[] result = new boolean[1]; 332 runTestOnUiThread(new Runnable() { 333 @Override 334 public void run() { 335 result[0] = mAnimator.animateAdd(vh); 336 } 337 }); 338 return result[0]; 339 } 340 341 boolean animateRemove(final RecyclerView.ViewHolder vh) throws Throwable { 342 final boolean[] result = new boolean[1]; 343 runTestOnUiThread(new Runnable() { 344 @Override 345 public void run() { 346 result[0] = mAnimator.animateRemove(vh); 347 } 348 }); 349 return result[0]; 350 } 351 352 boolean animateMove(final RecyclerView.ViewHolder vh, final int fromX, final int fromY, 353 final int toX, final int toY) throws Throwable { 354 final boolean[] result = new boolean[1]; 355 runTestOnUiThread(new Runnable() { 356 @Override 357 public void run() { 358 result[0] = mAnimator.animateMove(vh, fromX, fromY, toX, toY); 359 } 360 }); 361 return result[0]; 362 } 363 364 boolean animateChange(final RecyclerView.ViewHolder oldHolder, 365 final RecyclerView.ViewHolder newHolder, 366 final int fromX, final int fromY, final int toX, final int toY) throws Throwable { 367 final boolean[] result = new boolean[1]; 368 runTestOnUiThread(new Runnable() { 369 @Override 370 public void run() { 371 result[0] = mAnimator.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY); 372 } 373 }); 374 return result[0]; 375 } 376 377 private ViewHolder createViewHolder(final int pos) throws Throwable { 378 final ViewHolder vh = mAdapter.createViewHolder(mDummyParent, 1); 379 runTestOnUiThread(new Runnable() { 380 @Override 381 public void run() { 382 mAdapter.bindViewHolder(vh, pos); 383 mDummyParent.addView(vh.itemView); 384 } 385 }); 386 387 return vh; 388 } 389 390 void postExceptionToInstrumentation(Throwable t) { 391 if (mainThreadException == null) { 392 mainThreadException = t; 393 } else { 394 Log.e(TAG, "skipping secondary main thread exception", t); 395 } 396 } 397 398 399 private class Adapter extends RecyclerView.Adapter<ViewHolder> { 400 401 List<String> mItems; 402 403 private Adapter(int count) { 404 mItems = new ArrayList<>(); 405 for (int i = 0; i < count; i++) { 406 mItems.add("item-" + i); 407 } 408 } 409 410 @Override 411 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 412 return new ViewHolder(new TextView(parent.getContext())); 413 } 414 415 @Override 416 public void onBindViewHolder(ViewHolder holder, int position) { 417 holder.bind(mItems.get(position)); 418 } 419 420 @Override 421 public int getItemCount() { 422 return mItems.size(); 423 } 424 } 425 426 private class ViewHolder extends RecyclerView.ViewHolder { 427 428 String mBindedText; 429 430 public ViewHolder(View itemView) { 431 super(itemView); 432 } 433 434 public void bind(String text) { 435 mBindedText = text; 436 ((TextView) itemView).setText(text); 437 } 438 } 439 440 private interface ThrowingRunnable { 441 void run() throws Throwable; 442 } 443 444 @Override 445 public void runTestOnUiThread(Runnable r) throws Throwable { 446 if (Looper.myLooper() == Looper.getMainLooper()) { 447 r.run(); 448 } else { 449 super.runTestOnUiThread(r); 450 } 451 } 452} 453