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