/* * 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 mListeners = new CopyOnWriteArrayList(); 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 mEventQueue = new LinkedBlockingQueue(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 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(); } } } } }