1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/build/mobile/robocop/FennecNativeDriver.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,373 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko; 1.9 + 1.10 +import java.io.BufferedOutputStream; 1.11 +import java.io.BufferedReader; 1.12 +import java.io.DataOutputStream; 1.13 +import java.io.File; 1.14 +import java.io.FileOutputStream; 1.15 +import java.io.FileReader; 1.16 +import java.io.FileWriter; 1.17 +import java.io.IOException; 1.18 +import java.io.PrintWriter; 1.19 +import java.nio.IntBuffer; 1.20 +import java.util.HashMap; 1.21 +import java.util.List; 1.22 +import java.util.Map; 1.23 + 1.24 +import org.json.JSONException; 1.25 +import org.json.JSONObject; 1.26 +import org.mozilla.gecko.gfx.LayerView; 1.27 +import org.mozilla.gecko.gfx.PanningPerfAPI; 1.28 +import org.mozilla.gecko.util.GeckoEventListener; 1.29 + 1.30 +import android.app.Activity; 1.31 +import android.util.Log; 1.32 +import android.view.View; 1.33 + 1.34 +import com.jayway.android.robotium.solo.Solo; 1.35 + 1.36 +public class FennecNativeDriver implements Driver { 1.37 + private static final int FRAME_TIME_THRESHOLD = 25; // allow 25ms per frame (40fps) 1.38 + 1.39 + private Activity mActivity; 1.40 + private Solo mSolo; 1.41 + private String mRootPath; 1.42 + 1.43 + private static String mLogFile = null; 1.44 + private static LogLevel mLogLevel = LogLevel.INFO; 1.45 + 1.46 + public enum LogLevel { 1.47 + DEBUG(1), 1.48 + INFO(2), 1.49 + WARN(3), 1.50 + ERROR(4); 1.51 + 1.52 + private int mValue; 1.53 + LogLevel(int value) { 1.54 + mValue = value; 1.55 + } 1.56 + public boolean isEnabled(LogLevel configuredLevel) { 1.57 + return mValue >= configuredLevel.getValue(); 1.58 + } 1.59 + private int getValue() { 1.60 + return mValue; 1.61 + } 1.62 + } 1.63 + 1.64 + public FennecNativeDriver(Activity activity, Solo robocop, String rootPath) { 1.65 + mActivity = activity; 1.66 + mSolo = robocop; 1.67 + mRootPath = rootPath; 1.68 + } 1.69 + 1.70 + //Information on the location of the Gecko Frame. 1.71 + private boolean mGeckoInfo = false; 1.72 + private int mGeckoTop = 100; 1.73 + private int mGeckoLeft = 0; 1.74 + private int mGeckoHeight= 700; 1.75 + private int mGeckoWidth = 1024; 1.76 + 1.77 + private void getGeckoInfo() { 1.78 + View geckoLayout = mActivity.findViewById(R.id.gecko_layout); 1.79 + if (geckoLayout != null) { 1.80 + int[] pos = new int[2]; 1.81 + geckoLayout.getLocationOnScreen(pos); 1.82 + mGeckoTop = pos[1]; 1.83 + mGeckoLeft = pos[0]; 1.84 + mGeckoWidth = geckoLayout.getWidth(); 1.85 + mGeckoHeight = geckoLayout.getHeight(); 1.86 + mGeckoInfo = true; 1.87 + } else { 1.88 + throw new RoboCopException("Unable to find view gecko_layout"); 1.89 + } 1.90 + } 1.91 + 1.92 + public int getGeckoTop() { 1.93 + if (!mGeckoInfo) { 1.94 + getGeckoInfo(); 1.95 + } 1.96 + return mGeckoTop; 1.97 + } 1.98 + 1.99 + public int getGeckoLeft() { 1.100 + if (!mGeckoInfo) { 1.101 + getGeckoInfo(); 1.102 + } 1.103 + return mGeckoLeft; 1.104 + } 1.105 + 1.106 + public int getGeckoHeight() { 1.107 + if (!mGeckoInfo) { 1.108 + getGeckoInfo(); 1.109 + } 1.110 + return mGeckoHeight; 1.111 + } 1.112 + 1.113 + public int getGeckoWidth() { 1.114 + if (!mGeckoInfo) { 1.115 + getGeckoInfo(); 1.116 + } 1.117 + return mGeckoWidth; 1.118 + } 1.119 + 1.120 + /** Find the element with given id. 1.121 + * 1.122 + * @return An Element representing the view, or null if the view is not found. 1.123 + */ 1.124 + public Element findElement(Activity activity, int id) { 1.125 + return new FennecNativeElement(id, activity); 1.126 + } 1.127 + 1.128 + public void startFrameRecording() { 1.129 + PanningPerfAPI.startFrameTimeRecording(); 1.130 + } 1.131 + 1.132 + public int stopFrameRecording() { 1.133 + final List<Long> frames = PanningPerfAPI.stopFrameTimeRecording(); 1.134 + int badness = 0; 1.135 + for (int i = 1; i < frames.size(); i++) { 1.136 + long frameTime = frames.get(i) - frames.get(i - 1); 1.137 + int delay = (int)(frameTime - FRAME_TIME_THRESHOLD); 1.138 + // for each frame we miss, add the square of the delay. This 1.139 + // makes large delays much worse than small delays. 1.140 + if (delay > 0) { 1.141 + badness += delay * delay; 1.142 + } 1.143 + } 1.144 + 1.145 + // Don't do any averaging of the numbers because really we want to 1.146 + // know how bad the jank was at its worst 1.147 + return badness; 1.148 + } 1.149 + 1.150 + public void startCheckerboardRecording() { 1.151 + PanningPerfAPI.startCheckerboardRecording(); 1.152 + } 1.153 + 1.154 + public float stopCheckerboardRecording() { 1.155 + final List<Float> checkerboard = PanningPerfAPI.stopCheckerboardRecording(); 1.156 + float total = 0; 1.157 + for (float val : checkerboard) { 1.158 + total += val; 1.159 + } 1.160 + return total * 100.0f; 1.161 + } 1.162 + 1.163 + private LayerView getSurfaceView() { 1.164 + final LayerView layerView = mSolo.getView(LayerView.class, 0); 1.165 + 1.166 + if (layerView == null) { 1.167 + log(LogLevel.WARN, "getSurfaceView could not find LayerView"); 1.168 + for (final View v : mSolo.getViews()) { 1.169 + log(LogLevel.WARN, " View: " + v); 1.170 + } 1.171 + } 1.172 + return layerView; 1.173 + } 1.174 + 1.175 + public PaintedSurface getPaintedSurface() { 1.176 + final LayerView view = getSurfaceView(); 1.177 + if (view == null) { 1.178 + return null; 1.179 + } 1.180 + 1.181 + final IntBuffer pixelBuffer = view.getPixels(); 1.182 + 1.183 + // now we need to (1) flip the image, because GL likes to do things up-side-down, 1.184 + // and (2) rearrange the bits from AGBR-8888 to ARGB-8888. 1.185 + int w = view.getWidth(); 1.186 + int h = view.getHeight(); 1.187 + pixelBuffer.position(0); 1.188 + String mapFile = mRootPath + "/pixels.map"; 1.189 + 1.190 + FileOutputStream fos = null; 1.191 + BufferedOutputStream bos = null; 1.192 + DataOutputStream dos = null; 1.193 + try { 1.194 + fos = new FileOutputStream(mapFile); 1.195 + bos = new BufferedOutputStream(fos); 1.196 + dos = new DataOutputStream(bos); 1.197 + 1.198 + for (int y = h - 1; y >= 0; y--) { 1.199 + for (int x = 0; x < w; x++) { 1.200 + int agbr = pixelBuffer.get(); 1.201 + dos.writeInt((agbr & 0xFF00FF00) | ((agbr >> 16) & 0x000000FF) | ((agbr << 16) & 0x00FF0000)); 1.202 + } 1.203 + } 1.204 + } catch (IOException e) { 1.205 + throw new RoboCopException("exception with pixel writer on file: " + mapFile); 1.206 + } finally { 1.207 + try { 1.208 + if (dos != null) { 1.209 + dos.flush(); 1.210 + dos.close(); 1.211 + } 1.212 + // closing dos automatically closes bos 1.213 + if (fos != null) { 1.214 + fos.flush(); 1.215 + fos.close(); 1.216 + } 1.217 + } catch (IOException e) { 1.218 + log(LogLevel.ERROR, e); 1.219 + throw new RoboCopException("exception closing pixel writer on file: " + mapFile); 1.220 + } 1.221 + } 1.222 + return new PaintedSurface(mapFile, w, h); 1.223 + } 1.224 + 1.225 + public int mHeight=0; 1.226 + public int mScrollHeight=0; 1.227 + public int mPageHeight=10; 1.228 + 1.229 + public int getScrollHeight() { 1.230 + return mScrollHeight; 1.231 + } 1.232 + public int getPageHeight() { 1.233 + return mPageHeight; 1.234 + } 1.235 + public int getHeight() { 1.236 + return mHeight; 1.237 + } 1.238 + 1.239 + public void setupScrollHandling() { 1.240 + GeckoAppShell.registerEventListener("robocop:scroll", new GeckoEventListener() { 1.241 + @Override 1.242 + public void handleMessage(final String event, final JSONObject message) { 1.243 + try { 1.244 + mScrollHeight = message.getInt("y"); 1.245 + mHeight = message.getInt("cheight"); 1.246 + // We don't want a height of 0. That means it's a bad response. 1.247 + if (mHeight > 0) { 1.248 + mPageHeight = message.getInt("height"); 1.249 + } 1.250 + } catch (JSONException e) { 1.251 + FennecNativeDriver.log(FennecNativeDriver.LogLevel.WARN, 1.252 + "WARNING: ScrollReceived, but message does not contain " + 1.253 + "expected fields: " + e); 1.254 + } 1.255 + } 1.256 + }); 1.257 + } 1.258 + 1.259 + /** 1.260 + * Takes a filename, loads the file, and returns a string version of the entire file. 1.261 + */ 1.262 + public static String getFile(String filename) 1.263 + { 1.264 + StringBuilder text = new StringBuilder(); 1.265 + 1.266 + BufferedReader br = null; 1.267 + try { 1.268 + br = new BufferedReader(new FileReader(filename)); 1.269 + String line; 1.270 + 1.271 + while ((line = br.readLine()) != null) { 1.272 + text.append(line); 1.273 + text.append('\n'); 1.274 + } 1.275 + } catch (IOException e) { 1.276 + log(LogLevel.ERROR, e); 1.277 + } finally { 1.278 + try { 1.279 + br.close(); 1.280 + } catch (IOException e) { 1.281 + } 1.282 + } 1.283 + return text.toString(); 1.284 + } 1.285 + 1.286 + /** 1.287 + * Takes a string of "key=value" pairs split by \n and creates a hash table. 1.288 + */ 1.289 + public static Map<String, String> convertTextToTable(String data) 1.290 + { 1.291 + HashMap<String, String> retVal = new HashMap<String, String>(); 1.292 + 1.293 + String[] lines = data.split("\n"); 1.294 + for (int i = 0; i < lines.length; i++) { 1.295 + String[] parts = lines[i].split("=", 2); 1.296 + retVal.put(parts[0].trim(), parts[1].trim()); 1.297 + } 1.298 + return retVal; 1.299 + } 1.300 + 1.301 + public static void logAllStackTraces(LogLevel level) { 1.302 + StringBuffer sb = new StringBuffer(); 1.303 + sb.append("Dumping ALL the threads!\n"); 1.304 + Map<Thread, StackTraceElement[]> allStacks = Thread.getAllStackTraces(); 1.305 + for (Thread t : allStacks.keySet()) { 1.306 + sb.append(t.toString()).append('\n'); 1.307 + for (StackTraceElement ste : allStacks.get(t)) { 1.308 + sb.append(ste.toString()).append('\n'); 1.309 + } 1.310 + sb.append('\n'); 1.311 + } 1.312 + log(level, sb.toString()); 1.313 + } 1.314 + 1.315 + /** 1.316 + * Set the filename used for logging. If the file already exists, delete it 1.317 + * as a safe-guard against accidentally appending to an old log file. 1.318 + */ 1.319 + public static void setLogFile(String filename) { 1.320 + mLogFile = filename; 1.321 + File file = new File(mLogFile); 1.322 + if (file.exists()) { 1.323 + file.delete(); 1.324 + } 1.325 + } 1.326 + 1.327 + public static void setLogLevel(LogLevel level) { 1.328 + mLogLevel = level; 1.329 + } 1.330 + 1.331 + public static void log(LogLevel level, String message) { 1.332 + log(level, message, null); 1.333 + } 1.334 + 1.335 + public static void log(LogLevel level, Throwable t) { 1.336 + log(level, null, t); 1.337 + } 1.338 + 1.339 + public static void log(LogLevel level, String message, Throwable t) { 1.340 + if (mLogFile == null) { 1.341 + assert(false); 1.342 + } 1.343 + 1.344 + if (level.isEnabled(mLogLevel)) { 1.345 + PrintWriter pw = null; 1.346 + try { 1.347 + pw = new PrintWriter(new FileWriter(mLogFile, true)); 1.348 + if (message != null) { 1.349 + pw.println(message); 1.350 + } 1.351 + if (t != null) { 1.352 + t.printStackTrace(pw); 1.353 + } 1.354 + } catch (IOException ioe) { 1.355 + Log.e("Robocop", "exception with file writer on: " + mLogFile); 1.356 + } finally { 1.357 + pw.close(); 1.358 + } 1.359 + // PrintWriter doesn't throw IOE but sets an error flag instead, 1.360 + // so check for that 1.361 + if (pw.checkError()) { 1.362 + Log.e("Robocop", "exception with file writer on: " + mLogFile); 1.363 + } 1.364 + } 1.365 + 1.366 + if (level == LogLevel.INFO) { 1.367 + Log.i("Robocop", message, t); 1.368 + } else if (level == LogLevel.DEBUG) { 1.369 + Log.d("Robocop", message, t); 1.370 + } else if (level == LogLevel.WARN) { 1.371 + Log.w("Robocop", message, t); 1.372 + } else if (level == LogLevel.ERROR) { 1.373 + Log.e("Robocop", message, t); 1.374 + } 1.375 + } 1.376 +}