blob: 89e2022a1d2bac6b9cb223535550a37823211ce6 [file] [log] [blame]
/*
* 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<R, E extends BaseUiElement<R, E>> {
private final Instrumentation instrumentation;
private final BaseDroidDriver<R, E> driver;
private final Map<R, E> map;
public DroidDriverContext(Instrumentation instrumentation, BaseDroidDriver<R, E> driver) {
this.instrumentation = instrumentation;
this.driver = driver;
map = new WeakHashMap<R, E>();
}
public Instrumentation getInstrumentation() {
return instrumentation;
}
public BaseDroidDriver<R, E> 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<Void>(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<Void>(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");
}
}
}