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

400 lines
15 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.Class;
import java.lang.ClassLoader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.Long;
import java.lang.NoSuchMethodException;
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.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 mGel;
private Class mGe;
private Class mGas;
private Class mDrawListener;
private Method mRegisterGEL;
private Method mUnregisterGEL;
private Method mSendGE;
private Method mGetLayerClient;
private Method mSetDrawListener;
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();
mGel = mClassLoader.loadClass("org.mozilla.gecko.GeckoEventListener");
mGe = mClassLoader.loadClass("org.mozilla.gecko.GeckoEvent");
mGas = mClassLoader.loadClass("org.mozilla.gecko.GeckoAppShell");
Class [] parameters = new Class[2];
parameters[0] = String.class;
parameters[1] = mGel;
mRegisterGEL = mGas.getMethod("registerGeckoEventListener", parameters);
mUnregisterGEL = mGas.getMethod("unregisterGeckoEventListener", parameters);
parameters = new Class[1];
parameters[0] = mGe;
mSendGE = mGas.getMethod("sendEventToGecko", parameters);
mGetLayerClient = activity.getClass().getMethod("getLayerClient");
Class gslc = mClassLoader.loadClass("org.mozilla.gecko.gfx.GeckoLayerClient");
mDrawListener = mClassLoader.loadClass("org.mozilla.gecko.gfx.GeckoLayerClient$DrawListener");
mSetDrawListener = gslc.getDeclaredMethod("setDrawListener", mDrawListener);
} catch (ClassNotFoundException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
} catch (SecurityException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
} catch (NoSuchMethodException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
} catch (IllegalArgumentException 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 "wakeInvocationHandler";
}
if(methodName.equals("equals")) {
return this == args[0];
}
if(methodName.equals("clone")) {
return this;
}
if(methodName.equals("hashCode")) {
return 314;
}
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
"Waking up on "+methodName);
mEventExpecter.notifyOfEvent();
return null;
}
}
class GeckoEventExpecter implements EventExpecter {
private final String mGeckoEvent;
private final Object[] mRegistrationParams;
private boolean mEventReceived;
private static final int MAX_WAIT_MS = 90000;
GeckoEventExpecter(String geckoEvent, Object[] registrationParams) {
mGeckoEvent = geckoEvent;
mRegistrationParams = registrationParams;
}
public synchronized void blockForEvent() {
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)) {
mAsserter.ok(false, "GeckoEventExpecter",
"blockForEvent timeout: "+mGeckoEvent);
return;
}
}
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
"unblocked on expecter for " + mGeckoEvent);
}
public synchronized boolean eventReceived() {
return mEventReceived;
}
void notifyOfEvent() {
try {
mUnregisterGEL.invoke(null, mRegistrationParams);
} catch (IllegalAccessException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
} catch (InvocationTargetException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
}
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
"received event " + mGeckoEvent);
synchronized (this) {
mEventReceived = true;
this.notifyAll();
}
}
}
public EventExpecter expectGeckoEvent(String geckoEvent) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
"waiting for "+geckoEvent);
try {
Class [] interfaces = new Class[1];
interfaces[0] = mGel;
Object[] finalParams = new Object[2];
finalParams[0] = geckoEvent;
GeckoEventExpecter expecter = new GeckoEventExpecter(geckoEvent, finalParams);
wakeInvocationHandler wIH = new wakeInvocationHandler(expecter);
Object proxy = Proxy.newProxyInstance(mClassLoader, interfaces, wIH);
finalParams[1] = proxy;
mRegisterGEL.invoke(null, 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 {
Method cbe = mGe.getMethod("createBroadcastEvent", String.class, String.class);
Object event = cbe.invoke(null, geckoEvent, data);
mSendGE.invoke(null, event);
} catch (NoSuchMethodException e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
} 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();
} 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 Object mLayerClient;
private boolean mPaintDone;
private static final int MAX_WAIT_MS = 90000;
PaintExpecter() throws IllegalAccessException, InvocationTargetException {
mLayerClient = mGetLayerClient.invoke(mGeckoApp);
mSetDrawListener.invoke(mLayerClient, Proxy.newProxyInstance(mClassLoader, new Class[] { mDrawListener }, new DrawListenerProxy(this)));
}
void notifyOfEvent() {
synchronized (this) {
mPaintDone = true;
this.notifyAll();
}
}
public synchronized void blockForEvent() {
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)) {
mAsserter.ok(false, "PaintExpecter", "blockForEvent timeout");
return;
}
}
try {
mSetDrawListener.invoke(mLayerClient, (Object)null);
} catch (Exception e) {
FennecNativeDriver.log(LogLevel.ERROR, e);
}
}
public synchronized boolean eventReceived() {
return mPaintDone;
}
public synchronized void blockUntilClear(long millis) {
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)) {
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;
}
try {
mSetDrawListener.invoke(mLayerClient, (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:
mInstr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
break;
case UP:
mInstr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_UP);
break;
case LEFT:
mInstr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_LEFT);
break;
case RIGHT:
mInstr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_RIGHT);
break;
case ENTER:
mInstr.sendCharacterSync(KeyEvent.KEYCODE_ENTER);
break;
case MENU:
mInstr.sendCharacterSync(KeyEvent.KEYCODE_MENU);
break;
case BACK:
mInstr.sendCharacterSync(KeyEvent.KEYCODE_BACK);
break;
default:
break;
}
}
@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 {
ClassLoader classLoader = mGeckoApp.getClassLoader();
Class sqlClass = classLoader.loadClass("org.mozilla.gecko.sqlite.SQLiteBridge");
Class stringClass = String.class;
Class stringArrayClass = String[].class;
Class appshell = classLoader.loadClass("org.mozilla.gecko.GeckoAppShell");
Class contextClass = Context.class;
Constructor bridgeConstructor = sqlClass.getConstructor(stringClass);
Method query = sqlClass.getMethod("rawQuery", stringClass, stringArrayClass);
Method loadSQLiteLibs = appshell.getMethod("loadSQLiteLibs", contextClass, stringClass);
Object bridge = bridgeConstructor.newInstance(dbPath);
String resourcePath = mGeckoApp.getApplication().getPackageResourcePath();
loadSQLiteLibs.invoke(null, mGeckoApp, resourcePath);
return (Cursor)query.invoke(bridge, sql, null);
} catch(ClassNotFoundException ex) {
Log.e(LOGTAG, "Error getting class", ex);
} catch(NoSuchMethodException ex) {
Log.e(LOGTAG, "Error getting method", ex);
} catch(InvocationTargetException ex) {
Log.e(LOGTAG, "Error invoking method", ex);
} catch(InstantiationException ex) {
Log.e(LOGTAG, "Error calling constructor", ex);
} catch(IllegalAccessException ex) {
Log.e(LOGTAG, "Error using field", ex);
}
return null;
}
}