1 /*
2  * Copyright (C) 2017 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 com.android.internal.widget;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.util.ArrayMap;
22 import android.util.LongSparseArray;
23 import android.util.Pools;
24 
25 import static com.android.internal.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
26 import static com.android.internal.widget.RecyclerView.ViewHolder;
27 import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR;
28 import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_AND_DISAPPEAR;
29 import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_PRE_AND_POST;
30 import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED;
31 import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_POST;
32 import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_PRE;
33 import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_PRE_AND_POST;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 
37 /**
38  * This class abstracts all tracking for Views to run animations.
39  */
40 class ViewInfoStore {
41 
42     private static final boolean DEBUG = false;
43 
44     /**
45      * View data records for pre-layout
46      */
47     @VisibleForTesting
48     final ArrayMap<ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();
49 
50     @VisibleForTesting
51     final LongSparseArray<ViewHolder> mOldChangedHolders = new LongSparseArray<>();
52 
53     /**
54      * Clears the state and all existing tracking data
55      */
clear()56     void clear() {
57         mLayoutHolderMap.clear();
58         mOldChangedHolders.clear();
59     }
60 
61     /**
62      * Adds the item information to the prelayout tracking
63      * @param holder The ViewHolder whose information is being saved
64      * @param info The information to save
65      */
addToPreLayout(ViewHolder holder, ItemHolderInfo info)66     void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
67         InfoRecord record = mLayoutHolderMap.get(holder);
68         if (record == null) {
69             record = InfoRecord.obtain();
70             mLayoutHolderMap.put(holder, record);
71         }
72         record.preInfo = info;
73         record.flags |= FLAG_PRE;
74     }
75 
isDisappearing(ViewHolder holder)76     boolean isDisappearing(ViewHolder holder) {
77         final InfoRecord record = mLayoutHolderMap.get(holder);
78         return record != null && ((record.flags & FLAG_DISAPPEARED) != 0);
79     }
80 
81     /**
82      * Finds the ItemHolderInfo for the given ViewHolder in preLayout list and removes it.
83      *
84      * @param vh The ViewHolder whose information is being queried
85      * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
86      */
87     @Nullable
popFromPreLayout(ViewHolder vh)88     ItemHolderInfo popFromPreLayout(ViewHolder vh) {
89         return popFromLayoutStep(vh, FLAG_PRE);
90     }
91 
92     /**
93      * Finds the ItemHolderInfo for the given ViewHolder in postLayout list and removes it.
94      *
95      * @param vh The ViewHolder whose information is being queried
96      * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
97      */
98     @Nullable
popFromPostLayout(ViewHolder vh)99     ItemHolderInfo popFromPostLayout(ViewHolder vh) {
100         return popFromLayoutStep(vh, FLAG_POST);
101     }
102 
popFromLayoutStep(ViewHolder vh, int flag)103     private ItemHolderInfo popFromLayoutStep(ViewHolder vh, int flag) {
104         int index = mLayoutHolderMap.indexOfKey(vh);
105         if (index < 0) {
106             return null;
107         }
108         final InfoRecord record = mLayoutHolderMap.valueAt(index);
109         if (record != null && (record.flags & flag) != 0) {
110             record.flags &= ~flag;
111             final ItemHolderInfo info;
112             if (flag == FLAG_PRE) {
113                 info = record.preInfo;
114             } else if (flag == FLAG_POST) {
115                 info = record.postInfo;
116             } else {
117                 throw new IllegalArgumentException("Must provide flag PRE or POST");
118             }
119             // if not pre-post flag is left, clear.
120             if ((record.flags & (FLAG_PRE | FLAG_POST)) == 0) {
121                 mLayoutHolderMap.removeAt(index);
122                 InfoRecord.recycle(record);
123             }
124             return info;
125         }
126         return null;
127     }
128 
129     /**
130      * Adds the given ViewHolder to the oldChangeHolders list
131      * @param key The key to identify the ViewHolder.
132      * @param holder The ViewHolder to store
133      */
addToOldChangeHolders(long key, ViewHolder holder)134     void addToOldChangeHolders(long key, ViewHolder holder) {
135         mOldChangedHolders.put(key, holder);
136     }
137 
138     /**
139      * Adds the given ViewHolder to the appeared in pre layout list. These are Views added by the
140      * LayoutManager during a pre-layout pass. We distinguish them from other views that were
141      * already in the pre-layout so that ItemAnimator can choose to run a different animation for
142      * them.
143      *
144      * @param holder The ViewHolder to store
145      * @param info The information to save
146      */
addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info)147     void addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info) {
148         InfoRecord record = mLayoutHolderMap.get(holder);
149         if (record == null) {
150             record = InfoRecord.obtain();
151             mLayoutHolderMap.put(holder, record);
152         }
153         record.flags |= FLAG_APPEAR;
154         record.preInfo = info;
155     }
156 
157     /**
158      * Checks whether the given ViewHolder is in preLayout list
159      * @param viewHolder The ViewHolder to query
160      *
161      * @return True if the ViewHolder is present in preLayout, false otherwise
162      */
isInPreLayout(ViewHolder viewHolder)163     boolean isInPreLayout(ViewHolder viewHolder) {
164         final InfoRecord record = mLayoutHolderMap.get(viewHolder);
165         return record != null && (record.flags & FLAG_PRE) != 0;
166     }
167 
168     /**
169      * Queries the oldChangeHolder list for the given key. If they are not tracked, simply returns
170      * null.
171      * @param key The key to be used to find the ViewHolder.
172      *
173      * @return A ViewHolder if exists or null if it does not exist.
174      */
getFromOldChangeHolders(long key)175     ViewHolder getFromOldChangeHolders(long key) {
176         return mOldChangedHolders.get(key);
177     }
178 
179     /**
180      * Adds the item information to the post layout list
181      * @param holder The ViewHolder whose information is being saved
182      * @param info The information to save
183      */
addToPostLayout(ViewHolder holder, ItemHolderInfo info)184     void addToPostLayout(ViewHolder holder, ItemHolderInfo info) {
185         InfoRecord record = mLayoutHolderMap.get(holder);
186         if (record == null) {
187             record = InfoRecord.obtain();
188             mLayoutHolderMap.put(holder, record);
189         }
190         record.postInfo = info;
191         record.flags |= FLAG_POST;
192     }
193 
194     /**
195      * A ViewHolder might be added by the LayoutManager just to animate its disappearance.
196      * This list holds such items so that we can animate / recycle these ViewHolders properly.
197      *
198      * @param holder The ViewHolder which disappeared during a layout.
199      */
addToDisappearedInLayout(ViewHolder holder)200     void addToDisappearedInLayout(ViewHolder holder) {
201         InfoRecord record = mLayoutHolderMap.get(holder);
202         if (record == null) {
203             record = InfoRecord.obtain();
204             mLayoutHolderMap.put(holder, record);
205         }
206         record.flags |= FLAG_DISAPPEARED;
207     }
208 
209     /**
210      * Removes a ViewHolder from disappearing list.
211      * @param holder The ViewHolder to be removed from the disappearing list.
212      */
removeFromDisappearedInLayout(ViewHolder holder)213     void removeFromDisappearedInLayout(ViewHolder holder) {
214         InfoRecord record = mLayoutHolderMap.get(holder);
215         if (record == null) {
216             return;
217         }
218         record.flags &= ~FLAG_DISAPPEARED;
219     }
220 
process(ProcessCallback callback)221     void process(ProcessCallback callback) {
222         for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
223             final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
224             final InfoRecord record = mLayoutHolderMap.removeAt(index);
225             if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
226                 // Appeared then disappeared. Not useful for animations.
227                 callback.unused(viewHolder);
228             } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
229                 // Set as "disappeared" by the LayoutManager (addDisappearingView)
230                 if (record.preInfo == null) {
231                     // similar to appear disappear but happened between different layout passes.
232                     // this can happen when the layout manager is using auto-measure
233                     callback.unused(viewHolder);
234                 } else {
235                     callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
236                 }
237             } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
238                 // Appeared in the layout but not in the adapter (e.g. entered the viewport)
239                 callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
240             } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
241                 // Persistent in both passes. Animate persistence
242                 callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
243             } else if ((record.flags & FLAG_PRE) != 0) {
244                 // Was in pre-layout, never been added to post layout
245                 callback.processDisappeared(viewHolder, record.preInfo, null);
246             } else if ((record.flags & FLAG_POST) != 0) {
247                 // Was not in pre-layout, been added to post layout
248                 callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
249             } else if ((record.flags & FLAG_APPEAR) != 0) {
250                 // Scrap view. RecyclerView will handle removing/recycling this.
251             } else if (DEBUG) {
252                 throw new IllegalStateException("record without any reasonable flag combination:/");
253             }
254             InfoRecord.recycle(record);
255         }
256     }
257 
258     /**
259      * Removes the ViewHolder from all list
260      * @param holder The ViewHolder which we should stop tracking
261      */
removeViewHolder(ViewHolder holder)262     void removeViewHolder(ViewHolder holder) {
263         for (int i = mOldChangedHolders.size() - 1; i >= 0; i--) {
264             if (holder == mOldChangedHolders.valueAt(i)) {
265                 mOldChangedHolders.removeAt(i);
266                 break;
267             }
268         }
269         final InfoRecord info = mLayoutHolderMap.remove(holder);
270         if (info != null) {
271             InfoRecord.recycle(info);
272         }
273     }
274 
onDetach()275     void onDetach() {
276         InfoRecord.drainCache();
277     }
278 
onViewDetached(ViewHolder viewHolder)279     public void onViewDetached(ViewHolder viewHolder) {
280         removeFromDisappearedInLayout(viewHolder);
281     }
282 
283     interface ProcessCallback {
processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo, @Nullable ItemHolderInfo postInfo)284         void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
285                 @Nullable ItemHolderInfo postInfo);
processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo, ItemHolderInfo postInfo)286         void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
287                 ItemHolderInfo postInfo);
processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo)288         void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
289                 @NonNull ItemHolderInfo postInfo);
unused(ViewHolder holder)290         void unused(ViewHolder holder);
291     }
292 
293     static class InfoRecord {
294         // disappearing list
295         static final int FLAG_DISAPPEARED = 1;
296         // appear in pre layout list
297         static final int FLAG_APPEAR = 1 << 1;
298         // pre layout, this is necessary to distinguish null item info
299         static final int FLAG_PRE = 1 << 2;
300         // post layout, this is necessary to distinguish null item info
301         static final int FLAG_POST = 1 << 3;
302         static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED;
303         static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST;
304         static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST;
305         int flags;
306         @Nullable ItemHolderInfo preInfo;
307         @Nullable ItemHolderInfo postInfo;
308         static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);
309 
InfoRecord()310         private InfoRecord() {
311         }
312 
obtain()313         static InfoRecord obtain() {
314             InfoRecord record = sPool.acquire();
315             return record == null ? new InfoRecord() : record;
316         }
317 
recycle(InfoRecord record)318         static void recycle(InfoRecord record) {
319             record.flags = 0;
320             record.preInfo = null;
321             record.postInfo = null;
322             sPool.release(record);
323         }
324 
drainCache()325         static void drainCache() {
326             //noinspection StatementWithEmptyBody
327             while (sPool.acquire() != null);
328         }
329     }
330 }
331