mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
484 lines
18 KiB
Java
484 lines
18 KiB
Java
#filter substitution
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package @ANDROID_PACKAGE_NAME@;
|
|
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Proxy;
|
|
import java.lang.reflect.InvocationHandler;
|
|
import java.util.concurrent.SynchronousQueue;
|
|
import java.util.ArrayList;
|
|
|
|
import android.app.Activity;
|
|
import android.content.Context;
|
|
import android.app.Instrumentation;
|
|
import android.database.Cursor;
|
|
import android.os.SystemClock;
|
|
import android.text.TextUtils;
|
|
import android.view.View;
|
|
import android.view.KeyEvent;
|
|
import android.util.Log;
|
|
|
|
import org.json.*;
|
|
|
|
import com.jayway.android.robotium.solo.Solo;
|
|
|
|
import static @ANDROID_PACKAGE_NAME@.FennecNativeDriver.LogLevel;
|
|
|
|
public class FennecNativeActions implements Actions {
|
|
private Solo mSolo;
|
|
private Instrumentation mInstr;
|
|
private Activity mGeckoApp;
|
|
private Assert mAsserter;
|
|
|
|
// Objects for reflexive access of fennec classes.
|
|
private ClassLoader mClassLoader;
|
|
private Class mApiClass;
|
|
private Class mEventListenerClass;
|
|
private Class mDrawListenerClass;
|
|
private Method mRegisterEventListener;
|
|
private Method mUnregisterEventListener;
|
|
private Method mBroadcastEvent;
|
|
private Method mSetDrawListener;
|
|
private Method mQuerySql;
|
|
private Object mRobocopApi;
|
|
|
|
private static final String LOGTAG = "FennecNativeActions";
|
|
|
|
public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation, Assert asserter) {
|
|
mSolo = robocop;
|
|
mInstr = instrumentation;
|
|
mGeckoApp = activity;
|
|
mAsserter = asserter;
|
|
// Set up reflexive access of java classes and methods.
|
|
try {
|
|
mClassLoader = activity.getClassLoader();
|
|
|
|
mApiClass = mClassLoader.loadClass("org.mozilla.gecko.RobocopAPI");
|
|
mEventListenerClass = mClassLoader.loadClass("org.mozilla.gecko.util.GeckoEventListener");
|
|
mDrawListenerClass = mClassLoader.loadClass("org.mozilla.gecko.gfx.GeckoLayerClient$DrawListener");
|
|
|
|
mRegisterEventListener = mApiClass.getMethod("registerEventListener", String.class, mEventListenerClass);
|
|
mUnregisterEventListener = mApiClass.getMethod("unregisterEventListener", String.class, mEventListenerClass);
|
|
mBroadcastEvent = mApiClass.getMethod("broadcastEvent", String.class, String.class);
|
|
mSetDrawListener = mApiClass.getMethod("setDrawListener", mDrawListenerClass);
|
|
mQuerySql = mApiClass.getMethod("querySql", String.class, String.class);
|
|
|
|
mRobocopApi = mApiClass.getConstructor(Activity.class).newInstance(activity);
|
|
} catch (Exception e) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, e);
|
|
}
|
|
}
|
|
|
|
class wakeInvocationHandler implements InvocationHandler {
|
|
private final GeckoEventExpecter mEventExpecter;
|
|
|
|
public wakeInvocationHandler(GeckoEventExpecter expecter) {
|
|
mEventExpecter = expecter;
|
|
}
|
|
|
|
public Object invoke(Object proxy, Method method, Object[] args) {
|
|
String methodName = method.getName();
|
|
//Depending on the method, return a completely different type.
|
|
if(methodName.equals("toString")) {
|
|
return this.toString();
|
|
}
|
|
if(methodName.equals("equals")) {
|
|
return
|
|
args[0] == null ? false :
|
|
this.toString().equals(args[0].toString());
|
|
}
|
|
if(methodName.equals("clone")) {
|
|
return this;
|
|
}
|
|
if(methodName.equals("hashCode")) {
|
|
return 314;
|
|
}
|
|
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
|
|
"Waking up on "+methodName);
|
|
mEventExpecter.notifyOfEvent(args);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class GeckoEventExpecter implements RepeatedEventExpecter {
|
|
private final String mGeckoEvent;
|
|
private Object[] mRegistrationParams;
|
|
private boolean mEventReceived;
|
|
private boolean mEventEverReceived;
|
|
private String mEventData;
|
|
private static final int MAX_WAIT_MS = 90000;
|
|
|
|
GeckoEventExpecter(String geckoEvent, Object[] registrationParams) {
|
|
if (TextUtils.isEmpty(geckoEvent)) {
|
|
throw new IllegalArgumentException("geckoEvent must not be empty");
|
|
}
|
|
if (registrationParams == null || registrationParams.length == 0) {
|
|
throw new IllegalArgumentException("registrationParams must not be empty");
|
|
}
|
|
|
|
mGeckoEvent = geckoEvent;
|
|
mRegistrationParams = registrationParams;
|
|
}
|
|
|
|
public synchronized void blockForEvent() {
|
|
blockForEvent(MAX_WAIT_MS, true);
|
|
}
|
|
|
|
private synchronized void blockForEvent(long millis, boolean failOnTimeout) {
|
|
if (mRegistrationParams == null) {
|
|
throw new IllegalStateException("listener not registered");
|
|
}
|
|
long startTime = SystemClock.uptimeMillis();
|
|
long endTime = 0;
|
|
while (! mEventReceived) {
|
|
try {
|
|
this.wait(millis);
|
|
} catch (InterruptedException ie) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, ie);
|
|
break;
|
|
}
|
|
endTime = SystemClock.uptimeMillis();
|
|
if (!mEventReceived && (endTime - startTime >= millis)) {
|
|
if (failOnTimeout) {
|
|
FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR);
|
|
mAsserter.ok(false, "GeckoEventExpecter",
|
|
"blockForEvent timeout: "+mGeckoEvent);
|
|
}
|
|
mEventData = null;
|
|
return;
|
|
}
|
|
}
|
|
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
|
|
"unblocked on expecter for " + mGeckoEvent);
|
|
mEventReceived = false;
|
|
}
|
|
|
|
public synchronized void blockUntilClear(long millis) {
|
|
if (mRegistrationParams == null) {
|
|
throw new IllegalStateException("listener not registered");
|
|
}
|
|
if (millis <= 0) {
|
|
throw new IllegalArgumentException("millis must be > 0");
|
|
}
|
|
// wait for at least one event
|
|
long startTime = SystemClock.uptimeMillis();
|
|
long endTime = 0;
|
|
while (!mEventReceived) {
|
|
try {
|
|
this.wait(MAX_WAIT_MS);
|
|
} catch (InterruptedException ie) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, ie);
|
|
break;
|
|
}
|
|
endTime = SystemClock.uptimeMillis();
|
|
if (!mEventReceived && (endTime - startTime >= MAX_WAIT_MS)) {
|
|
FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR);
|
|
mAsserter.ok(false, "GeckoEventExpecter", "blockUtilClear timeout");
|
|
return;
|
|
}
|
|
}
|
|
// now wait for a period of millis where we don't get an event
|
|
startTime = SystemClock.uptimeMillis();
|
|
while (true) {
|
|
try {
|
|
this.wait(millis);
|
|
} catch (InterruptedException ie) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, ie);
|
|
break;
|
|
}
|
|
endTime = SystemClock.uptimeMillis();
|
|
if (endTime - startTime >= millis) {
|
|
// success
|
|
break;
|
|
}
|
|
// we got a notify() before we could wait long enough, so we need to start over
|
|
startTime = endTime;
|
|
}
|
|
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
|
|
"unblocked on expecter for " + mGeckoEvent);
|
|
mEventReceived = false;
|
|
}
|
|
|
|
public synchronized String blockForEventData() {
|
|
blockForEvent();
|
|
return mEventData;
|
|
}
|
|
|
|
public synchronized String blockForEventDataWithTimeout(long millis) {
|
|
blockForEvent(millis, false);
|
|
return mEventData;
|
|
}
|
|
|
|
public synchronized void unregisterListener() {
|
|
if (mRegistrationParams == null) {
|
|
throw new IllegalStateException("listener not registered");
|
|
}
|
|
try {
|
|
FennecNativeDriver.log(LogLevel.INFO, "EventExpecter: no longer listening for "+mGeckoEvent);
|
|
mUnregisterEventListener.invoke(mRobocopApi, mRegistrationParams);
|
|
mRegistrationParams = null;
|
|
} catch (IllegalAccessException e) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, e);
|
|
} catch (InvocationTargetException e) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, e);
|
|
}
|
|
}
|
|
|
|
public synchronized boolean eventReceived() {
|
|
return mEventEverReceived;
|
|
}
|
|
|
|
void notifyOfEvent(Object[] args) {
|
|
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
|
|
"received event " + mGeckoEvent);
|
|
synchronized (this) {
|
|
mEventReceived = true;
|
|
mEventEverReceived = true;
|
|
mEventData = args[1].toString();
|
|
this.notifyAll();
|
|
}
|
|
}
|
|
}
|
|
|
|
public RepeatedEventExpecter expectGeckoEvent(String geckoEvent) {
|
|
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
|
|
"waiting for "+geckoEvent);
|
|
try {
|
|
Object[] finalParams = new Object[2];
|
|
finalParams[0] = geckoEvent;
|
|
GeckoEventExpecter expecter = new GeckoEventExpecter(geckoEvent, finalParams);
|
|
wakeInvocationHandler wIH = new wakeInvocationHandler(expecter);
|
|
Object proxy = Proxy.newProxyInstance(mClassLoader, new Class[] { mEventListenerClass }, wIH);
|
|
finalParams[1] = proxy;
|
|
|
|
mRegisterEventListener.invoke(mRobocopApi, finalParams);
|
|
return expecter;
|
|
} catch (IllegalAccessException e) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, e);
|
|
} catch (InvocationTargetException e) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void sendGeckoEvent(String geckoEvent, String data) {
|
|
try {
|
|
mBroadcastEvent.invoke(mRobocopApi, geckoEvent, data);
|
|
} catch (IllegalAccessException e) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, e);
|
|
} catch (InvocationTargetException e) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, e);
|
|
}
|
|
}
|
|
|
|
class DrawListenerProxy implements InvocationHandler {
|
|
private final PaintExpecter mPaintExpecter;
|
|
|
|
DrawListenerProxy(PaintExpecter paintExpecter) {
|
|
mPaintExpecter = paintExpecter;
|
|
}
|
|
|
|
public Object invoke(Object proxy, Method method, Object[] args) {
|
|
String methodName = method.getName();
|
|
if ("drawFinished".equals(methodName)) {
|
|
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
|
|
"Received drawFinished notification");
|
|
mPaintExpecter.notifyOfEvent(args);
|
|
} else if ("toString".equals(methodName)) {
|
|
return "DrawListenerProxy";
|
|
} else if ("equals".equals(methodName)) {
|
|
return false;
|
|
} else if ("hashCode".equals(methodName)) {
|
|
return 0;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class PaintExpecter implements RepeatedEventExpecter {
|
|
private boolean mPaintDone;
|
|
private boolean mListening;
|
|
private static final int MAX_WAIT_MS = 90000;
|
|
|
|
PaintExpecter() throws IllegalAccessException, InvocationTargetException {
|
|
Object proxy = Proxy.newProxyInstance(mClassLoader, new Class[] { mDrawListenerClass }, new DrawListenerProxy(this));
|
|
mSetDrawListener.invoke(mRobocopApi, proxy);
|
|
mListening = true;
|
|
}
|
|
|
|
void notifyOfEvent(Object[] args) {
|
|
synchronized (this) {
|
|
mPaintDone = true;
|
|
this.notifyAll();
|
|
}
|
|
}
|
|
|
|
private synchronized void blockForEvent(long millis, boolean failOnTimeout) {
|
|
if (!mListening) {
|
|
throw new IllegalStateException("draw listener not registered");
|
|
}
|
|
long startTime = SystemClock.uptimeMillis();
|
|
long endTime = 0;
|
|
while (!mPaintDone) {
|
|
try {
|
|
this.wait(millis);
|
|
} catch (InterruptedException ie) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, ie);
|
|
break;
|
|
}
|
|
endTime = SystemClock.uptimeMillis();
|
|
if (!mPaintDone && (endTime - startTime >= millis)) {
|
|
if (failOnTimeout) {
|
|
FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR);
|
|
mAsserter.ok(false, "PaintExpecter", "blockForEvent timeout");
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public synchronized void blockForEvent() {
|
|
blockForEvent(MAX_WAIT_MS, true);
|
|
}
|
|
|
|
public synchronized String blockForEventData() {
|
|
blockForEvent();
|
|
return null;
|
|
}
|
|
|
|
public synchronized String blockForEventDataWithTimeout(long millis) {
|
|
blockForEvent(millis, false);
|
|
return null;
|
|
}
|
|
|
|
public synchronized boolean eventReceived() {
|
|
return mPaintDone;
|
|
}
|
|
|
|
public synchronized void blockUntilClear(long millis) {
|
|
if (!mListening) {
|
|
throw new IllegalStateException("draw listener not registered");
|
|
}
|
|
if (millis <= 0) {
|
|
throw new IllegalArgumentException("millis must be > 0");
|
|
}
|
|
// wait for at least one event
|
|
long startTime = SystemClock.uptimeMillis();
|
|
long endTime = 0;
|
|
while (!mPaintDone) {
|
|
try {
|
|
this.wait(MAX_WAIT_MS);
|
|
} catch (InterruptedException ie) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, ie);
|
|
break;
|
|
}
|
|
endTime = SystemClock.uptimeMillis();
|
|
if (!mPaintDone && (endTime - startTime >= MAX_WAIT_MS)) {
|
|
FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR);
|
|
mAsserter.ok(false, "PaintExpecter", "blockUtilClear timeout");
|
|
return;
|
|
}
|
|
}
|
|
// now wait for a period of millis where we don't get an event
|
|
startTime = SystemClock.uptimeMillis();
|
|
while (true) {
|
|
try {
|
|
this.wait(millis);
|
|
} catch (InterruptedException ie) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, ie);
|
|
break;
|
|
}
|
|
endTime = SystemClock.uptimeMillis();
|
|
if (endTime - startTime >= millis) {
|
|
// success
|
|
break;
|
|
}
|
|
// we got a notify() before we could wait long enough, so we need to start over
|
|
startTime = endTime;
|
|
}
|
|
}
|
|
|
|
public synchronized void unregisterListener() {
|
|
if (!mListening) {
|
|
throw new IllegalStateException("listener not registered");
|
|
}
|
|
try {
|
|
FennecNativeDriver.log(LogLevel.INFO, "PaintExpecter: no longer listening for events");
|
|
mListening = false;
|
|
mSetDrawListener.invoke(mRobocopApi, (Object)null);
|
|
} catch (Exception e) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public RepeatedEventExpecter expectPaint() {
|
|
try {
|
|
return new PaintExpecter();
|
|
} catch (Exception e) {
|
|
FennecNativeDriver.log(LogLevel.ERROR, e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void sendSpecialKey(SpecialKey button) {
|
|
switch(button) {
|
|
case DOWN:
|
|
sendKeyCode(KeyEvent.KEYCODE_DPAD_DOWN);
|
|
break;
|
|
case UP:
|
|
sendKeyCode(KeyEvent.KEYCODE_DPAD_UP);
|
|
break;
|
|
case LEFT:
|
|
sendKeyCode(KeyEvent.KEYCODE_DPAD_LEFT);
|
|
break;
|
|
case RIGHT:
|
|
sendKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT);
|
|
break;
|
|
case ENTER:
|
|
sendKeyCode(KeyEvent.KEYCODE_ENTER);
|
|
break;
|
|
case MENU:
|
|
sendKeyCode(KeyEvent.KEYCODE_MENU);
|
|
break;
|
|
case BACK:
|
|
sendKeyCode(KeyEvent.KEYCODE_BACK);
|
|
break;
|
|
default:
|
|
mAsserter.ok(false, "sendSpecialKey", "Unknown SpecialKey " + button);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void sendKeyCode(int keyCode) {
|
|
if (keyCode <= 0 || keyCode > KeyEvent.getMaxKeyCode()) {
|
|
mAsserter.ok(false, "sendKeyCode", "Unknown keyCode " + keyCode);
|
|
}
|
|
mInstr.sendCharacterSync(keyCode);
|
|
}
|
|
|
|
@Override
|
|
public void sendKeys(String input) {
|
|
mInstr.sendStringSync(input);
|
|
}
|
|
|
|
public void drag(int startingX, int endingX, int startingY, int endingY) {
|
|
mSolo.drag(startingX, endingX, startingY, endingY, 10);
|
|
}
|
|
|
|
public Cursor querySql(String dbPath, String sql) {
|
|
try {
|
|
return (Cursor)mQuerySql.invoke(mRobocopApi, dbPath, sql);
|
|
} catch(InvocationTargetException ex) {
|
|
Log.e(LOGTAG, "Error invoking method", ex);
|
|
} catch(IllegalAccessException ex) {
|
|
Log.e(LOGTAG, "Error using field", ex);
|
|
}
|
|
return null;
|
|
}
|
|
}
|