#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.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 "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 RepeatedEventExpecter { 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)) { FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR); mAsserter.ok(false, "GeckoEventExpecter", "blockForEvent timeout: "+mGeckoEvent); return; } } try { mUnregisterEventListener.invoke(mRobocopApi, mRegistrationParams); } catch (IllegalAccessException e) { FennecNativeDriver.log(LogLevel.ERROR, e); } catch (InvocationTargetException e) { FennecNativeDriver.log(LogLevel.ERROR, e); } FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, "unblocked on expecter for " + mGeckoEvent); } 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 (!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; } try { mUnregisterEventListener.invoke(mRobocopApi, mRegistrationParams); } catch (IllegalAccessException e) { FennecNativeDriver.log(LogLevel.ERROR, e); } catch (InvocationTargetException e) { FennecNativeDriver.log(LogLevel.ERROR, e); } FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, "unblocked on expecter for " + mGeckoEvent); } public synchronized boolean eventReceived() { return mEventReceived; } void notifyOfEvent() { FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, "received event " + mGeckoEvent); synchronized (this) { mEventReceived = true; 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(); } 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 { Object proxy = Proxy.newProxyInstance(mClassLoader, new Class[] { mDrawListenerClass }, new DrawListenerProxy(this)); mSetDrawListener.invoke(mRobocopApi, proxy); } 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)) { FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR); mAsserter.ok(false, "PaintExpecter", "blockForEvent timeout"); return; } } try { mSetDrawListener.invoke(mRobocopApi, (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)) { 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; } try { 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: 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 { 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; } }