diff options
Diffstat (limited to 'src/com/android/uiautomator/core/UiSelector.java')
-rw-r--r-- | src/com/android/uiautomator/core/UiSelector.java | 1022 |
1 files changed, 1022 insertions, 0 deletions
diff --git a/src/com/android/uiautomator/core/UiSelector.java b/src/com/android/uiautomator/core/UiSelector.java new file mode 100644 index 0000000..bd61bfd --- /dev/null +++ b/src/com/android/uiautomator/core/UiSelector.java @@ -0,0 +1,1022 @@ +/* + * 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 com.android.uiautomator.core; + +import android.util.SparseArray; +import android.view.accessibility.AccessibilityNodeInfo; + +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. + * @since API Level 16 + */ +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<Object>(); + + /** + * @since API Level 16 + */ + public UiSelector() { + } + + UiSelector(UiSelector selector) { + mSelectorAttributes = selector.cloneSelector().mSelectorAttributes; + } + + /** + * @since API Level 17 + */ + 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 + * @since API Level 16 + */ + public UiSelector text(String text) { + 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 + * @since API Level 17 + */ + public UiSelector textMatches(String regex) { + return buildSelector(SELECTOR_TEXT_REGEX, Pattern.compile(regex)); + } + + /** + * 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 + * @since API Level 16 + */ + public UiSelector textStartsWith(String text) { + 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 + * @since API Level 16 + */ + public UiSelector textContains(String text) { + 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 + * @since API Level 16 + */ + public UiSelector className(String className) { + 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 + * @since API Level 17 + */ + public UiSelector classNameMatches(String regex) { + 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 + * @since API Level 17 + */ + public <T> UiSelector className(Class<T> type) { + 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 + * @since API Level 16 + */ + public UiSelector description(String desc) { + 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 + * @since API Level 17 + */ + public UiSelector descriptionMatches(String regex) { + return buildSelector(SELECTOR_DESCRIPTION_REGEX, Pattern.compile(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 start + * with the string in your input argument. + * + * Matching is case-insensitive. + * + * @param desc Value to match + * @return UiSelector with the specified search criteria + * @since API Level 16 + */ + public UiSelector descriptionStartsWith(String desc) { + 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 + * @since API Level 16 + */ + public UiSelector descriptionContains(String desc) { + 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 + * @since API Level 18 + */ + public UiSelector resourceId(String id) { + return buildSelector(SELECTOR_RESOURCE_ID, id); + } + + /** + * Set the search criteria to match the resource ID + * of the widget, using a regular expression.http://blog.bettersoftwaretesting.com/ + * + * @param regex a regular expression + * @return UiSelector with the specified search criteria + * @since API Level 18 + */ + public UiSelector resourceIdMatches(String regex) { + 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 + * @since API Level 16 + */ + 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 + * @since API Level 16 + */ + 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 + * @since API Level 16 + */ + 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 + * @since API Level 16 + */ + 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 + * @since API Level 16 + */ + 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 + * @since API Level 16 + */ + 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 + * @since API Level 16 + */ + 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 + * @since API Level 16 + */ + 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 + * @since API Level 16 + */ + 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 + * @since API Level 18 + */ + 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 + * @since API Level 17 + */ + 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 + * @since API Level 16 + */ + public UiSelector childSelector(UiSelector selector) { + 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 + * @since API Level 16 + */ + public UiSelector fromParent(UiSelector selector) { + 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 + * @since API Level 16 + */ + public UiSelector packageName(String name) { + 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 + * @since API Level 17 + */ + public UiSelector packageNameMatches(String regex) { + 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() { + if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0 && + mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) < 0) { + return true; + } + return false; + } + + boolean hasChildSelector() { + if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0) { + return false; + } + return true; + } + + boolean hasPatternSelector() { + if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PATTERN) < 0) { + return false; + } + return true; + } + + boolean hasContainerSelector() { + if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CONTAINER) < 0) { + return false; + } + return true; + } + + boolean hasParentSelector() { + if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) < 0) { + return false; + } + return true; + } + + /** + * 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() + "["); + 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="+criterion+" ").append(mSelectorAttributes.valueAt(i)); + } + } + builder.append("]"); + return builder.toString(); + } +} |