public class

UiSelector

extends java.lang.Object

 java.lang.Object

↳androidx.test.uiautomator.UiSelector

Gradle dependencies

compile group: 'androidx.test.uiautomator', name: 'uiautomator', version: '2.4.0-alpha01'

  • groupId: androidx.test.uiautomator
  • artifactId: uiautomator
  • version: 2.4.0-alpha01

Artifact androidx.test.uiautomator:uiautomator:2.4.0-alpha01 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.test.uiautomator:uiautomator com.android.support.test.uiautomator:uiautomator

Androidx class mapping:

androidx.test.uiautomator.UiSelector android.support.test.uiautomator.UiSelector

Overview

Specifies the elements in the layout hierarchy for tests to target, filtered by properties such as text value, content-description, class name, and state information. You can also target an element by its location in a layout hierarchy.

Summary

Constructors
publicUiSelector()

Methods
public UiSelectorcheckable(boolean val)

Set the search criteria to match widgets that are checkable.

public UiSelectorchecked(boolean val)

Set the search criteria to match widgets that are currently checked (usually for checkboxes).

public UiSelectorchildSelector(UiSelector selector)

Adds a child UiSelector criteria to this selector.

public UiSelectorclassName(java.lang.Class<java.lang.Object> type)

Set the search criteria to match the class property for a widget (for example, "android.widget.Button").

public UiSelectorclassName(java.lang.String className)

Set the search criteria to match the class property for a widget (for example, "android.widget.Button").

public UiSelectorclassNameMatches(java.lang.String regex)

Set the search criteria to match the class property for a widget, using a regular expression.

public UiSelectorclickable(boolean val)

Set the search criteria to match widgets that are clickable.

protected UiSelectorcloneSelector()

public UiSelectordescription(java.lang.String desc)

Set the search criteria to match the content-description property for a widget.

public UiSelectordescriptionContains(java.lang.String desc)

Set the search criteria to match the content-description property for a widget.

public UiSelectordescriptionMatches(java.lang.String regex)

Set the search criteria to match the content-description property for a widget.

public UiSelectordescriptionStartsWith(java.lang.String desc)

Set the search criteria to match the content-description property for a widget.

public UiSelectorenabled(boolean val)

Set the search criteria to match widgets that are enabled.

public UiSelectorfocusable(boolean val)

Set the search criteria to match widgets that are focusable.

public UiSelectorfocused(boolean val)

Set the search criteria to match widgets that have focus.

public UiSelectorfromParent(UiSelector selector)

Adds a child UiSelector criteria to this selector which is used to start search from the parent widget.

public UiSelectorindex(int index)

Set the search criteria to match the widget by its node index in the layout hierarchy.

public UiSelectorinstance(int instance)

Set the search criteria to match the widget by its instance number.

public UiSelectorlongClickable(boolean val)

Set the search criteria to match widgets that are long-clickable.

public UiSelectorpackageName(java.lang.String name)

Set the search criteria to match the package name of the application that contains the widget.

public UiSelectorpackageNameMatches(java.lang.String regex)

Set the search criteria to match the package name of the application that contains the widget.

public UiSelectorresourceId(java.lang.String id)

Set the search criteria to match the given resource ID.

public UiSelectorresourceIdMatches(java.lang.String regex)

Set the search criteria to match the resource ID of the widget, using a regular expression.

public UiSelectorscrollable(boolean val)

Set the search criteria to match widgets that are scrollable.

public UiSelectorselected(boolean val)

Set the search criteria to match widgets that are currently selected.

public UiSelectortext(java.lang.String text)

Set the search criteria to match the visible text displayed in a widget (for example, the text label to launch an app).

public UiSelectortextContains(java.lang.String text)

Set the search criteria to match the visible text in a widget where the visible text must contain the string in your input argument.

public UiSelectortextMatches(java.lang.String regex)

Set the search criteria to match the visible text displayed in a layout element, using a regular expression.

public UiSelectortextStartsWith(java.lang.String text)

Set the search criteria to match visible text in a widget that is prefixed by the text parameter.

public java.lang.StringtoString()

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

Constructors

public UiSelector()

Methods

protected UiSelector cloneSelector()

public UiSelector text(java.lang.String text)

Set the search criteria to match the visible text displayed in a widget (for example, the text label to launch an app). The text for the element must match exactly with the string in your input argument. Matching is case-sensitive.

Parameters:

text: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector textMatches(java.lang.String regex)

Set the search criteria to match the visible text displayed in a layout element, using a regular expression. The text in the widget must match exactly with the string in your input argument.

Parameters:

regex: a regular expression

Returns:

UiSelector with the specified search criteria

public UiSelector textStartsWith(java.lang.String text)

Set the search criteria to match visible text in a widget that is prefixed by the text parameter. The matching is case-insensitive.

Parameters:

text: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector textContains(java.lang.String text)

Set the search criteria to match the visible text in a widget where the visible text must contain the string in your input argument. The matching is case-sensitive.

Parameters:

text: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector className(java.lang.String className)

Set the search criteria to match the class property for a widget (for example, "android.widget.Button").

Parameters:

className: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector classNameMatches(java.lang.String regex)

Set the search criteria to match the class property for a widget, using a regular expression.

Parameters:

regex: a regular expression

Returns:

UiSelector with the specified search criteria

public UiSelector className(java.lang.Class<java.lang.Object> type)

Set the search criteria to match the class property for a widget (for example, "android.widget.Button").

Parameters:

type: type

Returns:

UiSelector with the specified search criteria

public UiSelector description(java.lang.String desc)

Set the search criteria to match the content-description property for a widget. The content-description is typically used by the Android Accessibility framework to provide an audio prompt for the widget when the widget is selected. The content-description for the widget must match exactly with the string in your input argument. Matching is case-sensitive.

Parameters:

desc: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector descriptionMatches(java.lang.String regex)

Set the search criteria to match the content-description property for a widget. The content-description is typically used by the Android Accessibility framework to provide an audio prompt for the widget when the widget is selected. The content-description for the widget must match exactly with the string in your input argument.

Parameters:

regex: a regular expression

Returns:

UiSelector with the specified search criteria

public UiSelector descriptionStartsWith(java.lang.String desc)

Set the search criteria to match the content-description property for a widget. The content-description is typically used by the Android Accessibility framework to provide an audio prompt for the widget when the widget is selected. The content-description for the widget must start with the string in your input argument. Matching is case-insensitive.

Parameters:

desc: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector descriptionContains(java.lang.String desc)

Set the search criteria to match the content-description property for a widget. The content-description is typically used by the Android Accessibility framework to provide an audio prompt for the widget when the widget is selected. The content-description for the widget must contain the string in your input argument. Matching is case-insensitive.

Parameters:

desc: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector resourceId(java.lang.String id)

Set the search criteria to match the given resource ID.

Parameters:

id: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector resourceIdMatches(java.lang.String regex)

Set the search criteria to match the resource ID of the widget, using a regular expression.

Parameters:

regex: a regular expression

Returns:

UiSelector with the specified search criteria

public UiSelector index(int index)

Set the search criteria to match the widget by its node index in the layout hierarchy. The index value must be 0 or greater. Using the index can be unreliable and should only be used as a last resort for matching. Instead, consider using the UiSelector.instance(int) method.

Parameters:

index: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector instance(int instance)

Set the search criteria to match the widget by its instance number. The instance value must be 0 or greater, where the first instance is 0. For example, to simulate a user click on the third image that is enabled in a UI screen, you could specify a a search criteria where the instance is 2, the UiSelector.className(String) matches the image widget class, and UiSelector.enabled(boolean) is true. The code would look like this: new UiSelector().className("android.widget.ImageView") .enabled(true).instance(2);

Parameters:

instance: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector enabled(boolean val)

Set the search criteria to match widgets that are enabled. Typically, using this search criteria alone is not useful. You should also include additional criteria, such as text, content-description, or the class name for a widget. If no other search criteria is specified, and there is more than one matching widget, the first widget in the tree is selected.

Parameters:

val: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector focused(boolean val)

Set the search criteria to match widgets that have focus. Typically, using this search criteria alone is not useful. You should also include additional criteria, such as text, content-description, or the class name for a widget. If no other search criteria is specified, and there is more than one matching widget, the first widget in the tree is selected.

Parameters:

val: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector focusable(boolean val)

Set the search criteria to match widgets that are focusable. Typically, using this search criteria alone is not useful. You should also include additional criteria, such as text, content-description, or the class name for a widget. If no other search criteria is specified, and there is more than one matching widget, the first widget in the tree is selected.

Parameters:

val: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector scrollable(boolean val)

Set the search criteria to match widgets that are scrollable. Typically, using this search criteria alone is not useful. You should also include additional criteria, such as text, content-description, or the class name for a widget. If no other search criteria is specified, and there is more than one matching widget, the first widget in the tree is selected.

Parameters:

val: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector selected(boolean val)

Set the search criteria to match widgets that are currently selected. Typically, using this search criteria alone is not useful. You should also include additional criteria, such as text, content-description, or the class name for a widget. If no other search criteria is specified, and there is more than one matching widget, the first widget in the tree is selected.

Parameters:

val: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector checked(boolean val)

Set the search criteria to match widgets that are currently checked (usually for checkboxes). Typically, using this search criteria alone is not useful. You should also include additional criteria, such as text, content-description, or the class name for a widget. If no other search criteria is specified, and there is more than one matching widget, the first widget in the tree is selected.

Parameters:

val: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector clickable(boolean val)

Set the search criteria to match widgets that are clickable. Typically, using this search criteria alone is not useful. You should also include additional criteria, such as text, content-description, or the class name for a widget. If no other search criteria is specified, and there is more than one matching widget, the first widget in the tree is selected.

Parameters:

val: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector checkable(boolean val)

Set the search criteria to match widgets that are checkable. Typically, using this search criteria alone is not useful. You should also include additional criteria, such as text, content-description, or the class name for a widget. If no other search criteria is specified, and there is more than one matching widget, the first widget in the tree is selected.

Parameters:

val: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector longClickable(boolean val)

Set the search criteria to match widgets that are long-clickable. Typically, using this search criteria alone is not useful. You should also include additional criteria, such as text, content-description, or the class name for a widget. If no other search criteria is specified, and there is more than one matching widget, the first widget in the tree is selected.

Parameters:

val: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector childSelector(UiSelector selector)

Adds a child UiSelector criteria to this selector. Use this selector to narrow the search scope to child widgets under a specific parent widget.

Parameters:

selector:

Returns:

UiSelector with this added search criterion

public UiSelector fromParent(UiSelector selector)

Adds a child UiSelector criteria to this selector which is used to start search from the parent widget. Use this selector to narrow the search scope to sibling widgets as well all child widgets under a parent.

Parameters:

selector:

Returns:

UiSelector with this added search criterion

public UiSelector packageName(java.lang.String name)

Set the search criteria to match the package name of the application that contains the widget.

Parameters:

name: Value to match

Returns:

UiSelector with the specified search criteria

public UiSelector packageNameMatches(java.lang.String regex)

Set the search criteria to match the package name of the application that contains the widget.

Parameters:

regex: a regular expression

Returns:

UiSelector with the specified search criteria

public java.lang.String toString()

Source

/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.test.uiautomator;

import static java.util.Objects.requireNonNull;

import android.util.SparseArray;
import android.view.accessibility.AccessibilityNodeInfo;

import androidx.annotation.NonNull;

import java.util.regex.Pattern;

/**
 * Specifies the elements in the layout hierarchy for tests to target, filtered
 * by properties such as text value, content-description, class name, and state
 * information. You can also target an element by its location in a layout
 * hierarchy.
 */
public class UiSelector {
    static final int SELECTOR_NIL = 0;
    static final int SELECTOR_TEXT = 1;
    static final int SELECTOR_START_TEXT = 2;
    static final int SELECTOR_CONTAINS_TEXT = 3;
    static final int SELECTOR_CLASS = 4;
    static final int SELECTOR_DESCRIPTION = 5;
    static final int SELECTOR_START_DESCRIPTION = 6;
    static final int SELECTOR_CONTAINS_DESCRIPTION = 7;
    static final int SELECTOR_INDEX = 8;
    static final int SELECTOR_INSTANCE = 9;
    static final int SELECTOR_ENABLED = 10;
    static final int SELECTOR_FOCUSED = 11;
    static final int SELECTOR_FOCUSABLE = 12;
    static final int SELECTOR_SCROLLABLE = 13;
    static final int SELECTOR_CLICKABLE = 14;
    static final int SELECTOR_CHECKED = 15;
    static final int SELECTOR_SELECTED = 16;
    static final int SELECTOR_ID = 17;
    static final int SELECTOR_PACKAGE_NAME = 18;
    static final int SELECTOR_CHILD = 19;
    static final int SELECTOR_CONTAINER = 20;
    static final int SELECTOR_PATTERN = 21;
    static final int SELECTOR_PARENT = 22;
    static final int SELECTOR_COUNT = 23;
    static final int SELECTOR_LONG_CLICKABLE = 24;
    static final int SELECTOR_TEXT_REGEX = 25;
    static final int SELECTOR_CLASS_REGEX = 26;
    static final int SELECTOR_DESCRIPTION_REGEX = 27;
    static final int SELECTOR_PACKAGE_NAME_REGEX = 28;
    static final int SELECTOR_RESOURCE_ID = 29;
    static final int SELECTOR_CHECKABLE = 30;
    static final int SELECTOR_RESOURCE_ID_REGEX = 31;

    private SparseArray<Object> mSelectorAttributes = new SparseArray<>();

    public UiSelector() {
    }

    UiSelector(UiSelector selector) {
        mSelectorAttributes = selector.cloneSelector().mSelectorAttributes;
    }

    @NonNull
    protected UiSelector cloneSelector() {
        UiSelector ret = new UiSelector();
        ret.mSelectorAttributes = mSelectorAttributes.clone();
        if (hasChildSelector())
            ret.mSelectorAttributes.put(SELECTOR_CHILD, new UiSelector(getChildSelector()));
        if (hasParentSelector())
            ret.mSelectorAttributes.put(SELECTOR_PARENT, new UiSelector(getParentSelector()));
        if (hasPatternSelector())
            ret.mSelectorAttributes.put(SELECTOR_PATTERN, new UiSelector(getPatternSelector()));
        return ret;
    }

    static UiSelector patternBuilder(UiSelector selector) {
        if (!selector.hasPatternSelector()) {
            return new UiSelector().patternSelector(selector);
        }
        return selector;
    }

    static UiSelector patternBuilder(UiSelector container, UiSelector pattern) {
        return new UiSelector(
                new UiSelector().containerSelector(container).patternSelector(pattern));
    }

    /**
     * Set the search criteria to match the visible text displayed
     * in a widget (for example, the text label to launch an app).
     *
     * The text for the element must match exactly with the string in your input
     * argument. Matching is case-sensitive.
     *
     * @param text Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector text(@NonNull String text) {
        requireNonNull(text, "text cannot be null");
        return buildSelector(SELECTOR_TEXT, text);
    }

    /**
     * Set the search criteria to match the visible text displayed in a layout
     * element, using a regular expression.
     *
     * The text in the widget must match exactly with the string in your
     * input argument.
     *
     * @param regex a regular expression
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector textMatches(@NonNull String regex) {
        requireNonNull(regex, "regex cannot be null");
        return buildSelector(SELECTOR_TEXT_REGEX, Pattern.compile(regex, Pattern.DOTALL));
    }

    /**
     * Set the search criteria to match visible text in a widget that is
     * prefixed by the text parameter.
     *
     * The matching is case-insensitive.
     *
     * @param text Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector textStartsWith(@NonNull String text) {
        requireNonNull(text, "text cannot be null");
        return buildSelector(SELECTOR_START_TEXT, text);
    }

    /**
     * Set the search criteria to match the visible text in a widget
     * where the visible text must contain the string in your input argument.
     *
     * The matching is case-sensitive.
     *
     * @param text Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector textContains(@NonNull String text) {
        requireNonNull(text, "text cannot be null");
        return buildSelector(SELECTOR_CONTAINS_TEXT, text);
    }

    /**
     * Set the search criteria to match the class property
     * for a widget (for example, "android.widget.Button").
     *
     * @param className Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector className(@NonNull String className) {
        requireNonNull(className, "className cannot be null");
        return buildSelector(SELECTOR_CLASS, className);
    }

    /**
     * Set the search criteria to match the class property
     * for a widget, using a regular expression.
     *
     * @param regex a regular expression
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector classNameMatches(@NonNull String regex) {
        requireNonNull(regex, "regex cannot be null");
        return buildSelector(SELECTOR_CLASS_REGEX, Pattern.compile(regex));
    }

    /**
     * Set the search criteria to match the class property
     * for a widget (for example, "android.widget.Button").
     *
     * @param type type
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public <T> UiSelector className(@NonNull Class<T> type) {
        requireNonNull(type, "type cannot be null");
        return buildSelector(SELECTOR_CLASS, type.getName());
    }

    /**
     * Set the search criteria to match the content-description
     * property for a widget.
     *
     * The content-description is typically used
     * by the Android Accessibility framework to
     * provide an audio prompt for the widget when
     * the widget is selected. The content-description
     * for the widget must match exactly
     * with the string in your input argument.
     *
     * Matching is case-sensitive.
     *
     * @param desc Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector description(@NonNull String desc) {
        requireNonNull(desc, "desc cannot be null");
        return buildSelector(SELECTOR_DESCRIPTION, desc);
    }

    /**
     * Set the search criteria to match the content-description
     * property for a widget.
     *
     * The content-description is typically used
     * by the Android Accessibility framework to
     * provide an audio prompt for the widget when
     * the widget is selected. The content-description
     * for the widget must match exactly
     * with the string in your input argument.
     *
     * @param regex a regular expression
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector descriptionMatches(@NonNull String regex) {
        requireNonNull(regex, "regex cannot be null");
        return buildSelector(SELECTOR_DESCRIPTION_REGEX, Pattern.compile(regex, Pattern.DOTALL));
    }

    /**
     * Set the search criteria to match the content-description
     * property for a widget.
     *
     * The content-description is typically used
     * by the Android Accessibility framework to
     * provide an audio prompt for the widget when
     * the widget is selected. The content-description
     * for the widget must start
     * with the string in your input argument.
     *
     * Matching is case-insensitive.
     *
     * @param desc Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector descriptionStartsWith(@NonNull String desc) {
        requireNonNull(desc, "desc cannot be null");
        return buildSelector(SELECTOR_START_DESCRIPTION, desc);
    }

    /**
     * Set the search criteria to match the content-description
     * property for a widget.
     *
     * The content-description is typically used
     * by the Android Accessibility framework to
     * provide an audio prompt for the widget when
     * the widget is selected. The content-description
     * for the widget must contain
     * the string in your input argument.
     *
     * Matching is case-insensitive.
     *
     * @param desc Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector descriptionContains(@NonNull String desc) {
        requireNonNull(desc, "desc cannot be null");
        return buildSelector(SELECTOR_CONTAINS_DESCRIPTION, desc);
    }

    /**
     * Set the search criteria to match the given resource ID.
     *
     * @param id Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector resourceId(@NonNull String id) {
        requireNonNull(id, "id cannot be null");
        return buildSelector(SELECTOR_RESOURCE_ID, id);
    }

    /**
     * Set the search criteria to match the resource ID
     * of the widget, using a regular expression.
     *
     * @param regex a regular expression
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector resourceIdMatches(@NonNull String regex) {
        requireNonNull(regex, "regex cannot be null");
        return buildSelector(SELECTOR_RESOURCE_ID_REGEX, Pattern.compile(regex));
    }

    /**
     * Set the search criteria to match the widget by its node
     * index in the layout hierarchy.
     *
     * The index value must be 0 or greater.
     *
     * Using the index can be unreliable and should only
     * be used as a last resort for matching. Instead,
     * consider using the {@link #instance(int)} method.
     *
     * @param index Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector index(final int index) {
        return buildSelector(SELECTOR_INDEX, index);
    }

    /**
     * Set the search criteria to match the
     * widget by its instance number.
     *
     * The instance value must be 0 or greater, where
     * the first instance is 0.
     *
     * For example, to simulate a user click on
     * the third image that is enabled in a UI screen, you
     * could specify a a search criteria where the instance is
     * 2, the {@link #className(String)} matches the image
     * widget class, and {@link #enabled(boolean)} is true.
     * The code would look like this:
     * <code>
     * new UiSelector().className("android.widget.ImageView")
     *    .enabled(true).instance(2);
     * </code>
     *
     * @param instance Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector instance(final int instance) {
        return buildSelector(SELECTOR_INSTANCE, instance);
    }

    /**
     * Set the search criteria to match widgets that are enabled.
     *
     * Typically, using this search criteria alone is not useful.
     * You should also include additional criteria, such as text,
     * content-description, or the class name for a widget.
     *
     * If no other search criteria is specified, and there is more
     * than one matching widget, the first widget in the tree
     * is selected.
     *
     * @param val Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector enabled(boolean val) {
        return buildSelector(SELECTOR_ENABLED, val);
    }

    /**
     * Set the search criteria to match widgets that have focus.
     *
     * Typically, using this search criteria alone is not useful.
     * You should also include additional criteria, such as text,
     * content-description, or the class name for a widget.
     *
     * If no other search criteria is specified, and there is more
     * than one matching widget, the first widget in the tree
     * is selected.
     *
     * @param val Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector focused(boolean val) {
        return buildSelector(SELECTOR_FOCUSED, val);
    }

    /**
     * Set the search criteria to match widgets that are focusable.
     *
     * Typically, using this search criteria alone is not useful.
     * You should also include additional criteria, such as text,
     * content-description, or the class name for a widget.
     *
     * If no other search criteria is specified, and there is more
     * than one matching widget, the first widget in the tree
     * is selected.
     *
     * @param val Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector focusable(boolean val) {
        return buildSelector(SELECTOR_FOCUSABLE, val);
    }

    /**
     * Set the search criteria to match widgets that are scrollable.
     *
     * Typically, using this search criteria alone is not useful.
     * You should also include additional criteria, such as text,
     * content-description, or the class name for a widget.
     *
     * If no other search criteria is specified, and there is more
     * than one matching widget, the first widget in the tree
     * is selected.
     *
     * @param val Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector scrollable(boolean val) {
        return buildSelector(SELECTOR_SCROLLABLE, val);
    }

    /**
     * Set the search criteria to match widgets that
     * are currently selected.
     *
     * Typically, using this search criteria alone is not useful.
     * You should also include additional criteria, such as text,
     * content-description, or the class name for a widget.
     *
     * If no other search criteria is specified, and there is more
     * than one matching widget, the first widget in the tree
     * is selected.
     *
     * @param val Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector selected(boolean val) {
        return buildSelector(SELECTOR_SELECTED, val);
    }

    /**
     * Set the search criteria to match widgets that
     * are currently checked (usually for checkboxes).
     *
     * Typically, using this search criteria alone is not useful.
     * You should also include additional criteria, such as text,
     * content-description, or the class name for a widget.
     *
     * If no other search criteria is specified, and there is more
     * than one matching widget, the first widget in the tree
     * is selected.
     *
     * @param val Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector checked(boolean val) {
        return buildSelector(SELECTOR_CHECKED, val);
    }

    /**
     * Set the search criteria to match widgets that are clickable.
     *
     * Typically, using this search criteria alone is not useful.
     * You should also include additional criteria, such as text,
     * content-description, or the class name for a widget.
     *
     * If no other search criteria is specified, and there is more
     * than one matching widget, the first widget in the tree
     * is selected.
     *
     * @param val Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector clickable(boolean val) {
        return buildSelector(SELECTOR_CLICKABLE, val);
    }

    /**
     * Set the search criteria to match widgets that are checkable.
     *
     * Typically, using this search criteria alone is not useful.
     * You should also include additional criteria, such as text,
     * content-description, or the class name for a widget.
     *
     * If no other search criteria is specified, and there is more
     * than one matching widget, the first widget in the tree
     * is selected.
     *
     * @param val Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector checkable(boolean val) {
        return buildSelector(SELECTOR_CHECKABLE, val);
    }

    /**
     * Set the search criteria to match widgets that are long-clickable.
     *
     * Typically, using this search criteria alone is not useful.
     * You should also include additional criteria, such as text,
     * content-description, or the class name for a widget.
     *
     * If no other search criteria is specified, and there is more
     * than one matching widget, the first widget in the tree
     * is selected.
     *
     * @param val Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector longClickable(boolean val) {
        return buildSelector(SELECTOR_LONG_CLICKABLE, val);
    }

    /**
     * Adds a child UiSelector criteria to this selector.
     *
     * Use this selector to narrow the search scope to
     * child widgets under a specific parent widget.
     *
     * @param selector
     * @return UiSelector with this added search criterion
     */
    @NonNull
    public UiSelector childSelector(@NonNull UiSelector selector) {
        requireNonNull(selector, "selector cannot be null");
        return buildSelector(SELECTOR_CHILD, selector);
    }

    private UiSelector patternSelector(UiSelector selector) {
        return buildSelector(SELECTOR_PATTERN, selector);
    }

    private UiSelector containerSelector(UiSelector selector) {
        return buildSelector(SELECTOR_CONTAINER, selector);
    }

    /**
     * Adds a child UiSelector criteria to this selector which is used to
     * start search from the parent widget.
     *
     * Use this selector to narrow the search scope to
     * sibling widgets as well all child widgets under a parent.
     *
     * @param selector
     * @return UiSelector with this added search criterion
     */
    @NonNull
    public UiSelector fromParent(@NonNull UiSelector selector) {
        requireNonNull(selector, "selector cannot be null");
        return buildSelector(SELECTOR_PARENT, selector);
    }

    /**
     * Set the search criteria to match the package name
     * of the application that contains the widget.
     *
     * @param name Value to match
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector packageName(@NonNull String name) {
        requireNonNull(name, "name cannot be null");
        return buildSelector(SELECTOR_PACKAGE_NAME, name);
    }

    /**
     * Set the search criteria to match the package name
     * of the application that contains the widget.
     *
     * @param regex a regular expression
     * @return UiSelector with the specified search criteria
     */
    @NonNull
    public UiSelector packageNameMatches(@NonNull String regex) {
        requireNonNull(regex, "regex cannot be null");
        return buildSelector(SELECTOR_PACKAGE_NAME_REGEX, Pattern.compile(regex));
    }

    /**
     * Building a UiSelector always returns a new UiSelector and never modifies the
     * existing UiSelector being used.
     */
    private UiSelector buildSelector(int selectorId, Object selectorValue) {
        UiSelector selector = new UiSelector(this);
        if (selectorId == SELECTOR_CHILD || selectorId == SELECTOR_PARENT)
            selector.getLastSubSelector().mSelectorAttributes.put(selectorId, selectorValue);
        else
            selector.mSelectorAttributes.put(selectorId, selectorValue);
        return selector;
    }

    /**
     * Selectors may have a hierarchy defined by specifying child nodes to be matched.
     * It is not necessary that every selector have more than one level. A selector
     * can also be a single level referencing only one node. In such cases the return
     * it null.
     *
     * @return a child selector if one exists. Else null if this selector does not
     * reference child node.
     */
    UiSelector getChildSelector() {
        UiSelector selector = (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_CHILD, null);
        if (selector != null)
            return new UiSelector(selector);
        return null;
    }

    UiSelector getPatternSelector() {
        UiSelector selector =
                (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_PATTERN, null);
        if (selector != null)
            return new UiSelector(selector);
        return null;
    }

    UiSelector getContainerSelector() {
        UiSelector selector =
                (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_CONTAINER, null);
        if (selector != null)
            return new UiSelector(selector);
        return null;
    }

    UiSelector getParentSelector() {
        UiSelector selector =
                (UiSelector) mSelectorAttributes.get(UiSelector.SELECTOR_PARENT, null);
        if (selector != null)
            return new UiSelector(selector);
        return null;
    }

    int getInstance() {
        return getInt(UiSelector.SELECTOR_INSTANCE);
    }

    String getString(int criterion) {
        return (String) mSelectorAttributes.get(criterion, null);
    }

    boolean getBoolean(int criterion) {
        return (Boolean) mSelectorAttributes.get(criterion, false);
    }

    int getInt(int criterion) {
        return (Integer) mSelectorAttributes.get(criterion, 0);
    }

    Pattern getPattern(int criterion) {
        return (Pattern) mSelectorAttributes.get(criterion, null);
    }

    boolean isMatchFor(AccessibilityNodeInfo node, int index) {
        int size = mSelectorAttributes.size();
        for(int x = 0; x < size; x++) {
            CharSequence s = null;
            int criterion = mSelectorAttributes.keyAt(x);
            switch(criterion) {
            case UiSelector.SELECTOR_INDEX:
                if (index != this.getInt(criterion))
                    return false;
                break;
            case UiSelector.SELECTOR_CHECKED:
                if (node.isChecked() != getBoolean(criterion)) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_CLASS:
                s = node.getClassName();
                if (s == null || !s.toString().contentEquals(getString(criterion))) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_CLASS_REGEX:
                s = node.getClassName();
                if (s == null || !getPattern(criterion).matcher(s).matches()) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_CLICKABLE:
                if (node.isClickable() != getBoolean(criterion)) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_CHECKABLE:
                if (node.isCheckable() != getBoolean(criterion)) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_LONG_CLICKABLE:
                if (node.isLongClickable() != getBoolean(criterion)) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_CONTAINS_DESCRIPTION:
                s = node.getContentDescription();
                if (s == null || !s.toString().toLowerCase()
                        .contains(getString(criterion).toLowerCase())) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_START_DESCRIPTION:
                s = node.getContentDescription();
                if (s == null || !s.toString().toLowerCase()
                        .startsWith(getString(criterion).toLowerCase())) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_DESCRIPTION:
                s = node.getContentDescription();
                if (s == null || !s.toString().contentEquals(getString(criterion))) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_DESCRIPTION_REGEX:
                s = node.getContentDescription();
                if (s == null || !getPattern(criterion).matcher(s).matches()) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_CONTAINS_TEXT:
                s = node.getText();
                if (s == null || !s.toString().toLowerCase()
                        .contains(getString(criterion).toLowerCase())) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_START_TEXT:
                s = node.getText();
                if (s == null || !s.toString().toLowerCase()
                        .startsWith(getString(criterion).toLowerCase())) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_TEXT:
                s = node.getText();
                if (s == null || !s.toString().contentEquals(getString(criterion))) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_TEXT_REGEX:
                s = node.getText();
                if (s == null || !getPattern(criterion).matcher(s).matches()) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_ENABLED:
                if (node.isEnabled() != getBoolean(criterion)) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_FOCUSABLE:
                if (node.isFocusable() != getBoolean(criterion)) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_FOCUSED:
                if (node.isFocused() != getBoolean(criterion)) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_ID:
                break; //TODO: do we need this for AccessibilityNodeInfo.id?
            case UiSelector.SELECTOR_PACKAGE_NAME:
                s = node.getPackageName();
                if (s == null || !s.toString().contentEquals(getString(criterion))) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_PACKAGE_NAME_REGEX:
                s = node.getPackageName();
                if (s == null || !getPattern(criterion).matcher(s).matches()) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_SCROLLABLE:
                if (node.isScrollable() != getBoolean(criterion)) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_SELECTED:
                if (node.isSelected() != getBoolean(criterion)) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_RESOURCE_ID:
                s = node.getViewIdResourceName();
                if (s == null || !s.toString().contentEquals(getString(criterion))) {
                    return false;
                }
                break;
            case UiSelector.SELECTOR_RESOURCE_ID_REGEX:
                s = node.getViewIdResourceName();
                if (s == null || !getPattern(criterion).matcher(s).matches()) {
                    return false;
                }
                break;
            }
        }
        return matchOrUpdateInstance();
    }

    private boolean matchOrUpdateInstance() {
        int currentSelectorCounter = 0;
        int currentSelectorInstance = 0;

        // matched attributes - now check for matching instance number
        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_INSTANCE) >= 0) {
            currentSelectorInstance =
                    (Integer) mSelectorAttributes.get(UiSelector.SELECTOR_INSTANCE);
        }

        // instance is required. Add count if not already counting
        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_COUNT) >= 0) {
            currentSelectorCounter = (Integer) mSelectorAttributes.get(UiSelector.SELECTOR_COUNT);
        }

        // Verify
        if (currentSelectorInstance == currentSelectorCounter) {
            return true;
        }
        // Update count
        if (currentSelectorInstance > currentSelectorCounter) {
            mSelectorAttributes.put(UiSelector.SELECTOR_COUNT, ++currentSelectorCounter);
        }
        return false;
    }

    /**
     * Leaf selector indicates no more child or parent selectors
     * are declared in the this selector.
     * @return true if is leaf.
     */
    boolean isLeaf() {
        return mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0
                && mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) < 0;
    }

    boolean hasChildSelector() {
        return mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) >= 0;
    }

    boolean hasPatternSelector() {
        return mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PATTERN) >= 0;
    }

    boolean hasContainerSelector() {
        return mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CONTAINER) >= 0;
    }

    boolean hasParentSelector() {
        return mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) >= 0;
    }

    /**
     * Returns the deepest selector in the chain of possible sub selectors.
     * A chain of selector is created when either of {@link UiSelector#childSelector(UiSelector)}
     * or {@link UiSelector#fromParent(UiSelector)} are used once or more in the construction of
     * a selector.
     * @return last UiSelector in chain
     */
    private UiSelector getLastSubSelector() {
        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) >= 0) {
            UiSelector child = (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_CHILD);
            if (child.getLastSubSelector() == null) {
                return child;
            }
            return child.getLastSubSelector();
        } else if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) >= 0) {
            UiSelector parent = (UiSelector)mSelectorAttributes.get(UiSelector.SELECTOR_PARENT);
            if (parent.getLastSubSelector() == null) {
                return parent;
            }
            return parent.getLastSubSelector();
        }
        return this;
    }

    @Override
    public String toString() {
        return dumpToString(true);
    }

    String dumpToString(boolean all) {
        StringBuilder builder = new StringBuilder();
        builder.append(UiSelector.class.getSimpleName()).append("[");
        final int criterionCount = mSelectorAttributes.size();
        for (int i = 0; i < criterionCount; i++) {
            if (i > 0) {
                builder.append(", ");
            }
            final int criterion = mSelectorAttributes.keyAt(i);
            switch (criterion) {
                case SELECTOR_TEXT:
                    builder.append("TEXT=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_TEXT_REGEX:
                    builder.append("TEXT_REGEX=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_START_TEXT:
                    builder.append("START_TEXT=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_CONTAINS_TEXT:
                    builder.append("CONTAINS_TEXT=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_CLASS:
                    builder.append("CLASS=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_CLASS_REGEX:
                    builder.append("CLASS_REGEX=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_DESCRIPTION:
                    builder.append("DESCRIPTION=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_DESCRIPTION_REGEX:
                    builder.append("DESCRIPTION_REGEX=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_START_DESCRIPTION:
                    builder.append("START_DESCRIPTION=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_CONTAINS_DESCRIPTION:
                    builder.append("CONTAINS_DESCRIPTION=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_INDEX:
                    builder.append("INDEX=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_INSTANCE:
                    builder.append("INSTANCE=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_ENABLED:
                    builder.append("ENABLED=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_FOCUSED:
                    builder.append("FOCUSED=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_FOCUSABLE:
                    builder.append("FOCUSABLE=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_SCROLLABLE:
                    builder.append("SCROLLABLE=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_CLICKABLE:
                    builder.append("CLICKABLE=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_CHECKABLE:
                    builder.append("CHECKABLE=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_LONG_CLICKABLE:
                    builder.append("LONG_CLICKABLE=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_CHECKED:
                    builder.append("CHECKED=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_SELECTED:
                    builder.append("SELECTED=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_ID:
                    builder.append("ID=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_CHILD:
                    if (all) {
                        builder.append("CHILD=").append(mSelectorAttributes.valueAt(i));
                    } else {
                        builder.append("CHILD[..]");
                    }
                    break;
                case SELECTOR_PATTERN:
                    if (all) {
                        builder.append("PATTERN=").append(mSelectorAttributes.valueAt(i));
                    } else {
                        builder.append("PATTERN[..]");
                    }
                    break;
                case SELECTOR_CONTAINER:
                    if (all) {
                        builder.append("CONTAINER=").append(mSelectorAttributes.valueAt(i));
                    } else {
                        builder.append("CONTAINER[..]");
                    }
                    break;
                case SELECTOR_PARENT:
                    if (all) {
                        builder.append("PARENT=").append(mSelectorAttributes.valueAt(i));
                    } else {
                        builder.append("PARENT[..]");
                    }
                    break;
                case SELECTOR_COUNT:
                    builder.append("COUNT=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_PACKAGE_NAME:
                    builder.append("PACKAGE_NAME=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_PACKAGE_NAME_REGEX:
                    builder.append("PACKAGE_NAME_REGEX=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_RESOURCE_ID:
                    builder.append("RESOURCE_ID=").append(mSelectorAttributes.valueAt(i));
                    break;
                case SELECTOR_RESOURCE_ID_REGEX:
                    builder.append("RESOURCE_ID_REGEX=").append(mSelectorAttributes.valueAt(i));
                    break;
                default:
                    builder.append("UNDEFINED=").append(criterion).append(" ").append(
                            mSelectorAttributes.valueAt(i));
            }
        }
        builder.append("]");
        return builder.toString();
    }
}