Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.uiautomator.core;
     18 
     19 import android.util.SparseArray;
     20 import android.view.accessibility.AccessibilityNodeInfo;
     21 
     22 import java.util.regex.Pattern;
     23 
     24 /**
     25  * Specifies the elements in the layout hierarchy for tests to target, filtered
     26  * by properties such as text value, content-description, class name, and state
     27  * information. You can also target an element by its location in a layout
     28  * hierarchy.
     29  * @since API Level 16
     30  */
     31 public class UiSelector {
     32     static final int SELECTOR_NIL = 0;
     33     static final int SELECTOR_TEXT = 1;
     34     static final int SELECTOR_START_TEXT = 2;
     35     static final int SELECTOR_CONTAINS_TEXT = 3;
     36     static final int SELECTOR_CLASS = 4;
     37     static final int SELECTOR_DESCRIPTION = 5;
     38     static final int SELECTOR_START_DESCRIPTION = 6;
     39     static final int SELECTOR_CONTAINS_DESCRIPTION = 7;
     40     static final int SELECTOR_INDEX = 8;
     41     static final int SELECTOR_INSTANCE = 9;
     42     static final int SELECTOR_ENABLED = 10;
     43     static final int SELECTOR_FOCUSED = 11;
     44     static final int SELECTOR_FOCUSABLE = 12;
     45     static final int SELECTOR_SCROLLABLE = 13;
     46     static final int SELECTOR_CLICKABLE = 14;
     47     static final int SELECTOR_CHECKED = 15;
     48     static final int SELECTOR_SELECTED = 16;
     49     static final int SELECTOR_ID = 17;
     50     static final int SELECTOR_PACKAGE_NAME = 18;
     51     static final int SELECTOR_CHILD = 19;
     52     static final int SELECTOR_CONTAINER = 20;
     53     static final int SELECTOR_PATTERN = 21;
     54     static final int SELECTOR_PARENT = 22;
     55     static final int SELECTOR_COUNT = 23;
     56     static final int SELECTOR_LONG_CLICKABLE = 24;
     57     static final int SELECTOR_TEXT_REGEX = 25;
     58     static final int SELECTOR_CLASS_REGEX = 26;
     59     static final int SELECTOR_DESCRIPTION_REGEX = 27;
     60     static final int SELECTOR_PACKAGE_NAME_REGEX = 28;
     61     static final int SELECTOR_RESOURCE_ID = 29;
     62     static final int SELECTOR_CHECKABLE = 30;
     63     static final int SELECTOR_RESOURCE_ID_REGEX = 31;
     64 
     65     private SparseArray<Object> mSelectorAttributes = new SparseArray<Object>();
     66 
     67     /**
     68      * @since API Level 16
     69      */
     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