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