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