AccessibilityEventScrollStepStrategy.java revision 74676fdd3c8a9e599eddd13bea56898674d9916a
1ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com/* 2ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * Copyright (C) 2013 DroidDriver committers 3ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * 4ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * Licensed under the Apache License, Version 2.0 (the "License"); 5ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * you may not use this file except in compliance with the License. 6ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * You may obtain a copy of the License at 7ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * 8ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * http://www.apache.org/licenses/LICENSE-2.0 98a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com * 108a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com * Unless required by applicable law or agreed to in writing, software 118a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com * distributed under the License is distributed on an "AS IS" BASIS, 128a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 138a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com * See the License for the specific language governing permissions and 142e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org * limitations under the License. 152e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org */ 162e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.orgpackage com.google.android.droiddriver.scroll; 172e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org 18945a139553a9c9da03766213661d7f5fd6ed3042reed@android.comimport android.app.UiAutomation; 19945a139553a9c9da03766213661d7f5fd6ed3042reed@android.comimport android.app.UiAutomation.AccessibilityEventFilter; 20945a139553a9c9da03766213661d7f5fd6ed3042reed@android.comimport android.util.Log; 212e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.orgimport android.view.accessibility.AccessibilityEvent; 222e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org 232e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.orgimport com.google.android.droiddriver.DroidDriver; 242e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.orgimport com.google.android.droiddriver.UiElement; 25945a139553a9c9da03766213661d7f5fd6ed3042reed@android.comimport com.google.android.droiddriver.actions.SwipeAction; 262e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.orgimport com.google.android.droiddriver.exceptions.UnrecoverableException; 27945a139553a9c9da03766213661d7f5fd6ed3042reed@android.comimport com.google.android.droiddriver.finders.Finder; 28945a139553a9c9da03766213661d7f5fd6ed3042reed@android.comimport com.google.android.droiddriver.scroll.Direction.Axis; 29945a139553a9c9da03766213661d7f5fd6ed3042reed@android.comimport com.google.android.droiddriver.scroll.Direction.DirectionConverter; 30945a139553a9c9da03766213661d7f5fd6ed3042reed@android.comimport com.google.android.droiddriver.scroll.Direction.PhysicalDirection; 31945a139553a9c9da03766213661d7f5fd6ed3042reed@android.comimport com.google.android.droiddriver.util.Logs; 32945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com 33945a139553a9c9da03766213661d7f5fd6ed3042reed@android.comimport java.util.concurrent.TimeoutException; 34945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com 35945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com/** 362e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org * A {@link ScrollStepStrategy} that determines whether more scrolling is 37945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com * possible by checking the {@link AccessibilityEvent} returned by 38945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com * {@link android.app.UiAutomation}. 392e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org * <p> 402e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org * This implementation behaves just like the <a href= 412e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org * "http://developer.android.com/tools/help/uiautomator/UiScrollable.html" 422e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org * >UiScrollable</a> class. It may not work in all cases. For instance, 432e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org * sometimes {@link android.support.v4.widget.DrawerLayout} does not send 442e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org * correct {@link AccessibilityEvent}s after scrolling. 452e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org * </p> 462e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org */ 472e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.orgpublic class AccessibilityEventScrollStepStrategy implements ScrollStepStrategy { 482e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org /** 492e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org * Stores the data if we reached end at the last {@link #scroll}. If the data 502e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org * match when a new scroll is requested, we can return immediately. 512e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org */ 522e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org private static class EndData { 532e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org private Finder containerFinderAtEnd; 542e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org private PhysicalDirection directionAtEnd; 552e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org 562e086190e55a01dc2f7b74df6f2828e8cac2b9abkbr@chromium.org public boolean match(Finder containerFinder, PhysicalDirection direction) { 57945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com return containerFinderAtEnd == containerFinder && directionAtEnd == direction; 58945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com } 59945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com 60945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com public void set(Finder containerFinder, PhysicalDirection direction) { 61945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com containerFinderAtEnd = containerFinder; 62945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com directionAtEnd = direction; 63945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com } 64945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com 65945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com public void reset() { 66945a139553a9c9da03766213661d7f5fd6ed3042reed@android.com set(null, null); 678a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 688a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 698a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 708a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com /** 718a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com * This filter allows us to grab the last accessibility event generated for a 728a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com * scroll up to {@code scrollEventTimeoutMillis}. 738a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com */ 748a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com private static class LastScrollEventFilter implements AccessibilityEventFilter { 758a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com private AccessibilityEvent lastEvent; 768a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 778a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com @Override 788a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com public boolean accept(AccessibilityEvent event) { 798a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com if ((event.getEventType() & AccessibilityEvent.TYPE_VIEW_SCROLLED) != 0) { 808a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // Recycle the current last event. 818a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com if (lastEvent != null) { 828a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com lastEvent.recycle(); 838a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 848a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com lastEvent = AccessibilityEvent.obtain(event); 858a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 868a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // Return false to collect events until scrollEventTimeoutMillis has 878a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // elapsed. 888a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return false; 898a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 908a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 918a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com public AccessibilityEvent getLastEvent() { 928a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return lastEvent; 938a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 948a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 958a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 968a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com private final UiAutomation uiAutomation; 978a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com private final long scrollEventTimeoutMillis; 988a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com private final DirectionConverter directionConverter; 998a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com private final EndData endData = new EndData(); 1008a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1018a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com public AccessibilityEventScrollStepStrategy(UiAutomation uiAutomation, 1028a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com long scrollEventTimeoutMillis, DirectionConverter converter) { 1038a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com this.uiAutomation = uiAutomation; 1048a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com this.scrollEventTimeoutMillis = scrollEventTimeoutMillis; 1058a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com this.directionConverter = converter; 1068a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1078a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1088a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com @Override 1098a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com public boolean scroll(DroidDriver driver, Finder containerFinder, 1108a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com final PhysicalDirection direction) { 1118a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // Check if we've reached end after last scroll. 1128a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com if (endData.match(containerFinder, direction)) { 1138a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return false; 1148a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1158a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1168a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com AccessibilityEvent event = doScrollAndReturnEvent(driver.on(containerFinder), direction); 1178a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com if (detectEnd(event, direction.axis())) { 1188a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com endData.set(containerFinder, direction); 11915161620bee33efcb706685486c9ce0fb51a72bbreed@android.com Logs.log(Log.DEBUG, "reached scroll end with event: " + event); 12015161620bee33efcb706685486c9ce0fb51a72bbreed@android.com } 12115161620bee33efcb706685486c9ce0fb51a72bbreed@android.com 1228a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // Clean up the event after use. 1238a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com if (event != null) { 1248a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com event.recycle(); 1258a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1268a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1278a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // Even if event == null, that does not mean scroll has no effect! 1288a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // Some views may not emit correct events when the content changed. 1298a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return true; 1308a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1318a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1328a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // Copied from UiAutomator. 1338a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // AdapterViews have indices we can use to check for the beginning. 1348a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com protected boolean detectEnd(AccessibilityEvent event, Axis axis) { 1358a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com if (event == null) { 1368a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return true; 1378a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1388a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1398a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com if (event.getFromIndex() != -1 && event.getToIndex() != -1 && event.getItemCount() != -1) { 1408a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return event.getFromIndex() == 0 || (event.getItemCount() - 1) == event.getToIndex(); 1418a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1428a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com if (event.getScrollX() != -1 && event.getScrollY() != -1) { 1438a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com if (axis == Axis.VERTICAL) { 1448a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return event.getScrollY() == 0 || event.getScrollY() == event.getMaxScrollY(); 1458a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } else if (axis == Axis.HORIZONTAL) { 14615161620bee33efcb706685486c9ce0fb51a72bbreed@android.com return event.getScrollX() == 0 || event.getScrollX() == event.getMaxScrollX(); 1478a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 14815161620bee33efcb706685486c9ce0fb51a72bbreed@android.com } 1498a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1508a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // This case is different from UiAutomator. 1518a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return event.getFromIndex() == -1 && event.getToIndex() == -1 && event.getItemCount() == -1 1528a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com && event.getScrollX() == -1 && event.getScrollY() == -1; 1538a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1548a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1558a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com @Override 1568a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com public final DirectionConverter getDirectionConverter() { 1578a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return directionConverter; 1588a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1598a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1608a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com @Override 1618a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com public String toString() { 1628a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return String.format("%s{scrollEventTimeoutMillis=%d}", getClass().getSimpleName(), 1638a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com scrollEventTimeoutMillis); 1648a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1658a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1668a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com @Override 1678a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com public void beginScrolling(DroidDriver driver, Finder containerFinder, Finder itemFinder, 1688a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com PhysicalDirection direction) { 1698a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com endData.reset(); 1708a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1718a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1728a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com @Override 1738a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com public void endScrolling(DroidDriver driver, Finder containerFinder, Finder itemFinder, 1748a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com PhysicalDirection direction) {} 1758a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1768a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com protected AccessibilityEvent doScrollAndReturnEvent(final UiElement container, 1778a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com final PhysicalDirection direction) { 1788a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com LastScrollEventFilter filter = new LastScrollEventFilter(); 1798a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com try { 1808a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com uiAutomation.executeAndWaitForEvent(new Runnable() { 1818a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com @Override 1828a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com public void run() { 1838a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com doScroll(container, direction); 1848a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1858a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com }, filter, scrollEventTimeoutMillis); 1868a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } catch (IllegalStateException e) { 1878a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com throw new UnrecoverableException(e); 1888a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } catch (TimeoutException e) { 1898a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // We expect this because LastScrollEventFilter.accept always returns 1908a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // false. 1918a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1928a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return filter.getLastEvent(); 1938a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 1948a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 1958a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com @Override 1968a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com public void doScroll(final UiElement container, final PhysicalDirection direction) { 1978a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // We do not call container.scroll(direction) because container.scroll 1988a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // internally calls UiAutomation.executeAndWaitForEvent which clears the 1998a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // AccessibilityEvent Queue, preventing us from fetching the last 2008a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com // accessibility event to determine if scrolling has finished. 2018a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com SwipeAction.toScroll(direction).perform(container); 2028a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 2038a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 2048a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com /** 2058a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com * Some widgets may not always fire correct {@link AccessibilityEvent}. 2068a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com * Detecting end by null event is safer (at the cost of a extra scroll) than 2078a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com * examining indices. 2088a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com */ 2098a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com public static class NullAccessibilityEventScrollStepStrategy extends 2108a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com AccessibilityEventScrollStepStrategy { 2118a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 2128a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com public NullAccessibilityEventScrollStepStrategy(UiAutomation uiAutomation, 2138a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com long scrollEventTimeoutMillis, DirectionConverter converter) { 2148a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com super(uiAutomation, scrollEventTimeoutMillis, converter); 2158a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 2168a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 2178a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com @Override 2188a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com protected boolean detectEnd(AccessibilityEvent event, Axis axis) { 2198a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return event == null; 2208a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 2218a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com } 2228a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com} 2238a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com