1 /** 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 * express or implied. See the License for the specific language governing permissions and 12 * limitations under the License. 13 */ 14 15 package android.accessibilityservice.cts.utils; 16 17 import static org.hamcrest.CoreMatchers.allOf; 18 import static org.hamcrest.CoreMatchers.both; 19 20 import android.app.UiAutomation; 21 import android.app.UiAutomation.AccessibilityEventFilter; 22 import android.util.SparseArray; 23 import android.view.Display; 24 import android.view.accessibility.AccessibilityEvent; 25 import android.view.accessibility.AccessibilityWindowInfo; 26 27 import androidx.annotation.NonNull; 28 29 import org.hamcrest.Description; 30 import org.hamcrest.TypeSafeMatcher; 31 32 import java.util.Arrays; 33 import java.util.LinkedList; 34 import java.util.List; 35 import java.util.function.BiPredicate; 36 37 /** 38 * Utility class for creating AccessibilityEventFilters 39 */ 40 public class AccessibilityEventFilterUtils { filterForEventType(int eventType)41 public static AccessibilityEventFilter filterForEventType(int eventType) { 42 return (new AccessibilityEventTypeMatcher(eventType))::matches; 43 } 44 filterWindowContentChangedWithChangeTypes(int changes)45 public static AccessibilityEventFilter filterWindowContentChangedWithChangeTypes(int changes) { 46 return (both(new AccessibilityEventTypeMatcher( 47 AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)).and( 48 new ContentChangesMatcher(changes)))::matches; 49 } 50 filterWindowsChangedWithChangeTypes(int changes)51 public static AccessibilityEventFilter filterWindowsChangedWithChangeTypes(int changes) { 52 return (both(new AccessibilityEventTypeMatcher(AccessibilityEvent.TYPE_WINDOWS_CHANGED)) 53 .and(new WindowChangesMatcher(changes)))::matches; 54 } 55 filterForEventTypeWithResource(int eventType, String ResourceName)56 public static AccessibilityEventFilter filterForEventTypeWithResource(int eventType, 57 String ResourceName) { 58 TypeSafeMatcher<AccessibilityEvent> matchResourceName = new PropertyMatcher<>( 59 ResourceName, "Resource name", 60 (event, expect) -> event.getSource() != null 61 && event.getSource().getViewIdResourceName() != null 62 && event.getSource().getViewIdResourceName().equals(expect)); 63 return (both(new AccessibilityEventTypeMatcher(eventType)).and(matchResourceName))::matches; 64 } 65 filterForEventTypeWithAction(int eventType, int action)66 public static AccessibilityEventFilter filterForEventTypeWithAction(int eventType, int action) { 67 TypeSafeMatcher<AccessibilityEvent> matchAction = 68 new PropertyMatcher<>( 69 action, "Action", (event, expect) -> event.getAction() == action); 70 return (both(new AccessibilityEventTypeMatcher(eventType)).and(matchAction))::matches; 71 } 72 filterWindowsChangeTypesAndWindowTitle( @onNull UiAutomation uiAutomation, int changeTypes, @NonNull String title)73 public static AccessibilityEventFilter filterWindowsChangeTypesAndWindowTitle( 74 @NonNull UiAutomation uiAutomation, int changeTypes, @NonNull String title) { 75 return filterWindowsChangeTypesAndWindowTitle(uiAutomation, changeTypes, title, 76 Display.DEFAULT_DISPLAY); 77 } 78 filterWindowsChangeTypesAndWindowTitle( @onNull UiAutomation uiAutomation, int changeTypes, @NonNull String title, int displayId)79 public static AccessibilityEventFilter filterWindowsChangeTypesAndWindowTitle( 80 @NonNull UiAutomation uiAutomation, int changeTypes, @NonNull String title, 81 int displayId) { 82 return allOf(new AccessibilityEventTypeMatcher(AccessibilityEvent.TYPE_WINDOWS_CHANGED), 83 new WindowChangesMatcher(changeTypes), 84 new WindowTitleMatcher(uiAutomation, title, displayId))::matches; 85 } 86 filterWindowsChangTypesAndWindowId(int windowId, int changeTypes)87 public static AccessibilityEventFilter filterWindowsChangTypesAndWindowId(int windowId, 88 int changeTypes) { 89 return allOf(new AccessibilityEventTypeMatcher(AccessibilityEvent.TYPE_WINDOWS_CHANGED), 90 new WindowChangesMatcher(changeTypes), 91 new WindowIdMatcher(windowId))::matches; 92 } 93 94 /** 95 * Creates an {@link AccessibilityEventFilter} that returns {@code true} once all the given 96 * filters return {@code true} for any event. 97 * Each given filters are invoked on every AccessibilityEvent until it returns {@code true}. 98 * After all filters return {@code true} once, the created filter returns {@code true} forever. 99 */ filterWaitForAll(AccessibilityEventFilter... filters)100 public static AccessibilityEventFilter filterWaitForAll(AccessibilityEventFilter... filters) { 101 return new AccessibilityEventFilter() { 102 private final List<AccessibilityEventFilter> mUnresolved = 103 new LinkedList<>(Arrays.asList(filters)); 104 105 @Override 106 public boolean accept(AccessibilityEvent event) { 107 mUnresolved.removeIf(filter -> filter.accept(event)); 108 return mUnresolved.isEmpty(); 109 } 110 }; 111 } 112 113 /** 114 * Returns a matcher for a display id from getDisplayId(). 115 * @param displayId the display id to match. 116 * @return a matcher for comparing display ids. 117 */ matcherForDisplayId(int displayId)118 public static TypeSafeMatcher<AccessibilityEvent> matcherForDisplayId(int displayId) { 119 final TypeSafeMatcher<AccessibilityEvent> matchAction = 120 new PropertyMatcher<>( 121 displayId, "Display id", 122 (event, expect) -> event.getDisplayId() == displayId); 123 return matchAction; 124 } 125 126 /** 127 * Returns a matcher for a class name from getClassName(). 128 * @param className the class name to match. 129 * @return a matcher for comparing class names. 130 */ matcherForClassName(CharSequence className)131 public static TypeSafeMatcher<AccessibilityEvent> matcherForClassName(CharSequence className) { 132 final TypeSafeMatcher<AccessibilityEvent> matchAction = 133 new PropertyMatcher<>( 134 className, "Class name", 135 (event, expect) -> event.getClassName().equals(className)); 136 return matchAction; 137 } 138 139 /** 140 * Returns a matcher for the first text instance from getText(). 141 * @param text the text to match. 142 * @return a matcher for comparing first text instances. 143 */ matcherForFirstText(CharSequence text)144 public static TypeSafeMatcher<AccessibilityEvent> matcherForFirstText(CharSequence text) { 145 final TypeSafeMatcher<AccessibilityEvent> matchAction = 146 new PropertyMatcher<>( 147 text, "Text", 148 (event, expect) -> { 149 if (event.getText() != null && event.getText().size() > 0) { 150 return event.getText().get(0).equals(text); 151 } 152 return false; 153 }); 154 return matchAction; 155 } 156 157 public static class AccessibilityEventTypeMatcher extends TypeSafeMatcher<AccessibilityEvent> { 158 private int mType; 159 AccessibilityEventTypeMatcher(int type)160 public AccessibilityEventTypeMatcher(int type) { 161 super(); 162 mType = type; 163 } 164 165 @Override matchesSafely(AccessibilityEvent event)166 protected boolean matchesSafely(AccessibilityEvent event) { 167 return event.getEventType() == mType; 168 } 169 170 @Override describeTo(Description description)171 public void describeTo(Description description) { 172 description.appendText("Matching to type " + mType); 173 } 174 } 175 176 public static class WindowChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> { 177 private int mWindowChanges; 178 WindowChangesMatcher(int windowChanges)179 public WindowChangesMatcher(int windowChanges) { 180 super(); 181 mWindowChanges = windowChanges; 182 } 183 184 @Override matchesSafely(AccessibilityEvent event)185 protected boolean matchesSafely(AccessibilityEvent event) { 186 return (event.getWindowChanges() & mWindowChanges) == mWindowChanges; 187 } 188 189 @Override describeTo(Description description)190 public void describeTo(Description description) { 191 description.appendText("With window change type " + mWindowChanges); 192 } 193 } 194 195 public static class ContentChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> { 196 private int mContentChanges; 197 ContentChangesMatcher(int contentChanges)198 public ContentChangesMatcher(int contentChanges) { 199 super(); 200 mContentChanges = contentChanges; 201 } 202 203 @Override matchesSafely(AccessibilityEvent event)204 protected boolean matchesSafely(AccessibilityEvent event) { 205 return (event.getContentChangeTypes() & mContentChanges) == mContentChanges; 206 } 207 208 @Override describeTo(Description description)209 public void describeTo(Description description) { 210 description.appendText("With content change type " + mContentChanges); 211 } 212 } 213 214 public static class PropertyMatcher<T> extends TypeSafeMatcher<AccessibilityEvent> { 215 private T mProperty; 216 private String mDescription; 217 private BiPredicate<AccessibilityEvent, T> mComparator; 218 PropertyMatcher(T property, String description, BiPredicate<AccessibilityEvent, T> comparator)219 public PropertyMatcher(T property, String description, 220 BiPredicate<AccessibilityEvent, T> comparator) { 221 super(); 222 mProperty = property; 223 mDescription = description; 224 mComparator = comparator; 225 } 226 227 @Override matchesSafely(AccessibilityEvent event)228 protected boolean matchesSafely(AccessibilityEvent event) { 229 return mComparator.test(event, mProperty); 230 } 231 232 @Override describeTo(Description description)233 public void describeTo(Description description) { 234 description.appendText("Matching to " + mDescription + " " + mProperty.toString()); 235 } 236 } 237 238 public static class WindowTitleMatcher extends TypeSafeMatcher<AccessibilityEvent> { 239 private final UiAutomation mUiAutomation; 240 private final String mTitle; 241 private final int mDisplayId; 242 WindowTitleMatcher(@onNull UiAutomation uiAutomation, @NonNull String title, int displayId)243 public WindowTitleMatcher(@NonNull UiAutomation uiAutomation, @NonNull String title, 244 int displayId) { 245 super(); 246 mUiAutomation = uiAutomation; 247 mTitle = title; 248 mDisplayId = displayId; 249 } 250 251 @Override matchesSafely(AccessibilityEvent event)252 protected boolean matchesSafely(AccessibilityEvent event) { 253 final SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays = 254 mUiAutomation.getWindowsOnAllDisplays(); 255 final List<AccessibilityWindowInfo> windows = windowsOnAllDisplays.get(mDisplayId); 256 final int eventWindowId = event.getWindowId(); 257 for (AccessibilityWindowInfo info : windows) { 258 if (eventWindowId == info.getId() && mTitle.equals(info.getTitle())) { 259 return true; 260 } 261 } 262 return false; 263 } 264 265 @Override describeTo(Description description)266 public void describeTo(Description description) { 267 description.appendText("With window title " + mTitle); 268 } 269 } 270 271 public static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> { 272 private int mWindowId; 273 WindowIdMatcher(int windowId)274 public WindowIdMatcher(int windowId) { 275 super(); 276 mWindowId = windowId; 277 } 278 279 @Override matchesSafely(AccessibilityEvent event)280 protected boolean matchesSafely(AccessibilityEvent event) { 281 return event.getWindowId() == mWindowId; 282 } 283 284 @Override describeTo(Description description)285 public void describeTo(Description description) { 286 description.appendText("With window Id " + mWindowId); 287 } 288 } 289 } 290