2012-05-21 04:12:37 -07:00
|
|
|
/* 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/. */
|
2011-12-22 06:09:41 -08:00
|
|
|
|
2013-11-07 08:18:51 -08:00
|
|
|
package org.mozilla.gecko;
|
2011-12-22 06:09:41 -08:00
|
|
|
|
2013-12-06 11:43:10 -08:00
|
|
|
import org.mozilla.gecko.gfx.LayerView;
|
2013-12-06 11:43:10 -08:00
|
|
|
import org.mozilla.gecko.gfx.PanningPerfAPI;
|
2013-12-06 11:43:11 -08:00
|
|
|
import org.mozilla.gecko.util.GeckoEventListener;
|
2013-12-06 11:43:10 -08:00
|
|
|
|
2012-03-22 10:36:20 -07:00
|
|
|
import java.io.BufferedOutputStream;
|
2011-12-22 06:09:41 -08:00
|
|
|
import java.io.BufferedReader;
|
2012-03-22 10:36:20 -07:00
|
|
|
import java.io.DataOutputStream;
|
2011-12-22 06:09:41 -08:00
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileReader;
|
|
|
|
import java.io.FileWriter;
|
2012-03-22 10:36:20 -07:00
|
|
|
import java.io.FileOutputStream;
|
2011-12-22 06:09:41 -08:00
|
|
|
import java.io.IOException;
|
2012-03-22 10:36:20 -07:00
|
|
|
import java.io.OutputStream;
|
2012-03-27 13:16:13 -07:00
|
|
|
import java.io.PrintWriter;
|
2012-01-30 19:46:13 -08:00
|
|
|
import java.nio.IntBuffer;
|
2013-01-24 14:00:31 -08:00
|
|
|
import java.util.ArrayList;
|
2011-12-22 06:09:41 -08:00
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
2012-08-13 09:03:00 -07:00
|
|
|
import java.util.Map;
|
2011-12-22 06:09:41 -08:00
|
|
|
|
|
|
|
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 {
|
2012-05-03 11:59:10 -07:00
|
|
|
private static final int FRAME_TIME_THRESHOLD = 25; // allow 25ms per frame (40fps)
|
2012-03-23 08:58:03 -07:00
|
|
|
|
2012-02-15 06:56:20 -08:00
|
|
|
// Map of IDs to element names.
|
|
|
|
private HashMap mLocators = null;
|
|
|
|
private Activity mActivity;
|
|
|
|
private Solo mSolo;
|
2012-09-12 04:56:31 -07:00
|
|
|
private String mRootPath;
|
2012-02-15 06:56:20 -08:00
|
|
|
|
|
|
|
private static String mLogFile = null;
|
2012-04-10 12:20:46 -07:00
|
|
|
private static LogLevel mLogLevel = LogLevel.INFO;
|
2012-02-15 06:56:20 -08:00
|
|
|
|
|
|
|
public enum LogLevel {
|
2012-04-10 12:20:46 -07:00
|
|
|
DEBUG(1),
|
|
|
|
INFO(2),
|
|
|
|
WARN(3),
|
|
|
|
ERROR(4);
|
2012-02-15 06:56:20 -08:00
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2012-09-12 04:56:31 -07:00
|
|
|
public FennecNativeDriver(Activity activity, Solo robocop, String rootPath) {
|
2012-02-15 06:56:20 -08:00
|
|
|
mActivity = activity;
|
|
|
|
mSolo = robocop;
|
2012-09-12 04:56:31 -07:00
|
|
|
mRootPath = rootPath;
|
2011-12-22 06:09:41 -08:00
|
|
|
|
2012-02-15 06:56:20 -08:00
|
|
|
// Set up table of fennec_ids.
|
2012-09-12 04:56:31 -07:00
|
|
|
mLocators = convertTextToTable(getFile(mRootPath + "/fennec_ids.txt"));
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
|
|
|
|
2012-02-15 06:56:20 -08:00
|
|
|
//Information on the location of the Gecko Frame.
|
|
|
|
private boolean mGeckoInfo = false;
|
|
|
|
private int mGeckoTop = 100;
|
|
|
|
private int mGeckoLeft = 0;
|
|
|
|
private int mGeckoHeight= 700;
|
|
|
|
private int mGeckoWidth = 1024;
|
|
|
|
|
|
|
|
private void getGeckoInfo() {
|
|
|
|
View geckoLayout = mActivity.findViewById(Integer.decode((String)mLocators.get("gecko_layout")));
|
|
|
|
if (geckoLayout != null) {
|
|
|
|
int[] pos = new int[2];
|
|
|
|
geckoLayout.getLocationOnScreen(pos);
|
|
|
|
mGeckoTop = pos[1];
|
|
|
|
mGeckoLeft = pos[0];
|
|
|
|
mGeckoWidth = geckoLayout.getWidth();
|
|
|
|
mGeckoHeight = geckoLayout.getHeight();
|
|
|
|
mGeckoInfo = true;
|
|
|
|
} else {
|
|
|
|
throw new RoboCopException("Unable to find view gecko_layout");
|
|
|
|
}
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
2012-02-15 06:56:20 -08:00
|
|
|
|
|
|
|
public int getGeckoTop() {
|
|
|
|
if (!mGeckoInfo) {
|
|
|
|
getGeckoInfo();
|
|
|
|
}
|
|
|
|
return mGeckoTop;
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
2012-02-15 06:56:20 -08:00
|
|
|
|
|
|
|
public int getGeckoLeft() {
|
|
|
|
if (!mGeckoInfo) {
|
|
|
|
getGeckoInfo();
|
|
|
|
}
|
|
|
|
return mGeckoLeft;
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
2012-02-15 06:56:20 -08:00
|
|
|
|
|
|
|
public int getGeckoHeight() {
|
|
|
|
if (!mGeckoInfo) {
|
|
|
|
getGeckoInfo();
|
|
|
|
}
|
|
|
|
return mGeckoHeight;
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
2012-02-15 06:56:20 -08:00
|
|
|
|
|
|
|
public int getGeckoWidth() {
|
|
|
|
if (!mGeckoInfo) {
|
|
|
|
getGeckoInfo();
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
2012-02-15 06:56:20 -08:00
|
|
|
return mGeckoWidth;
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
|
|
|
|
2012-05-22 16:25:30 -07:00
|
|
|
/** Find the named element in the list of known Fennec views.
|
|
|
|
* @return An Element representing the view, or null if the view is not found.
|
|
|
|
*/
|
2012-02-15 06:56:20 -08:00
|
|
|
public Element findElement(Activity activity, String name) {
|
|
|
|
if (name == null) {
|
2012-05-22 16:25:30 -07:00
|
|
|
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR,
|
|
|
|
"Can not findElements when passed a null");
|
|
|
|
return null;
|
2012-02-15 06:56:20 -08:00
|
|
|
}
|
|
|
|
if (mLocators.containsKey(name)) {
|
|
|
|
return new FennecNativeElement(Integer.decode((String)mLocators.get(name)), activity, mSolo);
|
|
|
|
}
|
2012-05-22 16:25:30 -07:00
|
|
|
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR,
|
|
|
|
"findElement: Element '"+name+"' does not exist in the list");
|
|
|
|
return null;
|
2012-02-02 01:02:49 -08:00
|
|
|
}
|
2012-02-15 06:56:20 -08:00
|
|
|
|
|
|
|
public void startFrameRecording() {
|
2013-12-06 11:43:10 -08:00
|
|
|
PanningPerfAPI.startFrameTimeRecording();
|
2012-02-02 01:02:49 -08:00
|
|
|
}
|
|
|
|
|
2012-02-15 06:56:20 -08:00
|
|
|
public int stopFrameRecording() {
|
2013-12-06 11:43:10 -08:00
|
|
|
final List<Long> frames = PanningPerfAPI.stopFrameTimeRecording();
|
|
|
|
int badness = 0;
|
|
|
|
for (int i = 1; i < frames.size(); i++) {
|
|
|
|
long frameTime = frames.get(i) - frames.get(i - 1);
|
|
|
|
int delay = (int)(frameTime - FRAME_TIME_THRESHOLD);
|
|
|
|
// for each frame we miss, add the square of the delay. This
|
|
|
|
// makes large delays much worse than small delays.
|
|
|
|
if (delay > 0) {
|
|
|
|
badness += delay * delay;
|
2012-02-15 06:56:20 -08:00
|
|
|
}
|
|
|
|
}
|
2012-02-02 01:02:49 -08:00
|
|
|
|
2013-12-06 11:43:10 -08:00
|
|
|
// Don't do any averaging of the numbers because really we want to
|
|
|
|
// know how bad the jank was at its worst
|
|
|
|
return badness;
|
2012-01-30 19:46:13 -08:00
|
|
|
}
|
|
|
|
|
2012-02-15 06:56:20 -08:00
|
|
|
public void startCheckerboardRecording() {
|
2013-12-06 11:43:10 -08:00
|
|
|
PanningPerfAPI.startCheckerboardRecording();
|
2012-01-30 19:46:13 -08:00
|
|
|
}
|
2012-02-15 06:56:20 -08:00
|
|
|
|
|
|
|
public float stopCheckerboardRecording() {
|
2013-12-06 11:43:10 -08:00
|
|
|
final List<Float> checkerboard = PanningPerfAPI.stopCheckerboardRecording();
|
|
|
|
float total = 0;
|
|
|
|
for (float val : checkerboard) {
|
|
|
|
total += val;
|
2012-02-15 06:56:20 -08:00
|
|
|
}
|
2013-12-06 11:43:10 -08:00
|
|
|
return total * 100.0f;
|
2012-01-30 19:46:13 -08:00
|
|
|
}
|
|
|
|
|
2013-12-06 11:43:10 -08:00
|
|
|
private LayerView getSurfaceView() {
|
|
|
|
return mSolo.getView(LayerView.class, 0);
|
2012-01-30 19:46:13 -08:00
|
|
|
}
|
2012-02-15 06:56:20 -08:00
|
|
|
|
2012-03-06 12:08:45 -08:00
|
|
|
public PaintedSurface getPaintedSurface() {
|
2013-12-06 11:43:10 -08:00
|
|
|
final LayerView view = getSurfaceView();
|
2012-02-15 06:56:20 -08:00
|
|
|
if (view == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2013-12-06 11:43:10 -08:00
|
|
|
|
|
|
|
final IntBuffer pixelBuffer = view.getPixels();
|
2011-12-22 06:09:41 -08:00
|
|
|
|
2012-02-15 06:56:20 -08:00
|
|
|
// 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);
|
2012-09-12 04:56:31 -07:00
|
|
|
String mapFile = mRootPath + "/pixels.map";
|
2012-03-06 12:08:45 -08:00
|
|
|
|
|
|
|
FileOutputStream fos = null;
|
2012-03-22 10:36:20 -07:00
|
|
|
BufferedOutputStream bos = null;
|
2012-03-06 12:08:45 -08:00
|
|
|
DataOutputStream dos = null;
|
|
|
|
try {
|
|
|
|
fos = new FileOutputStream(mapFile);
|
2012-03-22 10:36:20 -07:00
|
|
|
bos = new BufferedOutputStream(fos);
|
|
|
|
dos = new DataOutputStream(bos);
|
2012-03-06 12:08:45 -08:00
|
|
|
|
|
|
|
for (int y = h - 1; y >= 0; y--) {
|
|
|
|
for (int x = 0; x < w; x++) {
|
|
|
|
int agbr = pixelBuffer.get();
|
|
|
|
dos.writeInt((agbr & 0xFF00FF00) | ((agbr >> 16) & 0x000000FF) | ((agbr << 16) & 0x00FF0000));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
|
|
throw new RoboCopException("exception with pixel writer on file: " + mapFile);
|
|
|
|
} finally {
|
|
|
|
try {
|
2012-03-22 10:36:20 -07:00
|
|
|
if (dos != null) {
|
2012-03-06 12:08:45 -08:00
|
|
|
dos.flush();
|
|
|
|
dos.close();
|
2012-03-22 10:36:20 -07:00
|
|
|
}
|
|
|
|
// closing dos automatically closes bos
|
|
|
|
if (fos != null) {
|
|
|
|
fos.flush();
|
2012-03-06 12:08:45 -08:00
|
|
|
fos.close();
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
2012-04-10 12:20:55 -07:00
|
|
|
log(LogLevel.ERROR, e);
|
2012-03-06 12:08:45 -08:00
|
|
|
throw new RoboCopException("exception closing pixel writer on file: " + mapFile);
|
2012-02-15 06:56:20 -08:00
|
|
|
}
|
|
|
|
}
|
2012-03-22 10:36:20 -07:00
|
|
|
return new PaintedSurface(mapFile, w, h);
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
2012-02-15 06:56:20 -08:00
|
|
|
|
|
|
|
public int mHeight=0;
|
|
|
|
public int mScrollHeight=0;
|
|
|
|
public int mPageHeight=10;
|
|
|
|
|
|
|
|
public int getScrollHeight() {
|
|
|
|
return mScrollHeight;
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
2012-02-15 06:56:20 -08:00
|
|
|
public int getPageHeight() {
|
|
|
|
return mPageHeight;
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|
2012-02-15 06:56:20 -08:00
|
|
|
public int getHeight() {
|
|
|
|
return mHeight;
|
2012-02-08 06:32:42 -08:00
|
|
|
}
|
|
|
|
|
2012-02-15 06:56:20 -08:00
|
|
|
public void setupScrollHandling() {
|
2013-12-06 11:43:11 -08:00
|
|
|
GeckoAppShell.registerEventListener("robocop:scroll", new GeckoEventListener() {
|
|
|
|
@Override
|
|
|
|
public void handleMessage(final String event, final JSONObject message) {
|
|
|
|
try {
|
|
|
|
mScrollHeight = message.getInt("y");
|
|
|
|
mHeight = message.getInt("cheight");
|
|
|
|
// We don't want a height of 0. That means it's a bad response.
|
|
|
|
if (mHeight > 0) {
|
|
|
|
mPageHeight = message.getInt("height");
|
|
|
|
}
|
|
|
|
} catch (JSONException e) {
|
|
|
|
FennecNativeDriver.log(FennecNativeDriver.LogLevel.WARN,
|
|
|
|
"WARNING: ScrollReceived, but message does not contain " +
|
|
|
|
"expected fields: " + e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2012-02-08 06:32:42 -08:00
|
|
|
}
|
|
|
|
|
2012-02-15 06:56:20 -08:00
|
|
|
/**
|
|
|
|
* 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();
|
|
|
|
|
|
|
|
BufferedReader br = null;
|
2012-02-08 06:32:42 -08:00
|
|
|
try {
|
2012-02-15 06:56:20 -08:00
|
|
|
br = new BufferedReader(new FileReader(filename));
|
|
|
|
String line;
|
|
|
|
|
|
|
|
while ((line = br.readLine()) != null) {
|
|
|
|
text.append(line);
|
|
|
|
text.append('\n');
|
|
|
|
}
|
2012-04-10 12:20:55 -07:00
|
|
|
} catch (IOException e) {
|
|
|
|
log(LogLevel.ERROR, e);
|
2012-02-15 06:56:20 -08:00
|
|
|
} finally {
|
|
|
|
try {
|
|
|
|
br.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
}
|
2012-02-08 06:32:42 -08:00
|
|
|
}
|
2012-02-15 06:56:20 -08:00
|
|
|
return text.toString();
|
2012-02-08 06:32:42 -08:00
|
|
|
}
|
|
|
|
|
2012-02-15 06:56:20 -08:00
|
|
|
/**
|
|
|
|
* 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++) {
|
2012-10-16 10:25:23 -07:00
|
|
|
String[] parts = lines[i].split("=", 2);
|
2012-02-15 06:56:20 -08:00
|
|
|
retVal.put(parts[0].trim(), parts[1].trim());
|
|
|
|
}
|
|
|
|
return retVal;
|
|
|
|
}
|
|
|
|
|
2012-08-13 09:03:00 -07:00
|
|
|
public static void logAllStackTraces(LogLevel level) {
|
|
|
|
StringBuffer sb = new StringBuffer();
|
|
|
|
sb.append("Dumping ALL the threads!\n");
|
|
|
|
Map<Thread, StackTraceElement[]> allStacks = Thread.getAllStackTraces();
|
|
|
|
for (Thread t : allStacks.keySet()) {
|
|
|
|
sb.append(t.toString()).append('\n');
|
|
|
|
for (StackTraceElement ste : allStacks.get(t)) {
|
|
|
|
sb.append(ste.toString()).append('\n');
|
|
|
|
}
|
|
|
|
sb.append('\n');
|
|
|
|
}
|
|
|
|
log(level, sb.toString());
|
|
|
|
}
|
|
|
|
|
2012-02-15 06:56:20 -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) {
|
2012-03-27 13:16:13 -07:00
|
|
|
log(level, message, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void log(LogLevel level, Throwable t) {
|
|
|
|
log(level, null, t);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void log(LogLevel level, String message, Throwable t) {
|
2012-02-15 06:56:20 -08:00
|
|
|
if (mLogFile == null) {
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (level.isEnabled(mLogLevel)) {
|
2012-03-27 13:16:13 -07:00
|
|
|
PrintWriter pw = null;
|
2012-02-15 06:56:20 -08:00
|
|
|
try {
|
2012-03-27 13:16:13 -07:00
|
|
|
pw = new PrintWriter(new FileWriter(mLogFile, true));
|
|
|
|
if (message != null) {
|
|
|
|
pw.println(message);
|
|
|
|
}
|
|
|
|
if (t != null) {
|
|
|
|
t.printStackTrace(pw);
|
|
|
|
}
|
|
|
|
} catch (IOException ioe) {
|
2012-02-15 06:56:20 -08:00
|
|
|
Log.e("Robocop", "exception with file writer on: " + mLogFile);
|
|
|
|
} finally {
|
2012-03-27 13:16:13 -07:00
|
|
|
pw.close();
|
|
|
|
}
|
|
|
|
// PrintWriter doesn't throw IOE but sets an error flag instead,
|
|
|
|
// so check for that
|
|
|
|
if (pw.checkError()) {
|
|
|
|
Log.e("Robocop", "exception with file writer on: " + mLogFile);
|
2012-02-15 06:56:20 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-04-10 12:20:46 -07:00
|
|
|
if (level == LogLevel.INFO) {
|
2012-03-27 13:16:13 -07:00
|
|
|
Log.i("Robocop", message, t);
|
2012-04-10 12:20:46 -07:00
|
|
|
} else if (level == LogLevel.DEBUG) {
|
2012-03-27 13:16:13 -07:00
|
|
|
Log.d("Robocop", message, t);
|
2012-04-10 12:20:46 -07:00
|
|
|
} else if (level == LogLevel.WARN) {
|
2012-03-27 13:16:13 -07:00
|
|
|
Log.w("Robocop", message, t);
|
2012-04-10 12:20:46 -07:00
|
|
|
} else if (level == LogLevel.ERROR) {
|
2012-03-27 13:16:13 -07:00
|
|
|
Log.e("Robocop", message, t);
|
2012-02-15 06:56:20 -08:00
|
|
|
}
|
2012-02-08 06:32:42 -08:00
|
|
|
}
|
2011-12-22 06:09:41 -08:00
|
|
|
}
|