| /* |
| * 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; |
| |
| /** |
| * This class provides the mechanism for tests to describe the UI elements they |
| * intend to target. A UI element has many properties associated with it such as |
| * text value, content-description, class name and multiple state information like |
| * selected, enabled, checked etc. Additionally UiSelector allows targeting of UI |
| * elements within a specific display hierarchies to distinguish similar elements |
| * based in the hierarchies they're in. |
| */ |
| 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; |
| |
| private SparseArray<Object> mSelectorAttributes = new SparseArray<Object>(); |
| public static final String LOG_TAG = UiSelector.class.getSimpleName(); |
| |
| public UiSelector() { |
| } |
| |
| UiSelector(UiSelector selector) { |
| mSelectorAttributes = selector.cloneSelector().mSelectorAttributes; |
| } |
| |
| 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)); |
| } |
| |
| public UiSelector text(String text) { |
| return buildSelector(SELECTOR_TEXT, text); |
| } |
| |
| public UiSelector textStartsWith(String text) { |
| return buildSelector(SELECTOR_START_TEXT, text); |
| } |
| |
| public UiSelector textContains(String text) { |
| return buildSelector(SELECTOR_CONTAINS_TEXT, text); |
| } |
| |
| public UiSelector className(String className) { |
| return buildSelector(SELECTOR_CLASS, className); |
| } |
| |
| public UiSelector description(String desc) { |
| return buildSelector(SELECTOR_DESCRIPTION, desc); |
| } |
| |
| public UiSelector descriptionStartsWith(String desc) { |
| return buildSelector(SELECTOR_START_DESCRIPTION, desc); |
| } |
| |
| public UiSelector descriptionContains(String desc) { |
| return buildSelector(SELECTOR_CONTAINS_DESCRIPTION, desc); |
| } |
| |
| public UiSelector index(final int index) { |
| return buildSelector(SELECTOR_INDEX, index); |
| } |
| |
| public UiSelector instance(final int instance) { |
| return buildSelector(SELECTOR_INSTANCE, instance); |
| } |
| |
| public UiSelector enabled(boolean val) { |
| return buildSelector(SELECTOR_ENABLED, val); |
| } |
| |
| public UiSelector focused(boolean val) { |
| return buildSelector(SELECTOR_FOCUSED, val); |
| } |
| |
| public UiSelector focusable(boolean val) { |
| return buildSelector(SELECTOR_FOCUSABLE, val); |
| } |
| |
| public UiSelector scrollable(boolean val) { |
| return buildSelector(SELECTOR_SCROLLABLE, val); |
| } |
| |
| public UiSelector selected(boolean val) { |
| return buildSelector(SELECTOR_SELECTED, val); |
| } |
| |
| public UiSelector checked(boolean val) { |
| return buildSelector(SELECTOR_CHECKED, val); |
| } |
| |
| public UiSelector clickable(boolean val) { |
| return buildSelector(SELECTOR_CLICKABLE, val); |
| } |
| |
| 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); |
| } |
| |
| public UiSelector fromParent(UiSelector selector) { |
| return buildSelector(SELECTOR_PARENT, selector); |
| } |
| |
| public UiSelector packageName(String name) { |
| return buildSelector(SELECTOR_PACKAGE_NAME, name); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| 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_CLICKABLE: |
| if (node.isClickable() != 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_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_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_SCROLLABLE: |
| if(node.isScrollable() != getBoolean(criterion)) { |
| return false; |
| } |
| break; |
| case UiSelector.SELECTOR_SELECTED: |
| if(node.isSelected() != getBoolean(criterion)) { |
| 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_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_DESCRIPTION: |
| builder.append("DESCRIPTION=").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_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; |
| default: |
| builder.append("UNDEFINED="+criterion+" ").append(mSelectorAttributes.valueAt(i)); |
| } |
| } |
| builder.append("]"); |
| return builder.toString(); |
| } |
| } |