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