2011-12-22 06:09:41 -08:00
|
|
|
#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;
|
2012-01-30 19:46:13 -08:00
|
|
|
import java.nio.IntBuffer;
|
2011-12-22 06:09:41 -08:00
|
|
|
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;
|
2012-01-30 19:46:13 -08:00
|
|
|
import android.opengl.GLSurfaceView;
|
2011-12-22 06:09:41 -08:00
|
|
|
import android.view.View;
|
2012-02-08 06:32:42 -08:00
|
|
|
import android.util.Log;
|
2011-12-22 06:09:41 -08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2012-02-08 06:32:42 -08:00
|
|
|
private static String mLogFile = null;
|
|
|
|
private static LogLevel mLogLevel = LogLevel.LOG_LEVEL_INFO;
|
|
|
|
|
2011-12-22 06:09:41 -08:00
|
|
|
// 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;
|
2012-02-02 01:02:49 -08:00
|
|
|
private Method _startCheckerboardRecording;
|
|
|
|
private Method _stopCheckerboardRecording;
|
2012-01-30 19:46:13 -08:00
|
|
|
private Method _getPixels;
|
2011-12-22 06:09:41 -08:00
|
|
|
|
2012-02-08 06:32:42 -08:00
|
|
|
public enum LogLevel {
|
|
|
|
LOG_LEVEL_DEBUG(1),
|
|
|
|
LOG_LEVEL_INFO(2),
|
|
|
|
LOG_LEVEL_WARN(3),
|
|
|
|
LOG_LEVEL_ERROR(4);
|
|
|
|
|
|
|
|
private int mValue;
|
|
|
|
LogLevel(int value) {
|
|
|
|
mValue = value;
|
|
|
|
}
|
|
|
|
public boolean isEnabled(LogLevel configuredLevel) {
|
|
|
|
return mValue >= configuredLevel.getValue();
|
|
|
|
}
|
|
|
|
private int getValue() {
|
|
|
|
return mValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-22 06:09:41 -08:00
|
|
|
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");
|
2012-02-02 01:02:49 -08:00
|
|
|
_startCheckerboardRecording = gfx.getDeclaredMethod("startCheckerboardRecording");
|
|
|
|
_stopCheckerboardRecording = gfx.getDeclaredMethod("stopCheckerboardRecording");
|
2012-01-30 19:46:13 -08:00
|
|
|
|
|
|
|
Class layerView = classLoader.loadClass("org.mozilla.gecko.gfx.LayerView");
|
|
|
|
_getPixels = layerView.getDeclaredMethod("getPixels");
|
2011-12-22 06:09:41 -08:00
|
|
|
} 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) {
|
2012-01-31 21:32:29 -08:00
|
|
|
int[] pos = new int[2];
|
|
|
|
geckoLayout.getLocationOnScreen(pos);
|
|
|
|
geckoTop = pos[1];
|
|
|
|
geckoLeft = pos[0];
|
2011-12-22 06:09:41 -08:00
|
|
|
geckoWidth = geckoLayout.getWidth();
|
|
|
|
geckoHeight = geckoLayout.getHeight();
|
|
|
|
geckoInfo = true;
|
2012-01-31 21:32:29 -08:00
|
|
|
} else {
|
|
|
|
throw new RoboCopException("Unable to find view gecko_layout");
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-01-05 18:36:17 -08:00
|
|
|
public Element findElement(Activity activity, String name) {
|
2011-12-22 06:09:41 -08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-02-02 01:02:49 -08:00
|
|
|
public void startCheckerboardRecording() {
|
|
|
|
try {
|
|
|
|
Object [] params = null;
|
|
|
|
_startCheckerboardRecording.invoke(null, params);
|
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
} catch (InvocationTargetException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-09 03:37:17 -08:00
|
|
|
public float stopCheckerboardRecording() {
|
2012-02-02 01:02:49 -08:00
|
|
|
Class [] parameters = new Class[1];
|
|
|
|
parameters[0] = null;
|
|
|
|
List checkerboard;
|
|
|
|
|
|
|
|
try {
|
|
|
|
Object [] params = null;
|
|
|
|
checkerboard = (List)_stopCheckerboardRecording.invoke(null, params);
|
|
|
|
Object [] amountarray = checkerboard.toArray();
|
2012-02-09 03:37:17 -08:00
|
|
|
double completeness = 0;
|
|
|
|
for (Object obj : amountarray) {
|
|
|
|
float val = (Float)obj;
|
|
|
|
completeness += (1.0 - (double)val) / (double)amountarray.length;
|
2012-02-02 01:02:49 -08:00
|
|
|
}
|
2012-02-09 03:37:17 -08:00
|
|
|
return (float)completeness;
|
2012-02-02 01:02:49 -08:00
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
} catch (InvocationTargetException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
2012-02-09 03:37:17 -08:00
|
|
|
return 0.0f;
|
2012-02-02 01:02:49 -08:00
|
|
|
}
|
|
|
|
|
2012-01-30 19:46:13 -08:00
|
|
|
private GLSurfaceView getSurfaceView() {
|
|
|
|
for (View v : solo.getCurrentViews()) {
|
|
|
|
if (v instanceof GLSurfaceView) {
|
|
|
|
return (GLSurfaceView)v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int[][] getPaintedSurface() {
|
|
|
|
GLSurfaceView view = getSurfaceView();
|
|
|
|
if (view == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
IntBuffer pixelBuffer;
|
|
|
|
try {
|
|
|
|
pixelBuffer = (IntBuffer)_getPixels.invoke(view);
|
|
|
|
} catch (Exception e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// now we need to (1) flip the image, because GL likes to do things up-side-down,
|
|
|
|
// and (2) rearrange the bits from AGBR-8888 to ARGB-8888.
|
|
|
|
int w = view.getWidth();
|
|
|
|
int h = view.getHeight();
|
|
|
|
pixelBuffer.position(0);
|
|
|
|
int[][] pixels = new int[h][w];
|
|
|
|
for (int y = h - 1; y >= 0; y--) {
|
|
|
|
for (int x = 0; x < w; x++) {
|
|
|
|
int agbr = pixelBuffer.get();
|
|
|
|
pixels[y][x] = (agbr & 0xFF00FF00) | ((agbr >> 16) & 0x000000FF) | ((agbr << 16) & 0x00FF0000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return pixels;
|
|
|
|
}
|
|
|
|
|
2011-12-22 06:09:41 -08:00
|
|
|
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) {
|
2012-02-08 06:32:42 -08:00
|
|
|
FennecNativeDriver.log(FennecNativeDriver.LogLevel.LOG_LEVEL_WARN,
|
|
|
|
"WARNING: ScrollReceived, but read wrong!");
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
StringBuilder text = new StringBuilder();
|
|
|
|
|
2012-01-10 08:14:31 -08:00
|
|
|
BufferedReader br = null;
|
2011-12-22 06:09:41 -08:00
|
|
|
try {
|
2012-01-10 08:14:31 -08:00
|
|
|
br = new BufferedReader(new FileReader(filename));
|
2011-12-22 06:09:41 -08:00
|
|
|
String line;
|
|
|
|
|
|
|
|
while ((line = br.readLine()) != null) {
|
|
|
|
text.append(line);
|
|
|
|
text.append('\n');
|
|
|
|
}
|
|
|
|
} catch(IOException e) {
|
|
|
|
e.printStackTrace();
|
2012-01-10 08:14:31 -08:00
|
|
|
} finally {
|
|
|
|
try {
|
|
|
|
br.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
}
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
|
|
|
return text.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
2012-02-08 06:32:42 -08:00
|
|
|
|
|
|
|
// Set the filename used for logging. If the file already exists, delete it
|
|
|
|
// as a safe-guard against accidentally appending to an old log file.
|
|
|
|
public static void setLogFile(String filename) {
|
|
|
|
mLogFile = filename;
|
|
|
|
File file = new File(mLogFile);
|
|
|
|
if (file.exists()) {
|
|
|
|
file.delete();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void setLogLevel(LogLevel level) {
|
|
|
|
mLogLevel = level;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void log(LogLevel level, String message) {
|
|
|
|
if (mLogFile == null) {
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (level.isEnabled(mLogLevel)) {
|
|
|
|
File file = new File(mLogFile);
|
|
|
|
BufferedWriter bw = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
bw = new BufferedWriter(new FileWriter(mLogFile, true));
|
|
|
|
bw.write(message);
|
|
|
|
bw.newLine();
|
|
|
|
} catch(IOException e) {
|
|
|
|
Log.e("Robocop", "exception with file writer on: " + mLogFile);
|
|
|
|
} finally {
|
|
|
|
try {
|
|
|
|
if (bw != null) {
|
|
|
|
bw.flush();
|
|
|
|
bw.close();
|
|
|
|
}
|
|
|
|
} catch (IOException ex) {
|
|
|
|
ex.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (level == LogLevel.LOG_LEVEL_INFO) {
|
|
|
|
Log.i("Robocop", message);
|
|
|
|
} else if (level == LogLevel.LOG_LEVEL_DEBUG) {
|
|
|
|
Log.d("Robocop", message);
|
|
|
|
} else if (level == LogLevel.LOG_LEVEL_WARN) {
|
|
|
|
Log.w("Robocop", message);
|
|
|
|
} else if (level == LogLevel.LOG_LEVEL_ERROR) {
|
|
|
|
Log.e("Robocop", message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|