AdapterHelperTest.java revision a910619e83d0052e1d81aa5fe532821a2f99d76c
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 junit.framework.AssertionFailedError;
20import junit.framework.TestResult;
21
22import android.test.AndroidTestCase;
23import android.util.Log;
24import android.widget.TextView;
25
26import java.util.ArrayList;
27import java.util.LinkedList;
28import java.util.List;
29import java.util.Queue;
30import java.util.Random;
31import java.util.concurrent.atomic.AtomicInteger;
32
33import static android.support.v7.widget.RecyclerView.*;
34
35public class AdapterHelperTest extends AndroidTestCase {
36
37    private static final boolean DEBUG = false;
38
39    private boolean mCollectLogs = false;
40
41    private static final String TAG = "AHT";
42
43    List<ViewHolder> mViewHolders;
44
45    AdapterHelper mAdapterHelper;
46
47    List<AdapterHelper.UpdateOp> mFirstPassUpdates, mSecondPassUpdates;
48
49    TestAdapter mTestAdapter;
50
51    TestAdapter mPreProcessClone; // we clone adapter pre-process to run operations to see result
52
53    private List<TestAdapter.Item> mPreLayoutItems;
54
55    private StringBuilder mLog = new StringBuilder();
56
57    @Override
58    protected void setUp() throws Exception {
59        cleanState();
60    }
61
62    @Override
63    public void run(TestResult result) {
64        super.run(result);
65        if (!result.wasSuccessful()) {
66            result.addFailure(this, new AssertionFailedError(mLog.toString()));
67        }
68    }
69
70    private void cleanState() {
71        mLog.setLength(0);
72        mPreLayoutItems = new ArrayList<TestAdapter.Item>();
73        mViewHolders = new ArrayList<ViewHolder>();
74        mFirstPassUpdates = new ArrayList<AdapterHelper.UpdateOp>();
75        mSecondPassUpdates = new ArrayList<AdapterHelper.UpdateOp>();
76        mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
77            @Override
78            public RecyclerView.ViewHolder findViewHolder(int position) {
79                for (ViewHolder vh : mViewHolders) {
80                    if (vh.mPosition == position && !vh.isRemoved()) {
81                        return vh;
82                    }
83                }
84                return null;
85            }
86
87            @Override
88            public void offsetPositionsForRemovingInvisible(int positionStart, int itemCount) {
89                final int positionEnd = positionStart + itemCount;
90                for (ViewHolder holder : mViewHolders) {
91                    if (holder.mPosition >= positionEnd) {
92                        holder.offsetPosition(-itemCount, true);
93                    } else if (holder.mPosition >= positionStart) {
94                        holder.addFlags(ViewHolder.FLAG_REMOVED);
95                        holder.offsetPosition(-itemCount, true);
96                    }
97                }
98            }
99
100            @Override
101            public void offsetPositionsForRemovingLaidOutOrNewView(int positionStart,
102                    int itemCount) {
103                final int positionEnd = positionStart + itemCount;
104                for (ViewHolder holder : mViewHolders) {
105                    if (holder.mPosition >= positionEnd) {
106                        holder.offsetPosition(-itemCount, false);
107                    } else if (holder.mPosition >= positionStart) {
108                        holder.addFlags(ViewHolder.FLAG_REMOVED);
109                        holder.offsetPosition(-itemCount, false);
110                    }
111                }
112            }
113
114            @Override
115            public void markViewHoldersUpdated(int positionStart, int itemCount) {
116                final int positionEnd = positionStart + itemCount;
117                for (ViewHolder holder : mViewHolders) {
118                    if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
119                        holder.addFlags(ViewHolder.FLAG_UPDATE);
120                    }
121                }
122            }
123
124            @Override
125            public void onDispatchFirstPass(AdapterHelper.UpdateOp updateOp) {
126                if (DEBUG) {
127                    log("first pass:" + updateOp.toString());
128                }
129                for (ViewHolder viewHolder : mViewHolders) {
130                    for (int i = 0; i < updateOp.itemCount; i ++) {
131                        assertFalse("update op should not match any existing view holders",
132                                viewHolder.getPosition() == updateOp.positionStart + i);
133                    }
134                }
135
136                mFirstPassUpdates.add(updateOp);
137            }
138
139            @Override
140            public void onDispatchSecondPass(AdapterHelper.UpdateOp updateOp) {
141                if (DEBUG) {
142                    log("second pass:" + updateOp.toString());
143                }
144                mSecondPassUpdates.add(updateOp);
145            }
146
147            @Override
148            public void offsetPositionsForAdd(int positionStart, int itemCount) {
149                for (ViewHolder holder : mViewHolders) {
150                    if (holder != null && holder.mPosition >= positionStart) {
151                        holder.offsetPosition(itemCount, false);
152                    }
153                }
154            }
155
156            @Override
157            public void offsetPositionsForMove(int from, int to) {
158                final int start, end, inBetweenOffset;
159                if (from < to) {
160                    start = from;
161                    end = to;
162                    inBetweenOffset = -1;
163                } else {
164                    start = to;
165                    end = from;
166                    inBetweenOffset = 1;
167                }
168                for (ViewHolder holder : mViewHolders) {
169                    if (holder == null || holder.mPosition < start || holder.mPosition > end) {
170                        continue;
171                    }
172                    holder.offsetPosition(inBetweenOffset, false);
173                }
174            }
175        }, true) {
176            @Override
177            void createFakeAddForRemovedMove(int adapterIndex, int pendingUpdateIndex) {
178                int addsBefore = 0;
179                for (int i = 0; i < pendingUpdateIndex; i ++) {
180                    final UpdateOp updateOp = mPendingUpdates.get(i);
181                    if (updateOp.cmd == UpdateOp.ADD) {
182                        addsBefore += updateOp.itemCount;
183                    }
184                }
185                mTestAdapter.createFakeItemAt(addsBefore);
186                super.createFakeAddForRemovedMove(adapterIndex, pendingUpdateIndex);
187            }
188        };
189    }
190
191    void log(String msg) {
192        if (mCollectLogs) {
193            mLog.append(msg).append("\n");
194        } else {
195            Log.d(TAG, msg);
196        }
197    }
198
199    void setupBasic(int count, int visibleStart, int visibleCount) {
200        if (DEBUG) {
201            log("setupBasic(" + count + "," + visibleStart + "," + visibleCount + ");");
202        }
203        mTestAdapter = new TestAdapter(count, mAdapterHelper);
204        for (int i = 0; i < visibleCount; i++) {
205            addViewHolder(visibleStart + i);
206        }
207        mPreProcessClone = mTestAdapter.createCopy();
208    }
209
210    private void addViewHolder(int posiiton) {
211        ViewHolder viewHolder = new RecyclerViewBasicTest.MockViewHolder(
212                new TextView(getContext()));
213        viewHolder.mPosition = posiiton;
214        mViewHolders.add(viewHolder);
215    }
216
217    public void testFindPositionOffsetInPreLayout() {
218        setupBasic(50, 25, 10);
219        rm(24, 5);
220        mAdapterHelper.preProcess();
221        // since 25 is invisible, we offset by one while checking
222        assertEquals("find position for view 23",
223                23, mAdapterHelper.findPositionOffset(23));
224        assertEquals("find position for view 24",
225                -1, mAdapterHelper.findPositionOffset(24));
226        assertEquals("find position for view 25",
227                -1, mAdapterHelper.findPositionOffset(25));
228        assertEquals("find position for view 26",
229                -1, mAdapterHelper.findPositionOffset(26));
230        assertEquals("find position for view 27",
231                -1, mAdapterHelper.findPositionOffset(27));
232        assertEquals("find position for view 28",
233                24, mAdapterHelper.findPositionOffset(28));
234        assertEquals("find position for view 29",
235                25, mAdapterHelper.findPositionOffset(29));
236    }
237
238    public void testSinglePass() {
239        setupBasic(10, 2, 3);
240        add(2, 1);
241        rm(1, 2);
242        add(1, 5);
243        mAdapterHelper.consumeUpdatesInOnePass();
244        assertDispatch(0, 3);
245    }
246
247    public void testDeleteVisible() {
248        setupBasic(10, 2, 3);
249        rm(2, 1);
250        preProcess();
251        assertDispatch(0, 1);
252    }
253
254    public void testDeleteInvisible() {
255        setupBasic(10, 3, 4);
256        rm(2, 1);
257        preProcess();
258        assertDispatch(1, 0);
259    }
260
261    public void testAddCount() {
262        setupBasic(0, 0, 0);
263        add(0, 1);
264        assertEquals(1, mAdapterHelper.mPendingUpdates.size());
265    }
266
267    public void testDeleteCount() {
268        setupBasic(1, 0, 0);
269        rm(0, 1);
270        assertEquals(1, mAdapterHelper.mPendingUpdates.size());
271    }
272
273    public void testAddProcess() {
274        setupBasic(0, 0, 0);
275        add(0, 1);
276        preProcess();
277        assertEquals(0, mAdapterHelper.mPendingUpdates.size());
278    }
279
280    public void testAddRemoveSeparate() {
281        setupBasic(10, 2, 2);
282        add(6, 1);
283        rm(5, 1);
284        preProcess();
285        assertDispatch(1, 1);
286    }
287
288    public void testScenario1() {
289        setupBasic(10, 3, 2);
290        rm(4, 1);
291        rm(3, 1);
292        rm(3, 1);
293        preProcess();
294        assertDispatch(1, 2);
295    }
296
297    public void testDivideDelete() {
298        setupBasic(10, 3, 4);
299        rm(2, 2);
300        preProcess();
301        assertDispatch(1, 1);
302    }
303
304    public void testScenario2() {
305        setupBasic(10, 3, 3); // 3-4-5
306        add(4, 2); // 3 a b 4 5
307        rm(0, 1); // (0) 3(2) a(3) b(4) 4(3) 5(4)
308        rm(1, 3); // (1,2) (x) a(1) b(2) 4(3)
309        preProcess();
310        assertDispatch(2, 2);
311    }
312
313    public void testScenario3() {
314        setupBasic(10, 2, 2);
315        rm(0, 5);
316        preProcess();
317        assertDispatch(2, 1);
318        assertOps(mFirstPassUpdates, rmOp(0, 2), rmOp(2, 1));
319        assertOps(mSecondPassUpdates, rmOp(0, 2));
320    }
321    // TODO test MOVE then remove items in between.
322    // TODO test MOVE then remove it, make sure it is not dispatched
323
324    public void testScenario4() {
325        setupBasic(5, 0, 5);
326        // 0 1 2 3 4
327        // 0 1 2 a b 3 4
328        // 0 2 a b 3 4
329        // 0 c d 2 a b 3 4
330        // 0 c d 2 a 4
331        // c d 2 a 4
332        // pre: 0 1 2 3 4
333        add(3, 2);
334        rm(1, 1);
335        add(1, 2);
336        rm(5, 2);
337        rm(0, 1);
338        preProcess();
339    }
340
341    public void testScenario5() {
342        setupBasic(5, 0, 5);
343        // 0 1 2 3 4
344        // 0 1 2 a b 3 4
345        // 0 1 b 3 4
346        // pre: 0 1 2 3 4
347        // pre w/ adap: 0 1 2 b 3 4
348        add(3, 2);
349        rm(2, 2);
350        preProcess();
351    }
352
353    public void testScenario6() {
354//        setupBasic(47, 19, 24);
355//        mv(11, 12);
356//        add(24, 16);
357//        rm(9, 3);
358        setupBasic(10, 5, 3);
359        mv(2, 3);
360        add(6, 4);
361        rm(4, 1);
362        preProcess();
363    }
364
365    public void testScenario8() {
366        setupBasic(68, 51, 13);
367        mv(22, 11);
368        mv(22, 52);
369        rm(37, 19);
370        add(12, 38);
371        preProcess();
372    }
373
374    public void testScenario9() {
375        setupBasic(44, 3, 7);
376        add(7, 21);
377        rm(31, 3);
378        rm(32, 11);
379        mv(29, 5);
380        mv(30, 32);
381        add(25, 32);
382        rm(15, 66);
383        preProcess();
384    }
385
386    public void testScenario10() {
387        setupBasic(14, 10, 3);
388        rm(4, 4);
389        add(5, 11);
390        mv(5, 18);
391        rm(2, 9);
392        preProcess();
393    }
394
395    public void testScenario11() {
396        setupBasic(78, 3, 64);
397        mv(34, 28);
398        add(1, 11);
399        rm(9, 74);
400        preProcess();
401    }
402
403    public void testScenario12() {
404        setupBasic(38, 9, 7);
405        rm(26, 3);
406        mv(29, 15);
407        rm(30, 1);
408        preProcess();
409    }
410
411    public void testScenario13() {
412        setupBasic(49, 41, 3);
413        rm(30, 13);
414        add(4, 10);
415        mv(3, 38);
416        mv(20, 17);
417        rm(18, 23);
418        preProcess();
419    }
420
421    public void testScenario14() {
422        setupBasic(24, 3, 11);
423        rm(2, 15);
424        mv(2, 1);
425        add(2, 34);
426        add(11, 3);
427        rm(10, 25);
428        rm(13, 6);
429        rm(4, 4);
430        rm(6, 4);
431        preProcess();
432    }
433
434    public void testScenario15() {
435        setupBasic(10, 8, 1);
436        mv(6, 1);
437        mv(1, 4);
438        rm(3, 1);
439        preProcess();
440    }
441
442    public void testScenario16() {
443        setupBasic(10, 3, 3);
444        rm(2, 1);
445        rm(1, 7);
446        rm(0, 1);
447        preProcess();
448    }
449
450    public void testScenario17() {
451        setupBasic(10, 8, 1);
452        mv(1, 0);
453        mv(5, 1);
454        rm(1, 7);
455        preProcess();
456    }
457
458    public void testScenario18() throws InterruptedException {
459        setupBasic(10, 1, 4);
460        add(2, 11);
461        rm(16, 1);
462        add(3, 1);
463        rm(9, 10);
464        preProcess();
465    }
466
467    public void testScenario19() {
468        setupBasic(10,8,1);
469        mv(9,7);
470        mv(9,3);
471        rm(5,4);
472        preProcess();
473    }
474
475    public void testScenario20() {
476        setupBasic(10,7,1);
477        mv(9,1);
478        mv(3,9);
479        rm(7,2);
480        preProcess();
481    }
482
483    public void testScenario21() {
484        setupBasic(10,5,2);
485        mv(1,0);
486        mv(9,1);
487        rm(2,3);
488        preProcess();
489    }
490
491    public void testScenario22() {
492        setupBasic(10,7,2);
493        add(2,16);
494        mv(20,9);
495        rm(17,6);
496        preProcess();
497    }
498
499    public void testScenario23() {
500        setupBasic(10,5,3);
501        mv(9,6);
502        add(4,15);
503        rm(21,3);
504        preProcess();
505    }
506
507    public void testScenario24() {
508        setupBasic(10,1,6);
509        add(6,5);
510        mv(14,6);
511        rm(7,6);
512        preProcess();
513    }
514
515    public void testScenario25() {
516        setupBasic(10,3,4);
517        mv(3,9);
518        mv(2,9);
519        rm(5,4);
520        preProcess();
521    }
522
523    public void testScenario26() {
524        setupBasic(10,4,4);
525        rm(3,5);
526        mv(2,0);
527        mv(1,0);
528        rm(1,1);
529        mv(0,2);
530        preProcess();
531    }
532
533    public void testScenario27() {
534        setupBasic(10,0,3);
535        mv(9,4);
536        mv(8,4);
537        add(7,6);
538        rm(5,5);
539        preProcess();
540    }
541
542    public void testScenerio28() {
543        setupBasic(10,4,1);
544        mv(8,6);
545        rm(8,1);
546        mv(7,5);
547        rm(3,3);
548        rm(1,4);
549        preProcess();
550    }
551
552    public void testMoveAdded() {
553        setupBasic(10, 2, 2);
554        add(3, 5);
555        mv(4, 2);
556        preProcess();
557    }
558
559    public void testRandom() throws Throwable {
560        mCollectLogs = true;
561        Random random = new Random(System.nanoTime());
562        for (int i = 0; i < 1000; i++) {
563            try {
564                randomTest(random, i + 10);
565            } catch (Throwable t) {
566                throw new Throwable(t.getMessage() + "\n" + mLog.toString(), t);
567            }
568        }
569    }
570
571    public void randomTest(Random random, int opCount) {
572        cleanState();
573        if (DEBUG) {
574            log("randomTest");
575        }
576        final int count = 10;// + random.nextInt(100);
577        final int start = random.nextInt(count - 1);
578        final int layoutCount = Math.max(1, random.nextInt(count - start));
579        setupBasic(count, start, layoutCount);
580
581        while (opCount-- > 0) {
582            final int op = random.nextInt(3);
583            switch (op) {
584                case 0:
585                    if (mTestAdapter.mItems.size() > 1) {
586                        int s = random.nextInt(mTestAdapter.mItems.size() - 1);
587                        int len = Math.max(1, random.nextInt(mTestAdapter.mItems.size() - s));
588                        rm(s, len);
589                    }
590                    break;
591                case 1:
592                    int s = mTestAdapter.mItems.size() == 0 ? 0 :
593                            random.nextInt(mTestAdapter.mItems.size());
594                    add(s, random.nextInt(50));
595                    break;
596                case 2:
597                    if (mTestAdapter.mItems.size() >= 2) {
598                        int from = random.nextInt(mTestAdapter.mItems.size());
599                        int to;
600                        do {
601                            to = random.nextInt(mTestAdapter.mItems.size());
602                        } while (to == from);
603                        mv(from, to);
604                    }
605            }
606        }
607        preProcess();
608    }
609
610    public void assertOps(List<AdapterHelper.UpdateOp> actual,
611            AdapterHelper.UpdateOp... expected) {
612        assertEquals(expected.length, actual.size());
613        for (int i = 0; i < expected.length; i++) {
614            assertEquals(expected[i], actual.get(i));
615        }
616    }
617
618    void assertDispatch(int firstPass, int secondPass) {
619        assertEquals(firstPass, mFirstPassUpdates.size());
620        assertEquals(secondPass, mSecondPassUpdates.size());
621    }
622
623    void preProcess() {
624        mAdapterHelper.preProcess();
625        for (int i = 0; i < mPreProcessClone.mItems.size(); i++) {
626            TestAdapter.Item item = mPreProcessClone.mItems.get(i);
627            final int preLayoutIndex = mPreLayoutItems.indexOf(item);
628            final int endIndex = mTestAdapter.mItems.indexOf(item);
629            if (preLayoutIndex != -1) {
630                assertEquals("find position offset should work properly for existing elements" + i
631                        + " at pre layout position " + preLayoutIndex + " and post layout position "
632                        + endIndex, endIndex, mAdapterHelper.findPositionOffset(preLayoutIndex));
633            }
634        }
635        mAdapterHelper.consumePostponedUpdates();
636        // now assert these two adapters have identical data.
637        mPreProcessClone.applyOps(mFirstPassUpdates, mTestAdapter);
638        mPreProcessClone.applyOps(mSecondPassUpdates, mTestAdapter);
639        assertAdaptersEqual(mTestAdapter, mPreProcessClone);
640    }
641
642    private void assertAdaptersEqual(TestAdapter a1, TestAdapter a2) {
643        assertEquals(a1.mItems.size(), a2.mItems.size());
644        for (int i = 0; i < a1.mItems.size(); i++) {
645            TestAdapter.Item item = a1.mItems.get(i);
646            assertSame(item, a2.mItems.get(i));
647            assertEquals(0, item.getUpdateCount());
648        }
649        assertEquals(0, a1.mPendingAdded.size());
650        assertEquals(0, a2.mPendingAdded.size());
651    }
652
653    AdapterHelper.UpdateOp op(int cmd, int start, int count) {
654        return new AdapterHelper.UpdateOp(cmd, start, count);
655    }
656
657    AdapterHelper.UpdateOp addOp(int start, int count) {
658        return op(AdapterHelper.UpdateOp.ADD, start, count);
659    }
660
661    AdapterHelper.UpdateOp rmOp(int start, int count) {
662        return op(AdapterHelper.UpdateOp.REMOVE, start, count);
663    }
664
665    AdapterHelper.UpdateOp upOp(int start, int count) {
666        return op(AdapterHelper.UpdateOp.UPDATE, start, count);
667    }
668
669    void add(int start, int count) {
670        if (DEBUG) {
671            log("add(" + start + "," + count + ");");
672        }
673        mTestAdapter.add(start, count);
674    }
675
676    boolean isItemLaidOut(int pos) {
677        for (ViewHolder viewHolder : mViewHolders) {
678            if (viewHolder.mOldPosition == pos) {
679                return true;
680            }
681        }
682        return false;
683    }
684
685    private void mv(int from, int to) {
686        if (DEBUG) {
687            log("mv(" + from + "," + to + ");");
688        }
689        mTestAdapter.move(from, to);
690    }
691
692    void rm(int start, int count) {
693        if (DEBUG) {
694            log("rm(" + start + "," + count + ");");
695        }
696        for (int i = start; i < start + count; i++) {
697            if (!isItemLaidOut(i)) {
698                TestAdapter.Item item = mTestAdapter.mItems.get(i);
699                mPreLayoutItems.remove(item);
700            }
701        }
702        mTestAdapter.remove(start, count);
703    }
704
705    void up(int start, int count) {
706        mTestAdapter.update(start, count);
707    }
708
709    static class TestAdapter {
710
711        List<Item> mItems;
712
713        final AdapterHelper mAdapterHelper;
714
715        Queue<Item> mPendingAdded;
716
717        public TestAdapter(int initialCount, AdapterHelper container) {
718            mItems = new ArrayList<Item>();
719            mAdapterHelper = container;
720            mPendingAdded = new LinkedList<Item>();
721            for (int i = 0; i < initialCount; i++) {
722                mItems.add(new Item());
723            }
724        }
725
726        public void add(int index, int count) {
727            for (int i = 0; i < count; i++) {
728                Item item = new Item();
729                mPendingAdded.add(item);
730                mItems.add(index + i, item);
731            }
732            mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp(
733                    AdapterHelper.UpdateOp.ADD, index, count
734            ));
735        }
736
737        public void move(int from, int to) {
738            mItems.add(to, mItems.remove(from));
739            mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp(
740                    AdapterHelper.UpdateOp.MOVE, from, to
741            ));
742        }
743        public void remove(int index, int count) {
744            for (int i = 0; i < count; i++) {
745                mItems.remove(index);
746            }
747            mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp(
748                    AdapterHelper.UpdateOp.REMOVE, index, count
749            ));
750        }
751
752        public void update(int index, int count) {
753            for (int i = 0; i < count; i++) {
754                mItems.get(index + i).update();
755            }
756            mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp(
757                    AdapterHelper.UpdateOp.UPDATE, index, count
758            ));
759        }
760
761        protected TestAdapter createCopy() {
762            TestAdapter adapter = new TestAdapter(0, mAdapterHelper);
763            for (Item item : mItems) {
764                adapter.mItems.add(item);
765            }
766            return adapter;
767        }
768
769        public void applyOps(List<AdapterHelper.UpdateOp> updates,
770                TestAdapter dataSource) {
771            for (AdapterHelper.UpdateOp op : updates) {
772                switch (op.cmd) {
773                    case AdapterHelper.UpdateOp.ADD:
774                        for (int i = 0; i < op.itemCount; i++) {
775                            mItems.add(op.positionStart + i, dataSource.consumeNextAdded());
776                        }
777                        break;
778                    case AdapterHelper.UpdateOp.REMOVE:
779                        for (int i = 0; i < op.itemCount; i++) {
780                            mItems.remove(op.positionStart);
781                        }
782                        break;
783                    case AdapterHelper.UpdateOp.UPDATE:
784                        for (int i = 0; i < op.itemCount; i++) {
785                            mItems.get(i).handleUpdate();
786                        }
787                        break;
788                    case AdapterHelper.UpdateOp.MOVE:
789                        mItems.add(op.itemCount, mItems.remove(op.positionStart));
790                        break;
791                }
792            }
793        }
794
795        private Item consumeNextAdded() {
796            return mPendingAdded.remove();
797        }
798
799        public void createFakeItemAt(int fakeAddedItemIndex) {
800            Item fakeItem = new Item();
801            ((LinkedList<Item>)mPendingAdded).add(fakeAddedItemIndex, fakeItem);
802        }
803
804        public static class Item {
805
806            private static AtomicInteger itemCounter = new AtomicInteger();
807
808            private final int id;
809
810            private int mVersionCount = 0;
811
812            private int mUpdateCount;
813
814            public Item() {
815                id = itemCounter.incrementAndGet();
816            }
817
818            public void update() {
819                mVersionCount++;
820            }
821
822            public void handleUpdate() {
823                mVersionCount--;
824            }
825
826            public int getUpdateCount() {
827                return mUpdateCount;
828            }
829        }
830    }
831}
832