FileDocCategorySizeDatePackage
UiAutomation.javaAPI DocAndroid 5.1 API36955Thu Mar 12 22:22:10 GMT 2015android.app

UiAutomation

public final class UiAutomation extends Object
Class for interacting with the device's UI by simulation user actions and introspection of the screen content. It relies on the platform accessibility APIs to introspect the screen and to perform some actions on the remote view tree. It also allows injecting of arbitrary raw input events simulating user interaction with keyboards and touch devices. One can think of a UiAutomation as a special type of {@link android.accessibilityservice.AccessibilityService} which does not provide hooks for the service life cycle and exposes other APIs that are useful for UI test automation.

The APIs exposed by this class are low-level to maximize flexibility when developing UI test automation tools and libraries. Generally, a UiAutomation client should be using a higher-level library or implement high-level functions. For example, performing a tap on the screen requires construction and injecting of a touch down and up events which have to be delivered to the system by a call to {@link #injectInputEvent(InputEvent, boolean)}.

The APIs exposed by this class operate across applications enabling a client to write tests that cover use cases spanning over multiple applications. For example, going to the settings application to change a setting and then interacting with another application whose behavior depends on that setting.

Fields Summary
private static final String
LOG_TAG
private static final boolean
DEBUG
private static final int
CONNECTION_ID_UNDEFINED
private static final long
CONNECT_TIMEOUT_MILLIS
public static final int
ROTATION_UNFREEZE
Rotation constant: Unfreeze rotation (rotating the device changes its rotation state).
public static final int
ROTATION_FREEZE_CURRENT
Rotation constant: Freeze rotation to its current state.
public static final int
ROTATION_FREEZE_0
Rotation constant: Freeze rotation to 0 degrees (natural orientation)
public static final int
ROTATION_FREEZE_90
Rotation constant: Freeze rotation to 90 degrees .
public static final int
ROTATION_FREEZE_180
Rotation constant: Freeze rotation to 180 degrees .
public static final int
ROTATION_FREEZE_270
Rotation constant: Freeze rotation to 270 degrees .
private final Object
mLock
private final ArrayList
mEventQueue
private final android.accessibilityservice.IAccessibilityServiceClient
mClient
private final IUiAutomationConnection
mUiAutomationConnection
private int
mConnectionId
private OnAccessibilityEventListener
mOnAccessibilityEventListener
private boolean
mWaitingForEventDelivery
private long
mLastEventTimeMillis
private boolean
mIsConnecting
Constructors Summary
public UiAutomation(android.os.Looper looper, IUiAutomationConnection connection)
Creates a new instance that will handle callbacks from the accessibility layer on the thread of the provided looper and perform requests for privileged operations on the provided connection.

param
looper The looper on which to execute accessibility callbacks.
param
connection The connection for performing privileged operations.
hide


                
        

                                                                  
           
    

              
        

                                                
           
    

                                                         
         
        if (looper == null) {
            throw new IllegalArgumentException("Looper cannot be null!");
        }
        if (connection == null) {
            throw new IllegalArgumentException("Connection cannot be null!");
        }
        mUiAutomationConnection = connection;
        mClient = new IAccessibilityServiceClientImpl(looper);
    
Methods Summary
public voidclearWindowAnimationFrameStats()
Clears the window animation rendering statistics. These statistics contain information about the most recently rendered window animation frames, i.e. for window transition animations.

see
android.view.WindowAnimationFrameStats
see
#getWindowAnimationFrameStats()
see
android.R.styleable#WindowAnimation

        synchronized (mLock) {
            throwIfNotConnectedLocked();
        }
        try {
            if (DEBUG) {
                Log.i(LOG_TAG, "Clearing window animation frame stats");
            }
            // Calling out without a lock held.
            mUiAutomationConnection.clearWindowAnimationFrameStats();
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error clearing window animation frame stats!", re);
        }
    
public booleanclearWindowContentFrameStats(int windowId)
Clears the frame statistics for the content of a given window. These statistics contain information about the most recently rendered content frames.

param
windowId The window id.
return
Whether the window is present and its frame statistics were cleared.
see
android.view.WindowContentFrameStats
see
#getWindowContentFrameStats(int)
see
#getWindows()
see
AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()

        synchronized (mLock) {
            throwIfNotConnectedLocked();
        }
        try {
            if (DEBUG) {
                Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId);
            }
            // Calling out without a lock held.
            return mUiAutomationConnection.clearWindowContentFrameStats(windowId);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error clearing window content frame stats!", re);
        }
        return false;
    
public voidconnect()
Connects this UiAutomation to the accessibility introspection APIs.

hide

        synchronized (mLock) {
            throwIfConnectedLocked();
            if (mIsConnecting) {
                return;
            }
            mIsConnecting = true;
        }

        try {
            // Calling out without a lock held.
            mUiAutomationConnection.connect(mClient);
        } catch (RemoteException re) {
            throw new RuntimeException("Error while connecting UiAutomation", re);
        }

        synchronized (mLock) {
            final long startTimeMillis = SystemClock.uptimeMillis();
            try {
                while (true) {
                    if (isConnectedLocked()) {
                        break;
                    }
                    final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
                    final long remainingTimeMillis = CONNECT_TIMEOUT_MILLIS - elapsedTimeMillis;
                    if (remainingTimeMillis <= 0) {
                        throw new RuntimeException("Error while connecting UiAutomation");
                    }
                    try {
                        mLock.wait(remainingTimeMillis);
                    } catch (InterruptedException ie) {
                        /* ignore */
                    }
                }
            } finally {
                mIsConnecting = false;
            }
        }
    
public voiddisconnect()
Disconnects this UiAutomation from the accessibility introspection APIs.

hide

        synchronized (mLock) {
            if (mIsConnecting) {
                throw new IllegalStateException(
                        "Cannot call disconnect() while connecting!");
            }
            throwIfNotConnectedLocked();
            mConnectionId = CONNECTION_ID_UNDEFINED;
        }
        try {
            // Calling out without a lock held.
            mUiAutomationConnection.disconnect();
        } catch (RemoteException re) {
            throw new RuntimeException("Error while disconnecting UiAutomation", re);
        }
    
public android.view.accessibility.AccessibilityEventexecuteAndWaitForEvent(java.lang.Runnable command, android.app.UiAutomation$AccessibilityEventFilter filter, long timeoutMillis)
Executes a command and waits for a specific accessibility event up to a given wait timeout. To detect a sequence of events one can implement a filter that keeps track of seen events of the expected sequence and returns true after the last event of that sequence is received.

Note: It is caller's responsibility to recycle the returned event.

param
command The command to execute.
param
filter Filter that recognizes the expected event.
param
timeoutMillis The wait timeout in milliseconds.
throws
TimeoutException If the expected event is not received within the timeout.

        // Acquire the lock and prepare for receiving events.
        synchronized (mLock) {
            throwIfNotConnectedLocked();
            mEventQueue.clear();
            // Prepare to wait for an event.
            mWaitingForEventDelivery = true;
        }

        // Note: We have to release the lock since calling out with this lock held
        // can bite. We will correctly filter out events from other interactions,
        // so starting to collect events before running the action is just fine.

        // We will ignore events from previous interactions.
        final long executionStartTimeMillis = SystemClock.uptimeMillis();
        // Execute the command *without* the lock being held.
        command.run();

        // Acquire the lock and wait for the event.
        synchronized (mLock) {
            try {
                // Wait for the event.
                final long startTimeMillis = SystemClock.uptimeMillis();
                while (true) {
                    // Drain the event queue
                    while (!mEventQueue.isEmpty()) {
                        AccessibilityEvent event = mEventQueue.remove(0);
                        // Ignore events from previous interactions.
                        if (event.getEventTime() < executionStartTimeMillis) {
                            continue;
                        }
                        if (filter.accept(event)) {
                            return event;
                        }
                        event.recycle();
                    }
                    // Check if timed out and if not wait.
                    final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
                    final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
                    if (remainingTimeMillis <= 0) {
                        throw new TimeoutException("Expected event not received within: "
                                + timeoutMillis + " ms.");
                    }
                    try {
                        mLock.wait(remainingTimeMillis);
                    } catch (InterruptedException ie) {
                        /* ignore */
                    }
                }
            } finally {
                mWaitingForEventDelivery = false;
                mEventQueue.clear();
                mLock.notifyAll();
            }
        }
    
public android.os.ParcelFileDescriptorexecuteShellCommand(java.lang.String command)
Executes a shell command. This method returs a file descriptor that points to the standard output stream. The command execution is similar to running "adb shell " from a host connected to the device.

Note: It is your responsibility to close the retunred file descriptor once you are done reading.

param
command The command to execute.
return
A file descriptor to the standard output stream.

        synchronized (mLock) {
            throwIfNotConnectedLocked();
        }

        ParcelFileDescriptor source = null;
        ParcelFileDescriptor sink = null;

        try {
            ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
            source = pipe[0];
            sink = pipe[1];

            // Calling out without a lock held.
            mUiAutomationConnection.executeShellCommand(command, sink);
        } catch (IOException ioe) {
            Log.e(LOG_TAG, "Error executing shell command!", ioe);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error executing shell command!", re);
        } finally {
            IoUtils.closeQuietly(sink);
        }

        return source;
    
public android.view.accessibility.AccessibilityNodeInfofindFocus(int focus)
Find the view that has the specified focus type. The search is performed across all windows.

Note: In order to access the windows you have to opt-in to retrieve the interactive windows by setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag. Otherwise, the search will be performed only in the active window.

param
focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
return
The node info of the focused view or null.
see
AccessibilityNodeInfo#FOCUS_INPUT
see
AccessibilityNodeInfo#FOCUS_ACCESSIBILITY

        return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId,
                AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);
    
public intgetConnectionId()
The id of the {@link IAccessibilityInteractionConnection} for querying the screen content. This is here for legacy purposes since some tools use hidden APIs to introspect the screen.

hide

        synchronized (mLock) {
            throwIfNotConnectedLocked();
            return mConnectionId;
        }
    
private static floatgetDegreesForRotation(int value)

        switch (value) {
            case Surface.ROTATION_90: {
                return 360f - 90f;
            }
            case Surface.ROTATION_180: {
                return 360f - 180f;
            }
            case Surface.ROTATION_270: {
                return 360f - 270f;
            } default: {
                return 0;
            }
        }
    
public android.view.accessibility.AccessibilityNodeInfogetRootInActiveWindow()
Gets the root {@link AccessibilityNodeInfo} in the active window.

return
The root info.

        final int connectionId;
        synchronized (mLock) {
            throwIfNotConnectedLocked();
            connectionId = mConnectionId;
        }
        // Calling out without a lock held.
        return AccessibilityInteractionClient.getInstance()
                .getRootInActiveWindow(connectionId);
    
public final android.accessibilityservice.AccessibilityServiceInfogetServiceInfo()
Gets the an {@link AccessibilityServiceInfo} describing this UiAutomation. This method is useful if one wants to change some of the dynamically configurable properties at runtime.

return
The accessibility service info.
see
AccessibilityServiceInfo

        final IAccessibilityServiceConnection connection;
        synchronized (mLock) {
            throwIfNotConnectedLocked();
            connection = AccessibilityInteractionClient.getInstance()
                    .getConnection(mConnectionId);
        }
        // Calling out without a lock held.
        if (connection != null) {
            try {
                return connection.getServiceInfo();
            } catch (RemoteException re) {
                Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
            }
        }
        return null;
    
public android.view.WindowAnimationFrameStatsgetWindowAnimationFrameStats()
Gets the window animation frame statistics. These statistics contain information about the most recently rendered window animation frames, i.e. for window transition animations.

A typical usage requires clearing the window animation frame statistics via {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes a window transition which uses a window animation and finally getting the window animation frame statistics by calling this method.

// Start with a clean slate.
uiAutimation.clearWindowAnimationFrameStats();

// Do stuff to trigger a window transition.

// Get the frame statistics.
WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats();

return
The window animation frame statistics.
see
android.view.WindowAnimationFrameStats
see
#clearWindowAnimationFrameStats()
see
android.R.styleable#WindowAnimation

        synchronized (mLock) {
            throwIfNotConnectedLocked();
        }
        try {
            if (DEBUG) {
                Log.i(LOG_TAG, "Getting window animation frame stats");
            }
            // Calling out without a lock held.
            return mUiAutomationConnection.getWindowAnimationFrameStats();
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error getting window animation frame stats!", re);
        }
        return null;
    
public android.view.WindowContentFrameStatsgetWindowContentFrameStats(int windowId)
Gets the frame statistics for a given window. These statistics contain information about the most recently rendered content frames.

A typical usage requires clearing the window frame statistics via {@link #clearWindowContentFrameStats(int)} followed by an interaction with the UI and finally getting the window frame statistics via calling this method.

// Assume we have at least one window.
final int windowId = getWindows().get(0).getId();

// Start with a clean slate.
uiAutimation.clearWindowContentFrameStats(windowId);

// Do stuff with the UI.

// Get the frame statistics.
WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId);

param
windowId The window id.
return
The window frame statistics, or null if the window is not present.
see
android.view.WindowContentFrameStats
see
#clearWindowContentFrameStats(int)
see
#getWindows()
see
AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()

        synchronized (mLock) {
            throwIfNotConnectedLocked();
        }
        try {
            if (DEBUG) {
                Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId);
            }
            // Calling out without a lock held.
            return mUiAutomationConnection.getWindowContentFrameStats(windowId);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error getting window content frame stats!", re);
        }
        return null;
    
public java.util.ListgetWindows()
Gets the windows on the screen. This method returns only the windows that a sighted user can interact with, as opposed to all windows. For example, if there is a modal dialog shown and the user cannot touch anything behind it, then only the modal window will be reported (assuming it is the top one). For convenience the returned windows are ordered in a descending layer order, which is the windows that are higher in the Z-order are reported first.

Note: In order to access the windows you have to opt-in to retrieve the interactive windows by setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.

return
The windows if there are windows such, otherwise an empty list.

        final int connectionId;
        synchronized (mLock) {
            throwIfNotConnectedLocked();
            connectionId = mConnectionId;
        }
        // Calling out without a lock held.
        return AccessibilityInteractionClient.getInstance()
                .getWindows(connectionId);
    
public booleaninjectInputEvent(android.view.InputEvent event, boolean sync)
A method for injecting an arbitrary input event.

Note: It is caller's responsibility to recycle the event.

param
event The event to inject.
param
sync Whether to inject the event synchronously.
return
Whether event injection succeeded.

        synchronized (mLock) {
            throwIfNotConnectedLocked();
        }
        try {
            if (DEBUG) {
                Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
            }
            // Calling out without a lock held.
            return mUiAutomationConnection.injectInputEvent(event, sync);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while injecting input event!", re);
        }
        return false;
    
private booleanisConnectedLocked()

        return mConnectionId != CONNECTION_ID_UNDEFINED;
    
public final booleanperformGlobalAction(int action)
Performs a global action. Such an action can be performed at any moment regardless of the current application or user location in that application. For example going back, going home, opening recents, etc.

param
action The action to perform.
return
Whether the action was successfully performed.
see
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK
see
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_HOME
see
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS
see
android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_RECENTS

        final IAccessibilityServiceConnection connection;
        synchronized (mLock) {
            throwIfNotConnectedLocked();
            connection = AccessibilityInteractionClient.getInstance()
                    .getConnection(mConnectionId);
        }
        // Calling out without a lock held.
        if (connection != null) {
            try {
                return connection.performGlobalAction(action);
            } catch (RemoteException re) {
                Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
            }
        }
        return false;
    
public voidsetOnAccessibilityEventListener(android.app.UiAutomation$OnAccessibilityEventListener listener)
Sets a callback for observing the stream of {@link AccessibilityEvent}s.

param
listener The callback.

        synchronized (mLock) {
            mOnAccessibilityEventListener = listener;
        }
    
public booleansetRotation(int rotation)
Sets the device rotation. A client can freeze the rotation in desired state or freeze the rotation to its current state or unfreeze the rotation (rotating the device changes its rotation state).

param
rotation The desired rotation.
return
Whether the rotation was set successfully.
see
#ROTATION_FREEZE_0
see
#ROTATION_FREEZE_90
see
#ROTATION_FREEZE_180
see
#ROTATION_FREEZE_270
see
#ROTATION_FREEZE_CURRENT
see
#ROTATION_UNFREEZE

        synchronized (mLock) {
            throwIfNotConnectedLocked();
        }
        switch (rotation) {
            case ROTATION_FREEZE_0:
            case ROTATION_FREEZE_90:
            case ROTATION_FREEZE_180:
            case ROTATION_FREEZE_270:
            case ROTATION_UNFREEZE:
            case ROTATION_FREEZE_CURRENT: {
                try {
                    // Calling out without a lock held.
                    mUiAutomationConnection.setRotation(rotation);
                    return true;
                } catch (RemoteException re) {
                    Log.e(LOG_TAG, "Error while setting rotation!", re);
                }
            } return false;
            default: {
                throw new IllegalArgumentException("Invalid rotation.");
            }
        }
    
public voidsetRunAsMonkey(boolean enable)
Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing potentially undesirable actions such as calling 911 or posting on public forums etc.

param
enable whether to run in a "monkey" mode or not. Default is not.
see
{@link android.app.ActivityManager#isUserAMonkey()}

        synchronized (mLock) {
            throwIfNotConnectedLocked();
        }
        try {
            ActivityManagerNative.getDefault().setUserIsMonkey(enable);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while setting run as monkey!", re);
        }
    
public final voidsetServiceInfo(android.accessibilityservice.AccessibilityServiceInfo info)
Sets the {@link AccessibilityServiceInfo} that describes how this UiAutomation will be handled by the platform accessibility layer.

param
info The info.
see
AccessibilityServiceInfo

        final IAccessibilityServiceConnection connection;
        synchronized (mLock) {
            throwIfNotConnectedLocked();
            AccessibilityInteractionClient.getInstance().clearCache();
            connection = AccessibilityInteractionClient.getInstance()
                    .getConnection(mConnectionId);
        }
        // Calling out without a lock held.
        if (connection != null) {
            try {
                connection.setServiceInfo(info);
            } catch (RemoteException re) {
                Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
            }
        }
    
public android.graphics.BitmaptakeScreenshot()
Takes a screenshot.

return
The screenshot bitmap on success, null otherwise.

        synchronized (mLock) {
            throwIfNotConnectedLocked();
        }
        Display display = DisplayManagerGlobal.getInstance()
                .getRealDisplay(Display.DEFAULT_DISPLAY);
        Point displaySize = new Point();
        display.getRealSize(displaySize);
        final int displayWidth = displaySize.x;
        final int displayHeight = displaySize.y;

        final float screenshotWidth;
        final float screenshotHeight;

        final int rotation = display.getRotation();
        switch (rotation) {
            case ROTATION_FREEZE_0: {
                screenshotWidth = displayWidth;
                screenshotHeight = displayHeight;
            } break;
            case ROTATION_FREEZE_90: {
                screenshotWidth = displayHeight;
                screenshotHeight = displayWidth;
            } break;
            case ROTATION_FREEZE_180: {
                screenshotWidth = displayWidth;
                screenshotHeight = displayHeight;
            } break;
            case ROTATION_FREEZE_270: {
                screenshotWidth = displayHeight;
                screenshotHeight = displayWidth;
            } break;
            default: {
                throw new IllegalArgumentException("Invalid rotation: "
                        + rotation);
            }
        }

        // Take the screenshot
        Bitmap screenShot = null;
        try {
            // Calling out without a lock held.
            screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
                    (int) screenshotHeight);
            if (screenShot == null) {
                return null;
            }
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while taking screnshot!", re);
            return null;
        }

        // Rotate the screenshot to the current orientation
        if (rotation != ROTATION_FREEZE_0) {
            Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
                    Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(unrotatedScreenShot);
            canvas.translate(unrotatedScreenShot.getWidth() / 2,
                    unrotatedScreenShot.getHeight() / 2);
            canvas.rotate(getDegreesForRotation(rotation));
            canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
            canvas.drawBitmap(screenShot, 0, 0, null);
            canvas.setBitmap(null);
            screenShot.recycle();
            screenShot = unrotatedScreenShot;
        }

        // Optimization
        screenShot.setHasAlpha(false);

        return screenShot;
    
private voidthrowIfConnectedLocked()

        if (mConnectionId != CONNECTION_ID_UNDEFINED) {
            throw new IllegalStateException("UiAutomation not connected!");
        }
    
private voidthrowIfNotConnectedLocked()

        if (!isConnectedLocked()) {
            throw new IllegalStateException("UiAutomation not connected!");
        }
    
public voidwaitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)
Waits for the accessibility event stream to become idle, which is not to have received an accessibility event within idleTimeoutMillis. The total time spent to wait for an idle accessibility event stream is bounded by the globalTimeoutMillis.

param
idleTimeoutMillis The timeout in milliseconds between two events to consider the device idle.
param
globalTimeoutMillis The maximal global timeout in milliseconds in which to wait for an idle state.
throws
TimeoutException If no idle state was detected within globalTimeoutMillis.

        synchronized (mLock) {
            throwIfNotConnectedLocked();

            final long startTimeMillis = SystemClock.uptimeMillis();
            if (mLastEventTimeMillis <= 0) {
                mLastEventTimeMillis = startTimeMillis;
            }

            while (true) {
                final long currentTimeMillis = SystemClock.uptimeMillis();
                // Did we get idle state within the global timeout?
                final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
                final long remainingGlobalTimeMillis =
                        globalTimeoutMillis - elapsedGlobalTimeMillis;
                if (remainingGlobalTimeMillis <= 0) {
                    throw new TimeoutException("No idle state with idle timeout: "
                            + idleTimeoutMillis + " within global timeout: "
                            + globalTimeoutMillis);
                }
                // Did we get an idle state within the idle timeout?
                final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
                final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
                if (remainingIdleTimeMillis <= 0) {
                    return;
                }
                try {
                     mLock.wait(remainingIdleTimeMillis);
                } catch (InterruptedException ie) {
                     /* ignore */
                }
            }
        }