gecko/build/mobile/robocop/FennecNativeActions.java.in

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;
}
}