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 android.support.v4.view.AccessibilityDelegateCompat;
20import android.support.v4.view.accessibility.AccessibilityEventCompat;
21import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
22import android.support.v4.view.accessibility.AccessibilityRecordCompat;
23import android.view.View;
24import android.view.accessibility.AccessibilityEvent;
25
26import java.util.concurrent.atomic.AtomicBoolean;
27
28public class RecyclerViewAccessibilityTest extends BaseRecyclerViewInstrumentationTest {
29
30    public RecyclerViewAccessibilityTest() {
31        super(false);
32    }
33
34    public void testOnInitializeAccessibilityNodeInfo() throws Throwable {
35        for (boolean vBefore : new boolean[]{true, false}) {
36            for (boolean vAfter : new boolean[]{true, false}) {
37                for (boolean hBefore : new boolean[]{true, false}) {
38                    for (boolean hAfter : new boolean[]{true, false}) {
39                        onInitializeAccessibilityNodeInfoTest(vBefore, hBefore,
40                                vAfter, hAfter);
41                        removeRecyclerView();
42                    }
43                }
44            }
45        }
46    }
47
48    public void onInitializeAccessibilityNodeInfoTest(final boolean verticalScrollBefore,
49            final boolean horizontalScrollBefore, final boolean verticalScrollAfter,
50            final boolean horizontalScrollAfter) throws Throwable {
51        final RecyclerView recyclerView = new RecyclerView(getActivity()) {
52            //@Override
53            public boolean canScrollHorizontally(int direction) {
54                return direction < 0 && horizontalScrollBefore ||
55                        direction > 0 && horizontalScrollAfter;
56            }
57
58            //@Override
59            public boolean canScrollVertically(int direction) {
60                return direction < 0 && verticalScrollBefore ||
61                        direction > 0 && verticalScrollAfter;
62            }
63        };
64        final TestAdapter adapter = new TestAdapter(10);
65        final AtomicBoolean hScrolledBack = new AtomicBoolean(false);
66        final AtomicBoolean vScrolledBack = new AtomicBoolean(false);
67        final AtomicBoolean hScrolledFwd = new AtomicBoolean(false);
68        final AtomicBoolean vScrolledFwd = new AtomicBoolean(false);
69        recyclerView.setAdapter(adapter);
70        recyclerView.setLayoutManager(new TestLayoutManager() {
71
72            @Override
73            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
74                layoutRange(recycler, 0, 5);
75            }
76
77            @Override
78            public RecyclerView.LayoutParams generateDefaultLayoutParams() {
79                return new RecyclerView.LayoutParams(-1, -1);
80            }
81
82            @Override
83            public boolean canScrollVertically() {
84                return verticalScrollAfter || verticalScrollBefore;
85            }
86
87            @Override
88            public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
89                    RecyclerView.State state) {
90                if (dx > 0) {
91                    hScrolledFwd.set(true);
92                } else if (dx < 0) {
93                    hScrolledBack.set(true);
94                }
95                return 0;
96            }
97
98            @Override
99            public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
100                    RecyclerView.State state) {
101                if (dy > 0) {
102                    vScrolledFwd.set(true);
103                } else if (dy < 0) {
104                    vScrolledBack.set(true);
105                }
106                return 0;
107            }
108
109            @Override
110            public boolean canScrollHorizontally() {
111                return horizontalScrollAfter || horizontalScrollBefore;
112            }
113        });
114        setRecyclerView(recyclerView);
115        final RecyclerViewAccessibilityDelegate delegateCompat = recyclerView
116                .getCompatAccessibilityDelegate();
117        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
118        runTestOnUiThread(new Runnable() {
119            @Override
120            public void run() {
121                delegateCompat.onInitializeAccessibilityNodeInfo(recyclerView, info);
122            }
123        });
124        assertEquals(horizontalScrollAfter || horizontalScrollBefore
125                || verticalScrollAfter || verticalScrollBefore, info.isScrollable());
126        assertEquals(horizontalScrollBefore || verticalScrollBefore,
127                (info.getActions() & AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD) != 0);
128        assertEquals(horizontalScrollAfter || verticalScrollAfter,
129                (info.getActions() & AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD) != 0);
130        final AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo = info
131                .getCollectionInfo();
132        assertNotNull(collectionInfo);
133        if (recyclerView.getLayoutManager().canScrollVertically()) {
134            assertEquals(adapter.getItemCount(), collectionInfo.getRowCount());
135        }
136        if (recyclerView.getLayoutManager().canScrollHorizontally()) {
137            assertEquals(adapter.getItemCount(), collectionInfo.getColumnCount());
138        }
139
140        final AccessibilityEvent event = AccessibilityEvent.obtain();
141        runTestOnUiThread(new Runnable() {
142            @Override
143            public void run() {
144                delegateCompat.onInitializeAccessibilityEvent(recyclerView, event);
145            }
146        });
147        final AccessibilityRecordCompat record = AccessibilityEventCompat
148                .asRecord(event);
149        assertEquals(record.isScrollable(), verticalScrollAfter || horizontalScrollAfter ||
150        verticalScrollBefore || horizontalScrollBefore);
151        assertEquals(record.getItemCount(), adapter.getItemCount());
152
153        getInstrumentation().waitForIdleSync();
154        for (int i = 0; i < mRecyclerView.getChildCount(); i ++) {
155            final View view = mRecyclerView.getChildAt(i);
156            final AccessibilityNodeInfoCompat childInfo = AccessibilityNodeInfoCompat.obtain();
157            runTestOnUiThread(new Runnable() {
158                @Override
159                public void run() {
160                    delegateCompat.getItemDelegate().
161                            onInitializeAccessibilityNodeInfo(view, childInfo);
162                }
163            });
164            final AccessibilityNodeInfoCompat.CollectionItemInfoCompat collectionItemInfo
165                    = childInfo.getCollectionItemInfo();
166            assertNotNull(collectionItemInfo);
167            if (recyclerView.getLayoutManager().canScrollHorizontally()) {
168                assertEquals(i, collectionItemInfo.getColumnIndex());
169            } else {
170                assertEquals(0, collectionItemInfo.getColumnIndex());
171            }
172
173            if (recyclerView.getLayoutManager().canScrollVertically()) {
174                assertEquals(i, collectionItemInfo.getRowIndex());
175            } else {
176                assertEquals(0, collectionItemInfo.getRowIndex());
177            }
178        }
179
180        runTestOnUiThread(new Runnable() {
181            @Override
182            public void run() {
183
184            }
185        });
186        hScrolledBack.set(false);
187        vScrolledBack.set(false);
188        hScrolledFwd.set(false);
189        vScrolledBack.set(false);
190        performAccessibilityAction(delegateCompat, recyclerView,
191                AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
192        assertEquals(horizontalScrollBefore, hScrolledBack.get());
193        assertEquals(verticalScrollBefore, vScrolledBack.get());
194        assertEquals(false, hScrolledFwd.get());
195        assertEquals(false, vScrolledFwd.get());
196
197        hScrolledBack.set(false);
198        vScrolledBack.set(false);
199        hScrolledFwd.set(false);
200        vScrolledBack.set(false);
201        performAccessibilityAction(delegateCompat, recyclerView,
202                AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
203        assertEquals(false, hScrolledBack.get());
204        assertEquals(false, vScrolledBack.get());
205        assertEquals(horizontalScrollAfter, hScrolledFwd.get());
206        assertEquals(verticalScrollAfter, vScrolledFwd.get());
207    }
208
209    public void testIgnoreAccessibilityIfAdapterHasChanged() throws Throwable {
210        final RecyclerView recyclerView = new RecyclerView(getActivity()) {
211            //@Override
212            public boolean canScrollHorizontally(int direction) {
213                return true;
214            }
215
216            //@Override
217            public boolean canScrollVertically(int direction) {
218                return true;
219            }
220        };
221        final DumbLayoutManager layoutManager = new DumbLayoutManager();
222        final TestAdapter adapter = new TestAdapter(10);
223        recyclerView.setAdapter(adapter);
224        recyclerView.setLayoutManager(layoutManager);
225        layoutManager.expectLayouts(1);
226        setRecyclerView(recyclerView);
227        layoutManager.waitForLayout(1);
228
229        final RecyclerViewAccessibilityDelegate delegateCompat = recyclerView
230                .getCompatAccessibilityDelegate();
231        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
232        runTestOnUiThread(new Runnable() {
233            @Override
234            public void run() {
235                delegateCompat.onInitializeAccessibilityNodeInfo(recyclerView, info);
236            }
237        });
238        assertTrue("test sanity", info.isScrollable());
239        final AccessibilityNodeInfoCompat info2 = AccessibilityNodeInfoCompat.obtain();
240        layoutManager.blockLayout();
241        layoutManager.expectLayouts(1);
242        adapter.deleteAndNotify(1, 1);
243        // we can run this here since we blocked layout.
244        delegateCompat.onInitializeAccessibilityNodeInfo(recyclerView, info2);
245        layoutManager.unblockLayout();
246        assertFalse("info should not be filled if data is out of date", info2.isScrollable());
247        layoutManager.waitForLayout(1);
248    }
249
250    boolean performAccessibilityAction(final AccessibilityDelegateCompat delegate,
251            final RecyclerView recyclerView,  final int action) throws Throwable {
252        final boolean[] result = new boolean[1];
253        runTestOnUiThread(new Runnable() {
254            @Override
255            public void run() {
256                result[0] = delegate.performAccessibilityAction(recyclerView, action, null);
257            }
258        });
259        getInstrumentation().waitForIdleSync();
260        Thread.sleep(250);
261        return result[0];
262    }
263}
264