Bug 701076 - core robocop toolchain (1.03) a=tfair; r=gbrown,blassey
authorJoel Maher <jmaher@mozilla.com>
Thu, 22 Dec 2011 09:09:41 -0500
changeset 83254 65f3838752d8df85d52974369ddf36ff0c2944ac
parent 83253 f75ee6fa2587786a28716c62896f33c198ffaecc
child 83255 b84f3d11305814346dfb6f75fa40c4d854e00f91
push id21744
push userbmo@edmorley.co.uk
push dateFri, 23 Dec 2011 23:56:40 +0000
treeherdermozilla-central@ede336ccaed0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstfair, gbrown, blassey
bugs701076
milestone12.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 701076 - core robocop toolchain (1.03) a=tfair; r=gbrown,blassey
build/mobile/robocop/Actions.java.in
build/mobile/robocop/AndroidManifest.xml.in
build/mobile/robocop/Driver.java.in
build/mobile/robocop/Element.java.in
build/mobile/robocop/FennecNativeActions.java.in
build/mobile/robocop/FennecNativeDriver.java.in
build/mobile/robocop/FennecNativeElement.java.in
build/mobile/robocop/Makefile.in
build/mobile/robocop/RoboCopException.java.in
build/mobile/robocop/parse_ids.py
build/mobile/robocop/res/values/strings.xml
new file mode 100644
--- /dev/null
+++ b/build/mobile/robocop/Actions.java.in
@@ -0,0 +1,60 @@
+#filter substitution
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firefox Mobile Test Framework.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Trevor Fairey <tnfairey@gmail.com>
+ * David Burns <dburns@mozilla.com>
+ * Joel Maher <joel.maher@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package @ANDROID_PACKAGE_NAME@;
+import java.util.List;
+
+public interface Actions {
+  public enum SpecialKey {
+    DOWN, UP, LEFT, RIGHT, ENTER
+  }
+  /**
+   * Waits for a gecko event to be sent from the Gecko instance.
+   * 
+   * @param geckoEvent The geckoEvent JSONObject's type
+   */
+
+  void waitForGeckoEvent(String geckoEvent);
+  // Send the string kewsToSend to the application 
+  void sendKeys(String keysToSend);
+  //Send any of the above keys to the element
+  void sendSpecialKey(SpecialKey button);
+
+  void drag(int startingX, int endingX, int startingY, int endingY);
+}
new file mode 100644
--- /dev/null
+++ b/build/mobile/robocop/AndroidManifest.xml.in
@@ -0,0 +1,19 @@
+#filter substitution
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.mozilla.roboexample.test"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="8" />
+
+    <instrumentation
+        android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="@ANDROID_PACKAGE_NAME@" />
+
+    <application
+        android:label="@string/app_name" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+</manifest>
new file mode 100644
--- /dev/null
+++ b/build/mobile/robocop/Driver.java.in
@@ -0,0 +1,78 @@
+#filter substitution
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firefox Mobile Test Framework.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Trevor Fairey <tnfairey@gmail.com>
+ * David Burns <dburns@mozilla.com>
+ * Joel Maher <joel.maher@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package @ANDROID_PACKAGE_NAME@;
+import java.util.List;
+
+public interface Driver {
+  /**
+   * Find the first Element using the given method.
+   * 
+   * @param name The name of the element
+   * @return The first matching element on the current context
+   * @throws RoboCopException If no matching elements are found
+   */
+  Element findElement(String name);
+
+  /**
+   * Sets up scroll handling so that data is received from the extension.
+   */
+  void setupScrollHandling();
+
+  int getPageHeight();
+  int getScrollHeight();
+  int getHeight();
+  int getGeckoTop();
+  int getGeckoLeft();
+  int getGeckoWidth();
+  int getGeckoHeight();
+
+  void startFrameRecording();
+  int stopFrameRecording();
+  void dumpLog(String message);
+  void setLogFile(String filename);
+
+  void ok(boolean condition, String name, String diag);
+  void is(Object a, Object b, String name);
+  void isnot(Object a, Object b, String name);
+  void todo(boolean condition, String name, String diag);
+  void todo_is(Object a, Object b, String name);
+  void todo_isnot(Object a, Object b, String name);
+  void info(String name, String message);
+}
new file mode 100644
--- /dev/null
+++ b/build/mobile/robocop/Element.java.in
@@ -0,0 +1,49 @@
+#filter substitution
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firefox Mobile Test Framework.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Trevor Fairey <tnfairey@gmail.com>
+ * David Burns <dburns@mozilla.com>
+ * Joel Maher <joel.maher@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package @ANDROID_PACKAGE_NAME@;
+
+public interface Element {
+  //Click on the element
+  void click();
+  //Returns true if the element is currently displayed
+  boolean isDisplayed();
+  //Returns the text currently displayed on the element.
+  String getText();
+}
new file mode 100644
--- /dev/null
+++ b/build/mobile/robocop/FennecNativeActions.java.in
@@ -0,0 +1,197 @@
+#filter substitution
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firefox Mobile Test Framework.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Trevor Fairey <tnfairey@gmail.com>
+ * David Burns <dburns@mozilla.com>
+ * Joel Maher <joel.maher@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package @ANDROID_PACKAGE_NAME@;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.HashMap;
+import java.util.List;
+
+import java.lang.Class;
+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 android.app.Activity;
+import android.app.Instrumentation;
+import android.util.Log;
+import android.view.View;
+import android.view.KeyEvent;
+
+import java.util.concurrent.SynchronousQueue;
+
+import org.json.*;
+
+import com.jayway.android.robotium.solo.Solo;
+
+public class FennecNativeActions implements Actions {
+  // Map of IDs to element names.
+  private Solo solo;
+  private Instrumentation instr;
+
+  // Objects for reflexive access of fennec classes.
+  private ClassLoader classLoader;
+  private Class gel;
+  private Class ge;
+  private Class gas;
+  private Method registerGEL;
+  private Method unregisterGEL;
+  private Method sendGE;
+
+
+  // If waiting for an event.
+  private SynchronousQueue waitqueue = new SynchronousQueue<Boolean>();
+
+  public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation){
+    this.solo = robocop;
+    this.instr = instrumentation;
+    // Set up reflexive access of java classes and methods.
+    try {
+      classLoader = activity.getClassLoader();
+      gel = classLoader.loadClass("org.mozilla.gecko.GeckoEventListener");
+      ge = classLoader.loadClass("org.mozilla.gecko.GeckoEvent");
+      gas = classLoader.loadClass("org.mozilla.gecko.GeckoAppShell");
+      Class [] parameters = new Class[2];
+      parameters[0] = String.class;
+      parameters[1] = gel;
+      registerGEL = gas.getMethod("registerGeckoEventListener", parameters);
+      unregisterGEL = gas.getMethod("unregisterGeckoEventListener", parameters);
+      parameters = new Class[1];
+      parameters[0] = ge;
+      sendGE = gas.getMethod("sendEventToGecko", parameters);
+     } catch (ClassNotFoundException e) {
+       e.printStackTrace();
+     } catch (SecurityException e) {
+       e.printStackTrace();
+     } catch (NoSuchMethodException e) {
+       e.printStackTrace();
+     } catch (IllegalArgumentException e) {
+       e.printStackTrace();
+     }
+  }
+
+  class wakeInvocationHandler implements InvocationHandler {
+    public wakeInvocationHandler(){};
+    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;
+      }
+      Log.i("Robocop", "Waking up on "+methodName);
+      waitqueue.offer(new Boolean(true));
+      return null;
+    }
+  }
+  
+  public void waitForGeckoEvent(String geckoEvent) {
+    Log.i("Robocop", "waiting for "+geckoEvent);
+    try {
+      Class [] interfaces = new Class[1];
+      interfaces[0] = gel;
+      Object[] finalParams = new Object[2];
+      finalParams[0] = geckoEvent;
+     
+      wakeInvocationHandler wIH = new wakeInvocationHandler();
+      Object proxy = Proxy.newProxyInstance(classLoader, interfaces, wIH);
+      finalParams[1] = proxy;
+      registerGEL.invoke(null, finalParams);
+      
+      waitqueue.take();
+      unregisterGEL.invoke(null, finalParams);
+    } catch (IllegalAccessException e) {
+      e.printStackTrace();
+    } catch (InvocationTargetException e) {
+      e.printStackTrace();
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+    Log.i("Robocop", "wait ends for: "+geckoEvent);
+  }
+
+  public void sendSpecialKey(SpecialKey button) {
+    switch( button) {
+      case DOWN:
+        instr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        break;
+      case UP:
+        instr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_UP);
+        break;
+      case LEFT:
+        instr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_LEFT);
+        break;
+      case RIGHT:
+        instr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_RIGHT);
+        break;
+      case ENTER:
+        instr.sendCharacterSync(KeyEvent.KEYCODE_ENTER);
+        break;
+      default:
+        break;
+    }
+  }
+
+  @Override
+  public void sendKeys(String input) {
+    instr.sendStringSync(input);
+  }
+
+
+  public void drag(int startingX, int endingX, int startingY, int endingY) {
+    solo.drag(startingX, endingX, startingY, endingY, 10);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/build/mobile/robocop/FennecNativeDriver.java.in
@@ -0,0 +1,425 @@
+#filter substitution
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firefox Mobile Test Framework.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Trevor Fairey <tnfairey@gmail.com>
+ * David Burns <dburns@mozilla.com>
+ * Joel Maher <joel.maher@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package @ANDROID_PACKAGE_NAME@;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.HashMap;
+import java.util.List;
+
+import java.lang.Class;
+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 android.app.Activity;
+import android.util.Log;
+import android.view.View;
+
+import org.json.*;
+
+import com.jayway.android.robotium.solo.Solo;
+
+public class FennecNativeDriver implements Driver {
+  // Map of IDs to element names.
+  private HashMap locators = null;
+  private Activity activity;
+  private Solo solo;
+  private String logFile;
+
+  // Objects for reflexive access of fennec classes.
+  private ClassLoader classLoader;
+  private Class gel;
+  private Class ge;
+  private Class gas;
+  private Method registerGEL;
+  private Method unregisterGEL;
+  private Method sendGE;
+  private Method _startFrameRecording;
+  private Method _stopFrameRecording;
+
+
+  private LinkedList<testInfo> testList = new LinkedList<testInfo>();
+
+  public FennecNativeDriver(Activity activity, Solo robocop){
+    this.activity = activity;
+    this.solo = robocop;
+
+    // Set up table of fennec_ids.
+    locators = convertTextToTable(getFile("/mnt/sdcard/fennec_ids.txt"));
+
+    // Set up reflexive access of java classes and methods.
+    try {
+      classLoader = activity.getClassLoader();
+      gel = classLoader.loadClass("org.mozilla.gecko.GeckoEventListener");
+      ge = classLoader.loadClass("org.mozilla.gecko.GeckoEvent");
+      gas = classLoader.loadClass("org.mozilla.gecko.GeckoAppShell");
+      Class [] parameters = new Class[2];
+      parameters[0] = String.class;
+      parameters[1] = gel;
+      registerGEL = gas.getMethod("registerGeckoEventListener", parameters);
+      unregisterGEL = gas.getMethod("unregisterGeckoEventListener", parameters);
+      parameters = new Class[1];
+      parameters[0] = ge;
+      sendGE = gas.getMethod("sendEventToGecko", parameters);
+
+      Class gfx = classLoader.loadClass("org.mozilla.gecko.gfx.PanningPerfAPI");
+      _startFrameRecording = gfx.getDeclaredMethod("startFrameTimeRecording");
+      _stopFrameRecording = gfx.getDeclaredMethod("stopFrameTimeRecording");
+     } catch (ClassNotFoundException e) {
+       e.printStackTrace();
+     } catch (SecurityException e) {
+       e.printStackTrace();
+     } catch (NoSuchMethodException e) {
+       e.printStackTrace();
+     } catch (IllegalArgumentException e) {
+       e.printStackTrace();
+     }
+  }
+
+  //Information on the location of the Gecko Frame.
+  private boolean geckoInfo = false;
+  private int geckoTop = 100;
+  private int geckoLeft = 0;
+  private int geckoHeight= 700;
+  private int geckoWidth = 1024;
+
+  private void getGeckoInfo() {
+    View geckoLayout = activity.findViewById(Integer.decode((String)locators.get("gecko_layout")));
+    if (geckoLayout != null) {
+      geckoTop = geckoLayout.getTop();
+      geckoLeft = geckoLayout.getLeft();
+      geckoWidth = geckoLayout.getWidth();
+      geckoHeight = geckoLayout.getHeight();
+      geckoInfo = true;
+    }
+  }
+
+  public int getGeckoTop() {
+    if(!geckoInfo) {
+      getGeckoInfo();
+    }
+    return geckoTop;
+  }
+
+  public int getGeckoLeft() {
+    if(!geckoInfo) {
+      getGeckoInfo();
+    }
+    return geckoLeft;
+  }
+
+  public int getGeckoHeight() {
+    if(!geckoInfo) {
+      getGeckoInfo();
+    }
+    return geckoHeight;
+  }
+  public int getGeckoWidth() {
+    if(!geckoInfo) {
+      getGeckoInfo();
+    }
+    return geckoWidth;
+  }
+
+  @Override
+  public Element findElement(String name) {
+    if (name == null)
+      throw new IllegalArgumentException("Can not findElements when passed a null");
+    if (locators.containsKey(name)){
+      return new FennecNativeElement(Integer.decode((String)locators.get(name)), activity, solo);
+    }
+    throw new RoboCopException("Element does not exist in the list");
+  }
+
+  public void startFrameRecording() {
+    try {
+      Object [] params = null;
+      _startFrameRecording.invoke(null, params);
+    } catch (IllegalAccessException e) {
+      e.printStackTrace();
+    } catch (InvocationTargetException e) {
+      e.printStackTrace();
+    }
+  }
+
+  public int stopFrameRecording() {
+    Class [] parameters = new Class[1];
+    parameters[0] = null;
+    List frames;
+
+    try {
+      Object [] params = null;
+      frames = (List)_stopFrameRecording.invoke(null, params);
+      Object [] framearray = frames.toArray();
+      Long last = new Long(0);
+      Long threshold = new Long(17);
+      int numDelays = 0;
+      for (int i=0; i < framearray.length; i++) {
+        Long val = (Long)framearray[i];
+        if ((val - last) > threshold) {
+          numDelays++;
+        }
+        last = val;
+      }
+      return numDelays;
+    } catch (IllegalAccessException e) {
+      e.printStackTrace();
+    } catch (InvocationTargetException e) {
+      e.printStackTrace();
+    }
+
+    return 0;
+  }
+
+  class scrollHandler implements InvocationHandler {
+    public scrollHandler(){};
+    public Object invoke(Object proxy, Method method, Object[] args) {
+      try{
+        //Disect the JSON object into the appropriate variables 
+        JSONObject jo = ((JSONObject)args[1]);
+        scrollHeight = jo.getInt("y");
+        height = jo.getInt("cheight");
+        //We don't want a height of 0. That means it's a bad response.
+        if( height > 0) {
+          pageHeight = jo.getInt("height");
+        }
+
+      } catch( Throwable e) {
+        Log.i("Robocop", "WARNING: ScrollReceived, but read wrong!");
+      }
+      return null;
+    }
+  }
+  public int getScrollHeight() {
+    return scrollHeight;
+  }
+  public int getPageHeight() {
+    return pageHeight;
+  }
+  public int getHeight() {
+    return height;
+  }
+
+  public int height=0;
+  public int scrollHeight=0;
+  public int pageHeight=10;
+  public void setupScrollHandling() {
+    //Setup scrollHandler to catch "robocop:scroll" events. 
+    try {
+      Class [] interfaces = new Class[1];
+      interfaces[0] = gel;
+      Object[] finalParams = new Object[2];
+      finalParams[0] = "robocop:scroll";
+      finalParams[1] = Proxy.newProxyInstance(classLoader, interfaces, new scrollHandler());
+      registerGEL.invoke(null, finalParams);
+    } catch (IllegalAccessException e) {
+      e.printStackTrace();
+    } catch (InvocationTargetException e) {
+      e.printStackTrace();
+    }
+
+  }
+
+  //Takes a filename, loads the file, 
+  //  and returns a string version of the entire file.
+  public static String getFile(String filename)
+  {
+    File file = new File(filename);
+    StringBuilder text = new StringBuilder();
+
+    try {
+      BufferedReader br = new BufferedReader(new FileReader(file));
+      String line;
+
+      while ((line = br.readLine()) != null) {
+        text.append(line);
+        text.append('\n');
+      }
+    } catch(IOException e) {
+      e.printStackTrace();
+    }
+    return text.toString();  
+  }
+
+  // Write information to a logfile and logcat
+  public void dumpLog(String message)
+  {
+    File file = new File(logFile);
+    BufferedWriter bw = null;
+
+    try {
+      bw = new BufferedWriter(new FileWriter(logFile, true));
+      bw.write(message);
+      bw.newLine();
+    } catch(IOException e) {
+      e.printStackTrace();
+    } finally {
+      try {
+        if (bw != null) {
+          bw.flush();
+          bw.close();
+        }
+      } catch (IOException ex) {
+        ex.printStackTrace();
+      }
+    }
+
+    Log.i("Robocop", message);
+  }
+
+  // Set the filename used for dumpLog.
+  public void setLogFile(String filename)
+  {
+    logFile = filename;
+  }
+
+  // Takes a string of "key=value" pairs split by \n and creates a hash table.
+  public static HashMap convertTextToTable(String data)
+  {
+    HashMap retVal = new HashMap();
+
+    String[] lines = data.split("\n");
+    for (int i = 0; i < lines.length; i++) {
+      String[] parts = lines[i].split("=");
+      retVal.put(parts[0].trim(), parts[1].trim());
+    }
+    return retVal;
+  }
+
+  class testInfo {
+    public boolean result;
+    public String name;
+    public String diag;
+    public boolean todo;
+    public testInfo(boolean r, String n, String d, boolean t) {
+      result = r;
+      name = n;
+      diag = d;
+      todo = t;
+    }
+
+  }
+
+
+  private void _logResult(testInfo test, String passString, String failString)
+  {
+    boolean isError = true;
+    String resultString = failString;
+    if(test.result || test.todo){
+      isError = false;
+    }
+    if(test.result)
+    {
+      resultString = passString;
+    }
+    String diag= test.name;
+    if(test.diag!=null) diag+= " - " + test.diag;
+
+    String message = resultString + " | " + "ROBOCOP" + " | " + diag;
+    if(isError) {
+      dumpLog(message);
+    }
+    else {
+      dumpLog(message);
+    }
+  }
+
+  public void ok(boolean condition, String name, String diag) {
+    testInfo test = new testInfo(condition, name, diag, false);
+    _logResult(test, "TEST-PASS", "TEST-UNEXPECTED-FAIL");
+    testList.add(test);
+  }
+
+  public void is(Object a, Object b, String name) {
+    boolean pass = a.equals(b);
+    String diag = "got " + a.toString() + ", expected " + b.toString();
+    if(pass) {
+      diag = a.toString() + " should equal " + b.toString();
+    }
+    ok(pass, name, diag);
+  }
+  
+  public void isnot(Object a, Object b, String name) {
+    boolean pass = !a.equals(b);
+    String diag = "didn't expect " + a.toString() + ", but got it";
+    if(pass) {
+      diag = a.toString() + " should not equal " + b.toString();
+    }
+    ok(pass, name, diag);
+  }
+
+  public void todo(boolean condition, String name, String diag) {
+    testInfo test = new testInfo(condition, name, diag, true);
+    _logResult(test, "TEST-UNEXPECTED-PASS", "TEST-KNOWN-FAIL");
+    testList.add(test);
+  }
+
+  public void todo_is(Object a, Object b, String name) {
+    boolean pass = a.equals(b);
+    String diag = "got " + a.toString() + ", expected " + b.toString();
+    if(pass) {
+      diag = a.toString() + " should equal " + b.toString();
+    }
+    todo(pass, name, diag);
+  }
+  
+  public void todo_isnot(Object a, Object b, String name) {
+    boolean pass = !a.equals(b);
+    String diag = "didn't expect " + a.toString() + ", but got it";
+    if(pass) {
+      diag = a.toString() + " should not equal " + b.toString();
+    }
+    todo(pass, name, diag);
+  }
+
+  public void info(String name, String message) {
+    testInfo test = new testInfo(true, name, message, false);
+    _logResult(test, "TEST-INFO", "INFO FAILED?");
+  }
+}
new file mode 100644
--- /dev/null
+++ b/build/mobile/robocop/FennecNativeElement.java.in
@@ -0,0 +1,149 @@
+#filter substitution
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firefox Mobile Test Framework.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Trevor Fairey <tnfairey@gmail.com>
+ * David Burns <dburns@mozilla.com>
+ * Joel Maher <joel.maher@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package @ANDROID_PACKAGE_NAME@;
+
+import java.util.List;
+
+import android.app.Activity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.TextSwitcher;
+import android.app.Instrumentation;
+import android.util.Log;
+import com.jayway.android.robotium.solo.Solo;
+import java.util.concurrent.SynchronousQueue;
+
+public class FennecNativeElement implements Element {
+  private Integer id;
+  private Activity currentActivity;
+  private Solo robocop;
+
+  public FennecNativeElement(Integer id, Activity activity, Solo solo){
+    this.id = id;
+    robocop = solo;
+    currentActivity = activity;
+  }
+
+  public Integer getId() {
+    return id;
+  }
+
+  @Override
+  public void click() {
+    final SynchronousQueue syncQueue = new SynchronousQueue();
+    currentActivity = robocop.getCurrentActivity();
+    currentActivity.runOnUiThread(
+        new Runnable() {
+          public void run() {
+            View view = (View)currentActivity.findViewById(id);
+            if(view != null) {
+              view.performClick();
+            } else {
+              throw new RoboCopException("click: unable to find view "+id); 
+            }
+            syncQueue.offer(new Object());
+          }
+        });
+    try {
+      syncQueue.take();
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+  }
+
+  private Object text;
+  private Activity elementActivity;
+
+  @Override
+  public String getText() {
+    elementActivity = robocop.getCurrentActivity();
+    final SynchronousQueue syncQueue = new SynchronousQueue();
+    elementActivity.runOnUiThread(
+        new Runnable() {
+          public void run() {
+            View v = elementActivity.findViewById(id);
+            if(v instanceof EditText) {
+              EditText et = (EditText)v;
+              text = et.getEditableText();
+            }else if(v instanceof TextSwitcher) {
+              TextSwitcher ts = (TextSwitcher)v;
+              ts.getNextView();
+              text = ((TextView)ts.getCurrentView()).getText();
+            }else if(v instanceof ViewGroup) {
+              ViewGroup vg = (ViewGroup)v;
+              for(int i = 0; i < vg.getChildCount(); i++) {
+                if(vg.getChildAt(i) instanceof TextView) {
+                  text = ((TextView)vg.getChildAt(i)).getText();
+                }
+              } //end of for
+            } else if(v instanceof TextView) {
+              text = ((TextView)v).getText(); 
+            } else if(v == null) {
+              throw new RoboCopException("getText: unable to find view "+id); 
+            } else {
+              throw new RoboCopException("getText: unhandled type for view "+id); 
+            }
+            syncQueue.offer(new Object());
+          } // end of run() method definition
+        } // end of anonymous Runnable object instantiation
+    );
+    try {   
+      //Wait for the UiThread code to finish running
+      syncQueue.take();
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+    if(text == null) {
+      throw new RoboCopException("getText: Text is null for view "+id);
+    }
+    return text.toString();
+  }
+
+  @Override
+  public boolean isDisplayed() {
+    // TODO Auto-generated method stub
+    return false;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/build/mobile/robocop/Makefile.in
@@ -0,0 +1,136 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Android sutagent for testing.
+#
+# The Initial Developer of the Original Code is
+#   Mozilla Foundation
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Clint Talbert <ctalbert@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH       = ../../..
+topsrcdir   = @top_srcdir@
+srcdir      = @srcdir@
+VPATH       = @srcdir@
+TESTPATH    = $(topsrcdir)/mobile/android/base/tests
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = robocop
+
+ROBOTIUM_PATH = $(srcdir)/robotium-solo-3.0.jar
+
+JAVAFILES = \
+  R.java \
+
+_JAVA_HARNESS = \
+  Driver.java \
+  Element.java \
+  Actions.java \
+  FennecNativeElement.java \
+  RoboCopException.java \
+  FennecNativeDriver.java \
+  FennecNativeActions.java \
+
+_JAVA_TESTS = $(patsubst $(TESTPATH)/%.in,%,$(wildcard $(TESTPATH)/*.java.in))
+
+_ROBOCOP_TOOLS = \
+  $(TESTPATH)/robocop.ini \
+  parse_ids.py \
+  $(NULL)
+
+GARBAGE += \
+  AndroidManifest.xml \
+  _JAVA_TESTS \
+  _JAVA_HARNESS \
+  classes.dex \
+  robocop.apk \
+  robocop.ap_ \
+  robocop-unsigned-unaligned.apk \
+  robocop-unaligned.apk \
+  $(NULL)
+
+DEFINES += \
+  -DANDROID_PACKAGE_NAME=$(ANDROID_PACKAGE_NAME) \
+  $(NULL)
+
+GARBAGE_DIRS += res
+
+JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar:$(ROBOTIUM_PATH)
+
+include $(topsrcdir)/config/rules.mk
+
+# Override rules.mk java flags with the android specific ones
+include $(topsrcdir)/config/android-common.mk
+
+$(_JAVA_HARNESS): % : %.in
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $< > $@
+
+AndroidManifest.xml: % : %.in
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $< > $@
+
+$(_JAVA_TESTS): % : $(TESTPATH)/%.in
+	$(NSINSTALL) -D $(DEPTH)/mobile/android/base/tests
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $< > $(DEPTH)/mobile/android/base/tests/$@
+
+$(_ROBOCOP_TOOLS):
+	cp $(TESTPATH)/robocop.ini robocop.ini
+	cp $(srcdir)/parse_ids.txt parse_ids.txt
+
+tools:: robocop.apk
+
+classes.dex: robocop.ap_
+classes.dex: $(_ROBOCOP_TOOLS)
+classes.dex: $(_JAVA_HARNESS)
+classes.dex: $(_JAVA_TESTS)
+classes.dex: $(TEST_FILES)
+	$(NSINSTALL) -D classes
+	$(JAVAC) $(JAVAC_FLAGS) -d classes $(JAVAFILES) $(_JAVA_HARNESS) $(addprefix $(DEPTH)/mobile/android/base/tests/,$(_JAVA_TESTS))
+	$(DX) --dex --output=$@ classes $(ROBOTIUM_PATH)
+
+robocop.ap_: AndroidManifest.xml
+	$(AAPT) package -f -M AndroidManifest.xml -I $(ANDROID_SDK)/android.jar -I . -S res -F $@ -J ./
+
+robocop-unsigned-unaligned.apk: robocop.ap_ classes.dex
+	$(APKBUILDER) $@ -v $(APKBUILDER_FLAGS) -z robocop.ap_ -f classes.dex
+
+robocop-unaligned.apk: robocop-unsigned-unaligned.apk
+	cp robocop-unsigned-unaligned.apk $@
+	jarsigner -keystore ~/.android/debug.keystore -storepass android -keypass android $@ androiddebugkey
+
+robocop.apk: robocop-unaligned.apk
+	$(ZIPALIGN) -f -v 4 robocop-unaligned.apk $@
+	cp $(TESTPATH)/robocop.ini robocop.ini
+	cp $(srcdir)/parse_ids.py parse_ids.py
+
+export::
+	$(NSINSTALL) -D res
+	@(cd $(srcdir)/res && tar $(TAR_CREATE_FLAGS) - *) | (cd $(DEPTH)/build/mobile/robocop/res && tar -xf -)
+
new file mode 100644
--- /dev/null
+++ b/build/mobile/robocop/RoboCopException.java.in
@@ -0,0 +1,59 @@
+#filter substitution
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firefox Mobile Test Framework.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Trevor Fairey <tnfairey@gmail.com>
+ * David Burns <dburns@mozilla.com>
+ * Joel Maher <joel.maher@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package @ANDROID_PACKAGE_NAME@;
+
+public class RoboCopException extends RuntimeException {
+  
+  public RoboCopException(){
+    super();
+  }
+  
+  public RoboCopException(String message){
+    super(message);
+  }
+  
+  public RoboCopException(Throwable cause){
+    super(cause);
+  }
+  
+  public RoboCopException(String message, Throwable cause){
+    super(message, cause);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/build/mobile/robocop/parse_ids.py
@@ -0,0 +1,60 @@
+import re
+import os
+import sys
+import optparse
+
+def getFile(filename):
+  fHandle = open(filename, 'r')
+  data = fHandle.read()
+  fHandle.close()
+  return data
+  
+def findIDs(data):
+  start_function = False
+  reID = re.compile('.*public static final class id {.*')
+  reEnd = re.compile('.*}.*')
+  idlist = []
+  
+  for line in data.split('\n'):
+    if reEnd.match(line):
+      start_function = False
+      
+    if start_function:
+      id_value = line.split(' ')[-1]
+      idlist.append(id_value.split(';')[0].split('='))
+      
+    if reID.match(line):
+      start_function = True
+      
+  return idlist
+  
+  
+def printIDs(outputFile, idlist):
+  fOutput = open(outputFile, 'w')
+  for item in idlist:
+    fOutput.write("%s=%s\n" % (item[0], item[1]))
+  fOutput.close()
+
+def main(args=sys.argv[1:]):
+  parser = optparse.OptionParser()
+  parser.add_option('-o', '--output', dest='outputFile', default='',
+                    help="output file with the id=value pairs")
+  parser.add_option('-i', '--input', dest='inputFile', default='',
+                    help="filename of the input R.java file")
+  options, args = parser.parse_args(args)
+  
+  if options.inputFile == '':
+    print "Error: please provide input file: -i <filename>"
+    sys.exit(1)
+
+  if options.outputFile == '':
+    print "Error: please provide output file: -o <filename>"
+    sys.exit(1)
+
+  data = getFile(os.path.abspath(options.inputFile));
+  idlist = findIDs(data)
+  printIDs(os.path.abspath(options.outputFile), idlist)
+
+if __name__ == "__main__":
+    main()
+
new file mode 100644
--- /dev/null
+++ b/build/mobile/robocop/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">Roboexample</string>
+
+</resources>