DefaultItemAnimatorTest.java revision 42e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790
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 static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertFalse;
21import static org.junit.Assert.assertNotNull;
22import static org.junit.Assert.assertTrue;
23
24import android.support.test.filters.MediumTest;
25import android.support.test.runner.AndroidJUnit4;
26import android.util.Log;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.TextView;
30
31import org.junit.Before;
32import org.junit.Test;
33import org.junit.runner.RunWith;
34
35import java.util.ArrayList;
36import java.util.Arrays;
37import java.util.HashSet;
38import java.util.List;
39import java.util.Set;
40import java.util.concurrent.Semaphore;
41import java.util.concurrent.TimeUnit;
42
43@MediumTest
44@RunWith(AndroidJUnit4.class)
45public class DefaultItemAnimatorTest extends BaseRecyclerViewInstrumentationTest {
46
47    private static final String TAG = "DefaultItemAnimatorTest";
48    Throwable mainThreadException;
49
50    DefaultItemAnimator mAnimator;
51    Adapter mAdapter;
52    ViewGroup mDummyParent;
53    List<RecyclerView.ViewHolder> mExpectedItems = new ArrayList<RecyclerView.ViewHolder>();
54
55    Set<RecyclerView.ViewHolder> mRemoveFinished = new HashSet<RecyclerView.ViewHolder>();
56    Set<RecyclerView.ViewHolder> mAddFinished = new HashSet<RecyclerView.ViewHolder>();
57    Set<RecyclerView.ViewHolder> mMoveFinished = new HashSet<RecyclerView.ViewHolder>();
58    Set<RecyclerView.ViewHolder> mChangeFinished = new HashSet<RecyclerView.ViewHolder>();
59
60    Semaphore mExpectedItemCount = new Semaphore(0);
61
62    @Before
63    public void setUp() throws Exception {
64        mAnimator = new DefaultItemAnimator() {
65            @Override
66            public void onRemoveFinished(RecyclerView.ViewHolder item) {
67                try {
68                    assertTrue(mRemoveFinished.add(item));
69                    onFinished(item);
70                } catch (Throwable t) {
71                    postExceptionToInstrumentation(t);
72                }
73            }
74
75            @Override
76            public void onAddFinished(RecyclerView.ViewHolder item) {
77                try {
78                    assertTrue(mAddFinished.add(item));
79                    onFinished(item);
80                } catch (Throwable t) {
81                    postExceptionToInstrumentation(t);
82                }
83            }
84
85            @Override
86            public void onMoveFinished(RecyclerView.ViewHolder item) {
87                try {
88                    assertTrue(mMoveFinished.add(item));
89                    onFinished(item);
90                } catch (Throwable t) {
91                    postExceptionToInstrumentation(t);
92                }
93            }
94
95            @Override
96            public void onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) {
97                try {
98                    assertTrue(mChangeFinished.add(item));
99                    onFinished(item);
100                } catch (Throwable t) {
101                    postExceptionToInstrumentation(t);
102                }
103            }
104
105            private void onFinished(RecyclerView.ViewHolder item) {
106                assertNotNull(mExpectedItems.remove(item));
107                mExpectedItemCount.release(1);
108            }
109        };
110        mAdapter = new Adapter(20);
111        mDummyParent = getActivity().getContainer();
112    }
113
114    void checkForMainThreadException() throws Throwable {
115        if (mainThreadException != null) {
116            throw mainThreadException;
117        }
118    }
119
120    @Test
121    public void reUseWithPayload() {
122        RecyclerView.ViewHolder vh = new ViewHolder(new TextView(getActivity()));
123        assertFalse(mAnimator.canReuseUpdatedViewHolder(vh, new ArrayList<>()));
124        assertTrue(mAnimator.canReuseUpdatedViewHolder(vh, Arrays.asList((Object) "a")));
125    }
126
127    void expectItems(RecyclerView.ViewHolder... viewHolders) {
128        mExpectedItems.addAll(Arrays.asList(viewHolders));
129    }
130
131    void runAndWait(int itemCount, int seconds) throws Throwable {
132        runAndWait(itemCount, seconds, null);
133    }
134
135    void runAndWait(int itemCount, int seconds, final ThrowingRunnable postRun) throws Throwable {
136        mActivityRule.runOnUiThread(new Runnable() {
137            @Override
138            public void run() {
139                mAnimator.runPendingAnimations();
140                if (postRun != null) {
141                    try {
142                        postRun.run();
143                    } catch (Throwable e) {
144                        throw new RuntimeException(e);
145                    }
146                }
147            }
148        });
149        waitForItems(itemCount, seconds);
150        checkForMainThreadException();
151    }
152
153    void waitForItems(int itemCount, int seconds) throws InterruptedException {
154        assertTrue("all vh animations should end",
155                mExpectedItemCount.tryAcquire(itemCount, seconds, TimeUnit.SECONDS));
156        assertEquals("all expected finish events should happen", 0, mExpectedItems.size());
157        // wait one more second for unwanted
158        assertFalse("should not receive any more permits",
159                mExpectedItemCount.tryAcquire(1, 2, TimeUnit.SECONDS));
160    }
161
162    @Test
163    public void animateAdd() throws Throwable {
164        ViewHolder vh = createViewHolder(1);
165        expectItems(vh);
166        assertTrue(animateAdd(vh));
167        assertTrue(mAnimator.isRunning());
168        runAndWait(1, 1);
169    }
170
171    @Test
172    public void animateRemove() throws Throwable {
173        ViewHolder vh = createViewHolder(1);
174        expectItems(vh);
175        assertTrue(animateRemove(vh));
176        assertTrue(mAnimator.isRunning());
177        runAndWait(1, 1);
178    }
179
180    @Test
181    public void animateMove() throws Throwable {
182        ViewHolder vh = createViewHolder(1);
183        expectItems(vh);
184        assertTrue(animateMove(vh, 0, 0, 100, 100));
185        assertTrue(mAnimator.isRunning());
186        runAndWait(1, 1);
187    }
188
189    @Test
190    public void animateChange() throws Throwable {
191        ViewHolder vh = createViewHolder(1);
192        ViewHolder vh2 = createViewHolder(2);
193        expectItems(vh, vh2);
194        assertTrue(animateChange(vh, vh2, 0, 0, 100, 100));
195        assertTrue(mAnimator.isRunning());
196        runAndWait(2, 1);
197    }
198
199    public void cancelBefore(int count, final RecyclerView.ViewHolder... toCancel)
200            throws Throwable {
201        cancelTest(true, count, toCancel);
202    }
203
204    public void cancelAfter(int count, final RecyclerView.ViewHolder... toCancel)
205            throws Throwable {
206        cancelTest(false, count, toCancel);
207    }
208
209    public void cancelTest(boolean before, int count, final RecyclerView.ViewHolder... toCancel) throws Throwable {
210        if (before) {
211            endAnimations(toCancel);
212            runAndWait(count, 1);
213        } else {
214            runAndWait(count, 1, new ThrowingRunnable() {
215                @Override
216                public void run() throws Throwable {
217                    endAnimations(toCancel);
218                }
219            });
220        }
221    }
222
223    @Test
224    public void cancelAddBefore() throws Throwable {
225        final ViewHolder vh = createViewHolder(1);
226        expectItems(vh);
227        assertTrue(animateAdd(vh));
228        cancelBefore(1, vh);
229    }
230
231    @Test
232    public void cancelAddAfter() throws Throwable {
233        final ViewHolder vh = createViewHolder(1);
234        expectItems(vh);
235        assertTrue(animateAdd(vh));
236        cancelAfter(1, vh);
237    }
238
239    @Test
240    public void cancelMoveBefore() throws Throwable {
241        ViewHolder vh = createViewHolder(1);
242        expectItems(vh);
243        assertTrue(animateMove(vh, 10, 10, 100, 100));
244        cancelBefore(1, vh);
245    }
246
247    @Test
248    public void cancelMoveAfter() throws Throwable {
249        ViewHolder vh = createViewHolder(1);
250        expectItems(vh);
251        assertTrue(animateMove(vh, 10, 10, 100, 100));
252        cancelAfter(1, vh);
253    }
254
255    @Test
256    public void cancelRemove() throws Throwable {
257        ViewHolder vh = createViewHolder(1);
258        expectItems(vh);
259        assertTrue(animateRemove(vh));
260        endAnimations(vh);
261        runAndWait(1, 1);
262    }
263
264    @Test
265    public void cancelChangeOldBefore() throws Throwable {
266        cancelChangeOldTest(true);
267    }
268    @Test
269    public void cancelChangeOldAfter() throws Throwable {
270        cancelChangeOldTest(false);
271    }
272
273    public void cancelChangeOldTest(boolean before) throws Throwable {
274        ViewHolder vh = createViewHolder(1);
275        ViewHolder vh2 = createViewHolder(1);
276        expectItems(vh, vh2);
277        assertTrue(animateChange(vh, vh2, 20, 20, 100, 100));
278        cancelTest(before, 2, vh);
279    }
280
281    @Test
282    public void cancelChangeNewBefore() throws Throwable {
283        cancelChangeNewTest(true);
284    }
285
286    @Test
287    public void cancelChangeNewAfter() throws Throwable {
288        cancelChangeNewTest(false);
289    }
290
291    public void cancelChangeNewTest(boolean before) throws Throwable {
292        ViewHolder vh = createViewHolder(1);
293        ViewHolder vh2 = createViewHolder(1);
294        expectItems(vh, vh2);
295        assertTrue(animateChange(vh, vh2, 20, 20, 100, 100));
296        cancelTest(before, 2, vh2);
297    }
298
299    @Test
300    public void cancelChangeBothBefore() throws Throwable {
301        cancelChangeBothTest(true);
302    }
303
304    @Test
305    public void cancelChangeBothAfter() throws Throwable {
306        cancelChangeBothTest(false);
307    }
308
309    public void cancelChangeBothTest(boolean before) throws Throwable {
310        ViewHolder vh = createViewHolder(1);
311        ViewHolder vh2 = createViewHolder(1);
312        expectItems(vh, vh2);
313        assertTrue(animateChange(vh, vh2, 20, 20, 100, 100));
314        cancelTest(before, 2, vh, vh2);
315    }
316
317    void endAnimations(final RecyclerView.ViewHolder... vhs) throws Throwable {
318        mActivityRule.runOnUiThread(new Runnable() {
319            @Override
320            public void run() {
321                for (RecyclerView.ViewHolder vh : vhs) {
322                    mAnimator.endAnimation(vh);
323                }
324            }
325        });
326    }
327
328    boolean animateAdd(final RecyclerView.ViewHolder vh) throws Throwable {
329        final boolean[] result = new boolean[1];
330        mActivityRule.runOnUiThread(new Runnable() {
331            @Override
332            public void run() {
333                result[0] = mAnimator.animateAdd(vh);
334            }
335        });
336        return result[0];
337    }
338
339    boolean animateRemove(final RecyclerView.ViewHolder vh) throws Throwable {
340        final boolean[] result = new boolean[1];
341        mActivityRule.runOnUiThread(new Runnable() {
342            @Override
343            public void run() {
344                result[0] = mAnimator.animateRemove(vh);
345            }
346        });
347        return result[0];
348    }
349
350    boolean animateMove(final RecyclerView.ViewHolder vh, final int fromX, final int fromY,
351            final int toX, final int toY) throws Throwable {
352        final boolean[] result = new boolean[1];
353        mActivityRule.runOnUiThread(new Runnable() {
354            @Override
355            public void run() {
356                result[0] = mAnimator.animateMove(vh, fromX, fromY, toX, toY);
357            }
358        });
359        return result[0];
360    }
361
362    boolean animateChange(final RecyclerView.ViewHolder oldHolder,
363            final RecyclerView.ViewHolder newHolder,
364            final int fromX, final int fromY, final int toX, final int toY) throws Throwable {
365        final boolean[] result = new boolean[1];
366        mActivityRule.runOnUiThread(new Runnable() {
367            @Override
368            public void run() {
369                result[0] = mAnimator.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY);
370            }
371        });
372        return result[0];
373    }
374
375    private ViewHolder createViewHolder(final int pos) throws Throwable {
376        final ViewHolder vh = mAdapter.createViewHolder(mDummyParent, 1);
377        mActivityRule.runOnUiThread(new Runnable() {
378            @Override
379            public void run() {
380                mAdapter.bindViewHolder(vh, pos);
381                mDummyParent.addView(vh.itemView);
382            }
383        });
384
385        return vh;
386    }
387
388    void postExceptionToInstrumentation(Throwable t) {
389        if (mainThreadException == null) {
390            mainThreadException = t;
391        } else {
392            Log.e(TAG, "skipping secondary main thread exception", t);
393        }
394    }
395
396
397    private class Adapter extends RecyclerView.Adapter<ViewHolder> {
398
399        List<String> mItems;
400
401        private Adapter(int count) {
402            mItems = new ArrayList<>();
403            for (int i = 0; i < count; i++) {
404                mItems.add("item-" + i);
405            }
406        }
407
408        @Override
409        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
410            return new ViewHolder(new TextView(parent.getContext()));
411        }
412
413        @Override
414        public void onBindViewHolder(ViewHolder holder, int position) {
415            holder.bind(mItems.get(position));
416        }
417
418        @Override
419        public int getItemCount() {
420            return mItems.size();
421        }
422    }
423
424    private class ViewHolder extends RecyclerView.ViewHolder {
425
426        String mBindedText;
427
428        public ViewHolder(View itemView) {
429            super(itemView);
430        }
431
432        public void bind(String text) {
433            mBindedText = text;
434            ((TextView) itemView).setText(text);
435        }
436    }
437
438    private interface ThrowingRunnable {
439        void run() throws Throwable;
440    }
441}
442