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 package android.support.v7.widget;
17 
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.assertNotSame;
22 import static org.junit.Assert.assertTrue;
23 
24 import android.content.Context;
25 import android.graphics.Canvas;
26 import android.util.AttributeSet;
27 import android.util.Log;
28 import android.view.View;
29 
30 import java.util.ArrayList;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Set;
34 import java.util.concurrent.CountDownLatch;
35 import java.util.concurrent.TimeUnit;
36 
37 /**
38  * Base class for animation related tests.
39  */
40 public class BaseRecyclerViewAnimationsTest extends BaseRecyclerViewInstrumentationTest {
41 
42     protected static final boolean DEBUG = false;
43 
44     protected static final String TAG = "RecyclerViewAnimationsTest";
45 
46     AnimationLayoutManager mLayoutManager;
47 
48     TestAdapter mTestAdapter;
49 
BaseRecyclerViewAnimationsTest()50     public BaseRecyclerViewAnimationsTest() {
51         super(DEBUG);
52     }
53 
setupBasic(int itemCount)54     RecyclerView setupBasic(int itemCount) throws Throwable {
55         return setupBasic(itemCount, 0, itemCount);
56     }
57 
setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount)58     RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount)
59             throws Throwable {
60         return setupBasic(itemCount, firstLayoutStartIndex, firstLayoutItemCount, null);
61     }
62 
setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount, TestAdapter testAdapter)63     RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount,
64             TestAdapter testAdapter)
65             throws Throwable {
66         final TestRecyclerView recyclerView = new TestRecyclerView(getActivity());
67         recyclerView.setHasFixedSize(true);
68         if (testAdapter == null) {
69             mTestAdapter = new TestAdapter(itemCount);
70         } else {
71             mTestAdapter = testAdapter;
72         }
73         recyclerView.setAdapter(mTestAdapter);
74         recyclerView.setItemAnimator(createItemAnimator());
75         mLayoutManager = new AnimationLayoutManager();
76         recyclerView.setLayoutManager(mLayoutManager);
77         mLayoutManager.mOnLayoutCallbacks.mLayoutMin = firstLayoutStartIndex;
78         mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = firstLayoutItemCount;
79 
80         mLayoutManager.expectLayouts(1);
81         recyclerView.expectDraw(1);
82         setRecyclerView(recyclerView);
83         mLayoutManager.waitForLayout(2);
84         recyclerView.waitForDraw(1);
85         mLayoutManager.mOnLayoutCallbacks.reset();
86         getInstrumentation().waitForIdleSync();
87         checkForMainThreadException();
88         assertEquals("extra layouts should not happen", 1, mLayoutManager.getTotalLayoutCount());
89         assertEquals("all expected children should be laid out", firstLayoutItemCount,
90                 mLayoutManager.getChildCount());
91         return recyclerView;
92     }
93 
createItemAnimator()94     protected RecyclerView.ItemAnimator createItemAnimator() {
95         return new DefaultItemAnimator();
96     }
97 
getTestRecyclerView()98     public TestRecyclerView getTestRecyclerView() {
99         return (TestRecyclerView) mRecyclerView;
100     }
101 
102     class AnimationLayoutManager extends TestLayoutManager {
103 
104         protected int mTotalLayoutCount = 0;
105         private String log;
106 
107         OnLayoutCallbacks mOnLayoutCallbacks = new OnLayoutCallbacks() {
108         };
109 
110 
111 
112         @Override
supportsPredictiveItemAnimations()113         public boolean supportsPredictiveItemAnimations() {
114             return true;
115         }
116 
getLog()117         public String getLog() {
118             return log;
119         }
120 
prepareLog(RecyclerView.Recycler recycler, RecyclerView.State state, boolean done)121         private String prepareLog(RecyclerView.Recycler recycler, RecyclerView.State state, boolean done) {
122             StringBuilder builder = new StringBuilder();
123             builder.append("is pre layout:").append(state.isPreLayout()).append(", done:").append(done);
124             builder.append("\nViewHolders:\n");
125             for (RecyclerView.ViewHolder vh : ((TestRecyclerView)mRecyclerView).collectViewHolders()) {
126                 builder.append(vh).append("\n");
127             }
128             builder.append("scrap:\n");
129             for (RecyclerView.ViewHolder vh : recycler.getScrapList()) {
130                 builder.append(vh).append("\n");
131             }
132 
133             if (state.isPreLayout() && !done) {
134                 log = "\n" + builder.toString();
135             } else {
136                 log += "\n" + builder.toString();
137             }
138             return log;
139         }
140 
141         @Override
expectLayouts(int count)142         public void expectLayouts(int count) {
143             super.expectLayouts(count);
144             mOnLayoutCallbacks.mLayoutCount = 0;
145         }
146 
setOnLayoutCallbacks(OnLayoutCallbacks onLayoutCallbacks)147         public void setOnLayoutCallbacks(OnLayoutCallbacks onLayoutCallbacks) {
148             mOnLayoutCallbacks = onLayoutCallbacks;
149         }
150 
151         @Override
onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)152         public final void onLayoutChildren(RecyclerView.Recycler recycler,
153                 RecyclerView.State state) {
154             try {
155                 mTotalLayoutCount++;
156                 prepareLog(recycler, state, false);
157                 if (state.isPreLayout()) {
158                     validateOldPositions(recycler, state);
159                 } else {
160                     validateClearedOldPositions(recycler, state);
161                 }
162                 mOnLayoutCallbacks.onLayoutChildren(recycler, this, state);
163                 prepareLog(recycler, state, true);
164             } finally {
165                 layoutLatch.countDown();
166             }
167         }
168 
validateClearedOldPositions(RecyclerView.Recycler recycler, RecyclerView.State state)169         private void validateClearedOldPositions(RecyclerView.Recycler recycler,
170                 RecyclerView.State state) {
171             if (getTestRecyclerView() == null) {
172                 return;
173             }
174             for (RecyclerView.ViewHolder viewHolder : getTestRecyclerView().collectViewHolders()) {
175                 assertEquals("there should NOT be an old position in post layout",
176                         RecyclerView.NO_POSITION, viewHolder.mOldPosition);
177                 assertEquals("there should NOT be a pre layout position in post layout",
178                         RecyclerView.NO_POSITION, viewHolder.mPreLayoutPosition);
179             }
180         }
181 
validateOldPositions(RecyclerView.Recycler recycler, RecyclerView.State state)182         private void validateOldPositions(RecyclerView.Recycler recycler,
183                 RecyclerView.State state) {
184             if (getTestRecyclerView() == null) {
185                 return;
186             }
187             for (RecyclerView.ViewHolder viewHolder : getTestRecyclerView().collectViewHolders()) {
188                 if (!viewHolder.isRemoved() && !viewHolder.isInvalid()) {
189                     assertTrue("there should be an old position in pre-layout",
190                             viewHolder.mOldPosition != RecyclerView.NO_POSITION);
191                 }
192             }
193         }
194 
getTotalLayoutCount()195         public int getTotalLayoutCount() {
196             return mTotalLayoutCount;
197         }
198 
199         @Override
canScrollVertically()200         public boolean canScrollVertically() {
201             return true;
202         }
203 
204         @Override
scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state)205         public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
206                 RecyclerView.State state) {
207             mOnLayoutCallbacks.onScroll(dy, recycler, state);
208             return super.scrollVerticallyBy(dy, recycler, state);
209         }
210 
onPostDispatchLayout()211         public void onPostDispatchLayout() {
212             mOnLayoutCallbacks.postDispatchLayout();
213         }
214     }
215 
216     abstract class OnLayoutCallbacks {
217 
218         int mLayoutMin = Integer.MIN_VALUE;
219 
220         int mLayoutItemCount = Integer.MAX_VALUE;
221 
222         int expectedPreLayoutItemCount = -1;
223 
224         int expectedPostLayoutItemCount = -1;
225 
226         int mDeletedViewCount;
227 
228         int mLayoutCount = 0;
229 
setExpectedItemCounts(int preLayout, int postLayout)230         void setExpectedItemCounts(int preLayout, int postLayout) {
231             expectedPreLayoutItemCount = preLayout;
232             expectedPostLayoutItemCount = postLayout;
233         }
234 
reset()235         void reset() {
236             mLayoutMin = Integer.MIN_VALUE;
237             mLayoutItemCount = Integer.MAX_VALUE;
238             expectedPreLayoutItemCount = -1;
239             expectedPostLayoutItemCount = -1;
240             mLayoutCount = 0;
241         }
242 
beforePreLayout(RecyclerView.Recycler recycler, AnimationLayoutManager lm, RecyclerView.State state)243         void beforePreLayout(RecyclerView.Recycler recycler,
244                 AnimationLayoutManager lm, RecyclerView.State state) {
245             mDeletedViewCount = 0;
246             for (int i = 0; i < lm.getChildCount(); i++) {
247                 View v = lm.getChildAt(i);
248                 if (lm.getLp(v).isItemRemoved()) {
249                     mDeletedViewCount++;
250                 }
251             }
252         }
253 
doLayout(RecyclerView.Recycler recycler, AnimationLayoutManager lm, RecyclerView.State state)254         void doLayout(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
255                 RecyclerView.State state) {
256             if (DEBUG) {
257                 Log.d(TAG, "item count " + state.getItemCount());
258             }
259             lm.detachAndScrapAttachedViews(recycler);
260             final int start = mLayoutMin == Integer.MIN_VALUE ? 0 : mLayoutMin;
261             final int count = mLayoutItemCount
262                     == Integer.MAX_VALUE ? state.getItemCount() : mLayoutItemCount;
263             lm.layoutRange(recycler, start, start + count);
264             assertEquals("correct # of children should be laid out",
265                     count, lm.getChildCount());
266             lm.assertVisibleItemPositions();
267         }
268 
assertNoPreLayoutPosition(RecyclerView.Recycler recycler)269         private void assertNoPreLayoutPosition(RecyclerView.Recycler recycler) {
270             for (RecyclerView.ViewHolder vh : recycler.mAttachedScrap) {
271                 assertPreLayoutPosition(vh);
272             }
273         }
274 
assertNoPreLayoutPosition(RecyclerView.LayoutManager lm)275         private void assertNoPreLayoutPosition(RecyclerView.LayoutManager lm) {
276             for (int i = 0; i < lm.getChildCount(); i ++) {
277                 final RecyclerView.ViewHolder vh = mRecyclerView
278                         .getChildViewHolder(lm.getChildAt(i));
279                 assertPreLayoutPosition(vh);
280             }
281         }
282 
assertPreLayoutPosition(RecyclerView.ViewHolder vh)283         private void assertPreLayoutPosition(RecyclerView.ViewHolder vh) {
284             assertEquals("in post layout, there should not be a view holder w/ a pre "
285                     + "layout position", RecyclerView.NO_POSITION, vh.mPreLayoutPosition);
286             assertEquals("in post layout, there should not be a view holder w/ an old "
287                     + "layout position", RecyclerView.NO_POSITION, vh.mOldPosition);
288         }
289 
onLayoutChildren(RecyclerView.Recycler recycler, AnimationLayoutManager lm, RecyclerView.State state)290         void onLayoutChildren(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
291                 RecyclerView.State state) {
292             if (state.isPreLayout()) {
293                 if (expectedPreLayoutItemCount != -1) {
294                     assertEquals("on pre layout, state should return abstracted adapter size",
295                             expectedPreLayoutItemCount, state.getItemCount());
296                 }
297                 beforePreLayout(recycler, lm, state);
298             } else {
299                 if (expectedPostLayoutItemCount != -1) {
300                     assertEquals("on post layout, state should return real adapter size",
301                             expectedPostLayoutItemCount, state.getItemCount());
302                 }
303                 beforePostLayout(recycler, lm, state);
304             }
305             if (!state.isPreLayout()) {
306                 assertNoPreLayoutPosition(recycler);
307             }
308             doLayout(recycler, lm, state);
309             if (state.isPreLayout()) {
310                 afterPreLayout(recycler, lm, state);
311             } else {
312                 afterPostLayout(recycler, lm, state);
313                 assertNoPreLayoutPosition(lm);
314             }
315             mLayoutCount++;
316         }
317 
afterPreLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager, RecyclerView.State state)318         void afterPreLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
319                 RecyclerView.State state) {
320         }
321 
beforePostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager, RecyclerView.State state)322         void beforePostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
323                 RecyclerView.State state) {
324         }
325 
afterPostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager, RecyclerView.State state)326         void afterPostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
327                 RecyclerView.State state) {
328         }
329 
postDispatchLayout()330         void postDispatchLayout() {
331         }
332 
onScroll(int dx, RecyclerView.Recycler recycler, RecyclerView.State state)333         public void onScroll(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
334 
335         }
336     }
337 
338     class TestRecyclerView extends RecyclerView {
339 
340         CountDownLatch drawLatch;
341 
TestRecyclerView(Context context)342         public TestRecyclerView(Context context) {
343             super(context);
344         }
345 
TestRecyclerView(Context context, AttributeSet attrs)346         public TestRecyclerView(Context context, AttributeSet attrs) {
347             super(context, attrs);
348         }
349 
TestRecyclerView(Context context, AttributeSet attrs, int defStyle)350         public TestRecyclerView(Context context, AttributeSet attrs, int defStyle) {
351             super(context, attrs, defStyle);
352         }
353 
354         @Override
initAdapterManager()355         void initAdapterManager() {
356             super.initAdapterManager();
357             mAdapterHelper.mOnItemProcessedCallback = new Runnable() {
358                 @Override
359                 public void run() {
360                     validatePostUpdateOp();
361                 }
362             };
363         }
364 
365         @Override
isAccessibilityEnabled()366         boolean isAccessibilityEnabled() {
367             return true;
368         }
369 
expectDraw(int count)370         public void expectDraw(int count) {
371             drawLatch = new CountDownLatch(count);
372         }
373 
waitForDraw(long timeout)374         public void waitForDraw(long timeout) throws Throwable {
375             drawLatch.await(timeout * (DEBUG ? 100 : 1), TimeUnit.SECONDS);
376             assertEquals("all expected draws should happen at the expected time frame",
377                     0, drawLatch.getCount());
378         }
379 
collectViewHolders()380         List<ViewHolder> collectViewHolders() {
381             List<ViewHolder> holders = new ArrayList<ViewHolder>();
382             final int childCount = getChildCount();
383             for (int i = 0; i < childCount; i++) {
384                 ViewHolder holder = getChildViewHolderInt(getChildAt(i));
385                 if (holder != null) {
386                     holders.add(holder);
387                 }
388             }
389             return holders;
390         }
391 
392 
validateViewHolderPositions()393         private void validateViewHolderPositions() {
394             final Set<Integer> existingOffsets = new HashSet<Integer>();
395             int childCount = getChildCount();
396             StringBuilder log = new StringBuilder();
397             for (int i = 0; i < childCount; i++) {
398                 ViewHolder vh = getChildViewHolderInt(getChildAt(i));
399                 TestViewHolder tvh = (TestViewHolder) vh;
400                 log.append(tvh.mBoundItem).append(vh)
401                         .append(" hidden:")
402                         .append(mChildHelper.mHiddenViews.contains(vh.itemView))
403                         .append("\n");
404             }
405             for (int i = 0; i < childCount; i++) {
406                 ViewHolder vh = getChildViewHolderInt(getChildAt(i));
407                 if (vh.isInvalid()) {
408                     continue;
409                 }
410                 if (vh.getLayoutPosition() < 0) {
411                     LayoutManager lm = getLayoutManager();
412                     for (int j = 0; j < lm.getChildCount(); j ++) {
413                         assertNotSame("removed view holder should not be in LM's child list",
414                                 vh.itemView, lm.getChildAt(j));
415                     }
416                 } else if (!mChildHelper.mHiddenViews.contains(vh.itemView)) {
417                     if (!existingOffsets.add(vh.getLayoutPosition())) {
418                         throw new IllegalStateException("view holder position conflict for "
419                                 + "existing views " + vh + "\n" + log);
420                     }
421                 }
422             }
423         }
424 
validatePostUpdateOp()425         void validatePostUpdateOp() {
426             try {
427                 validateViewHolderPositions();
428                 if (super.mState.isPreLayout()) {
429                     validatePreLayoutSequence((AnimationLayoutManager) getLayoutManager());
430                 }
431                 validateAdapterPosition((AnimationLayoutManager) getLayoutManager());
432             } catch (Throwable t) {
433                 postExceptionToInstrumentation(t);
434             }
435         }
436 
437 
438 
validateAdapterPosition(AnimationLayoutManager lm)439         private void validateAdapterPosition(AnimationLayoutManager lm) {
440             for (ViewHolder vh : collectViewHolders()) {
441                 if (!vh.isRemoved() && vh.mPreLayoutPosition >= 0) {
442                     assertEquals("adapter position calculations should match view holder "
443                                     + "pre layout:" + mState.isPreLayout()
444                                     + " positions\n" + vh + "\n" + lm.getLog(),
445                             mAdapterHelper.findPositionOffset(vh.mPreLayoutPosition), vh.mPosition);
446                 }
447             }
448         }
449 
450         // ensures pre layout positions are continuous block. This is not necessarily a case
451         // but valid in test RV
validatePreLayoutSequence(AnimationLayoutManager lm)452         private void validatePreLayoutSequence(AnimationLayoutManager lm) {
453             Set<Integer> preLayoutPositions = new HashSet<Integer>();
454             for (ViewHolder vh : collectViewHolders()) {
455                 assertTrue("pre layout positions should be distinct " + lm.getLog(),
456                         preLayoutPositions.add(vh.mPreLayoutPosition));
457             }
458             int minPos = Integer.MAX_VALUE;
459             for (Integer pos : preLayoutPositions) {
460                 if (pos < minPos) {
461                     minPos = pos;
462                 }
463             }
464             for (int i = 1; i < preLayoutPositions.size(); i++) {
465                 assertNotNull("next position should exist " + lm.getLog(),
466                         preLayoutPositions.contains(minPos + i));
467             }
468         }
469 
470         @Override
dispatchDraw(Canvas canvas)471         protected void dispatchDraw(Canvas canvas) {
472             super.dispatchDraw(canvas);
473             if (drawLatch != null) {
474                 drawLatch.countDown();
475             }
476         }
477 
478         @Override
dispatchLayout()479         void dispatchLayout() {
480             try {
481                 super.dispatchLayout();
482                 if (getLayoutManager() instanceof AnimationLayoutManager) {
483                     ((AnimationLayoutManager) getLayoutManager()).onPostDispatchLayout();
484                 }
485             } catch (Throwable t) {
486                 postExceptionToInstrumentation(t);
487             }
488 
489         }
490 
491 
492     }
493 
494     abstract class AdapterOps {
495 
run(TestAdapter adapter)496         final public void run(TestAdapter adapter) throws Throwable {
497             onRun(adapter);
498         }
499 
onRun(TestAdapter testAdapter)500         abstract void onRun(TestAdapter testAdapter) throws Throwable;
501     }
502 
503     static class CollectPositionResult {
504 
505         // true if found in scrap
506         public RecyclerView.ViewHolder scrapResult;
507 
508         public RecyclerView.ViewHolder adapterResult;
509 
fromScrap(RecyclerView.ViewHolder viewHolder)510         static CollectPositionResult fromScrap(RecyclerView.ViewHolder viewHolder) {
511             CollectPositionResult cpr = new CollectPositionResult();
512             cpr.scrapResult = viewHolder;
513             return cpr;
514         }
515 
fromAdapter(RecyclerView.ViewHolder viewHolder)516         static CollectPositionResult fromAdapter(RecyclerView.ViewHolder viewHolder) {
517             CollectPositionResult cpr = new CollectPositionResult();
518             cpr.adapterResult = viewHolder;
519             return cpr;
520         }
521 
522         @Override
toString()523         public String toString() {
524             return "CollectPositionResult{" +
525                     "scrapResult=" + scrapResult +
526                     ", adapterResult=" + adapterResult +
527                     '}';
528         }
529     }
530 
531     static class PositionConstraint {
532 
533         public static enum Type {
534             scrap,
535             adapter,
536             adapterScrap /*first pass adapter, second pass scrap*/
537         }
538 
539         Type mType;
540 
541         int mOldPos; // if VH
542 
543         int mPreLayoutPos;
544 
545         int mPostLayoutPos;
546 
547         int mValidateCount = 0;
548 
scrap(int oldPos, int preLayoutPos, int postLayoutPos)549         public static PositionConstraint scrap(int oldPos, int preLayoutPos, int postLayoutPos) {
550             PositionConstraint constraint = new PositionConstraint();
551             constraint.mType = Type.scrap;
552             constraint.mOldPos = oldPos;
553             constraint.mPreLayoutPos = preLayoutPos;
554             constraint.mPostLayoutPos = postLayoutPos;
555             return constraint;
556         }
557 
adapterScrap(int preLayoutPos, int position)558         public static PositionConstraint adapterScrap(int preLayoutPos, int position) {
559             PositionConstraint constraint = new PositionConstraint();
560             constraint.mType = Type.adapterScrap;
561             constraint.mOldPos = RecyclerView.NO_POSITION;
562             constraint.mPreLayoutPos = preLayoutPos;
563             constraint.mPostLayoutPos = position;// adapter pos does not change
564             return constraint;
565         }
566 
adapter(int position)567         public static PositionConstraint adapter(int position) {
568             PositionConstraint constraint = new PositionConstraint();
569             constraint.mType = Type.adapter;
570             constraint.mPreLayoutPos = RecyclerView.NO_POSITION;
571             constraint.mOldPos = RecyclerView.NO_POSITION;
572             constraint.mPostLayoutPos = position;// adapter pos does not change
573             return constraint;
574         }
575 
assertValidate()576         public void assertValidate() {
577             int expectedValidate = 0;
578             if (mPreLayoutPos >= 0) {
579                 expectedValidate ++;
580             }
581             if (mPostLayoutPos >= 0) {
582                 expectedValidate ++;
583             }
584             assertEquals("should run all validates", expectedValidate, mValidateCount);
585         }
586 
587         @Override
toString()588         public String toString() {
589             return "Cons{" +
590                     "t=" + mType.name() +
591                     ", old=" + mOldPos +
592                     ", pre=" + mPreLayoutPos +
593                     ", post=" + mPostLayoutPos +
594                     '}';
595         }
596 
validate(RecyclerView.State state, CollectPositionResult result, String log)597         public void validate(RecyclerView.State state, CollectPositionResult result, String log) {
598             mValidateCount ++;
599             assertNotNull(this + ": result should not be null\n" + log, result);
600             RecyclerView.ViewHolder viewHolder;
601             if (mType == Type.scrap || (mType == Type.adapterScrap && !state.isPreLayout())) {
602                 assertNotNull(this + ": result should come from scrap\n" + log, result.scrapResult);
603                 viewHolder = result.scrapResult;
604             } else {
605                 assertNotNull(this + ": result should come from adapter\n"  + log,
606                         result.adapterResult);
607                 assertEquals(this + ": old position should be none when it came from adapter\n" + log,
608                         RecyclerView.NO_POSITION, result.adapterResult.getOldPosition());
609                 viewHolder = result.adapterResult;
610             }
611             if (state.isPreLayout()) {
612                 assertEquals(this + ": pre-layout position should match\n" + log, mPreLayoutPos,
613                         viewHolder.mPreLayoutPosition == -1 ? viewHolder.mPosition :
614                                 viewHolder.mPreLayoutPosition);
615                 assertEquals(this + ": pre-layout getPosition should match\n" + log, mPreLayoutPos,
616                         viewHolder.getLayoutPosition());
617                 if (mType == Type.scrap) {
618                     assertEquals(this + ": old position should match\n" + log, mOldPos,
619                             result.scrapResult.getOldPosition());
620                 }
621             } else if (mType == Type.adapter || mType == Type.adapterScrap || !result.scrapResult
622                     .isRemoved()) {
623                 assertEquals(this + ": post-layout position should match\n" + log + "\n\n"
624                         + viewHolder, mPostLayoutPos, viewHolder.getLayoutPosition());
625             }
626         }
627     }
628 
629     static class LoggingInfo extends RecyclerView.ItemAnimator.ItemHolderInfo {
630         final RecyclerView.ViewHolder viewHolder;
631         @RecyclerView.ItemAnimator.AdapterChanges
632         final int changeFlags;
633         final List<Object> payloads;
634 
LoggingInfo(RecyclerView.ViewHolder viewHolder, int changeFlags, List<Object> payloads)635         LoggingInfo(RecyclerView.ViewHolder viewHolder, int changeFlags, List<Object> payloads) {
636             this.viewHolder = viewHolder;
637             this.changeFlags = changeFlags;
638             if (payloads != null) {
639                 this.payloads = new ArrayList<>();
640                 this.payloads.addAll(payloads);
641             } else {
642                 this.payloads = null;
643             }
644             setFrom(viewHolder);
645         }
646 
647         @Override
toString()648         public String toString() {
649             return "LoggingInfo{" +
650                     "changeFlags=" + changeFlags +
651                     ", payloads=" + payloads +
652                     '}';
653         }
654     }
655 
656     static class AnimateChange extends AnimateLogBase {
657 
658         final RecyclerView.ViewHolder newHolder;
659 
AnimateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, LoggingInfo pre, LoggingInfo post)660         public AnimateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
661                 LoggingInfo pre, LoggingInfo post) {
662             super(oldHolder, pre, post);
663             this.newHolder = newHolder;
664         }
665     }
666 
667     static class AnimatePersistence extends AnimateLogBase {
668 
AnimatePersistence(RecyclerView.ViewHolder viewHolder, LoggingInfo pre, LoggingInfo post)669         public AnimatePersistence(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
670                 LoggingInfo post) {
671             super(viewHolder, pre, post);
672         }
673     }
674 
675     static class AnimateAppearance extends AnimateLogBase {
AnimateAppearance(RecyclerView.ViewHolder viewHolder, LoggingInfo pre, LoggingInfo post)676         public AnimateAppearance(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
677                 LoggingInfo post) {
678             super(viewHolder, pre, post);
679         }
680     }
681 
682     static class AnimateDisappearance extends AnimateLogBase {
AnimateDisappearance(RecyclerView.ViewHolder viewHolder, LoggingInfo pre, LoggingInfo post)683         public AnimateDisappearance(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
684                 LoggingInfo post) {
685             super(viewHolder, pre, post);
686         }
687     }
688     static class AnimateLogBase {
689 
690         public final RecyclerView.ViewHolder viewHolder;
691         public final LoggingInfo preInfo;
692         public final LoggingInfo postInfo;
693 
AnimateLogBase(RecyclerView.ViewHolder viewHolder, LoggingInfo pre, LoggingInfo postInfo)694         public AnimateLogBase(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
695                 LoggingInfo postInfo) {
696             this.viewHolder = viewHolder;
697             this.preInfo = pre;
698             this.postInfo = postInfo;
699         }
700 
log()701         public String log() {
702             return getClass().getSimpleName() + "[" +  log(preInfo) + " - " + log(postInfo) + "]";
703         }
704 
log(LoggingInfo info)705         public String log(LoggingInfo info) {
706             return info == null ? "null" : info.toString();
707         }
708 
709         @Override
equals(Object o)710         public boolean equals(Object o) {
711             if (this == o) {
712                 return true;
713             }
714             if (o == null || getClass() != o.getClass()) {
715                 return false;
716             }
717 
718             AnimateLogBase that = (AnimateLogBase) o;
719 
720             if (viewHolder != null ? !viewHolder.equals(that.viewHolder)
721                     : that.viewHolder != null) {
722                 return false;
723             }
724             if (preInfo != null ? !preInfo.equals(that.preInfo) : that.preInfo != null) {
725                 return false;
726             }
727             return !(postInfo != null ? !postInfo.equals(that.postInfo) : that.postInfo != null);
728 
729         }
730 
731         @Override
hashCode()732         public int hashCode() {
733             int result = viewHolder != null ? viewHolder.hashCode() : 0;
734             result = 31 * result + (preInfo != null ? preInfo.hashCode() : 0);
735             result = 31 * result + (postInfo != null ? postInfo.hashCode() : 0);
736             return result;
737         }
738     }
739 }
740