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