FileDocCategorySizeDatePackage
AccessibilityInteractionClient.javaAPI DocAndroid 5.1 API34053Thu Mar 12 22:22:10 GMT 2015android.view.accessibility

AccessibilityInteractionClient

public final class AccessibilityInteractionClient extends IAccessibilityInteractionConnectionCallback.Stub
This class is a singleton that performs accessibility interaction which is it queries remote view hierarchies about snapshots of their views as well requests from these hierarchies to perform certain actions on their views. Rationale: The content retrieval APIs are synchronous from a client's perspective but internally they are asynchronous. The client thread calls into the system requesting an action and providing a callback to receive the result after which it waits up to a timeout for that result. The system enforces security and the delegates the request to a given view hierarchy where a message is posted (from a binder thread) describing what to be performed by the main UI thread the result of which it delivered via the mentioned callback. However, the blocked client thread and the main UI thread of the target view hierarchy can be the same thread, for example an accessibility service and an activity run in the same process, thus they are executed on the same main thread. In such a case the retrieval will fail since the UI thread that has to process the message describing the work to be done is blocked waiting for a result is has to compute! To avoid this scenario when making a call the client also passes its process and thread ids so the accessed view hierarchy can detect if the client making the request is running in its main UI thread. In such a case the view hierarchy, specifically the binder thread performing the IPC to it, does not post a message to be run on the UI thread but passes it to the singleton interaction client through which all interactions occur and the latter is responsible to execute the message before starting to wait for the asynchronous result delivered via the callback. In this case the expected result is already received so no waiting is performed.
hide

Fields Summary
public static final int
NO_ID
private static final String
LOG_TAG
private static final boolean
DEBUG
private static final boolean
CHECK_INTEGRITY
private static final long
TIMEOUT_INTERACTION_MILLIS
private static final Object
sStaticLock
private static final android.util.LongSparseArray
sClients
private final AtomicInteger
mInteractionIdCounter
private final Object
mInstanceLock
private volatile int
mInteractionId
private AccessibilityNodeInfo
mFindAccessibilityNodeInfoResult
private List
mFindAccessibilityNodeInfosResult
private boolean
mPerformAccessibilityActionResult
private android.os.Message
mSameThreadMessage
private static final android.util.SparseArray
sConnectionCache
private static final AccessibilityCache
sAccessibilityCache
Constructors Summary
private AccessibilityInteractionClient()

        /* reducing constructor visibility */
    
Methods Summary
public voidaddConnection(int connectionId, android.accessibilityservice.IAccessibilityServiceConnection connection)
Adds a cached accessibility service connection.

param
connectionId The connection id.
param
connection The connection.

        synchronized (sConnectionCache) {
            sConnectionCache.put(connectionId, connection);
        }
    
private voidcheckFindAccessibilityNodeInfoResultIntegrity(java.util.List infos)
Checks whether the infos are a fully connected tree with no duplicates.

param
infos The result list to check.

        if (infos.size() == 0) {
            return;
        }
        // Find the root node.
        AccessibilityNodeInfo root = infos.get(0);
        final int infoCount = infos.size();
        for (int i = 1; i < infoCount; i++) {
            for (int j = i; j < infoCount; j++) {
                AccessibilityNodeInfo candidate = infos.get(j);
                if (root.getParentNodeId() == candidate.getSourceNodeId()) {
                    root = candidate;
                    break;
                }
            }
        }
        if (root == null) {
            Log.e(LOG_TAG, "No root.");
        }
        // Check for duplicates.
        HashSet<AccessibilityNodeInfo> seen = new HashSet<>();
        Queue<AccessibilityNodeInfo> fringe = new LinkedList<>();
        fringe.add(root);
        while (!fringe.isEmpty()) {
            AccessibilityNodeInfo current = fringe.poll();
            if (!seen.add(current)) {
                Log.e(LOG_TAG, "Duplicate node.");
                return;
            }
            final int childCount = current.getChildCount();
            for (int i = 0; i < childCount; i++) {
                final long childId = current.getChildId(i);
                for (int j = 0; j < infoCount; j++) {
                    AccessibilityNodeInfo child = infos.get(j);
                    if (child.getSourceNodeId() == childId) {
                        fringe.add(child);
                    }
                }
            }
        }
        final int disconnectedCount = infos.size() - seen.size();
        if (disconnectedCount > 0) {
            Log.e(LOG_TAG, disconnectedCount + " Disconnected nodes.");
        }
    
public voidclearCache()

        sAccessibilityCache.clear();
    
private voidclearResultLocked()
Clears the result state.

        mInteractionId = -1;
        mFindAccessibilityNodeInfoResult = null;
        mFindAccessibilityNodeInfosResult = null;
        mPerformAccessibilityActionResult = false;
    
private voidfinalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId)
Finalize an {@link AccessibilityNodeInfo} before passing it to the client.

param
info The info.
param
connectionId The id of the connection to the system.

        if (info != null) {
            info.setConnectionId(connectionId);
            info.setSealed(true);
            sAccessibilityCache.add(info);
        }
    
private voidfinalizeAndCacheAccessibilityNodeInfos(java.util.List infos, int connectionId)
Finalize {@link AccessibilityNodeInfo}s before passing them to the client.

param
infos The {@link AccessibilityNodeInfo}s.
param
connectionId The id of the connection to the system.

        if (infos != null) {
            final int infosCount = infos.size();
            for (int i = 0; i < infosCount; i++) {
                AccessibilityNodeInfo info = infos.get(i);
                finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
            }
        }
    
public AccessibilityNodeInfofindAccessibilityNodeInfoByAccessibilityId(int connectionId, int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache, int prefetchFlags)
Finds an {@link AccessibilityNodeInfo} by accessibility id.

param
connectionId The id of a connection for interacting with the system.
param
accessibilityWindowId A unique window id. Use {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} to query the currently active window.
param
accessibilityNodeId A unique view id or virtual descendant id from where to start the search. Use {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} to start from the root.
param
bypassCache Whether to bypass the cache while looking for the node.
param
prefetchFlags flags to guide prefetching.
return
An {@link AccessibilityNodeInfo} if found, null otherwise.

        if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0
                && (prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) == 0) {
            throw new IllegalArgumentException("FLAG_PREFETCH_SIBLINGS"
                + " requires FLAG_PREFETCH_PREDECESSORS");
        }
        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                if (!bypassCache) {
                    AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getNode(
                            accessibilityWindowId, accessibilityNodeId);
                    if (cachedInfo != null) {
                        if (DEBUG) {
                            Log.i(LOG_TAG, "Node cache hit");
                        }
                        return cachedInfo;
                    }
                    if (DEBUG) {
                        Log.i(LOG_TAG, "Node cache miss");
                    }
                }
                final int interactionId = mInteractionIdCounter.getAndIncrement();
                final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
                        accessibilityWindowId, accessibilityNodeId, interactionId, this,
                        prefetchFlags, Thread.currentThread().getId());
                // If the scale is zero the call has failed.
                if (success) {
                    List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                            interactionId);
                    finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
                    if (infos != null && !infos.isEmpty()) {
                        return infos.get(0);
                    }
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while calling remote"
                    + " findAccessibilityNodeInfoByAccessibilityId", re);
        }
        return null;
    
public java.util.ListfindAccessibilityNodeInfosByText(int connectionId, int accessibilityWindowId, long accessibilityNodeId, java.lang.String text)
Finds {@link AccessibilityNodeInfo}s by View text. The match is case insensitive containment. The search is performed in the window whose id is specified and starts from the node whose accessibility id is specified.

param
connectionId The id of a connection for interacting with the system.
param
accessibilityWindowId A unique window id. Use {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} to query the currently active window.
param
accessibilityNodeId A unique view id or virtual descendant id from where to start the search. Use {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} to start from the root.
param
text The searched text.
return
A list of found {@link AccessibilityNodeInfo}s.

        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                final int interactionId = mInteractionIdCounter.getAndIncrement();
                final boolean success = connection.findAccessibilityNodeInfosByText(
                        accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
                        Thread.currentThread().getId());
                if (success) {
                    List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                            interactionId);
                    if (infos != null) {
                        finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
                        return infos;
                    }
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            Log.w(LOG_TAG, "Error while calling remote"
                    + " findAccessibilityNodeInfosByViewText", re);
        }
        return Collections.emptyList();
    
public java.util.ListfindAccessibilityNodeInfosByViewId(int connectionId, int accessibilityWindowId, long accessibilityNodeId, java.lang.String viewId)
Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in the window whose id is specified and starts from the node whose accessibility id is specified.

param
connectionId The id of a connection for interacting with the system.
param
accessibilityWindowId A unique window id. Use {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} to query the currently active window.
param
accessibilityNodeId A unique view id or virtual descendant id from where to start the search. Use {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} to start from the root.
param
viewId The fully qualified resource name of the view id to find.
return
An list of {@link AccessibilityNodeInfo} if found, empty list otherwise.

        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                final int interactionId = mInteractionIdCounter.getAndIncrement();
                final boolean success = connection.findAccessibilityNodeInfosByViewId(
                        accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
                        Thread.currentThread().getId());
                if (success) {
                    List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                            interactionId);
                    if (infos != null) {
                        finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
                        return infos;
                    }
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            Log.w(LOG_TAG, "Error while calling remote"
                    + " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
        }
        return Collections.emptyList();
    
public AccessibilityNodeInfofindFocus(int connectionId, int accessibilityWindowId, long accessibilityNodeId, int focusType)
Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the specified focus type. The search is performed in the window whose id is specified and starts from the node whose accessibility id is specified.

param
connectionId The id of a connection for interacting with the system.
param
accessibilityWindowId A unique window id. Use {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} to query the currently active window.
param
accessibilityNodeId A unique view id or virtual descendant id from where to start the search. Use {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} to start from the root.
param
focusType The focus type.
return
The accessibility focused {@link AccessibilityNodeInfo}.

        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                final int interactionId = mInteractionIdCounter.getAndIncrement();
                final boolean success = connection.findFocus(accessibilityWindowId,
                        accessibilityNodeId, focusType, interactionId, this,
                        Thread.currentThread().getId());
                if (success) {
                    AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                            interactionId);
                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
                    return info;
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            Log.w(LOG_TAG, "Error while calling remote findFocus", re);
        }
        return null;
    
public AccessibilityNodeInfofocusSearch(int connectionId, int accessibilityWindowId, long accessibilityNodeId, int direction)
Finds the accessibility focused {@link android.view.accessibility.AccessibilityNodeInfo}. The search is performed in the window whose id is specified and starts from the node whose accessibility id is specified.

param
connectionId The id of a connection for interacting with the system.
param
accessibilityWindowId A unique window id. Use {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} to query the currently active window.
param
accessibilityNodeId A unique view id or virtual descendant id from where to start the search. Use {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} to start from the root.
param
direction The direction in which to search for focusable.
return
The accessibility focused {@link AccessibilityNodeInfo}.

        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                final int interactionId = mInteractionIdCounter.getAndIncrement();
                final boolean success = connection.focusSearch(accessibilityWindowId,
                        accessibilityNodeId, direction, interactionId, this,
                        Thread.currentThread().getId());
                if (success) {
                    AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                            interactionId);
                    finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
                    return info;
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            Log.w(LOG_TAG, "Error while calling remote accessibilityFocusSearch", re);
        }
        return null;
    
public android.accessibilityservice.IAccessibilityServiceConnectiongetConnection(int connectionId)
Gets a cached accessibility service connection.

param
connectionId The connection id.
return
The cached connection if such.

        synchronized (sConnectionCache) {
            return sConnectionCache.get(connectionId);
        }
    
private AccessibilityNodeInfogetFindAccessibilityNodeInfoResultAndClear(int interactionId)
Gets the the result of an async request that returns an {@link AccessibilityNodeInfo}.

param
interactionId The interaction id to match the result with the request.
return
The result {@link AccessibilityNodeInfo}.

        synchronized (mInstanceLock) {
            final boolean success = waitForResultTimedLocked(interactionId);
            AccessibilityNodeInfo result = success ? mFindAccessibilityNodeInfoResult : null;
            clearResultLocked();
            return result;
        }
    
private java.util.ListgetFindAccessibilityNodeInfosResultAndClear(int interactionId)
Gets the the result of an async request that returns {@link AccessibilityNodeInfo}s.

param
interactionId The interaction id to match the result with the request.
return
The result {@link AccessibilityNodeInfo}s.

        synchronized (mInstanceLock) {
            final boolean success = waitForResultTimedLocked(interactionId);
            List<AccessibilityNodeInfo> result = null;
            if (success) {
                result = mFindAccessibilityNodeInfosResult;
            } else {
                result = Collections.emptyList();
            }
            clearResultLocked();
            if (Build.IS_DEBUGGABLE && CHECK_INTEGRITY) {
                checkFindAccessibilityNodeInfoResultIntegrity(result);
            }
            return result;
        }
    
public static android.view.accessibility.AccessibilityInteractionClientgetInstance()

return
The client for the current thread.


                
        
        final long threadId = Thread.currentThread().getId();
        return getInstanceForThread(threadId);
    
public static android.view.accessibility.AccessibilityInteractionClientgetInstanceForThread(long threadId)
Note: We keep one instance per interrogating thread since the instance contains state which can lead to undesired thread interleavings. We do not have a thread local variable since other threads should be able to look up the correct client knowing a thread id. See ViewRootImpl for details.

return
The client for a given threadId.

        synchronized (sStaticLock) {
            AccessibilityInteractionClient client = sClients.get(threadId);
            if (client == null) {
                client = new AccessibilityInteractionClient();
                sClients.put(threadId, client);
            }
            return client;
        }
    
private booleangetPerformAccessibilityActionResultAndClear(int interactionId)
Gets the result of a request to perform an accessibility action.

param
interactionId The interaction id to match the result with the request.
return
Whether the action was performed.

        synchronized (mInstanceLock) {
            final boolean success = waitForResultTimedLocked(interactionId);
            final boolean result = success ? mPerformAccessibilityActionResult : false;
            clearResultLocked();
            return result;
        }
    
public AccessibilityNodeInfogetRootInActiveWindow(int connectionId)
Gets the root {@link AccessibilityNodeInfo} in the currently active window.

param
connectionId The id of a connection for interacting with the system.
return
The root {@link AccessibilityNodeInfo} if found, null otherwise.

        return findAccessibilityNodeInfoByAccessibilityId(connectionId,
                AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
                false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
    
private android.os.MessagegetSameProcessMessageAndClear()
Gets the message stored if the interacted and interacting threads are the same.

return
The message.

        synchronized (mInstanceLock) {
            Message result = mSameThreadMessage;
            mSameThreadMessage = null;
            return result;
        }
    
public AccessibilityWindowInfogetWindow(int connectionId, int accessibilityWindowId)
Gets the info for a window.

param
connectionId The id of a connection for interacting with the system.
param
accessibilityWindowId A unique window id. Use {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} to query the currently active window.
return
The {@link AccessibilityWindowInfo}.

        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                AccessibilityWindowInfo window = sAccessibilityCache.getWindow(
                        accessibilityWindowId);
                if (window != null) {
                    if (DEBUG) {
                        Log.i(LOG_TAG, "Window cache hit");
                    }
                    return window;
                }
                if (DEBUG) {
                    Log.i(LOG_TAG, "Window cache miss");
                }
                window = connection.getWindow(accessibilityWindowId);
                if (window != null) {
                    sAccessibilityCache.addWindow(window);
                    return window;
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while calling remote getWindow", re);
        }
        return null;
    
public java.util.ListgetWindows(int connectionId)
Gets the info for all windows.

param
connectionId The id of a connection for interacting with the system.
return
The {@link AccessibilityWindowInfo} list.

        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                List<AccessibilityWindowInfo> windows = sAccessibilityCache.getWindows();
                if (windows != null) {
                    if (DEBUG) {
                        Log.i(LOG_TAG, "Windows cache hit");
                    }
                    return windows;
                }
                if (DEBUG) {
                    Log.i(LOG_TAG, "Windows cache miss");
                }
                windows = connection.getWindows();
                if (windows != null) {
                    final int windowCount = windows.size();
                    for (int i = 0; i < windowCount; i++) {
                        AccessibilityWindowInfo window = windows.get(i);
                        sAccessibilityCache.addWindow(window);
                    }
                    return windows;
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while calling remote getWindows", re);
        }
        return Collections.emptyList();
    
public voidonAccessibilityEvent(AccessibilityEvent event)

        sAccessibilityCache.onAccessibilityEvent(event);
    
public booleanperformAccessibilityAction(int connectionId, int accessibilityWindowId, long accessibilityNodeId, int action, android.os.Bundle arguments)
Performs an accessibility action on an {@link AccessibilityNodeInfo}.

param
connectionId The id of a connection for interacting with the system.
param
accessibilityWindowId A unique window id. Use {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} to query the currently active window.
param
accessibilityNodeId A unique view id or virtual descendant id from where to start the search. Use {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} to start from the root.
param
action The action to perform.
param
arguments Optional action arguments.
return
Whether the action was performed.

        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                final int interactionId = mInteractionIdCounter.getAndIncrement();
                final boolean success = connection.performAccessibilityAction(
                        accessibilityWindowId, accessibilityNodeId, action, arguments,
                        interactionId, this, Thread.currentThread().getId());
                if (success) {
                    return getPerformAccessibilityActionResultAndClear(interactionId);
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re);
        }
        return false;
    
public voidremoveConnection(int connectionId)
Removes a cached accessibility service connection.

param
connectionId The connection id.

        synchronized (sConnectionCache) {
            sConnectionCache.remove(connectionId);
        }
    
public voidsetFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId)
{@inheritDoc}

        synchronized (mInstanceLock) {
            if (interactionId > mInteractionId) {
                mFindAccessibilityNodeInfoResult = info;
                mInteractionId = interactionId;
            }
            mInstanceLock.notifyAll();
        }
    
public voidsetFindAccessibilityNodeInfosResult(java.util.List infos, int interactionId)
{@inheritDoc}

        synchronized (mInstanceLock) {
            if (interactionId > mInteractionId) {
                if (infos != null) {
                    // If the call is not an IPC, i.e. it is made from the same process, we need to
                    // instantiate new result list to avoid passing internal instances to clients.
                    final boolean isIpcCall = (Binder.getCallingPid() != Process.myPid());
                    if (!isIpcCall) {
                        mFindAccessibilityNodeInfosResult = new ArrayList<>(infos);
                    } else {
                        mFindAccessibilityNodeInfosResult = infos;
                    }
                } else {
                    mFindAccessibilityNodeInfosResult = Collections.emptyList();
                }
                mInteractionId = interactionId;
            }
            mInstanceLock.notifyAll();
        }
    
public voidsetPerformAccessibilityActionResult(boolean succeeded, int interactionId)
{@inheritDoc}

        synchronized (mInstanceLock) {
            if (interactionId > mInteractionId) {
                mPerformAccessibilityActionResult = succeeded;
                mInteractionId = interactionId;
            }
            mInstanceLock.notifyAll();
        }
    
public voidsetSameThreadMessage(android.os.Message message)
Sets the message to be processed if the interacted view hierarchy and the interacting client are running in the same thread.

param
message The message.

        synchronized (mInstanceLock) {
            mSameThreadMessage = message;
            mInstanceLock.notifyAll();
        }
    
private booleanwaitForResultTimedLocked(int interactionId)
Waits up to a given bound for a result of a request and returns it.

param
interactionId The interaction id to match the result with the request.
return
Whether the result was received.

        long waitTimeMillis = TIMEOUT_INTERACTION_MILLIS;
        final long startTimeMillis = SystemClock.uptimeMillis();
        while (true) {
            try {
                Message sameProcessMessage = getSameProcessMessageAndClear();
                if (sameProcessMessage != null) {
                    sameProcessMessage.getTarget().handleMessage(sameProcessMessage);
                }

                if (mInteractionId == interactionId) {
                    return true;
                }
                if (mInteractionId > interactionId) {
                    return false;
                }
                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
                waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis;
                if (waitTimeMillis <= 0) {
                    return false;
                }
                mInstanceLock.wait(waitTimeMillis);
            } catch (InterruptedException ie) {
                /* ignore */
            }
        }