QueryControllerpublic class QueryController extends Object The QueryController main purpose is to translate a {@link UiSelector} selectors to
{@link AccessibilityNodeInfo}. This is all this controller does. |
Fields Summary |
---|
private static final String | LOG_TAG | private static final boolean | DEBUG | private static final boolean | VERBOSE | private final UiAutomatorBridge | mUiAutomatorBridge | private final Object | mLock | private String | mLastActivityName | private int | mPatternCounter | private int | mPatternIndexer | private int | mLogIndent | private int | mLogParentIndent | private String | mLastTraversedText |
Constructors Summary |
---|
public QueryController(UiAutomatorBridge bridge)
mUiAutomatorBridge = bridge;
bridge.setOnAccessibilityEventListener(new OnAccessibilityEventListener() {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
switch(event.getEventType()) {
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
// don't trust event.getText(), check for nulls
if (event.getText() != null && event.getText().size() > 0) {
if(event.getText().get(0) != null)
mLastActivityName = event.getText().get(0).toString();
}
break;
case AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY:
// don't trust event.getText(), check for nulls
if (event.getText() != null && event.getText().size() > 0)
if(event.getText().get(0) != null)
mLastTraversedText = event.getText().get(0).toString();
if (DEBUG)
Log.d(LOG_TAG, "Last text selection reported: " +
mLastTraversedText);
break;
}
mLock.notifyAll();
}
}
});
|
Methods Summary |
---|
public void | clearLastTraversedText()Clears the last text selection value saved from the TYPE_VIEW_TEXT_SELECTION_CHANGED
event
mUiAutomatorBridge.waitForIdle();
synchronized (mLock) {
mLastTraversedText = "";
}
| public android.view.accessibility.AccessibilityNodeInfo | findAccessibilityNodeInfo(UiSelector selector)Main search method for translating By selectors to AccessibilityInfoNodes
return findAccessibilityNodeInfo(selector, false);
| protected android.view.accessibility.AccessibilityNodeInfo | findAccessibilityNodeInfo(UiSelector selector, boolean isCounting)
mUiAutomatorBridge.waitForIdle();
initializeNewSearch();
if (DEBUG)
Log.d(LOG_TAG, "Searching: " + selector);
synchronized (mLock) {
AccessibilityNodeInfo rootNode = getRootNode();
if (rootNode == null) {
Log.e(LOG_TAG, "Cannot proceed when root node is null. Aborted search");
return null;
}
// Copy so that we don't modify the original's sub selectors
UiSelector uiSelector = new UiSelector(selector);
return translateCompoundSelector(uiSelector, rootNode, isCounting);
}
| private android.view.accessibility.AccessibilityNodeInfo | findNodePatternRecursive(UiSelector subSelector, android.view.accessibility.AccessibilityNodeInfo fromNode, int index, UiSelector originalPattern)
if (subSelector.isMatchFor(fromNode, index)) {
if(subSelector.isLeaf()) {
if(mPatternIndexer == 0) {
if (DEBUG)
Log.d(LOG_TAG, formatLog(
String.format("%s", subSelector.dumpToString(false))));
return fromNode;
} else {
if (DEBUG)
Log.d(LOG_TAG, formatLog(
String.format("%s", subSelector.dumpToString(false))));
mPatternCounter++; //count the pattern matched
mPatternIndexer--; //decrement until zero for the instance requested
// At a leaf selector within a group and still not instance matched
// then reset the selector to continue search from current position
// in the accessibility tree for the next pattern match up until the
// pattern index hits 0.
subSelector = originalPattern;
// starting over with next pattern search so reset to parent level
mLogIndent = mLogParentIndent;
}
} else {
if (DEBUG)
Log.d(LOG_TAG, formatLog(
String.format("%s", subSelector.dumpToString(false))));
if(subSelector.hasChildSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getChildSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Error: A child selector without content");
return null;
}
} else if(subSelector.hasParentSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getParentSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Error: A parent selector without content");
return null;
}
fromNode = fromNode.getParent();
if(fromNode == null)
return null;
}
}
}
int childCount = fromNode.getChildCount();
boolean hasNullChild = false;
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo childNode = fromNode.getChild(i);
if (childNode == null) {
Log.w(LOG_TAG, String.format(
"AccessibilityNodeInfo returned a null child (%d of %d)", i, childCount));
if (!hasNullChild) {
Log.w(LOG_TAG, String.format("parent = %s", fromNode.toString()));
}
hasNullChild = true;
continue;
}
if (!childNode.isVisibleToUser()) {
if (DEBUG)
Log.d(LOG_TAG,
String.format("Skipping invisible child: %s", childNode.toString()));
continue;
}
AccessibilityNodeInfo retNode = findNodePatternRecursive(
subSelector, childNode, i, originalPattern);
if (retNode != null) {
return retNode;
}
}
return null;
| private android.view.accessibility.AccessibilityNodeInfo | findNodeRegularRecursive(UiSelector subSelector, android.view.accessibility.AccessibilityNodeInfo fromNode, int index)
if (subSelector.isMatchFor(fromNode, index)) {
if (DEBUG) {
Log.d(LOG_TAG, formatLog(String.format("%s",
subSelector.dumpToString(false))));
}
if(subSelector.isLeaf()) {
return fromNode;
}
if(subSelector.hasChildSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getChildSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Error: A child selector without content");
return null; // there is an implementation fault
}
} else if(subSelector.hasParentSelector()) {
mLogIndent++; // next selector
subSelector = subSelector.getParentSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Error: A parent selector without content");
return null; // there is an implementation fault
}
// the selector requested we start at this level from
// the parent node from the one we just matched
fromNode = fromNode.getParent();
if(fromNode == null)
return null;
}
}
int childCount = fromNode.getChildCount();
boolean hasNullChild = false;
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo childNode = fromNode.getChild(i);
if (childNode == null) {
Log.w(LOG_TAG, String.format(
"AccessibilityNodeInfo returned a null child (%d of %d)", i, childCount));
if (!hasNullChild) {
Log.w(LOG_TAG, String.format("parent = %s", fromNode.toString()));
}
hasNullChild = true;
continue;
}
if (!childNode.isVisibleToUser()) {
if (VERBOSE)
Log.v(LOG_TAG,
String.format("Skipping invisible child: %s", childNode.toString()));
continue;
}
AccessibilityNodeInfo retNode = findNodeRegularRecursive(subSelector, childNode, i);
if (retNode != null) {
return retNode;
}
}
return null;
| private java.lang.String | formatLog(java.lang.String str)
StringBuilder l = new StringBuilder();
for(int space = 0; space < mLogIndent; space++)
l.append(". . ");
if(mLogIndent > 0)
l.append(String.format(". . [%d]: %s", mPatternCounter, str));
else
l.append(String.format(". . [%d]: %s", mPatternCounter, str));
return l.toString();
| public android.view.accessibility.AccessibilityNodeInfo | getAccessibilityRootNode()
return mUiAutomatorBridge.getRootInActiveWindow();
| public java.lang.String | getCurrentActivityName()Last activity to report accessibility events.
mUiAutomatorBridge.waitForIdle();
synchronized (mLock) {
return mLastActivityName;
}
| public java.lang.String | getCurrentPackageName()Last package to report accessibility events
mUiAutomatorBridge.waitForIdle();
AccessibilityNodeInfo rootNode = getRootNode();
if (rootNode == null)
return null;
return rootNode.getPackageName() != null ? rootNode.getPackageName().toString() : null;
| public java.lang.String | getLastTraversedText()Returns the last text selection reported by accessibility
event TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY. One way to cause
this event is using a DPad arrows to focus on UI elements.
mUiAutomatorBridge.waitForIdle();
synchronized (mLock) {
if (mLastTraversedText.length() > 0) {
return mLastTraversedText;
}
}
return null;
| public int | getPatternCount(UiSelector selector)Counts the instances of the selector group. The selector must be in the following
format: [container_selector, PATTERN=[INSTANCE=x, PATTERN=[the_pattern]]
where the container_selector is used to find the containment region to search for patterns
and the INSTANCE=x is the instance of the_pattern to return.
findAccessibilityNodeInfo(selector, true /*counting*/);
return mPatternCounter;
| protected android.view.accessibility.AccessibilityNodeInfo | getRootNode()Gets the root node from accessibility and if it fails to get one it will
retry every 250ms for up to 1000ms.
final int maxRetry = 4;
final long waitInterval = 250;
AccessibilityNodeInfo rootNode = null;
for(int x = 0; x < maxRetry; x++) {
rootNode = mUiAutomatorBridge.getRootInActiveWindow();
if (rootNode != null) {
return rootNode;
}
if(x < maxRetry - 1) {
Log.e(LOG_TAG, "Got null root node from accessibility - Retrying...");
SystemClock.sleep(waitInterval);
}
}
return rootNode;
| private void | initializeNewSearch()
mPatternCounter = 0;
mPatternIndexer = 0;
mLogIndent = 0;
mLogParentIndent = 0;
| private android.view.accessibility.AccessibilityNodeInfo | translateCompoundSelector(UiSelector selector, android.view.accessibility.AccessibilityNodeInfo fromNode, boolean isCounting)A compoundSelector encapsulate both Regular and Pattern selectors. The formats follows:
regular_selector = By[attributes... CHILD=By[attributes... CHILD=By[....]]]
pattern_selector = ...CONTAINER=By[..] PATTERN=By[instance=x PATTERN=[regular_selector]
compound_selector = [regular_selector [pattern_selector]]
regular_selectors are the most common form of selectors and the search for them
is straightforward. On the other hand pattern_selectors requires search to be
performed as in regular_selector but where regular_selector search returns immediately
upon a successful match, the search for pattern_selector continues until the
requested matched _instance_ of that pattern is matched.
Counting UI objects requires using pattern_selectors. The counting search is the same
as a pattern_search however we're not looking to match an instance of the pattern but
rather continuously walking the accessibility node hierarchy while counting matched
patterns, until the end of the tree.
If both present, order of parsing begins with CONTAINER followed by PATTERN then the
top most selector is processed as regular_selector within the context of the previous
CONTAINER and its PATTERN information. If neither is present then the top selector is
directly treated as regular_selector. So the presence of a CONTAINER and PATTERN within
a selector simply dictates that the selector matching will be constraint to the sub tree
node where the CONTAINER and its child PATTERN have identified.
// Start translating compound selectors by translating the regular_selector first
// The regular_selector is then used as a container for any optional pattern_selectors
// that may or may not be specified.
if(selector.hasContainerSelector())
// nested pattern selectors
if(selector.getContainerSelector().hasContainerSelector()) {
fromNode = translateCompoundSelector(
selector.getContainerSelector(), fromNode, false);
initializeNewSearch();
} else
fromNode = translateReqularSelector(selector.getContainerSelector(), fromNode);
else
fromNode = translateReqularSelector(selector, fromNode);
if(fromNode == null) {
if (DEBUG)
Log.d(LOG_TAG, "Container selector not found: " + selector.dumpToString(false));
return null;
}
if(selector.hasPatternSelector()) {
fromNode = translatePatternSelector(selector.getPatternSelector(),
fromNode, isCounting);
if (isCounting) {
Log.i(LOG_TAG, String.format(
"Counted %d instances of: %s", mPatternCounter, selector));
return null;
} else {
if(fromNode == null) {
if (DEBUG)
Log.d(LOG_TAG, "Pattern selector not found: " +
selector.dumpToString(false));
return null;
}
}
}
// translate any additions to the selector that may have been added by tests
// with getChild(By selector) after a container and pattern selectors
if(selector.hasContainerSelector() || selector.hasPatternSelector()) {
if(selector.hasChildSelector() || selector.hasParentSelector())
fromNode = translateReqularSelector(selector, fromNode);
}
if(fromNode == null) {
if (DEBUG)
Log.d(LOG_TAG, "Object Not Found for selector " + selector);
return null;
}
Log.i(LOG_TAG, String.format("Matched selector: %s <<==>> [%s]", selector, fromNode));
return fromNode;
| private android.view.accessibility.AccessibilityNodeInfo | translatePatternSelector(UiSelector subSelector, android.view.accessibility.AccessibilityNodeInfo fromNode, boolean isCounting)Used by the {@link #translateCompoundSelector(UiSelector, AccessibilityNodeInfo, boolean)}
to translate the pattern_selector portion. It has the following format:
pattern_selector = ... PATTERN=By[instance=x PATTERN=[regular_selector]]
pattern_selectors requires search to be performed as regular_selector but where
regular_selector search returns immediately upon a successful match, the search for
pattern_selector continues until the requested matched instance of that pattern is
encountered.
Counting UI objects requires using pattern_selectors. The counting search is the same
as a pattern_search however we're not looking to match an instance of the pattern but
rather continuously walking the accessibility node hierarchy while counting patterns
until the end of the tree.
if(subSelector.hasPatternSelector()) {
// Since pattern_selectors are also the type of selectors used when counting,
// we check if this is a counting run or an indexing run
if(isCounting)
//since we're counting, we reset the indexer so to terminates the search when
// the end of tree is reached. The count will be in mPatternCount
mPatternIndexer = -1;
else
// terminates the search once we match the pattern's instance
mPatternIndexer = subSelector.getInstance();
// A pattern is wrapped in a PATTERN[instance=x PATTERN[the_pattern]]
subSelector = subSelector.getPatternSelector();
if(subSelector == null) {
Log.e(LOG_TAG, "Pattern portion of the selector is null or not defined");
return null; // there is an implementation fault
}
// save the current indent level as parent indent before pattern searches
// begin under the current tree position.
mLogParentIndent = ++mLogIndent;
return findNodePatternRecursive(subSelector, fromNode, 0, subSelector);
}
Log.e(LOG_TAG, "Selector must have a pattern selector defined"); // implementation fault?
return null;
| private android.view.accessibility.AccessibilityNodeInfo | translateReqularSelector(UiSelector selector, android.view.accessibility.AccessibilityNodeInfo fromNode)Used by the {@link #translateCompoundSelector(UiSelector, AccessibilityNodeInfo, boolean)}
to translate the regular_selector portion. It has the following format:
regular_selector = By[attributes... CHILD=By[attributes... CHILD=By[....]]]
regular_selectors are the most common form of selectors and the search for them
is straightforward. This method will only look for CHILD or PARENT sub selectors.
return findNodeRegularRecursive(selector, fromNode, 0);
|
|