/* * Copyright (C) 2013 DroidDriver committers * * 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 io.appium.droiddriver.base; import android.app.Instrumentation; import android.os.Looper; import android.util.Log; import java.util.Locale; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import io.appium.droiddriver.exceptions.DroidDriverException; import io.appium.droiddriver.exceptions.TimeoutException; import io.appium.droiddriver.finders.ByXPath; import io.appium.droiddriver.util.Logs; /** * Internal helper for DroidDriver implementation. */ public class DroidDriverContext> { private final Instrumentation instrumentation; private final BaseDroidDriver driver; private final Map map; public DroidDriverContext(Instrumentation instrumentation, BaseDroidDriver driver) { this.instrumentation = instrumentation; this.driver = driver; map = new WeakHashMap(); } public Instrumentation getInstrumentation() { return instrumentation; } public BaseDroidDriver getDriver() { return driver; } public E getElement(R rawElement, E parent) { E element = map.get(rawElement); if (element == null) { element = driver.newUiElement(rawElement, parent); map.put(rawElement, element); } return element; } public E newRootElement(R rawRoot) { clearData(); return getElement(rawRoot, null /* parent */); } private void clearData() { map.clear(); ByXPath.clearData(); } /** * Tries to wait for an idle state on the main thread on best-effort basis up * to {@code timeoutMillis}. The main thread may not enter the idle state when * animation is playing, for example, the ProgressBar. */ public boolean tryWaitForIdleSync(long timeoutMillis) { validateNotAppThread(); FutureTask futureTask = new FutureTask(new Runnable() { @Override public void run() {} }, null); instrumentation.waitForIdle(futureTask); try { futureTask.get(timeoutMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new DroidDriverException(e); } catch (ExecutionException e) { throw new DroidDriverException(e); } catch (java.util.concurrent.TimeoutException e) { Logs.log(Log.DEBUG, String.format(Locale.US, "Timed out after %d milliseconds waiting for idle on main looper", timeoutMillis)); return false; } return true; } /** * Tries to run {@code runnable} on the main thread on best-effort basis up to * {@code timeoutMillis}. The {@code runnable} may never run, for example, in * case that the main Looper has exited due to uncaught exception. */ public boolean tryRunOnMainSync(Runnable runnable, long timeoutMillis) { validateNotAppThread(); final FutureTask futureTask = new FutureTask(runnable, null); new Thread(new Runnable() { @Override public void run() { instrumentation.runOnMainSync(futureTask); } }).start(); try { futureTask.get(timeoutMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new DroidDriverException(e); } catch (ExecutionException e) { throw new DroidDriverException(e); } catch (java.util.concurrent.TimeoutException e) { Logs.log(Log.WARN, getRunOnMainSyncTimeoutMessage(timeoutMillis)); return false; } return true; } public void runOnMainSync(Runnable runnable) { long timeoutMillis = getDriver().getPoller().getTimeoutMillis(); if (!tryRunOnMainSync(runnable, timeoutMillis)) { throw new TimeoutException(getRunOnMainSyncTimeoutMessage(timeoutMillis)); } } private String getRunOnMainSyncTimeoutMessage(long timeoutMillis) { return String.format(Locale.US, "Timed out after %d milliseconds waiting for Instrumentation.runOnMainSync", timeoutMillis); } private void validateNotAppThread() { if (Looper.myLooper() == Looper.getMainLooper()) { throw new DroidDriverException( "This method can not be called from the main application thread"); } } }