1 /*
2  * Copyright 2018 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 androidx.recyclerview.widget;
18 
19 import static java.lang.annotation.ElementType.FIELD;
20 import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
21 import static java.lang.annotation.ElementType.METHOD;
22 import static java.lang.annotation.ElementType.PARAMETER;
23 import static java.lang.annotation.RetentionPolicy.CLASS;
24 
25 import org.jspecify.annotations.NonNull;
26 
27 import java.lang.annotation.Retention;
28 import java.lang.annotation.Target;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.concurrent.CountDownLatch;
32 import java.util.concurrent.TimeUnit;
33 
34 /**
35  * This is a ItemAnimator test double implementation that does not actually run any animations.
36  * Tests that utilize this class must manually end animations during (or at the end of) test
37  * execution.
38  *
39  * 1. Test MUST call endAnimation(ViewHolder) on UI thread to finish animation of a given ViewHolder
40  *    Or Test calls endAnimations() on UI thread to end animations for all.
41  * 2. Test can call getAddAnimations() etc. to get ViewHolders that currently running animation.
42  * 3. Test can call {@link #expect(int, int)} and {@link #waitFor(int)} to wait given
43  *    Events are fired.
44  */
45 public class ItemAnimatorTestDouble extends SimpleItemAnimator {
46 
47     static final long TIMEOUT_SECOND = 10;
48 
49     ArrayList<RecyclerView.ViewHolder> mAdds = new ArrayList<>();
50     ArrayList<RecyclerView.ViewHolder> mRemoves = new ArrayList<>();
51     ArrayList<RecyclerView.ViewHolder> mMoves = new ArrayList<>();
52     ArrayList<RecyclerView.ViewHolder> mChangesOld = new ArrayList<>();
53     ArrayList<RecyclerView.ViewHolder> mChangesNew = new ArrayList<>();
54 
55     @Retention(CLASS)
56     @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
57     public @interface CountDownLatchIndex {
58     }
59 
60     @CountDownLatchIndex
61     public static final int ADD_START = 0;
62 
63     @CountDownLatchIndex
64     public static final int ADD_FINISHED = 1;
65 
66     @CountDownLatchIndex
67     public static final int REMOVE_START = 2;
68 
69     @CountDownLatchIndex
70     public static final int REMOVE_FINISHED = 3;
71 
72     @CountDownLatchIndex
73     public static final int MOVE_START = 4;
74 
75     @CountDownLatchIndex
76     public static final int MOVE_FINISHED = 5;
77 
78     @CountDownLatchIndex
79     public static final int CHANGE_OLD_START = 6;
80 
81     @CountDownLatchIndex
82     public static final int CHANGE_OLD_FINISHED = 7;
83 
84     @CountDownLatchIndex
85     public static final int CHANGE_NEW_START = 8;
86 
87     @CountDownLatchIndex
88     public static final int CHANGE_NEW_FINISHED = 9;
89 
90     static final int NUM_COUNT_DOWN_LATCH = 10;
91 
92     CountDownLatch[] mCountDownLatches = new CountDownLatch[NUM_COUNT_DOWN_LATCH];
93 
94 
getAddAnimations()95     public List<RecyclerView.ViewHolder> getAddAnimations() {
96         return mAdds;
97     }
98 
getRemoveAnimations()99     public List<RecyclerView.ViewHolder> getRemoveAnimations() {
100         return mRemoves;
101     }
102 
getMovesAnimations()103     public List<RecyclerView.ViewHolder> getMovesAnimations() {
104         return mMoves;
105     }
106 
getChangesOldAnimations()107     public List<RecyclerView.ViewHolder> getChangesOldAnimations() {
108         return mChangesOld;
109     }
110 
getChangesNewAnimations()111     public List<RecyclerView.ViewHolder> getChangesNewAnimations() {
112         return mChangesNew;
113     }
114 
115     @Override
animateRemove(RecyclerView.@onNull ViewHolder holder)116     public boolean animateRemove(RecyclerView.@NonNull ViewHolder holder) {
117         mRemoves.add(holder);
118         dispatchRemoveStarting(holder);
119         return false;
120     }
121 
122     @Override
animateAdd(RecyclerView.@onNull ViewHolder holder)123     public boolean animateAdd(RecyclerView.@NonNull ViewHolder holder) {
124         mAdds.add(holder);
125         dispatchAddStarting(holder);
126         return false;
127     }
128 
129     @Override
animateMove(RecyclerView.@onNull ViewHolder holder, int fromX, int fromY, int toX, int toY)130     public boolean animateMove(RecyclerView.@NonNull ViewHolder holder, int fromX, int fromY,
131             int toX, int toY) {
132         mMoves.add(holder);
133         dispatchMoveStarting(holder);
134         return false;
135     }
136 
137     @Override
animateChange(RecyclerView.@onNull ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop)138     public boolean animateChange(RecyclerView.@NonNull ViewHolder oldHolder,
139             RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
140         mChangesOld.add(oldHolder);
141         mChangesNew.add(newHolder);
142         dispatchChangeStarting(oldHolder, true);
143         dispatchChangeStarting(newHolder, false);
144         return false;
145     }
146 
expect(@ountDownLatchIndex int index, int count)147     public void expect(@CountDownLatchIndex int index, int count) {
148         mCountDownLatches[index] = new CountDownLatch(count);
149     }
150 
waitFor(@ountDownLatchIndex int index)151     public boolean waitFor(@CountDownLatchIndex int index)
152             throws InterruptedException {
153         return mCountDownLatches[index].await(TIMEOUT_SECOND, TimeUnit.SECONDS);
154     }
155 
156     @Override
onChangeStarting(RecyclerView.ViewHolder item, boolean oldItem)157     public void onChangeStarting(RecyclerView.ViewHolder item, boolean oldItem) {
158         CountDownLatch latch = mCountDownLatches[oldItem ? CHANGE_OLD_START : CHANGE_NEW_START];
159         if (latch != null) {
160             latch.countDown();
161         }
162     }
163 
164     @Override
onMoveStarting(RecyclerView.ViewHolder item)165     public void onMoveStarting(RecyclerView.ViewHolder item) {
166         CountDownLatch latch = mCountDownLatches[MOVE_START];
167         if (latch != null) {
168             latch.countDown();
169         }
170     }
171 
172     @Override
onAddStarting(RecyclerView.ViewHolder item)173     public void onAddStarting(RecyclerView.ViewHolder item) {
174         CountDownLatch latch = mCountDownLatches[ADD_START];
175         if (latch != null) {
176             latch.countDown();
177         }
178     }
179 
180     @Override
onRemoveStarting(RecyclerView.ViewHolder item)181     public void onRemoveStarting(RecyclerView.ViewHolder item) {
182         CountDownLatch latch = mCountDownLatches[REMOVE_START];
183         if (latch != null) {
184             latch.countDown();
185         }
186     }
187 
188     @Override
onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem)189     public void onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) {
190         CountDownLatch latch = mCountDownLatches[oldItem
191                 ? CHANGE_OLD_FINISHED : CHANGE_NEW_FINISHED];
192         if (latch != null) {
193             latch.countDown();
194         }
195     }
196 
197     @Override
onMoveFinished(RecyclerView.ViewHolder item)198     public void onMoveFinished(RecyclerView.ViewHolder item) {
199         CountDownLatch latch = mCountDownLatches[MOVE_FINISHED];
200         if (latch != null) {
201             latch.countDown();
202         }
203     }
204 
205     @Override
onAddFinished(RecyclerView.ViewHolder item)206     public void onAddFinished(RecyclerView.ViewHolder item) {
207         CountDownLatch latch = mCountDownLatches[ADD_FINISHED];
208         if (latch != null) {
209             latch.countDown();
210         }
211     }
212 
213     @Override
onRemoveFinished(RecyclerView.ViewHolder item)214     public void onRemoveFinished(RecyclerView.ViewHolder item) {
215         CountDownLatch latch = mCountDownLatches[REMOVE_FINISHED];
216         if (latch != null) {
217             latch.countDown();
218         }
219     }
220 
221     @Override
runPendingAnimations()222     public void runPendingAnimations() {
223     }
224 
225     @Override
endAnimation(RecyclerView.@onNull ViewHolder item)226     public void endAnimation(RecyclerView.@NonNull ViewHolder item) {
227         if (mAdds.remove(item)) {
228             dispatchAddFinished(item);
229         } else if (mRemoves.remove(item)) {
230             dispatchRemoveFinished(item);
231         } else if (mMoves.remove(item)) {
232             dispatchMoveFinished(item);
233         } else if (mChangesOld.remove(item)) {
234             dispatchChangeFinished(item, true);
235         } else if (mChangesNew.remove(item)) {
236             dispatchChangeFinished(item, false);
237         }
238     }
239 
240     @Override
endAnimations()241     public void endAnimations() {
242         for (int i = mAdds.size() - 1; i >= 0; i--) {
243             endAnimation(mAdds.get(i));
244         }
245         for (int i = mRemoves.size() - 1; i >= 0; i--) {
246             endAnimation(mRemoves.get(i));
247         }
248         for (int i = mMoves.size() - 1; i >= 0; i--) {
249             endAnimation(mMoves.get(i));
250         }
251         for (int i = mChangesOld.size() - 1; i >= 0; i--) {
252             endAnimation(mChangesOld.get(i));
253         }
254         for (int i = mChangesNew.size() - 1; i >= 0; i--) {
255             endAnimation(mChangesNew.get(i));
256         }
257     }
258 
259     @Override
isRunning()260     public boolean isRunning() {
261         return mAdds.size() != 0
262                 || mRemoves.size() != 0
263                 || mMoves.size() != 0
264                 || mChangesOld.size() != 0
265                 || mChangesNew.size() != 0;
266     }
267 }
268