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 android.support.v4.util.Pools;
20import android.util.Log;
21
22import java.util.ArrayList;
23import java.util.Collections;
24import java.util.List;
25
26import static android.support.v7.widget.RecyclerView.*;
27
28/**
29 * Helper class that can enqueue and process adapter update operations.
30 * <p>
31 * To support animations, RecyclerView presents an older version the Adapter to best represent
32 * previous state of the layout. Sometimes, this is not trivial when items are removed that were
33 * not laid out, in which case, RecyclerView has no way of providing that item's view for
34 * animations.
35 * <p>
36 * AdapterHelper creates an UpdateOp for each adapter data change then pre-processes them. During
37 * pre processing, AdapterHelper finds out which UpdateOps can be deferred to second layout pass
38 * and which cannot. For the UpdateOps that cannot be deferred, AdapterHelper will change them
39 * according to previously deferred operation and dispatch them before the first layout pass. It
40 * also takes care of updating deferred UpdateOps since order of operations is changed by this
41 * process.
42 * <p>
43 * Although operations may be forwarded to LayoutManager in different orders, resulting data set
44 * is guaranteed to be the consistent.
45 */
46class AdapterHelper implements OpReorderer.Callback {
47
48    final static int POSITION_TYPE_INVISIBLE = 0;
49
50    final static int POSITION_TYPE_NEW_OR_LAID_OUT = 1;
51
52    private static final boolean DEBUG = false;
53
54    private static final String TAG = "AHT";
55
56    private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE);
57
58    final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();
59
60    final ArrayList<UpdateOp> mPostponedList = new ArrayList<UpdateOp>();
61
62    final Callback mCallback;
63
64    Runnable mOnItemProcessedCallback;
65
66    final boolean mDisableRecycler;
67
68    final OpReorderer mOpReorderer;
69
70    AdapterHelper(Callback callback) {
71        this(callback, false);
72    }
73
74    AdapterHelper(Callback callback, boolean disableRecycler) {
75        mCallback = callback;
76        mDisableRecycler = disableRecycler;
77        mOpReorderer = new OpReorderer(this);
78    }
79
80    AdapterHelper addUpdateOp(UpdateOp... ops) {
81        Collections.addAll(mPendingUpdates, ops);
82        return this;
83    }
84
85    void reset() {
86        recycleUpdateOpsAndClearList(mPendingUpdates);
87        recycleUpdateOpsAndClearList(mPostponedList);
88    }
89
90    void preProcess() {
91        mOpReorderer.reorderOps(mPendingUpdates);
92        final int count = mPendingUpdates.size();
93        for (int i = 0; i < count; i++) {
94            UpdateOp op = mPendingUpdates.get(i);
95            switch (op.cmd) {
96                case UpdateOp.ADD:
97                    applyAdd(op);
98                    break;
99                case UpdateOp.REMOVE:
100                    applyRemove(op);
101                    break;
102                case UpdateOp.UPDATE:
103                    applyUpdate(op);
104                    break;
105                case UpdateOp.MOVE:
106                    applyMove(op);
107                    break;
108            }
109            if (mOnItemProcessedCallback != null) {
110                mOnItemProcessedCallback.run();
111            }
112        }
113        mPendingUpdates.clear();
114    }
115
116    void consumePostponedUpdates() {
117        final int count = mPostponedList.size();
118        for (int i = 0; i < count; i++) {
119            mCallback.onDispatchSecondPass(mPostponedList.get(i));
120        }
121        recycleUpdateOpsAndClearList(mPostponedList);
122    }
123
124    private void applyMove(UpdateOp op) {
125        // MOVE ops are pre-processed so at this point, we know that item is still in the adapter.
126        // otherwise, it would be converted into a REMOVE operation
127        postponeAndUpdateViewHolders(op);
128    }
129
130    private void applyRemove(UpdateOp op) {
131        int tmpStart = op.positionStart;
132        int tmpCount = 0;
133        int tmpEnd = op.positionStart + op.itemCount;
134        int type = -1;
135        for (int position = op.positionStart; position < tmpEnd; position++) {
136            boolean typeChanged = false;
137            ViewHolder vh = mCallback.findViewHolder(position);
138            if (vh != null || canFindInPreLayout(position)) {
139                // If a ViewHolder exists or this is a newly added item, we can defer this update
140                // to post layout stage.
141                // * For existing ViewHolders, we'll fake its existence in the pre-layout phase.
142                // * For items that are added and removed in the same process cycle, they won't
143                // have any effect in pre-layout since their add ops are already deferred to
144                // post-layout pass.
145                if (type == POSITION_TYPE_INVISIBLE) {
146                    // Looks like we have other updates that we cannot merge with this one.
147                    // Create an UpdateOp and dispatch it to LayoutManager.
148                    UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null);
149                    dispatchAndUpdateViewHolders(newOp);
150                    typeChanged = true;
151                }
152                type = POSITION_TYPE_NEW_OR_LAID_OUT;
153            } else {
154                // This update cannot be recovered because we don't have a ViewHolder representing
155                // this position. Instead, post it to LayoutManager immediately
156                if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
157                    // Looks like we have other updates that we cannot merge with this one.
158                    // Create UpdateOp op and dispatch it to LayoutManager.
159                    UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null);
160                    postponeAndUpdateViewHolders(newOp);
161                    typeChanged = true;
162                }
163                type = POSITION_TYPE_INVISIBLE;
164            }
165            if (typeChanged) {
166                position -= tmpCount; // also equal to tmpStart
167                tmpEnd -= tmpCount;
168                tmpCount = 1;
169            } else {
170                tmpCount++;
171            }
172        }
173        if (tmpCount != op.itemCount) { // all 1 effect
174            recycleUpdateOp(op);
175            op = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null);
176        }
177        if (type == POSITION_TYPE_INVISIBLE) {
178            dispatchAndUpdateViewHolders(op);
179        } else {
180            postponeAndUpdateViewHolders(op);
181        }
182    }
183
184    private void applyUpdate(UpdateOp op) {
185        int tmpStart = op.positionStart;
186        int tmpCount = 0;
187        int tmpEnd = op.positionStart + op.itemCount;
188        int type = -1;
189        for (int position = op.positionStart; position < tmpEnd; position++) {
190            ViewHolder vh = mCallback.findViewHolder(position);
191            if (vh != null || canFindInPreLayout(position)) { // deferred
192                if (type == POSITION_TYPE_INVISIBLE) {
193                    UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount,
194                            op.payload);
195                    dispatchAndUpdateViewHolders(newOp);
196                    tmpCount = 0;
197                    tmpStart = position;
198                }
199                type = POSITION_TYPE_NEW_OR_LAID_OUT;
200            } else { // applied
201                if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
202                    UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount,
203                            op.payload);
204                    postponeAndUpdateViewHolders(newOp);
205                    tmpCount = 0;
206                    tmpStart = position;
207                }
208                type = POSITION_TYPE_INVISIBLE;
209            }
210            tmpCount++;
211        }
212        if (tmpCount != op.itemCount) { // all 1 effect
213            Object payload = op.payload;
214            recycleUpdateOp(op);
215            op = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount, payload);
216        }
217        if (type == POSITION_TYPE_INVISIBLE) {
218            dispatchAndUpdateViewHolders(op);
219        } else {
220            postponeAndUpdateViewHolders(op);
221        }
222    }
223
224    private void dispatchAndUpdateViewHolders(UpdateOp op) {
225        // tricky part.
226        // traverse all postpones and revert their changes on this op if necessary, apply updated
227        // dispatch to them since now they are after this op.
228        if (op.cmd == UpdateOp.ADD || op.cmd == UpdateOp.MOVE) {
229            throw new IllegalArgumentException("should not dispatch add or move for pre layout");
230        }
231        if (DEBUG) {
232            Log.d(TAG, "dispatch (pre)" + op);
233            Log.d(TAG, "postponed state before:");
234            for (UpdateOp updateOp : mPostponedList) {
235                Log.d(TAG, updateOp.toString());
236            }
237            Log.d(TAG, "----");
238        }
239
240        // handle each pos 1 by 1 to ensure continuity. If it breaks, dispatch partial
241        // TODO Since move ops are pushed to end, we should not need this anymore
242        int tmpStart = updatePositionWithPostponed(op.positionStart, op.cmd);
243        if (DEBUG) {
244            Log.d(TAG, "pos:" + op.positionStart + ",updatedPos:" + tmpStart);
245        }
246        int tmpCnt = 1;
247        int offsetPositionForPartial = op.positionStart;
248        final int positionMultiplier;
249        switch (op.cmd) {
250            case UpdateOp.UPDATE:
251                positionMultiplier = 1;
252                break;
253            case UpdateOp.REMOVE:
254                positionMultiplier = 0;
255                break;
256            default:
257                throw new IllegalArgumentException("op should be remove or update." + op);
258        }
259        for (int p = 1; p < op.itemCount; p++) {
260            final int pos = op.positionStart + (positionMultiplier * p);
261            int updatedPos = updatePositionWithPostponed(pos, op.cmd);
262            if (DEBUG) {
263                Log.d(TAG, "pos:" + pos + ",updatedPos:" + updatedPos);
264            }
265            boolean continuous = false;
266            switch (op.cmd) {
267                case UpdateOp.UPDATE:
268                    continuous = updatedPos == tmpStart + 1;
269                    break;
270                case UpdateOp.REMOVE:
271                    continuous = updatedPos == tmpStart;
272                    break;
273            }
274            if (continuous) {
275                tmpCnt++;
276            } else {
277                // need to dispatch this separately
278                UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt, op.payload);
279                if (DEBUG) {
280                    Log.d(TAG, "need to dispatch separately " + tmp);
281                }
282                dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
283                recycleUpdateOp(tmp);
284                if (op.cmd == UpdateOp.UPDATE) {
285                    offsetPositionForPartial += tmpCnt;
286                }
287                tmpStart = updatedPos;// need to remove previously dispatched
288                tmpCnt = 1;
289            }
290        }
291        Object payload = op.payload;
292        recycleUpdateOp(op);
293        if (tmpCnt > 0) {
294            UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt, payload);
295            if (DEBUG) {
296                Log.d(TAG, "dispatching:" + tmp);
297            }
298            dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
299            recycleUpdateOp(tmp);
300        }
301        if (DEBUG) {
302            Log.d(TAG, "post dispatch");
303            Log.d(TAG, "postponed state after:");
304            for (UpdateOp updateOp : mPostponedList) {
305                Log.d(TAG, updateOp.toString());
306            }
307            Log.d(TAG, "----");
308        }
309    }
310
311    void dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart) {
312        mCallback.onDispatchFirstPass(op);
313        switch (op.cmd) {
314            case UpdateOp.REMOVE:
315                mCallback.offsetPositionsForRemovingInvisible(offsetStart, op.itemCount);
316                break;
317            case UpdateOp.UPDATE:
318                mCallback.markViewHoldersUpdated(offsetStart, op.itemCount, op.payload);
319                break;
320            default:
321                throw new IllegalArgumentException("only remove and update ops can be dispatched"
322                        + " in first pass");
323        }
324    }
325
326    private int updatePositionWithPostponed(int pos, int cmd) {
327        final int count = mPostponedList.size();
328        for (int i = count - 1; i >= 0; i--) {
329            UpdateOp postponed = mPostponedList.get(i);
330            if (postponed.cmd == UpdateOp.MOVE) {
331                int start, end;
332                if (postponed.positionStart < postponed.itemCount) {
333                    start = postponed.positionStart;
334                    end = postponed.itemCount;
335                } else {
336                    start = postponed.itemCount;
337                    end = postponed.positionStart;
338                }
339                if (pos >= start && pos <= end) {
340                    //i'm affected
341                    if (start == postponed.positionStart) {
342                        if (cmd == UpdateOp.ADD) {
343                            postponed.itemCount++;
344                        } else if (cmd == UpdateOp.REMOVE) {
345                            postponed.itemCount--;
346                        }
347                        // op moved to left, move it right to revert
348                        pos++;
349                    } else {
350                        if (cmd == UpdateOp.ADD) {
351                            postponed.positionStart++;
352                        } else if (cmd == UpdateOp.REMOVE) {
353                            postponed.positionStart--;
354                        }
355                        // op was moved right, move left to revert
356                        pos--;
357                    }
358                } else if (pos < postponed.positionStart) {
359                    // postponed MV is outside the dispatched OP. if it is before, offset
360                    if (cmd == UpdateOp.ADD) {
361                        postponed.positionStart++;
362                        postponed.itemCount++;
363                    } else if (cmd == UpdateOp.REMOVE) {
364                        postponed.positionStart--;
365                        postponed.itemCount--;
366                    }
367                }
368            } else {
369                if (postponed.positionStart <= pos) {
370                    if (postponed.cmd == UpdateOp.ADD) {
371                        pos -= postponed.itemCount;
372                    } else if (postponed.cmd == UpdateOp.REMOVE) {
373                        pos += postponed.itemCount;
374                    }
375                } else {
376                    if (cmd == UpdateOp.ADD) {
377                        postponed.positionStart++;
378                    } else if (cmd == UpdateOp.REMOVE) {
379                        postponed.positionStart--;
380                    }
381                }
382            }
383            if (DEBUG) {
384                Log.d(TAG, "dispath (step" + i + ")");
385                Log.d(TAG, "postponed state:" + i + ", pos:" + pos);
386                for (UpdateOp updateOp : mPostponedList) {
387                    Log.d(TAG, updateOp.toString());
388                }
389                Log.d(TAG, "----");
390            }
391        }
392        for (int i = mPostponedList.size() - 1; i >= 0; i--) {
393            UpdateOp op = mPostponedList.get(i);
394            if (op.cmd == UpdateOp.MOVE) {
395                if (op.itemCount == op.positionStart || op.itemCount < 0) {
396                    mPostponedList.remove(i);
397                    recycleUpdateOp(op);
398                }
399            } else if (op.itemCount <= 0) {
400                mPostponedList.remove(i);
401                recycleUpdateOp(op);
402            }
403        }
404        return pos;
405    }
406
407    private boolean canFindInPreLayout(int position) {
408        final int count = mPostponedList.size();
409        for (int i = 0; i < count; i++) {
410            UpdateOp op = mPostponedList.get(i);
411            if (op.cmd == UpdateOp.MOVE) {
412                if (findPositionOffset(op.itemCount, i + 1) == position) {
413                    return true;
414                }
415            } else if (op.cmd == UpdateOp.ADD) {
416                // TODO optimize.
417                final int end = op.positionStart + op.itemCount;
418                for (int pos = op.positionStart; pos < end; pos++) {
419                    if (findPositionOffset(pos, i + 1) == position) {
420                        return true;
421                    }
422                }
423            }
424        }
425        return false;
426    }
427
428    private void applyAdd(UpdateOp op) {
429        postponeAndUpdateViewHolders(op);
430    }
431
432    private void postponeAndUpdateViewHolders(UpdateOp op) {
433        if (DEBUG) {
434            Log.d(TAG, "postponing " + op);
435        }
436        mPostponedList.add(op);
437        switch (op.cmd) {
438            case UpdateOp.ADD:
439                mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
440                break;
441            case UpdateOp.MOVE:
442                mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
443                break;
444            case UpdateOp.REMOVE:
445                mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,
446                        op.itemCount);
447                break;
448            case UpdateOp.UPDATE:
449                mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
450                break;
451            default:
452                throw new IllegalArgumentException("Unknown update op type for " + op);
453        }
454    }
455
456    boolean hasPendingUpdates() {
457        return mPendingUpdates.size() > 0;
458    }
459
460    int findPositionOffset(int position) {
461        return findPositionOffset(position, 0);
462    }
463
464    int findPositionOffset(int position, int firstPostponedItem) {
465        int count = mPostponedList.size();
466        for (int i = firstPostponedItem; i < count; ++i) {
467            UpdateOp op = mPostponedList.get(i);
468            if (op.cmd == UpdateOp.MOVE) {
469                if (op.positionStart == position) {
470                    position = op.itemCount;
471                } else {
472                    if (op.positionStart < position) {
473                        position--; // like a remove
474                    }
475                    if (op.itemCount <= position) {
476                        position++; // like an add
477                    }
478                }
479            } else if (op.positionStart <= position) {
480                if (op.cmd == UpdateOp.REMOVE) {
481                    if (position < op.positionStart + op.itemCount) {
482                        return -1;
483                    }
484                    position -= op.itemCount;
485                } else if (op.cmd == UpdateOp.ADD) {
486                    position += op.itemCount;
487                }
488            }
489        }
490        return position;
491    }
492
493    /**
494     * @return True if updates should be processed.
495     */
496    boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
497        mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
498        return mPendingUpdates.size() == 1;
499    }
500
501    /**
502     * @return True if updates should be processed.
503     */
504    boolean onItemRangeInserted(int positionStart, int itemCount) {
505        mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
506        return mPendingUpdates.size() == 1;
507    }
508
509    /**
510     * @return True if updates should be processed.
511     */
512    boolean onItemRangeRemoved(int positionStart, int itemCount) {
513        mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
514        return mPendingUpdates.size() == 1;
515    }
516
517    /**
518     * @return True if updates should be processed.
519     */
520    boolean onItemRangeMoved(int from, int to, int itemCount) {
521        if (from == to) {
522            return false;//no-op
523        }
524        if (itemCount != 1) {
525            throw new IllegalArgumentException("Moving more than 1 item is not supported yet");
526        }
527        mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to, null));
528        return mPendingUpdates.size() == 1;
529    }
530
531    /**
532     * Skips pre-processing and applies all updates in one pass.
533     */
534    void consumeUpdatesInOnePass() {
535        // we still consume postponed updates (if there is) in case there was a pre-process call
536        // w/o a matching consumePostponedUpdates.
537        consumePostponedUpdates();
538        final int count = mPendingUpdates.size();
539        for (int i = 0; i < count; i++) {
540            UpdateOp op = mPendingUpdates.get(i);
541            switch (op.cmd) {
542                case UpdateOp.ADD:
543                    mCallback.onDispatchSecondPass(op);
544                    mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
545                    break;
546                case UpdateOp.REMOVE:
547                    mCallback.onDispatchSecondPass(op);
548                    mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
549                    break;
550                case UpdateOp.UPDATE:
551                    mCallback.onDispatchSecondPass(op);
552                    mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
553                    break;
554                case UpdateOp.MOVE:
555                    mCallback.onDispatchSecondPass(op);
556                    mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
557                    break;
558            }
559            if (mOnItemProcessedCallback != null) {
560                mOnItemProcessedCallback.run();
561            }
562        }
563        recycleUpdateOpsAndClearList(mPendingUpdates);
564    }
565
566    public int applyPendingUpdatesToPosition(int position) {
567        final int size = mPendingUpdates.size();
568        for (int i = 0; i < size; i ++) {
569            UpdateOp op = mPendingUpdates.get(i);
570            switch (op.cmd) {
571                case UpdateOp.ADD:
572                    if (op.positionStart <= position) {
573                        position += op.itemCount;
574                    }
575                    break;
576                case UpdateOp.REMOVE:
577                    if (op.positionStart <= position) {
578                        final int end = op.positionStart + op.itemCount;
579                        if (end > position) {
580                            return RecyclerView.NO_POSITION;
581                        }
582                        position -= op.itemCount;
583                    }
584                    break;
585                case UpdateOp.MOVE:
586                    if (op.positionStart == position) {
587                        position = op.itemCount;//position end
588                    } else {
589                        if (op.positionStart < position) {
590                            position -= 1;
591                        }
592                        if (op.itemCount <= position) {
593                            position += 1;
594                        }
595                    }
596                    break;
597            }
598        }
599        return position;
600    }
601
602    /**
603     * Queued operation to happen when child views are updated.
604     */
605    static class UpdateOp {
606
607        static final int ADD = 0;
608
609        static final int REMOVE = 1;
610
611        static final int UPDATE = 2;
612
613        static final int MOVE = 3;
614
615        static final int POOL_SIZE = 30;
616
617        int cmd;
618
619        int positionStart;
620
621        Object payload;
622
623        // holds the target position if this is a MOVE
624        int itemCount;
625
626        UpdateOp(int cmd, int positionStart, int itemCount, Object payload) {
627            this.cmd = cmd;
628            this.positionStart = positionStart;
629            this.itemCount = itemCount;
630            this.payload = payload;
631        }
632
633        String cmdToString() {
634            switch (cmd) {
635                case ADD:
636                    return "add";
637                case REMOVE:
638                    return "rm";
639                case UPDATE:
640                    return "up";
641                case MOVE:
642                    return "mv";
643            }
644            return "??";
645        }
646
647        @Override
648        public String toString() {
649            return Integer.toHexString(System.identityHashCode(this))
650                    + "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount
651                    +",p:"+payload + "]";
652        }
653
654        @Override
655        public boolean equals(Object o) {
656            if (this == o) {
657                return true;
658            }
659            if (o == null || getClass() != o.getClass()) {
660                return false;
661            }
662
663            UpdateOp op = (UpdateOp) o;
664
665            if (cmd != op.cmd) {
666                return false;
667            }
668            if (cmd == MOVE && Math.abs(itemCount - positionStart) == 1) {
669                // reverse of this is also true
670                if (itemCount == op.positionStart && positionStart == op.itemCount) {
671                    return true;
672                }
673            }
674            if (itemCount != op.itemCount) {
675                return false;
676            }
677            if (positionStart != op.positionStart) {
678                return false;
679            }
680            if (payload != null) {
681                if (!payload.equals(op.payload)) {
682                    return false;
683                }
684            } else if (op.payload != null) {
685                return false;
686            }
687
688            return true;
689        }
690
691        @Override
692        public int hashCode() {
693            int result = cmd;
694            result = 31 * result + positionStart;
695            result = 31 * result + itemCount;
696            return result;
697        }
698    }
699
700    @Override
701    public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload) {
702        UpdateOp op = mUpdateOpPool.acquire();
703        if (op == null) {
704            op = new UpdateOp(cmd, positionStart, itemCount, payload);
705        } else {
706            op.cmd = cmd;
707            op.positionStart = positionStart;
708            op.itemCount = itemCount;
709            op.payload = payload;
710        }
711        return op;
712    }
713
714    @Override
715    public void recycleUpdateOp(UpdateOp op) {
716        if (!mDisableRecycler) {
717            op.payload = null;
718            mUpdateOpPool.release(op);
719        }
720    }
721
722    void recycleUpdateOpsAndClearList(List<UpdateOp> ops) {
723        final int count = ops.size();
724        for (int i = 0; i < count; i++) {
725            recycleUpdateOp(ops.get(i));
726        }
727        ops.clear();
728    }
729
730    /**
731     * Contract between AdapterHelper and RecyclerView.
732     */
733    static interface Callback {
734
735        ViewHolder findViewHolder(int position);
736
737        void offsetPositionsForRemovingInvisible(int positionStart, int itemCount);
738
739        void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount);
740
741        void markViewHoldersUpdated(int positionStart, int itemCount, Object payloads);
742
743        void onDispatchFirstPass(UpdateOp updateOp);
744
745        void onDispatchSecondPass(UpdateOp updateOp);
746
747        void offsetPositionsForAdd(int positionStart, int itemCount);
748
749        void offsetPositionsForMove(int from, int to);
750    }
751}
752