1 /*
2  * Copyright (C) 2012 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 
17 package com.android.uiautomator.core;
18 
19 import android.util.SparseArray;
20 import android.view.accessibility.AccessibilityNodeInfo;
21 
22 import java.util.regex.Pattern;
23 
24 /**
25  * Specifies the elements in the layout hierarchy for tests to target, filtered
26  * by properties such as text value, content-description, class name, and state
27  * information. You can also target an element by its location in a layout
28  * hierarchy.
29  * @since API Level 16
30  */
31 public class UiSelector {
32     static final int SELECTOR_NIL = 0;
33     static final int SELECTOR_TEXT = 1;
34     static final int SELECTOR_START_TEXT = 2;
35     static final int SELECTOR_CONTAINS_TEXT = 3;
36     static final int SELECTOR_CLASS = 4;
37     static final int SELECTOR_DESCRIPTION = 5;
38     static final int SELECTOR_START_DESCRIPTION = 6;
39     static final int SELECTOR_CONTAINS_DESCRIPTION = 7;
40     static final int SELECTOR_INDEX = 8;
41     static final int SELECTOR_INSTANCE = 9;
42     static final int SELECTOR_ENABLED = 10;
43     static final int SELECTOR_FOCUSED = 11;
44     static final int SELECTOR_FOCUSABLE = 12;
45     static final int SELECTOR_SCROLLABLE = 13;
46     static final int SELECTOR_CLICKABLE = 14;
47     static final int SELECTOR_CHECKED = 15;
48     static final int SELECTOR_SELECTED = 16;
49     static final int SELECTOR_ID = 17;
50     static final int SELECTOR_PACKAGE_NAME = 18;
51     static final int SELECTOR_CHILD = 19;
52     static final int SELECTOR_CONTAINER = 20;
53     static final int SELECTOR_PATTERN = 21;
54     static final int SELECTOR_PARENT = 22;
55     static final int SELECTOR_COUNT = 23;
56     static final int SELECTOR_LONG_CLICKABLE = 24;
57     static final int SELECTOR_TEXT_REGEX = 25;
58     static final int SELECTOR_CLASS_REGEX = 26;
59     static final int SELECTOR_DESCRIPTION_REGEX = 27;
60     static final int SELECTOR_PACKAGE_NAME_REGEX = 28;
61     static final int SELECTOR_RESOURCE_ID = 29;
62     static final int SELECTOR_CHECKABLE = 30;
63     static final int SELECTOR_RESOURCE_ID_REGEX = 31;
64 
65     private SparseArray<Object> mSelectorAttributes = new SparseArray<Object>();
66 
67     /**
68      * @since API Level 16
69      */
UiSelector()70     public UiSelector() {
71     }
72 
UiSelector(UiSelector selector)73     UiSelector(UiSelector selector) {
74         mSelectorAttributes = selector.cloneSelector().mSelectorAttributes;
75     }
76 
77     /**
78      * @since API Level 17
79      */
cloneSelector()80     protected UiSelector cloneSelector() {
81         UiSelector ret = new UiSelector();
82         ret.mSelectorAttributes = mSelectorAttributes.clone();
83         if (hasChildSelector())
84             ret.mSelectorAttributes.put(SELECTOR_CHILD, new UiSelector(getChildSelector()));
85         if (hasParentSelector())
86             ret.mSelectorAttributes.put(SELECTOR_PARENT, new UiSelector(getParentSelector()));
87         if (hasPatternSelector())
88             ret.mSelectorAttributes.put(SELECTOR_PATTERN, new UiSelector(getPatternSelector()));
89         return ret;
90     }
91 
patternBuilder(UiSelector selector)92     static UiSelector patternBuilder(UiSelector selector) {
93         if (!selector.hasPatternSelector()) {
94             return new UiSelector().patternSelector(selector);
95         }
96         return selector;
97     }
98 
patternBuilder(UiSelector container, UiSelector pattern)99     static UiSelector patternBuilder(UiSelector container, UiSelector pattern) {
100         return new UiSelector(
101                 new UiSelector().containerSelector(container).patternSelector(pattern));
102     }
103 
104     /**
105      * Set the search criteria to match the visible text displayed
106      * in a widget (for example, the text label to launch an app).
107      *
108      * The text for the element must match exactly with the string in your input
109      * argument. Matching is case-sensitive.
110      *
111      * @param text Value to match
112      * @return UiSelector with the specified search criteria
113      * @since API Level 16
114      */
text(String text)115     public UiSelector text(String text) {
116         return buildSelector(SELECTOR_TEXT, text);
117     }
118 
119     /**
120      * Set the search criteria to match the visible text displayed in a layout
121      * element, using a regular expression.
122      *
123      * The text in the widget must match exactly with the string in your
124      * input argument.
125      *
126      * @param regex a regular expression
127      * @return UiSelector with the specified search criteria
128      * @since API Level 17
129      */
textMatches(String regex)130     public UiSelector textMatches(String regex) {
131         return buildSelector(SELECTOR_TEXT_REGEX, Pattern.compile(regex));
132     }
133 
134     /**
135      * Set the search criteria to match visible text in a widget that is
136      * prefixed by the text parameter.
137      *
138      * The matching is case-insensitive.
139      *
140      * @param text Value to match
141      * @return UiSelector with the specified search criteria
142      * @since API Level 16
143      */
textStartsWith(String text)144     public UiSelector textStartsWith(String text) {
145         return buildSelector(SELECTOR_START_TEXT, text);
146     }
147 
148     /**
149      * Set the search criteria to match the visible text in a widget
150      * where the visible text must contain the string in your input argument.
151      *
152      * The matching is case-sensitive.
153      *
154      * @param text Value to match
155      * @return UiSelector with the specified search criteria
156      * @since API Level 16
157      */
textContains(String text)158     public UiSelector textContains(String text) {
159         return buildSelector(SELECTOR_CONTAINS_TEXT, text);
160     }
161 
162     /**
163      * Set the search criteria to match the class property
164      * for a widget (for example, "android.widget.Button").
165      *
166      * @param className Value to match
167      * @return UiSelector with the specified search criteria
168      * @since API Level 16
169      */
className(String className)170     public UiSelector className(String className) {
171         return buildSelector(SELECTOR_CLASS, className);
172     }
173 
174     /**
175      * Set the search criteria to match the class property
176      * for a widget, using a regular expression.
177      *
178      * @param regex a regular expression
179      * @return UiSelector with the specified search criteria
180      * @since API Level 17
181      */
classNameMatches(String regex)182     public UiSelector classNameMatches(String regex) {
183         return buildSelector(SELECTOR_CLASS_REGEX, Pattern.compile(regex));
184     }
185 
186     /**
187      * Set the search criteria to match the class property
188      * for a widget (for example, "android.widget.Button").
189      *
190      * @param type type
191      * @return UiSelector with the specified search criteria
192      * @since API Level 17
193      */
className(Class<T> type)194     public <T> UiSelector className(Class<T> type) {
195         return buildSelector(SELECTOR_CLASS, type.getName());
196     }
197 
198     /**
199      * Set the search criteria to match the content-description
200      * property for a widget.
201      *
202      * The content-description is typically used
203      * by the Android Accessibility framework to
204      * provide an audio prompt for the widget when
205      * the widget is selected. The content-description
206      * for the widget must match exactly
207      * with the string in your input argument.
208      *
209      * Matching is case-sensitive.
210      *
211      * @param desc Value to match
212      * @return UiSelector with the specified search criteria
213      * @since API Level 16
214      */
description(String desc)215     public UiSelector description(String desc) {
216         return buildSelector(SELECTOR_DESCRIPTION, desc);
217     }
218 
219     /**
220      * Set the search criteria to match the content-description
221      * property for a widget.
222      *
223      * The content-description is typically used
224      * by the Android Accessibility framework to
225      * provide an audio prompt for the widget when
226      * the widget is selected. The content-description
227      * for the widget must match exactly
228      * with the string in your input argument.
229      *
230      * @param regex a regular expression
231      * @return UiSelector with the specified search criteria
232      * @since API Level 17
233      */
descriptionMatches(String regex)234     public UiSelector descriptionMatches(String regex) {
235         return buildSelector(SELECTOR_DESCRIPTION_REGEX, Pattern.compile(regex));
236     }
237 
238     /**
239      * Set the search criteria to match the content-description
240      * property for a widget.
241      *
242      * The content-description is typically used
243      * by the Android Accessibility framework to
244      * provide an audio prompt for the widget when
245      * the widget is selected. The content-description
246      * for the widget must start
247      * with the string in your input argument.
248      *
249      * Matching is case-insensitive.
250      *
251      * @param desc Value to match
252      * @return UiSelector with the specified search criteria
253      * @since API Level 16
254      */
descriptionStartsWith(String desc)255     public UiSelector descriptionStartsWith(String desc) {
256         return buildSelector(SELECTOR_START_DESCRIPTION, desc);
257     }
258 
259     /**
260      * Set the search criteria to match the content-description
261      * property for a widget.
262      *
263      * The content-description is typically used
264      * by the Android Accessibility framework to
265      * provide an audio prompt for the widget when
266      * the widget is selected. The content-description
267      * for the widget must contain
268      * the string in your input argument.
269      *
270      * Matching is case-insensitive.
271      *
272      * @param desc Value to match
273      * @return UiSelector with the specified search criteria
274      * @since API Level 16
275      */
descriptionContains(String desc)276     public UiSelector descriptionContains(String desc) {
277         return buildSelector(SELECTOR_CONTAINS_DESCRIPTION, desc);
278     }
279 
280     /**
281      * Set the search criteria to match the given resource ID.
282      *
283      * @param id Value to match
284      * @return UiSelector with the specified search criteria
285      * @since API Level 18
286      */
resourceId(String id)287     public UiSelector resourceId(String id) {
288         return buildSelector(SELECTOR_RESOURCE_ID, id);
289     }
290 
291     /**
292      * Set the search criteria to match the resource ID
293      * of the widget, using a regular expression.
294      *
295      * @param regex a regular expression
296      * @return UiSelector with the specified search criteria
297      * @since API Level 18
298      */
resourceIdMatches(String regex)299     public UiSelector resourceIdMatches(String regex) {
300         return buildSelector(SELECTOR_RESOURCE_ID_REGEX, Pattern.compile(regex));
301     }
302 
303     /**
304      * Set the search criteria to match the widget by its node
305      * index in the layout hierarchy.
306      *
307      * The index value must be 0 or greater.
308      *
309      * Using the index can be unreliable and should only
310      * be used as a last resort for matching. Instead,
311      * consider using the {@link #instance(int)} method.
312      *
313      * @param index Value to match
314      * @return UiSelector with the specified search criteria
315      * @since API Level 16
316      */
index(final int index)317     public UiSelector index(final int index) {
318         return buildSelector(SELECTOR_INDEX, index);
319     }
320 
321     /**
322      * Set the search criteria to match the
323      * widget by its instance number.
324      *
325      * The instance value must be 0 or greater, where
326      * the first instance is 0.
327      *
328      * For example, to simulate a user click on
329      * the third image that is enabled in a UI screen, you
330      * could specify a a search criteria where the instance is
331      * 2, the {@link #className(String)} matches the image
332      * widget class, and {@link #enabled(boolean)} is true.
333      * The code would look like this:
334      * <code>
335      * new UiSelector().className("android.widget.ImageView")
336      *    .enabled(true).instance(2);
337      * </code>
338      *
339      * @param instance Value to match
340      * @return UiSelector with the specified search criteria
341      * @since API Level 16
342      */
instance(final int instance)343     public UiSelector instance(final int instance) {
344         return buildSelector(SELECTOR_INSTANCE, instance);
345     }
346 
347     /**
348      * Set the search criteria to match widgets that are enabled.
349      *
350      * Typically, using this search criteria alone is not useful.
351      * You should also include additional criteria, such as text,
352      * content-description, or the class name for a widget.
353      *
354      * If no other search criteria is specified, and there is more
355      * than one matching widget, the first widget in the tree
356      * is selected.
357      *
358      * @param val Value to match
359      * @return UiSelector with the specified search criteria
360      * @since API Level 16
361      */
enabled(boolean val)362     public UiSelector enabled(boolean val) {
363         return buildSelector(SELECTOR_ENABLED, val);
364     }
365 
366     /**
367      * Set the search criteria to match widgets that have focus.
368      *
369      * Typically, using this search criteria alone is not useful.
370      * You should also include additional criteria, such as text,
371      * content-description, or the class name for a widget.
372      *
373      * If no other search criteria is specified, and there is more
374      * than one matching widget, the first widget in the tree
375      * is selected.
376      *
377      * @param val Value to match
378      * @return UiSelector with the specified search criteria
379      * @since API Level 16
380      */
focused(boolean val)381     public UiSelector focused(boolean val) {
382         return buildSelector(SELECTOR_FOCUSED, val);
383     }
384 
385     /**
386      * Set the search criteria to match widgets that are focusable.
387      *
388      * Typically, using this search criteria alone is not useful.
389      * You should also include additional criteria, such as text,
390      * content-description, or the class name for a widget.
391      *
392      * If no other search criteria is specified, and there is more
393      * than one matching widget, the first widget in the tree
394      * is selected.
395      *
396      * @param val Value to match
397      * @return UiSelector with the specified search criteria
398      * @since API Level 16
399      */
focusable(boolean val)400     public UiSelector focusable(boolean val) {
401         return buildSelector(SELECTOR_FOCUSABLE, val);
402     }
403 
404     /**
405      * Set the search criteria to match widgets that are scrollable.
406      *
407      * Typically, using this search criteria alone is not useful.
408      * You should also include additional criteria, such as text,
409      * content-description, or the class name for a widget.
410      *
411      * If no other search criteria is specified, and there is more
412      * than one matching widget, the first widget in the tree
413      * is selected.
414      *
415      * @param val Value to match
416      * @return UiSelector with the specified search criteria
417      * @since API Level 16
418      */
scrollable(boolean val)419     public UiSelector scrollable(boolean val) {
420         return buildSelector(SELECTOR_SCROLLABLE, val);
421     }
422 
423     /**
424      * Set the search criteria to match widgets that
425      * are currently selected.
426      *
427      * Typically, using this search criteria alone is not useful.
428      * You should also include additional criteria, such as text,
429      * content-description, or the class name for a widget.
430      *
431      * If no other search criteria is specified, and there is more
432      * than one matching widget, the first widget in the tree
433      * is selected.
434      *
435      * @param val Value to match
436      * @return UiSelector with the specified search criteria
437      * @since API Level 16
438      */
selected(boolean val)439     public UiSelector selected(boolean val) {
440         return buildSelector(SELECTOR_SELECTED, val);
441     }
442 
443     /**
444      * Set the search criteria to match widgets that
445      * are currently checked (usually for checkboxes).
446      *
447      * Typically, using this search criteria alone is not useful.
448      * You should also include additional criteria, such as text,
449      * content-description, or the class name for a widget.
450      *
451      * If no other search criteria is specified, and there is more
452      * than one matching widget, the first widget in the tree
453      * is selected.
454      *
455      * @param val Value to match
456      * @return UiSelector with the specified search criteria
457      * @since API Level 16
458      */
checked(boolean val)459     public UiSelector checked(boolean val) {
460         return buildSelector(SELECTOR_CHECKED, val);
461     }
462 
463     /**
464      * Set the search criteria to match widgets that are clickable.
465      *
466      * Typically, using this search criteria alone is not useful.
467      * You should also include additional criteria, such as text,
468      * content-description, or the class name for a widget.
469      *
470      * If no other search criteria is specified, and there is more
471      * than one matching widget, the first widget in the tree
472      * is selected.
473      *
474      * @param val Value to match
475      * @return UiSelector with the specified search criteria
476      * @since API Level 16
477      */
clickable(boolean val)478     public UiSelector clickable(boolean val) {
479         return buildSelector(SELECTOR_CLICKABLE, val);
480     }
481 
482     /**
483      * Set the search criteria to match widgets that are checkable.
484      *
485      * Typically, using this search criteria alone is not useful.
486      * You should also include additional criteria, such as text,
487      * content-description, or the class name for a widget.
488      *
489      * If no other search criteria is specified, and there is more
490      * than one matching widget, the first widget in the tree
491      * is selected.
492      *
493      * @param val Value to match
494      * @return UiSelector with the specified search criteria
495      * @since API Level 18
496      */
checkable(boolean val)497     public UiSelector checkable(boolean val) {
498         return buildSelector(SELECTOR_CHECKABLE, val);
499     }
500 
501     /**
502      * Set the search criteria to match widgets that are long-clickable.
503      *
504      * Typically, using this search criteria alone is not useful.
505      * You should also include additional criteria, such as text,
506      * content-description, or the class name for a widget.
507      *
508      * If no other search criteria is specified, and there is more
509      * than one matching widget, the first widget in the tree
510      * is selected.
511      *
512      * @param val Value to match
513      * @return UiSelector with the specified search criteria
514      * @since API Level 17
515      */
longClickable(boolean val)516     public UiSelector longClickable(boolean val) {
517         return buildSelector(SELECTOR_LONG_CLICKABLE, val);
518     }
519 
520     /**
521      * Adds a child UiSelector criteria to this selector.
522      *
523      * Use this selector to narrow the search scope to
524      * child widgets under a specific parent widget.
525      *
526      * @param selector
527      * @return UiSelector with this added search criterion
528      * @since API Level 16
529      */
childSelector(UiSelector selector)530     public UiSelector childSelector(UiSelector selector) {
531         return buildSelector(SELECTOR_CHILD, selector);
532     }
533 
patternSelector(UiSelector selector)534     private UiSelector patternSelector(UiSelector selector) {
535         return buildSelector(SELECTOR_PATTERN, selector);
536     }
537 
containerSelector(UiSelector selector)538     private UiSelector containerSelector(UiSelector selector) {
539         return buildSelector(SELECTOR_CONTAINER, selector);
540     }
541 
542     /**
543      * Adds a child UiSelector criteria to this selector which is used to
544      * start search from the parent widget.
545      *
546      * Use this selector to narrow the search scope to
547      * sibling widgets as well all child widgets under a parent.
548      *
549      * @param selector
550      * @return UiSelector with this added search criterion
551      * @since API Level 16
552      */
fromParent(UiSelector selector)553     public UiSelector fromParent(UiSelector selector) {
554         return buildSelector(SELECTOR_PARENT, selector);
555     }
556 
557     /**
558      * Set the search criteria to match the package name
559      * of the application that contains the widget.
560      *
561      * @param name Value to match
562      * @return UiSelector with the specified search criteria
563      * @since API Level 16
564      */
packageName(String name)565     public UiSelector packageName(String name) {
566         return buildSelector(SELECTOR_PACKAGE_NAME, name);
567     }
568 
569     /**
570      * Set the search criteria to match the package name
571      * of the application that contains the widget.
572      *
573      * @param regex a regular expression
574      * @return UiSelector with the specified search criteria
575      * @since API Level 17
576      */
packageNameMatches(String regex)577     public UiSelector packageNameMatches(String regex) {
578         return buildSelector(SELECTOR_PACKAGE_NAME_REGEX, Pattern.compile(regex));
579     }
580 
581     /**
582      * Building a UiSelector always returns a new UiSelector and never modifies the
583      * existing UiSelector being used.
584      */
buildSelector(int selectorId, Object selectorValue)585     private UiSelector buildSelector(int selectorId, Object selectorValue) {
586         UiSelector selector = new UiSelector(this);
587         if (selectorId == SELECTOR_CHILD || selectorId == SELECTOR_PARENT)
588             selector.getLastSubSelector().mSelectorAttributes.put(selectorId, selectorValue);
589         else
590             selector.mSelectorAttributes.put(selectorId, selectorValue);
591         return selector;
592     }
593 
594     /**
595      * Selectors may have a hierarchy defined by specifying child nodes to be matched.
596      * It is not necessary that every selector have more than one level. A selector
597      * can also be a single level referencing only one node. In such cases the return
598      * it null.
599      *
600      * @return a child selector if one exists. Else null if this selector does not
601      * reference child node.
602      */
getChildSelector()603     UiSelector getChildSelector() {
604         UiSelector selector = (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_CHILD, null);
605         if (selector != null)
606             return new UiSelector(selector);
607         return null;
608     }
609 
getPatternSelector()610     UiSelector getPatternSelector() {
611         UiSelector selector =
612                 (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_PATTERN, null);
613         if (selector != null)
614             return new UiSelector(selector);
615         return null;
616     }
617 
getContainerSelector()618     UiSelector getContainerSelector() {
619         UiSelector selector =
620                 (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_CONTAINER, null);
621         if (selector != null)
622             return new UiSelector(selector);
623         return null;
624     }
625 
getParentSelector()626     UiSelector getParentSelector() {
627         UiSelector selector =
628                 (UiSelector) mSelectorAttributes.get(UiSelector.SELECTOR_PARENT, null);
629         if (selector != null)
630             return new UiSelector(selector);
631         return null;
632     }
633 
getInstance()634     int getInstance() {
635         return getInt(UiSelector.SELECTOR_INSTANCE);
636     }
637 
getString(int criterion)638     String getString(int criterion) {
639         return (String) mSelectorAttributes.get(criterion, null);
640     }
641 
getBoolean(int criterion)642     boolean getBoolean(int criterion) {
643         return (Boolean) mSelectorAttributes.get(criterion, false);
644     }
645 
getInt(int criterion)646     int getInt(int criterion) {
647         return (Integer) mSelectorAttributes.get(criterion, 0);
648     }
649 
getPattern(int criterion)650     Pattern getPattern(int criterion) {
651         return (Pattern) mSelectorAttributes.get(criterion, null);
652     }
653 
isMatchFor(AccessibilityNodeInfo node, int index)654     boolean isMatchFor(AccessibilityNodeInfo node, int index) {
655         int size = mSelectorAttributes.size();
656         for(int x = 0; x < size; x++) {
657             CharSequence s = null;
658             int criterion = mSelectorAttributes.keyAt(x);
659             switch(criterion) {
660             case UiSelector.SELECTOR_INDEX:
661                 if (index != this.getInt(criterion))
662                     return false;
663                 break;
664             case UiSelector.SELECTOR_CHECKED:
665                 if (node.isChecked() != getBoolean(criterion)) {
666                     return false;
667                 }
668                 break;
669             case UiSelector.SELECTOR_CLASS:
670                 s = node.getClassName();
671                 if (s == null || !s.toString().contentEquals(getString(criterion))) {
672                     return false;
673                 }
674                 break;
675             case UiSelector.SELECTOR_CLASS_REGEX:
676                 s = node.getClassName();
677                 if (s == null || !getPattern(criterion).matcher(s).matches()) {
678                     return false;
679                 }
680                 break;
681             case UiSelector.SELECTOR_CLICKABLE:
682                 if (node.isClickable() != getBoolean(criterion)) {
683                     return false;
684                 }
685                 break;
686             case UiSelector.SELECTOR_CHECKABLE:
687                 if (node.isCheckable() != getBoolean(criterion)) {
688                     return false;
689                 }
690                 break;
691             case UiSelector.SELECTOR_LONG_CLICKABLE:
692                 if (node.isLongClickable() != getBoolean(criterion)) {
693                     return false;
694                 }
695                 break;
696             case UiSelector.SELECTOR_CONTAINS_DESCRIPTION:
697                 s = node.getContentDescription();
698                 if (s == null || !s.toString().toLowerCase()
699                         .contains(getString(criterion).toLowerCase())) {
700                     return false;
701                 }
702                 break;
703             case UiSelector.SELECTOR_START_DESCRIPTION:
704                 s = node.getContentDescription();
705                 if (s == null || !s.toString().toLowerCase()
706                         .startsWith(getString(criterion).toLowerCase())) {
707                     return false;
708                 }
709                 break;
710             case UiSelector.SELECTOR_DESCRIPTION:
711                 s = node.getContentDescription();
712                 if (s == null || !s.toString().contentEquals(getString(criterion))) {
713                     return false;
714                 }
715                 break;
716             case UiSelector.SELECTOR_DESCRIPTION_REGEX:
717                 s = node.getContentDescription();
718                 if (s == null || !getPattern(criterion).matcher(s).matches()) {
719                     return false;
720                 }
721                 break;
722             case UiSelector.SELECTOR_CONTAINS_TEXT:
723                 s = node.getText();
724                 if (s == null || !s.toString().toLowerCase()
725                         .contains(getString(criterion).toLowerCase())) {
726                     return false;
727                 }
728                 break;
729             case UiSelector.SELECTOR_START_TEXT:
730                 s = node.getText();
731                 if (s == null || !s.toString().toLowerCase()
732                         .startsWith(getString(criterion).toLowerCase())) {
733                     return false;
734                 }
735                 break;
736             case UiSelector.SELECTOR_TEXT:
737                 s = node.getText();
738                 if (s == null || !s.toString().contentEquals(getString(criterion))) {
739                     return false;
740                 }
741                 break;
742             case UiSelector.SELECTOR_TEXT_REGEX:
743                 s = node.getText();
744                 if (s == null || !getPattern(criterion).matcher(s).matches()) {
745                     return false;
746                 }
747                 break;
748             case UiSelector.SELECTOR_ENABLED:
749                 if (node.isEnabled() != getBoolean(criterion)) {
750                     return false;
751                 }
752                 break;
753             case UiSelector.SELECTOR_FOCUSABLE:
754                 if (node.isFocusable() != getBoolean(criterion)) {
755                     return false;
756                 }
757                 break;
758             case UiSelector.SELECTOR_FOCUSED:
759                 if (node.isFocused() != getBoolean(criterion)) {
760                     return false;
761                 }
762                 break;
763             case UiSelector.SELECTOR_ID:
764                 break; //TODO: do we need this for AccessibilityNodeInfo.id?
765             case UiSelector.SELECTOR_PACKAGE_NAME:
766                 s = node.getPackageName();
767                 if (s == null || !s.toString().contentEquals(getString(criterion))) {
768                     return false;
769                 }
770                 break;
771             case UiSelector.SELECTOR_PACKAGE_NAME_REGEX:
772                 s = node.getPackageName();
773                 if (s == null || !getPattern(criterion).matcher(s).matches()) {
774                     return false;
775                 }
776                 break;
777             case UiSelector.SELECTOR_SCROLLABLE:
778                 if (node.isScrollable() != getBoolean(criterion)) {
779                     return false;
780                 }
781                 break;
782             case UiSelector.SELECTOR_SELECTED:
783                 if (node.isSelected() != getBoolean(criterion)) {
784                     return false;
785                 }
786                 break;
787             case UiSelector.SELECTOR_RESOURCE_ID:
788                 s = node.getViewIdResourceName();
789                 if (s == null || !s.toString().contentEquals(getString(criterion))) {
790                     return false;
791                 }
792                 break;
793             case UiSelector.SELECTOR_RESOURCE_ID_REGEX:
794                 s = node.getViewIdResourceName();
795                 if (s == null || !getPattern(criterion).matcher(s).matches()) {
796                     return false;
797                 }
798                 break;
799             }
800         }
801         return matchOrUpdateInstance();
802     }
803 
matchOrUpdateInstance()804     private boolean matchOrUpdateInstance() {
805         int currentSelectorCounter = 0;
806         int currentSelectorInstance = 0;
807 
808         // matched attributes - now check for matching instance number
809         if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_INSTANCE) >= 0) {
810             currentSelectorInstance =
811                     (Integer)mSelectorAttributes.get(UiSelector.SELECTOR_INSTANCE);
812         }
813 
814         // instance is required. Add count if not already counting
815         if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_COUNT) >= 0) {
816             currentSelectorCounter = (Integer)mSelectorAttributes.get(UiSelector.SELECTOR_COUNT);
817         }
818 
819         // Verify
820         if (currentSelectorInstance == currentSelectorCounter) {
821             return true;
822         }
823         // Update count
824         if (currentSelectorInstance > currentSelectorCounter) {
825             mSelectorAttributes.put(UiSelector.SELECTOR_COUNT, ++currentSelectorCounter);
826         }
827         return false;
828     }
829 
830     /**
831      * Leaf selector indicates no more child or parent selectors
832      * are declared in the this selector.
833      * @return true if is leaf.
834      */
isLeaf()835     boolean isLeaf() {
836         if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0 &&
837                 mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) < 0) {
838             return true;
839         }
840         return false;
841     }
842 
hasChildSelector()843     boolean hasChildSelector() {
844         if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0) {
845             return false;
846         }
847         return true;
848     }
849 
hasPatternSelector()850     boolean hasPatternSelector() {
851         if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PATTERN) < 0) {
852             return false;
853         }
854         return true;
855     }
856 
hasContainerSelector()857     boolean hasContainerSelector() {
858         if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CONTAINER) < 0) {
859             return false;
860         }
861         return true;
862     }
863 
hasParentSelector()864     boolean hasParentSelector() {
865         if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) < 0) {
866             return false;
867         }
868         return true;
869     }
870 
871     /**
872      * Returns the deepest selector in the chain of possible sub selectors.
873      * A chain of selector is created when either of {@link UiSelector#childSelector(UiSelector)}
874      * or {@link UiSelector#fromParent(UiSelector)} are used once or more in the construction of
875      * a selector.
876      * @return last UiSelector in chain
877      */
getLastSubSelector()878     private UiSelector getLastSubSelector() {
879         if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) >= 0) {
880             UiSelector child = (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_CHILD);
881             if (child.getLastSubSelector() == null) {
882                 return child;
883             }
884             return child.getLastSubSelector();
885         } else if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) >= 0) {
886             UiSelector parent = (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_PARENT);
887             if (parent.getLastSubSelector() == null) {
888                 return parent;
889             }
890             return parent.getLastSubSelector();
891         }
892         return this;
893     }
894 
895     @Override
toString()896     public String toString() {
897         return dumpToString(true);
898     }
899 
dumpToString(boolean all)900     String dumpToString(boolean all) {
901         StringBuilder builder = new StringBuilder();
902         builder.append(UiSelector.class.getSimpleName() + "[");
903         final int criterionCount = mSelectorAttributes.size();
904         for (int i = 0; i < criterionCount; i++) {
905             if (i > 0) {
906                 builder.append(", ");
907             }
908             final int criterion = mSelectorAttributes.keyAt(i);
909             switch (criterion) {
910             case SELECTOR_TEXT:
911                 builder.append("TEXT=").append(mSelectorAttributes.valueAt(i));
912                 break;
913             case SELECTOR_TEXT_REGEX:
914                 builder.append("TEXT_REGEX=").append(mSelectorAttributes.valueAt(i));
915                 break;
916             case SELECTOR_START_TEXT:
917                 builder.append("START_TEXT=").append(mSelectorAttributes.valueAt(i));
918                 break;
919             case SELECTOR_CONTAINS_TEXT:
920                 builder.append("CONTAINS_TEXT=").append(mSelectorAttributes.valueAt(i));
921                 break;
922             case SELECTOR_CLASS:
923                 builder.append("CLASS=").append(mSelectorAttributes.valueAt(i));
924                 break;
925             case SELECTOR_CLASS_REGEX:
926                 builder.append("CLASS_REGEX=").append(mSelectorAttributes.valueAt(i));
927                 break;
928             case SELECTOR_DESCRIPTION:
929                 builder.append("DESCRIPTION=").append(mSelectorAttributes.valueAt(i));
930                 break;
931             case SELECTOR_DESCRIPTION_REGEX:
932                 builder.append("DESCRIPTION_REGEX=").append(mSelectorAttributes.valueAt(i));
933                 break;
934             case SELECTOR_START_DESCRIPTION:
935                 builder.append("START_DESCRIPTION=").append(mSelectorAttributes.valueAt(i));
936                 break;
937             case SELECTOR_CONTAINS_DESCRIPTION:
938                 builder.append("CONTAINS_DESCRIPTION=").append(mSelectorAttributes.valueAt(i));
939                 break;
940             case SELECTOR_INDEX:
941                 builder.append("INDEX=").append(mSelectorAttributes.valueAt(i));
942                 break;
943             case SELECTOR_INSTANCE:
944                 builder.append("INSTANCE=").append(mSelectorAttributes.valueAt(i));
945                 break;
946             case SELECTOR_ENABLED:
947                 builder.append("ENABLED=").append(mSelectorAttributes.valueAt(i));
948                 break;
949             case SELECTOR_FOCUSED:
950                 builder.append("FOCUSED=").append(mSelectorAttributes.valueAt(i));
951                 break;
952             case SELECTOR_FOCUSABLE:
953                 builder.append("FOCUSABLE=").append(mSelectorAttributes.valueAt(i));
954                 break;
955             case SELECTOR_SCROLLABLE:
956                 builder.append("SCROLLABLE=").append(mSelectorAttributes.valueAt(i));
957                 break;
958             case SELECTOR_CLICKABLE:
959                 builder.append("CLICKABLE=").append(mSelectorAttributes.valueAt(i));
960                 break;
961             case SELECTOR_CHECKABLE:
962                 builder.append("CHECKABLE=").append(mSelectorAttributes.valueAt(i));
963                 break;
964             case SELECTOR_LONG_CLICKABLE:
965                 builder.append("LONG_CLICKABLE=").append(mSelectorAttributes.valueAt(i));
966                 break;
967             case SELECTOR_CHECKED:
968                 builder.append("CHECKED=").append(mSelectorAttributes.valueAt(i));
969                 break;
970             case SELECTOR_SELECTED:
971                 builder.append("SELECTED=").append(mSelectorAttributes.valueAt(i));
972                 break;
973             case SELECTOR_ID:
974                 builder.append("ID=").append(mSelectorAttributes.valueAt(i));
975                 break;
976             case SELECTOR_CHILD:
977                 if (all)
978                     builder.append("CHILD=").append(mSelectorAttributes.valueAt(i));
979                 else
980                     builder.append("CHILD[..]");
981                 break;
982             case SELECTOR_PATTERN:
983                 if (all)
984                     builder.append("PATTERN=").append(mSelectorAttributes.valueAt(i));
985                 else
986                     builder.append("PATTERN[..]");
987                 break;
988             case SELECTOR_CONTAINER:
989                 if (all)
990                     builder.append("CONTAINER=").append(mSelectorAttributes.valueAt(i));
991                 else
992                     builder.append("CONTAINER[..]");
993                 break;
994             case SELECTOR_PARENT:
995                 if (all)
996                     builder.append("PARENT=").append(mSelectorAttributes.valueAt(i));
997                 else
998                     builder.append("PARENT[..]");
999                 break;
1000             case SELECTOR_COUNT:
1001                 builder.append("COUNT=").append(mSelectorAttributes.valueAt(i));
1002                 break;
1003             case SELECTOR_PACKAGE_NAME:
1004                 builder.append("PACKAGE NAME=").append(mSelectorAttributes.valueAt(i));
1005                 break;
1006             case SELECTOR_PACKAGE_NAME_REGEX:
1007                 builder.append("PACKAGE_NAME_REGEX=").append(mSelectorAttributes.valueAt(i));
1008                 break;
1009             case SELECTOR_RESOURCE_ID:
1010                 builder.append("RESOURCE_ID=").append(mSelectorAttributes.valueAt(i));
1011                 break;
1012             case SELECTOR_RESOURCE_ID_REGEX:
1013                 builder.append("RESOURCE_ID_REGEX=").append(mSelectorAttributes.valueAt(i));
1014                 break;
1015             default:
1016                 builder.append("UNDEFINED="+criterion+" ").append(mSelectorAttributes.valueAt(i));
1017             }
1018         }
1019         builder.append("]");
1020         return builder.toString();
1021     }
1022 }
1023