blob: 90aa4dfdc2acfa134b1c62304269966ece4a2b58 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.uiautomator.core;
import com.android.internal.util.Predicate;
import android.accessibilityservice.UiTestAutomationBridge;
import android.os.SystemClock;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
class UiAutomatorBridge extends UiTestAutomationBridge {
private static final String LOGTAG = UiAutomatorBridge.class.getSimpleName();
// This value has the greatest bearing on the appearance of test execution speeds.
// This value is used as the minimum time to wait before considering the UI idle after
// each action.
private static final long QUIET_TIME_TO_BE_CONSIDERD_IDLE_STATE = 500;//ms
// This value is used to wait for the UI to go busy after an action. This has little
// bearing on the appearance of test execution speeds. This value is used as a maximum
// time to wait for busy state where it is possible to occur much sooner.
private static final long WAIT_TIME_FROM_IDLE_TO_BUSY_STATE = 500;//ms
// This is the maximum time the automation will wait for the UI to go idle. Execution
// will resume normally anyway. This is to prevent waiting forever on display updates
// that may be related to spinning wheels or progress updates of sorts etc...
private static final long TOTAL_TIME_TO_WAIT_FOR_IDLE_STATE = 1000 * 10;//ms
// Poll time used to check for last accessibility event time
private static final long BUSY_STATE_POLL_TIME = 50; //ms
private final CopyOnWriteArrayList<AccessibilityEventListener> mListeners =
new CopyOnWriteArrayList<AccessibilityEventListener>();
private final Object mLock = new Object();
private final InteractionController mInteractionController;
private final QueryController mQueryController;
private long mLastEventTime = 0;
private long mLastOperationTime = 0;
private volatile boolean mWaitingForEventDelivery;
public static final long TIMEOUT_ASYNC_PROCESSING = 5000;
private final LinkedBlockingQueue<AccessibilityEvent> mEventQueue =
new LinkedBlockingQueue<AccessibilityEvent>(10);
public interface AccessibilityEventListener {
public void onAccessibilityEvent(AccessibilityEvent event);
}
UiAutomatorBridge() {
mInteractionController = new InteractionController(this);
mQueryController = new QueryController(this);
connect();
}
InteractionController getInteractionController() {
return mInteractionController;
}
QueryController getQueryController() {
return mQueryController;
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
super.onAccessibilityEvent(event);
Log.d(LOGTAG, event.toString());
if (mWaitingForEventDelivery) {
try {
AccessibilityEvent clone = AccessibilityEvent.obtain(event);
mEventQueue.offer(clone, TIMEOUT_ASYNC_PROCESSING, TimeUnit.MILLISECONDS);
} catch (InterruptedException ie) {
/* ignore */
}
if (!mWaitingForEventDelivery) {
mEventQueue.clear();
}
}
mLastEventTime = SystemClock.uptimeMillis();
notifyListeners(event);
}
void addAccessibilityEventListener(AccessibilityEventListener listener) {
mListeners.add(listener);
}
private void notifyListeners(AccessibilityEvent event) {
for (AccessibilityEventListener listener : mListeners) {
listener.onAccessibilityEvent(event);
}
}
@Override
public void waitForIdle(long idleTimeout, long globalTimeout) {
long start = SystemClock.uptimeMillis();
while ((SystemClock.uptimeMillis() - start) < WAIT_TIME_FROM_IDLE_TO_BUSY_STATE) {
if (getLastOperationTime() > getLastEventTime()) {
SystemClock.sleep(BUSY_STATE_POLL_TIME);
} else {
break;
}
}
super.waitForIdle(idleTimeout, globalTimeout);
}
public void waitForIdle() {
waitForIdle(TOTAL_TIME_TO_WAIT_FOR_IDLE_STATE);
}
public void waitForIdle(long timeout) {
waitForIdle(QUIET_TIME_TO_BE_CONSIDERD_IDLE_STATE, timeout);
}
private long getLastEventTime() {
synchronized (mLock) {
return mLastEventTime;
}
}
private long getLastOperationTime() {
synchronized (mLock) {
return mLastOperationTime;
}
}
void setOperationTime() {
synchronized (mLock) {
mLastOperationTime = SystemClock.uptimeMillis();
}
}
void updateEventTime() {
synchronized (mLock) {
mLastEventTime = SystemClock.uptimeMillis();
}
}
public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command,
Predicate<AccessibilityEvent> predicate, long timeoutMillis)
throws TimeoutException, Exception {
// Prepare to wait for an event.
mWaitingForEventDelivery = true;
// Execute the command.
command.run();
// Wait for the event.
final long startTimeMillis = SystemClock.uptimeMillis();
while (true) {
// Check if timed out and if not wait.
final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
if (remainingTimeMillis <= 0) {
mWaitingForEventDelivery = false;
mEventQueue.clear();
throw new TimeoutException("Expected event not received within: "
+ timeoutMillis + " ms.");
}
AccessibilityEvent event = null;
try {
event = mEventQueue.poll(remainingTimeMillis, TimeUnit.MILLISECONDS);
} catch (InterruptedException ie) {
/* ignore */
}
if (event != null) {
if (predicate.apply(event)) {
mWaitingForEventDelivery = false;
mEventQueue.clear();
return event;
} else {
event.recycle();
}
}
}
}
}