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 * @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
34public 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     */
73    public UiSelector() {
74    }
75
76    UiSelector(UiSelector selector) {
77        mSelectorAttributes = selector.cloneSelector().mSelectorAttributes;
78    }
79
80    /**
81     * @since API Level 17
82     */
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
95    static UiSelector patternBuilder(UiSelector selector) {
96        if (!selector.hasPatternSelector()) {
97            return new UiSelector().patternSelector(selector);
98        }
99        return selector;
100    }
101
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
533    public UiSelector childSelector(UiSelector selector) {
534        return buildSelector(SELECTOR_CHILD, selector);
535    }
536
537    private UiSelector patternSelector(UiSelector selector) {
538        return buildSelector(SELECTOR_PATTERN, selector);
539    }
540
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     */
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     */
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     */
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     */
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     */
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
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
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
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
637    int getInstance() {
638        return getInt(UiSelector.SELECTOR_INSTANCE);
639    }
640
641    String getString(int criterion) {
642        return (String) mSelectorAttributes.get(criterion, null);
643    }
644
645    boolean getBoolean(int criterion) {
646        return (Boolean) mSelectorAttributes.get(criterion, false);
647    }
648
649    int getInt(int criterion) {
650        return (Integer) mSelectorAttributes.get(criterion, 0);
651    }
652
653    Pattern getPattern(int criterion) {
654        return (Pattern) mSelectorAttributes.get(criterion, null);
655    }
656
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
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     */
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
846    boolean hasChildSelector() {
847        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0) {
848            return false;
849        }
850        return true;
851    }
852
853    boolean hasPatternSelector() {
854        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PATTERN) < 0) {
855            return false;
856        }
857        return true;
858    }
859
860    boolean hasContainerSelector() {
861        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CONTAINER) < 0) {
862            return false;
863        }
864        return true;
865    }
866
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     */
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
899    public String toString() {
900        return dumpToString(true);
901    }
902
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