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, 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 
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                             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 
dispatchAndUpdateViewHolders(UpdateOp op)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 
dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart)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 
updatePositionWithPostponed(int pos, int cmd)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 
canFindInPreLayout(int position)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 
applyAdd(UpdateOp op)428     private void applyAdd(UpdateOp op) {
429         postponeAndUpdateViewHolders(op);
430     }
431 
postponeAndUpdateViewHolders(UpdateOp op)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 
hasPendingUpdates()456     boolean hasPendingUpdates() {
457         return mPendingUpdates.size() > 0;
458     }
459 
findPositionOffset(int position)460     int findPositionOffset(int position) {
461         return findPositionOffset(position, 0);
462     }
463 
findPositionOffset(int position, int firstPostponedItem)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      */
onItemRangeChanged(int positionStart, int itemCount, Object payload)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      */
onItemRangeInserted(int positionStart, int itemCount)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      */
onItemRangeRemoved(int positionStart, int itemCount)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      */
onItemRangeMoved(int from, int to, int itemCount)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      */
consumeUpdatesInOnePass()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 
applyPendingUpdatesToPosition(int position)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 
UpdateOp(int cmd, int positionStart, int itemCount, Object payload)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 
cmdToString()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
toString()648         public String toString() {
649             return Integer.toHexString(System.identityHashCode(this))
650                     + "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount
651                     +",p:"+payload + "]";
652         }
653 
654         @Override
equals(Object o)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
hashCode()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
obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload)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
recycleUpdateOp(UpdateOp op)715     public void recycleUpdateOp(UpdateOp op) {
716         if (!mDisableRecycler) {
717             op.payload = null;
718             mUpdateOpPool.release(op);
719         }
720     }
721 
recycleUpdateOpsAndClearList(List<UpdateOp> ops)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 
findViewHolder(int position)735         ViewHolder findViewHolder(int position);
736 
offsetPositionsForRemovingInvisible(int positionStart, int itemCount)737         void offsetPositionsForRemovingInvisible(int positionStart, int itemCount);
738 
offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount)739         void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount);
740 
markViewHoldersUpdated(int positionStart, int itemCount, Object payloads)741         void markViewHoldersUpdated(int positionStart, int itemCount, Object payloads);
742 
onDispatchFirstPass(UpdateOp updateOp)743         void onDispatchFirstPass(UpdateOp updateOp);
744 
onDispatchSecondPass(UpdateOp updateOp)745         void onDispatchSecondPass(UpdateOp updateOp);
746 
offsetPositionsForAdd(int positionStart, int itemCount)747         void offsetPositionsForAdd(int positionStart, int itemCount);
748 
offsetPositionsForMove(int from, int to)749         void offsetPositionsForMove(int from, int to);
750     }
751 }
752