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().equals(expect));
62         return (both(new AccessibilityEventTypeMatcher(eventType)).and(matchResourceName))::matches;
63     }
64 
filterForEventTypeWithAction(int eventType, int action)65     public static AccessibilityEventFilter filterForEventTypeWithAction(int eventType, int action) {
66         TypeSafeMatcher<AccessibilityEvent> matchAction =
67                 new PropertyMatcher<>(
68                         action, "Action", (event, expect) -> event.getAction() == action);
69         return (both(new AccessibilityEventTypeMatcher(eventType)).and(matchAction))::matches;
70     }
71 
filterWindowsChangeTypesAndWindowTitle( @onNull UiAutomation uiAutomation, int changeTypes, @NonNull String title)72     public static AccessibilityEventFilter filterWindowsChangeTypesAndWindowTitle(
73             @NonNull UiAutomation uiAutomation, int changeTypes, @NonNull String title) {
74         return filterWindowsChangeTypesAndWindowTitle(uiAutomation, changeTypes, title,
75                 Display.DEFAULT_DISPLAY);
76     }
77 
filterWindowsChangeTypesAndWindowTitle( @onNull UiAutomation uiAutomation, int changeTypes, @NonNull String title, int displayId)78     public static AccessibilityEventFilter filterWindowsChangeTypesAndWindowTitle(
79             @NonNull UiAutomation uiAutomation, int changeTypes, @NonNull String title,
80             int displayId) {
81         return allOf(new AccessibilityEventTypeMatcher(AccessibilityEvent.TYPE_WINDOWS_CHANGED),
82                 new WindowChangesMatcher(changeTypes),
83                 new WindowTitleMatcher(uiAutomation, title, displayId))::matches;
84     }
85 
filterWindowsChangTypesAndWindowId(int windowId, int changeTypes)86     public static AccessibilityEventFilter filterWindowsChangTypesAndWindowId(int windowId,
87             int changeTypes) {
88         return allOf(new AccessibilityEventTypeMatcher(AccessibilityEvent.TYPE_WINDOWS_CHANGED),
89                 new WindowChangesMatcher(changeTypes),
90                 new WindowIdMatcher(windowId))::matches;
91     }
92 
93     /**
94      * Creates an {@link AccessibilityEventFilter} that returns {@code true} once all the given
95      * filters return {@code true} for any event.
96      * Each given filters are invoked on every AccessibilityEvent until it returns {@code true}.
97      * After all filters return {@code true} once, the created filter returns {@code true} forever.
98      */
filterWaitForAll(AccessibilityEventFilter... filters)99     public static AccessibilityEventFilter filterWaitForAll(AccessibilityEventFilter... filters) {
100         return new AccessibilityEventFilter() {
101             private final List<AccessibilityEventFilter> mUnresolved =
102                     new LinkedList<>(Arrays.asList(filters));
103 
104             @Override
105             public boolean accept(AccessibilityEvent event) {
106                 mUnresolved.removeIf(filter -> filter.accept(event));
107                 return mUnresolved.isEmpty();
108             }
109         };
110     }
111 
112     /**
113      * Returns a matcher for a display id from getDisplayId().
114      * @param displayId the display id to match.
115      * @return a matcher for comparing display ids.
116      */
matcherForDisplayId(int displayId)117     public static TypeSafeMatcher<AccessibilityEvent> matcherForDisplayId(int displayId) {
118         final TypeSafeMatcher<AccessibilityEvent> matchAction =
119                 new PropertyMatcher<>(
120                         displayId, "Display id",
121                         (event, expect) -> event.getDisplayId() == displayId);
122         return matchAction;
123     }
124 
125     /**
126      * Returns a matcher for a class name from getClassName().
127      * @param className the class name to match.
128      * @return a matcher for comparing class names.
129      */
matcherForClassName(CharSequence className)130     public static TypeSafeMatcher<AccessibilityEvent> matcherForClassName(CharSequence className) {
131         final TypeSafeMatcher<AccessibilityEvent> matchAction =
132                 new PropertyMatcher<>(
133                         className, "Class name",
134                         (event, expect) -> event.getClassName().equals(className));
135         return matchAction;
136     }
137 
138     /**
139      * Returns a matcher for the first text instance from getText().
140      * @param text the text to match.
141      * @return a matcher for comparing first text instances.
142      */
matcherForFirstText(CharSequence text)143     public static TypeSafeMatcher<AccessibilityEvent> matcherForFirstText(CharSequence text) {
144         final TypeSafeMatcher<AccessibilityEvent> matchAction =
145                 new PropertyMatcher<>(
146                         text, "Text",
147                         (event, expect) -> {
148                             if (event.getText() != null && event.getText().size() > 0) {
149                                 return event.getText().get(0).equals(text);
150                             }
151                             return false;
152                         });
153         return matchAction;
154     }
155 
156     public static class AccessibilityEventTypeMatcher extends TypeSafeMatcher<AccessibilityEvent> {
157         private int mType;
158 
AccessibilityEventTypeMatcher(int type)159         public AccessibilityEventTypeMatcher(int type) {
160             super();
161             mType = type;
162         }
163 
164         @Override
matchesSafely(AccessibilityEvent event)165         protected boolean matchesSafely(AccessibilityEvent event) {
166             return event.getEventType() == mType;
167         }
168 
169         @Override
describeTo(Description description)170         public void describeTo(Description description) {
171             description.appendText("Matching to type " + mType);
172         }
173     }
174 
175     public static class WindowChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> {
176         private int mWindowChanges;
177 
WindowChangesMatcher(int windowChanges)178         public WindowChangesMatcher(int windowChanges) {
179             super();
180             mWindowChanges = windowChanges;
181         }
182 
183         @Override
matchesSafely(AccessibilityEvent event)184         protected boolean matchesSafely(AccessibilityEvent event) {
185             return (event.getWindowChanges() & mWindowChanges) == mWindowChanges;
186         }
187 
188         @Override
describeTo(Description description)189         public void describeTo(Description description) {
190             description.appendText("With window change type " + mWindowChanges);
191         }
192     }
193 
194     public static class ContentChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> {
195         private int mContentChanges;
196 
ContentChangesMatcher(int contentChanges)197         public ContentChangesMatcher(int contentChanges) {
198             super();
199             mContentChanges = contentChanges;
200         }
201 
202         @Override
matchesSafely(AccessibilityEvent event)203         protected boolean matchesSafely(AccessibilityEvent event) {
204             return (event.getContentChangeTypes() & mContentChanges) == mContentChanges;
205         }
206 
207         @Override
describeTo(Description description)208         public void describeTo(Description description) {
209             description.appendText("With content change type " + mContentChanges);
210         }
211     }
212 
213     public static class PropertyMatcher<T> extends TypeSafeMatcher<AccessibilityEvent> {
214         private T mProperty;
215         private String mDescription;
216         private BiPredicate<AccessibilityEvent, T> mComparator;
217 
PropertyMatcher(T property, String description, BiPredicate<AccessibilityEvent, T> comparator)218         public PropertyMatcher(T property, String description,
219                 BiPredicate<AccessibilityEvent, T> comparator) {
220             super();
221             mProperty = property;
222             mDescription = description;
223             mComparator = comparator;
224         }
225 
226         @Override
matchesSafely(AccessibilityEvent event)227         protected boolean matchesSafely(AccessibilityEvent event) {
228             return mComparator.test(event, mProperty);
229         }
230 
231         @Override
describeTo(Description description)232         public void describeTo(Description description) {
233             description.appendText("Matching to " + mDescription + " " + mProperty.toString());
234         }
235     }
236 
237     public static class WindowTitleMatcher extends TypeSafeMatcher<AccessibilityEvent> {
238         private final UiAutomation mUiAutomation;
239         private final String mTitle;
240         private final int mDisplayId;
241 
WindowTitleMatcher(@onNull UiAutomation uiAutomation, @NonNull String title, int displayId)242         public WindowTitleMatcher(@NonNull UiAutomation uiAutomation, @NonNull String title,
243                 int displayId) {
244             super();
245             mUiAutomation = uiAutomation;
246             mTitle = title;
247             mDisplayId = displayId;
248         }
249 
250         @Override
matchesSafely(AccessibilityEvent event)251         protected boolean matchesSafely(AccessibilityEvent event) {
252             final SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays =
253                     mUiAutomation.getWindowsOnAllDisplays();
254             final List<AccessibilityWindowInfo> windows = windowsOnAllDisplays.get(mDisplayId);
255             final int eventWindowId = event.getWindowId();
256             for (AccessibilityWindowInfo info : windows) {
257                 if (eventWindowId == info.getId() && mTitle.equals(info.getTitle())) {
258                     return true;
259                 }
260             }
261             return false;
262         }
263 
264         @Override
describeTo(Description description)265         public void describeTo(Description description) {
266             description.appendText("With window title " + mTitle);
267         }
268     }
269 
270     public static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
271         private int mWindowId;
272 
WindowIdMatcher(int windowId)273         public WindowIdMatcher(int windowId) {
274             super();
275             mWindowId = windowId;
276         }
277 
278         @Override
matchesSafely(AccessibilityEvent event)279         protected boolean matchesSafely(AccessibilityEvent event) {
280             return event.getWindowId() == mWindowId;
281         }
282 
283         @Override
describeTo(Description description)284         public void describeTo(Description description) {
285             description.appendText("With window Id " + mWindowId);
286         }
287     }
288 }
289