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 
17 package android.support.v7.widget;
18 
19 import android.support.v4.util.Pools;
20 import android.util.Log;
21 
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 
26 import 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  */
46 class 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 
AdapterHelper(Callback callback)70     AdapterHelper(Callback callback) {
71         this(callback, false);
72     }
73 
AdapterHelper(Callback callback, boolean disableRecycler)74     AdapterHelper(Callback callback, boolean disableRecycler) {
75         mCallback = callback;
76         mDisableRecycler = disableRecycler;
77         mOpReorderer = new OpReorderer(this);
78     }
79 
addUpdateOp(UpdateOp... ops)80     AdapterHelper addUpdateOp(UpdateOp... ops) {
81         Collections.addAll(mPendingUpdates, ops);
82         return this;
83     }
84 
reset()85     void reset() {
86         recycleUpdateOpsAndClearList(mPendingUpdates);
87         recycleUpdateOpsAndClearList(mPostponedList);
88     }
89 
preProcess()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 
consumePostponedUpdates()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 
applyMove(UpdateOp op)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 
applyRemove(UpdateOp op)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);
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);
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);
176         }
177         if (type == POSITION_TYPE_INVISIBLE) {
178             dispatchAndUpdateViewHolders(op);
179         } else {
180             postponeAndUpdateViewHolders(op);
181         }
182     }
183 
applyUpdate(UpdateOp op)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                     dispatchAndUpdateViewHolders(newOp);
195                     tmpCount = 0;
196                     tmpStart = position;
197                 }
198                 type = POSITION_TYPE_NEW_OR_LAID_OUT;
199             } else { // applied
200                 if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
201                     UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount);
202                     postponeAndUpdateViewHolders(newOp);
203                     tmpCount = 0;
204                     tmpStart = position;
205                 }
206                 type = POSITION_TYPE_INVISIBLE;
207             }
208             tmpCount++;
209         }
210         if (tmpCount != op.itemCount) { // all 1 effect
211             recycleUpdateOp(op);
212             op = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount);
213         }
214         if (type == POSITION_TYPE_INVISIBLE) {
215             dispatchAndUpdateViewHolders(op);
216         } else {
217             postponeAndUpdateViewHolders(op);
218         }
219     }
220 
dispatchAndUpdateViewHolders(UpdateOp op)221     private void dispatchAndUpdateViewHolders(UpdateOp op) {
222         // tricky part.
223         // traverse all postpones and revert their changes on this op if necessary, apply updated
224         // dispatch to them since now they are after this op.
225         if (op.cmd == UpdateOp.ADD || op.cmd == UpdateOp.MOVE) {
226             throw new IllegalArgumentException("should not dispatch add or move for pre layout");
227         }
228         if (DEBUG) {
229             Log.d(TAG, "dispatch (pre)" + op);
230             Log.d(TAG, "postponed state before:");
231             for (UpdateOp updateOp : mPostponedList) {
232                 Log.d(TAG, updateOp.toString());
233             }
234             Log.d(TAG, "----");
235         }
236 
237         // handle each pos 1 by 1 to ensure continuity. If it breaks, dispatch partial
238         // TODO Since move ops are pushed to end, we should not need this anymore
239         int tmpStart = updatePositionWithPostponed(op.positionStart, op.cmd);
240         if (DEBUG) {
241             Log.d(TAG, "pos:" + op.positionStart + ",updatedPos:" + tmpStart);
242         }
243         int tmpCnt = 1;
244         int offsetPositionForPartial = op.positionStart;
245         final int positionMultiplier;
246         switch (op.cmd) {
247             case UpdateOp.UPDATE:
248                 positionMultiplier = 1;
249                 break;
250             case UpdateOp.REMOVE:
251                 positionMultiplier = 0;
252                 break;
253             default:
254                 throw new IllegalArgumentException("op should be remove or update." + op);
255         }
256         for (int p = 1; p < op.itemCount; p++) {
257             final int pos = op.positionStart + (positionMultiplier * p);
258             int updatedPos = updatePositionWithPostponed(pos, op.cmd);
259             if (DEBUG) {
260                 Log.d(TAG, "pos:" + pos + ",updatedPos:" + updatedPos);
261             }
262             boolean continuous = false;
263             switch (op.cmd) {
264                 case UpdateOp.UPDATE:
265                     continuous = updatedPos == tmpStart + 1;
266                     break;
267                 case UpdateOp.REMOVE:
268                     continuous = updatedPos == tmpStart;
269                     break;
270             }
271             if (continuous) {
272                 tmpCnt++;
273             } else {
274                 // need to dispatch this separately
275                 UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt);
276                 if (DEBUG) {
277                     Log.d(TAG, "need to dispatch separately " + tmp);
278                 }
279                 dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
280                 recycleUpdateOp(tmp);
281                 if (op.cmd == UpdateOp.UPDATE) {
282                     offsetPositionForPartial += tmpCnt;
283                 }
284                 tmpStart = updatedPos;// need to remove previously dispatched
285                 tmpCnt = 1;
286             }
287         }
288         recycleUpdateOp(op);
289         if (tmpCnt > 0) {
290             UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt);
291             if (DEBUG) {
292                 Log.d(TAG, "dispatching:" + tmp);
293             }
294             dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
295             recycleUpdateOp(tmp);
296         }
297         if (DEBUG) {
298             Log.d(TAG, "post dispatch");
299             Log.d(TAG, "postponed state after:");
300             for (UpdateOp updateOp : mPostponedList) {
301                 Log.d(TAG, updateOp.toString());
302             }
303             Log.d(TAG, "----");
304         }
305     }
306 
dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart)307     void dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart) {
308         mCallback.onDispatchFirstPass(op);
309         switch (op.cmd) {
310             case UpdateOp.REMOVE:
311                 mCallback.offsetPositionsForRemovingInvisible(offsetStart, op.itemCount);
312                 break;
313             case UpdateOp.UPDATE:
314                 mCallback.markViewHoldersUpdated(offsetStart, op.itemCount);
315                 break;
316             default:
317                 throw new IllegalArgumentException("only remove and update ops can be dispatched"
318                         + " in first pass");
319         }
320     }
321 
updatePositionWithPostponed(int pos, int cmd)322     private int updatePositionWithPostponed(int pos, int cmd) {
323         final int count = mPostponedList.size();
324         for (int i = count - 1; i >= 0; i--) {
325             UpdateOp postponed = mPostponedList.get(i);
326             if (postponed.cmd == UpdateOp.MOVE) {
327                 int start, end;
328                 if (postponed.positionStart < postponed.itemCount) {
329                     start = postponed.positionStart;
330                     end = postponed.itemCount;
331                 } else {
332                     start = postponed.itemCount;
333                     end = postponed.positionStart;
334                 }
335                 if (pos >= start && pos <= end) {
336                     //i'm affected
337                     if (start == postponed.positionStart) {
338                         if (cmd == UpdateOp.ADD) {
339                             postponed.itemCount++;
340                         } else if (cmd == UpdateOp.REMOVE) {
341                             postponed.itemCount--;
342                         }
343                         // op moved to left, move it right to revert
344                         pos++;
345                     } else {
346                         if (cmd == UpdateOp.ADD) {
347                             postponed.positionStart++;
348                         } else if (cmd == UpdateOp.REMOVE) {
349                             postponed.positionStart--;
350                         }
351                         // op was moved right, move left to revert
352                         pos--;
353                     }
354                 } else if (pos < postponed.positionStart) {
355                     // postponed MV is outside the dispatched OP. if it is before, offset
356                     if (cmd == UpdateOp.ADD) {
357                         postponed.positionStart++;
358                         postponed.itemCount++;
359                     } else if (cmd == UpdateOp.REMOVE) {
360                         postponed.positionStart--;
361                         postponed.itemCount--;
362                     }
363                 }
364             } else {
365                 if (postponed.positionStart <= pos) {
366                     if (postponed.cmd == UpdateOp.ADD) {
367                         pos -= postponed.itemCount;
368                     } else if (postponed.cmd == UpdateOp.REMOVE) {
369                         pos += postponed.itemCount;
370                     }
371                 } else {
372                     if (cmd == UpdateOp.ADD) {
373                         postponed.positionStart++;
374                     } else if (cmd == UpdateOp.REMOVE) {
375                         postponed.positionStart--;
376                     }
377                 }
378             }
379             if (DEBUG) {
380                 Log.d(TAG, "dispath (step" + i + ")");
381                 Log.d(TAG, "postponed state:" + i + ", pos:" + pos);
382                 for (UpdateOp updateOp : mPostponedList) {
383                     Log.d(TAG, updateOp.toString());
384                 }
385                 Log.d(TAG, "----");
386             }
387         }
388         for (int i = mPostponedList.size() - 1; i >= 0; i--) {
389             UpdateOp op = mPostponedList.get(i);
390             if (op.cmd == UpdateOp.MOVE) {
391                 if (op.itemCount == op.positionStart || op.itemCount < 0) {
392                     mPostponedList.remove(i);
393                     recycleUpdateOp(op);
394                 }
395             } else if (op.itemCount <= 0) {
396                 mPostponedList.remove(i);
397                 recycleUpdateOp(op);
398             }
399         }
400         return pos;
401     }
402 
canFindInPreLayout(int position)403     private boolean canFindInPreLayout(int position) {
404         final int count = mPostponedList.size();
405         for (int i = 0; i < count; i++) {
406             UpdateOp op = mPostponedList.get(i);
407             if (op.cmd == UpdateOp.MOVE) {
408                 if (findPositionOffset(op.itemCount, i + 1) == position) {
409                     return true;
410                 }
411             } else if (op.cmd == UpdateOp.ADD) {
412                 // TODO optimize.
413                 final int end = op.positionStart + op.itemCount;
414                 for (int pos = op.positionStart; pos < end; pos++) {
415                     if (findPositionOffset(pos, i + 1) == position) {
416                         return true;
417                     }
418                 }
419             }
420         }
421         return false;
422     }
423 
applyAdd(UpdateOp op)424     private void applyAdd(UpdateOp op) {
425         postponeAndUpdateViewHolders(op);
426     }
427 
postponeAndUpdateViewHolders(UpdateOp op)428     private void postponeAndUpdateViewHolders(UpdateOp op) {
429         if (DEBUG) {
430             Log.d(TAG, "postponing " + op);
431         }
432         mPostponedList.add(op);
433         switch (op.cmd) {
434             case UpdateOp.ADD:
435                 mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
436                 break;
437             case UpdateOp.MOVE:
438                 mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
439                 break;
440             case UpdateOp.REMOVE:
441                 mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,
442                         op.itemCount);
443                 break;
444             case UpdateOp.UPDATE:
445                 mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount);
446                 break;
447             default:
448                 throw new IllegalArgumentException("Unknown update op type for " + op);
449         }
450     }
451 
hasPendingUpdates()452     boolean hasPendingUpdates() {
453         return mPendingUpdates.size() > 0;
454     }
455 
findPositionOffset(int position)456     int findPositionOffset(int position) {
457         return findPositionOffset(position, 0);
458     }
459 
findPositionOffset(int position, int firstPostponedItem)460     int findPositionOffset(int position, int firstPostponedItem) {
461         int count = mPostponedList.size();
462         for (int i = firstPostponedItem; i < count; ++i) {
463             UpdateOp op = mPostponedList.get(i);
464             if (op.cmd == UpdateOp.MOVE) {
465                 if (op.positionStart == position) {
466                     position = op.itemCount;
467                 } else {
468                     if (op.positionStart < position) {
469                         position--; // like a remove
470                     }
471                     if (op.itemCount <= position) {
472                         position++; // like an add
473                     }
474                 }
475             } else if (op.positionStart <= position) {
476                 if (op.cmd == UpdateOp.REMOVE) {
477                     if (position < op.positionStart + op.itemCount) {
478                         return -1;
479                     }
480                     position -= op.itemCount;
481                 } else if (op.cmd == UpdateOp.ADD) {
482                     position += op.itemCount;
483                 }
484             }
485         }
486         return position;
487     }
488 
489     /**
490      * @return True if updates should be processed.
491      */
onItemRangeChanged(int positionStart, int itemCount)492     boolean onItemRangeChanged(int positionStart, int itemCount) {
493         mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount));
494         return mPendingUpdates.size() == 1;
495     }
496 
497     /**
498      * @return True if updates should be processed.
499      */
onItemRangeInserted(int positionStart, int itemCount)500     boolean onItemRangeInserted(int positionStart, int itemCount) {
501         mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount));
502         return mPendingUpdates.size() == 1;
503     }
504 
505     /**
506      * @return True if updates should be processed.
507      */
onItemRangeRemoved(int positionStart, int itemCount)508     boolean onItemRangeRemoved(int positionStart, int itemCount) {
509         mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount));
510         return mPendingUpdates.size() == 1;
511     }
512 
513     /**
514      * @return True if updates should be processed.
515      */
onItemRangeMoved(int from, int to, int itemCount)516     boolean onItemRangeMoved(int from, int to, int itemCount) {
517         if (from == to) {
518             return false;//no-op
519         }
520         if (itemCount != 1) {
521             throw new IllegalArgumentException("Moving more than 1 item is not supported yet");
522         }
523         mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to));
524         return mPendingUpdates.size() == 1;
525     }
526 
527     /**
528      * Skips pre-processing and applies all updates in one pass.
529      */
consumeUpdatesInOnePass()530     void consumeUpdatesInOnePass() {
531         // we still consume postponed updates (if there is) in case there was a pre-process call
532         // w/o a matching consumePostponedUpdates.
533         consumePostponedUpdates();
534         final int count = mPendingUpdates.size();
535         for (int i = 0; i < count; i++) {
536             UpdateOp op = mPendingUpdates.get(i);
537             switch (op.cmd) {
538                 case UpdateOp.ADD:
539                     mCallback.onDispatchSecondPass(op);
540                     mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
541                     break;
542                 case UpdateOp.REMOVE:
543                     mCallback.onDispatchSecondPass(op);
544                     mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
545                     break;
546                 case UpdateOp.UPDATE:
547                     mCallback.onDispatchSecondPass(op);
548                     mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount);
549                     break;
550                 case UpdateOp.MOVE:
551                     mCallback.onDispatchSecondPass(op);
552                     mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
553                     break;
554             }
555             if (mOnItemProcessedCallback != null) {
556                 mOnItemProcessedCallback.run();
557             }
558         }
559         recycleUpdateOpsAndClearList(mPendingUpdates);
560     }
561 
applyPendingUpdatesToPosition(int position)562     public int applyPendingUpdatesToPosition(int position) {
563         final int size = mPendingUpdates.size();
564         for (int i = 0; i < size; i ++) {
565             UpdateOp op = mPendingUpdates.get(i);
566             switch (op.cmd) {
567                 case UpdateOp.ADD:
568                     if (op.positionStart <= position) {
569                         position += op.itemCount;
570                     }
571                     break;
572                 case UpdateOp.REMOVE:
573                     if (op.positionStart <= position) {
574                         final int end = op.positionStart + op.itemCount;
575                         if (end > position) {
576                             return RecyclerView.NO_POSITION;
577                         }
578                         position -= op.itemCount;
579                     }
580                     break;
581                 case UpdateOp.MOVE:
582                     if (op.positionStart == position) {
583                         position = op.itemCount;//position end
584                     } else {
585                         if (op.positionStart < position) {
586                             position -= 1;
587                         }
588                         if (op.itemCount <= position) {
589                             position += 1;
590                         }
591                     }
592                     break;
593             }
594         }
595         return position;
596     }
597 
598     /**
599      * Queued operation to happen when child views are updated.
600      */
601     static class UpdateOp {
602 
603         static final int ADD = 0;
604 
605         static final int REMOVE = 1;
606 
607         static final int UPDATE = 2;
608 
609         static final int MOVE = 3;
610 
611         static final int POOL_SIZE = 30;
612 
613         int cmd;
614 
615         int positionStart;
616 
617         // holds the target position if this is a MOVE
618         int itemCount;
619 
UpdateOp(int cmd, int positionStart, int itemCount)620         UpdateOp(int cmd, int positionStart, int itemCount) {
621             this.cmd = cmd;
622             this.positionStart = positionStart;
623             this.itemCount = itemCount;
624         }
625 
cmdToString()626         String cmdToString() {
627             switch (cmd) {
628                 case ADD:
629                     return "add";
630                 case REMOVE:
631                     return "rm";
632                 case UPDATE:
633                     return "up";
634                 case MOVE:
635                     return "mv";
636             }
637             return "??";
638         }
639 
640         @Override
toString()641         public String toString() {
642             return "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount + "]";
643         }
644 
645         @Override
equals(Object o)646         public boolean equals(Object o) {
647             if (this == o) {
648                 return true;
649             }
650             if (o == null || getClass() != o.getClass()) {
651                 return false;
652             }
653 
654             UpdateOp op = (UpdateOp) o;
655 
656             if (cmd != op.cmd) {
657                 return false;
658             }
659             if (cmd == MOVE && Math.abs(itemCount - positionStart) == 1) {
660                 // reverse of this is also true
661                 if (itemCount == op.positionStart && positionStart == op.itemCount) {
662                     return true;
663                 }
664             }
665             if (itemCount != op.itemCount) {
666                 return false;
667             }
668             if (positionStart != op.positionStart) {
669                 return false;
670             }
671 
672             return true;
673         }
674 
675         @Override
hashCode()676         public int hashCode() {
677             int result = cmd;
678             result = 31 * result + positionStart;
679             result = 31 * result + itemCount;
680             return result;
681         }
682     }
683 
684     @Override
obtainUpdateOp(int cmd, int positionStart, int itemCount)685     public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount) {
686         UpdateOp op = mUpdateOpPool.acquire();
687         if (op == null) {
688             op = new UpdateOp(cmd, positionStart, itemCount);
689         } else {
690             op.cmd = cmd;
691             op.positionStart = positionStart;
692             op.itemCount = itemCount;
693         }
694         return op;
695     }
696 
697     @Override
recycleUpdateOp(UpdateOp op)698     public void recycleUpdateOp(UpdateOp op) {
699         if (!mDisableRecycler) {
700             mUpdateOpPool.release(op);
701         }
702     }
703 
recycleUpdateOpsAndClearList(List<UpdateOp> ops)704     void recycleUpdateOpsAndClearList(List<UpdateOp> ops) {
705         final int count = ops.size();
706         for (int i = 0; i < count; i++) {
707             recycleUpdateOp(ops.get(i));
708         }
709         ops.clear();
710     }
711 
712     /**
713      * Contract between AdapterHelper and RecyclerView.
714      */
715     static interface Callback {
716 
findViewHolder(int position)717         ViewHolder findViewHolder(int position);
718 
offsetPositionsForRemovingInvisible(int positionStart, int itemCount)719         void offsetPositionsForRemovingInvisible(int positionStart, int itemCount);
720 
offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount)721         void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount);
722 
markViewHoldersUpdated(int positionStart, int itemCount)723         void markViewHoldersUpdated(int positionStart, int itemCount);
724 
onDispatchFirstPass(UpdateOp updateOp)725         void onDispatchFirstPass(UpdateOp updateOp);
726 
onDispatchSecondPass(UpdateOp updateOp)727         void onDispatchSecondPass(UpdateOp updateOp);
728 
offsetPositionsForAdd(int positionStart, int itemCount)729         void offsetPositionsForAdd(int positionStart, int itemCount);
730 
offsetPositionsForMove(int from, int to)731         void offsetPositionsForMove(int from, int to);
732     }
733 }
734