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