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
17package com.android.uiautomator.core;
18
19import android.util.SparseArray;
20import android.view.accessibility.AccessibilityNodeInfo;
21
22import 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 */
31public 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     */
70    public UiSelector() {
71    }
72
73    UiSelector(UiSelector selector) {
74        mSelectorAttributes = selector.cloneSelector().mSelectorAttributes;
75    }
76
77    /**
78     * @since API Level 17
79     */
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
92    static UiSelector patternBuilder(UiSelector selector) {
93        if (!selector.hasPatternSelector()) {
94            return new UiSelector().patternSelector(selector);
95        }
96        return selector;
97    }
98
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
530    public UiSelector childSelector(UiSelector selector) {
531        return buildSelector(SELECTOR_CHILD, selector);
532    }
533
534    private UiSelector patternSelector(UiSelector selector) {
535        return buildSelector(SELECTOR_PATTERN, selector);
536    }
537
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     */
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     */
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     */
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     */
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     */
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
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
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
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
634    int getInstance() {
635        return getInt(UiSelector.SELECTOR_INSTANCE);
636    }
637
638    String getString(int criterion) {
639        return (String) mSelectorAttributes.get(criterion, null);
640    }
641
642    boolean getBoolean(int criterion) {
643        return (Boolean) mSelectorAttributes.get(criterion, false);
644    }
645
646    int getInt(int criterion) {
647        return (Integer) mSelectorAttributes.get(criterion, 0);
648    }
649
650    Pattern getPattern(int criterion) {
651        return (Pattern) mSelectorAttributes.get(criterion, null);
652    }
653
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
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     */
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
843    boolean hasChildSelector() {
844        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0) {
845            return false;
846        }
847        return true;
848    }
849
850    boolean hasPatternSelector() {
851        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PATTERN) < 0) {
852            return false;
853        }
854        return true;
855    }
856
857    boolean hasContainerSelector() {
858        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CONTAINER) < 0) {
859            return false;
860        }
861        return true;
862    }
863
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     */
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
896    public String toString() {
897        return dumpToString(true);
898    }
899
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