FileDocCategorySizeDatePackage
QueryController.javaAPI DocAndroid 5.1 API22192Thu Mar 12 22:22:08 GMT 2015com.android.uiautomator.core

QueryController

public 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 voidclearLastTraversedText()
Clears the last text selection value saved from the TYPE_VIEW_TEXT_SELECTION_CHANGED event

        mUiAutomatorBridge.waitForIdle();
        synchronized (mLock) {
            mLastTraversedText = "";
        }
    
public android.view.accessibility.AccessibilityNodeInfofindAccessibilityNodeInfo(UiSelector selector)
Main search method for translating By selectors to AccessibilityInfoNodes

param
selector
return
AccessibilityNodeInfo

        return findAccessibilityNodeInfo(selector, false);
    
protected android.view.accessibility.AccessibilityNodeInfofindAccessibilityNodeInfo(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.AccessibilityNodeInfofindNodePatternRecursive(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.AccessibilityNodeInfofindNodeRegularRecursive(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.StringformatLog(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.AccessibilityNodeInfogetAccessibilityRootNode()

        return mUiAutomatorBridge.getRootInActiveWindow();
    
public java.lang.StringgetCurrentActivityName()
Last activity to report accessibility events.

deprecated
The results returned should be considered unreliable
return
String name of activity

        mUiAutomatorBridge.waitForIdle();
        synchronized (mLock) {
            return mLastActivityName;
        }
    
public java.lang.StringgetCurrentPackageName()
Last package to report accessibility events

return
String name of package

        mUiAutomatorBridge.waitForIdle();
        AccessibilityNodeInfo rootNode = getRootNode();
        if (rootNode == null)
            return null;
        return rootNode.getPackageName() != null ? rootNode.getPackageName().toString() : null;
    
public java.lang.StringgetLastTraversedText()
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 intgetPatternCount(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.

param
selector
return
number of pattern matches. Returns 0 for all other cases.

        findAccessibilityNodeInfo(selector, true /*counting*/);
        return mPatternCounter;
    
protected android.view.accessibility.AccessibilityNodeInfogetRootNode()
Gets the root node from accessibility and if it fails to get one it will retry every 250ms for up to 1000ms.

return
null if no root node is obtained

        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 voidinitializeNewSearch()

        mPatternCounter = 0;
        mPatternIndexer = 0;
        mLogIndent = 0;
        mLogParentIndent = 0;
    
private android.view.accessibility.AccessibilityNodeInfotranslateCompoundSelector(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.

param
selector
param
fromNode
param
isCounting
return
AccessibilityNodeInfo


        // 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.AccessibilityNodeInfotranslatePatternSelector(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.

param
subSelector
param
fromNode
param
isCounting
return
null of node is not found or if counting mode is true. See {@link #translateCompoundSelector(UiSelector, AccessibilityNodeInfo, boolean)}


        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.AccessibilityNodeInfotranslateReqularSelector(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.

param
selector
param
fromNode
return
AccessibilityNodeInfo if found else null


        return findNodeRegularRecursive(selector, fromNode, 0);