Merge the jank test tools to unbundle branch

bug: 8549606

Change-Id: I60c25d3dce60aa9da675c71c8245c789d42671ab
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/common/UiWatchers.java b/uiautomator_test_libraries/src/com/android/uiautomator/common/UiWatchers.java
new file mode 100644
index 0000000..dd1bb5b
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/common/UiWatchers.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.uiautomator.common;
+
+import android.util.Log;
+
+import com.android.uiautomator.core.UiDevice;
+import com.android.uiautomator.core.UiObject;
+import com.android.uiautomator.core.UiObjectNotFoundException;
+import com.android.uiautomator.core.UiSelector;
+import com.android.uiautomator.core.UiWatcher;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class UiWatchers {
+    private static final String LOG_TAG = UiWatchers.class.getSimpleName();
+    private final List<String> mErrors = new ArrayList<String>();
+
+    /**
+     * We can use the UiDevice registerWatcher to register a small script to be executed when the
+     * framework is waiting for a control to appear. Waiting may be the cause of an unexpected
+     * dialog on the screen and it is the time when the framework runs the registered watchers.
+     * This is a sample watcher looking for ANR and crashes. it closes it and moves on. You should
+     * create your own watchers and handle error logging properly for your type of tests.
+     */
+    public void registerAnrAndCrashWatchers() {
+
+        UiDevice.getInstance().registerWatcher("ANR", new UiWatcher() {
+            @Override
+            public boolean checkForCondition() {
+                UiObject window = new UiObject(new UiSelector().className(
+                        "com.android.server.am.AppNotRespondingDialog"));
+                String errorText = null;
+                if (window.exists()) {
+                    try {
+                        errorText = window.getText();
+                    } catch (UiObjectNotFoundException e) {
+                        Log.e(LOG_TAG, "dialog gone?", e);
+                    }
+                    onAnrDetected(errorText);
+                    postHandler();
+                    return true; // triggered
+                }
+                return false; // no trigger
+            }
+        });
+
+        // class names may have changed
+        UiDevice.getInstance().registerWatcher("ANR2", new UiWatcher() {
+            @Override
+            public boolean checkForCondition() {
+                UiObject window = new UiObject(new UiSelector().packageName("android")
+                        .textContains("isn't responding."));
+                if (window.exists()) {
+                    String errorText = null;
+                    try {
+                        errorText = window.getText();
+                    } catch (UiObjectNotFoundException e) {
+                        Log.e(LOG_TAG, "dialog gone?", e);
+                    }
+                    onAnrDetected(errorText);
+                    postHandler();
+                    return true; // triggered
+                }
+                return false; // no trigger
+            }
+        });
+
+        UiDevice.getInstance().registerWatcher("CRASH", new UiWatcher() {
+            @Override
+            public boolean checkForCondition() {
+                UiObject window = new UiObject(new UiSelector().className(
+                        "com.android.server.am.AppErrorDialog"));
+                if (window.exists()) {
+                    String errorText = null;
+                    try {
+                        errorText = window.getText();
+                    } catch (UiObjectNotFoundException e) {
+                        Log.e(LOG_TAG, "dialog gone?", e);
+                    }
+                    onCrashDetected(errorText);
+                    postHandler();
+                    return true; // triggered
+                }
+                return false; // no trigger
+            }
+        });
+
+        UiDevice.getInstance().registerWatcher("CRASH2", new UiWatcher() {
+            @Override
+            public boolean checkForCondition() {
+                UiObject window = new UiObject(new UiSelector().packageName("android")
+                        .textContains("has stopped"));
+                if (window.exists()) {
+                    String errorText = null;
+                    try {
+                        errorText = window.getText();
+                    } catch (UiObjectNotFoundException e) {
+                        Log.e(LOG_TAG, "dialog gone?", e);
+                    }
+                    onCrashDetected(errorText);
+                    postHandler();
+                    return true; // triggered
+                }
+                return false; // no trigger
+            }
+        });
+
+        Log.i(LOG_TAG, "Registed GUI Exception watchers");
+    }
+
+    public void onAnrDetected(String errorText) {
+        mErrors.add(errorText);
+    }
+
+    public void onCrashDetected(String errorText) {
+        mErrors.add(errorText);
+    }
+
+    public void reset() {
+        mErrors.clear();
+    }
+
+    public List<String> getErrors() {
+        return Collections.unmodifiableList(mErrors);
+    }
+
+    /**
+     * Current implementation ignores the exception and continues.
+     */
+    public void postHandler() {
+        // TODO: Add custom error logging here
+
+        String formatedOutput = String.format("UI Exception Message: %-20s\n", UiDevice
+                .getInstance().getCurrentPackageName());
+        Log.e(LOG_TAG, formatedOutput);
+
+        UiObject buttonOK = new UiObject(new UiSelector().text("OK").enabled(true));
+        // sometimes it takes a while for the OK button to become enabled
+        buttonOK.waitForExists(5000);
+        try {
+            buttonOK.click();
+        } catch (UiObjectNotFoundException e) {
+            Log.e(LOG_TAG, "Exception", e);
+        }
+    }
+}
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/platform/JankTestBase.java b/uiautomator_test_libraries/src/com/android/uiautomator/platform/JankTestBase.java
index 31e7773..bac710e 100644
--- a/uiautomator_test_libraries/src/com/android/uiautomator/platform/JankTestBase.java
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/platform/JankTestBase.java
@@ -23,28 +23,40 @@
 import com.android.uiautomator.core.UiDevice;
 import com.android.uiautomator.testrunner.UiAutomatorTestCase;
 
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Properties;
 
 /**
- * Helper class for jankiness test.
- * All the jank test must extend the JankBaseHelper
+ * Base class for jank test.
+ * All jank test needs to extend JankTestBase
  */
 public class JankTestBase extends UiAutomatorTestCase {
+    private final static String TAG = JankTestBase.class.getSimpleName();
+
     protected UiDevice mDevice;
+    protected TestWatchers mTestWatchers = null;
     protected BufferedWriter mWriter = null;
     protected BufferedWriter mStatusWriter = null;
     protected int mIteration = 20; // default iteration is set 20
+    /* can be used to enable/disable systrace in the test */
+    protected int mTraceTime = 0;
     protected Bundle mParams;
     protected String mTestCaseName;
     protected int mSuccessTestRuns = 0;
+    protected Thread mThread = null;
 
-    private final static String TAG = JankTestBase.class.getSimpleName();
     // holds all params for the derived tests
     private final static String PROPERTY_FILE_NAME = "UiJankinessTests.conf";
     private final static String PARAM_CONFIG = "conf";
@@ -53,20 +65,112 @@
     private static String OUTPUT_FILE_NAME = LOCAL_TMP_DIR + "UiJankinessTestsOutput.txt";
     // File that hold test status, e.g successful test iterations
     private static String STATUS_FILE_NAME = LOCAL_TMP_DIR + "UiJankinessTestsStatus.txt";
+    private final static String RAW_DATA_DIR = LOCAL_TMP_DIR + "UiJankinessRawData";
+
     private static int SUCCESS_THRESHOLD = 80;
     private static boolean DEBUG = false;
 
+    /* default animation time is set to 2 seconds */
+    protected final static long DEFAULT_ANIMATION_TIME = 2 * 1000;
+    /* default swipe steps for fling animation */
+    protected final static int DEFAULT_FLING_STEPS = 8;
+
     /* Array to record jankiness data in each test iteration */
     private int[] jankinessArray;
     /* Array to record frame rate in each test iteration */
     private double[] frameRateArray;
     /* Array to save max accumulated frame number in each test iteration */
     private int[] maxDeltaVsyncArray;
+    /* Default file to store the systrace */
+    private final static File SYSTRACE_DIR = new File(LOCAL_TMP_DIR, "systrace");
+    /* Default trace file name */
+    private final static String TRACE_FILE_NAME = "trace.txt";
+    /* Default tracing time is 5 seconds */
+    private final static int DEFAULT_TRACE_TIME = 5; // 5 seconds
+    // Command to dump compressed trace data
+    private final static String ATRACE_COMMAND = "atrace -z -t %d gfx input view sched freq";
+
+    /**
+     * Thread to capture systrace log from the test
+     */
+    public class SystraceTracker implements Runnable {
+        File mFile = new File(SYSTRACE_DIR, TRACE_FILE_NAME);
+        int mTime = DEFAULT_TRACE_TIME;
+
+        public SystraceTracker(int traceTime, String fileName) {
+            try {
+                if (!SYSTRACE_DIR.exists()) {
+                    if (!SYSTRACE_DIR.mkdir()) {
+                        log(String.format("create directory %s failed, you can manually create "
+                                + "it and start the test again", SYSTRACE_DIR.getAbsolutePath()));
+                        return;
+                    }
+                }
+            } catch (SecurityException e) {
+                Log.e(TAG, "creating directory failed?", e);
+            }
+
+            if (traceTime > 0) {
+                mTime = traceTime;
+            }
+            if (fileName != null) {
+                mFile = new File(SYSTRACE_DIR, fileName);
+            }
+        }
+
+        @Override
+        public void run() {
+            String command = String.format(ATRACE_COMMAND, mTime);
+            Log.v(TAG, "command: " + command);
+            Process p = null;
+            InputStream in = null;
+            BufferedOutputStream out = null;
+            try {
+                p = Runtime.getRuntime().exec(command);
+                Log.v(TAG, "write systrace into file: " + mFile.getAbsolutePath());
+                // read bytes from the process output stream as the output is compressed
+                byte[] buffer = new byte[1024];
+                in = p.getInputStream();
+                out = new BufferedOutputStream(new FileOutputStream(mFile));
+                int n;
+                while ((n = in.read(buffer)) != -1) {
+                    out.write(buffer, 0, n);
+                    out.flush();
+                }
+                in.close();
+                out.close();
+                // read error message
+                BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
+                String line;
+                while ((line = br.readLine()) != null) {
+                    Log.e(TAG, "Command return errors: " + line);
+                }
+                br.close();
+
+                // Due to limited buffer size for standard input and output stream,
+                // promptly reading from the input stream or output stream to avoid block
+                int status = p.waitFor();
+                if (status != 0) {
+                    Log.e(TAG, String.format("Run shell command: %s, status: %s",
+                            command, status));
+                }
+            } catch (InterruptedException e) {
+                Log.e(TAG, "Exception from command " + command + ":");
+                Log.e(TAG, "Thread interrupted? ", e);
+            } catch (IOException e) {
+                Log.e(TAG, "Open file error: ", e);
+            } catch (IllegalThreadStateException e) {
+                Log.e(TAG, "the process has not exit yet ", e);
+            }
+        }
+    }
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         mDevice = UiDevice.getInstance();
+        mTestWatchers = new TestWatchers(); // extends the common class UiWatchers
+        mTestWatchers.registerAnrAndCrashWatchers();
 
         mWriter = new BufferedWriter(new FileWriter(new File(OUTPUT_FILE_NAME), true));
         mStatusWriter = new BufferedWriter(new FileWriter(new File(STATUS_FILE_NAME), true));
@@ -74,10 +178,14 @@
         mParams = getParams();
         if (mParams != null && !mParams.isEmpty()) {
             log("mParams is not empty, get properties.");
-            String mIterationStr;
-            if ((mIterationStr = getPropertyString(mParams, "iteration")) != null) {
+            String mIterationStr = getPropertyString(mParams, "iteration");
+            if (mIterationStr != null) {
                 mIteration = Integer.valueOf(mIterationStr);
             }
+            String mTraceTimeStr = getPropertyString(mParams, "tracetime");
+            if (mTraceTimeStr  != null) {
+                mTraceTime = Integer.valueOf(mTraceTimeStr);
+            }
         }
         jankinessArray = new int[mIteration];
         frameRateArray = new double[mIteration];
@@ -89,6 +197,33 @@
     }
 
     /**
+     * Create a new thread for systrace and start the thread
+     *
+     * @param testCaseName
+     * @param iteration
+     */
+    protected void startTrace(String testCaseName, int iteration) {
+        if (mTraceTime > 0) {
+            String outputFile = String.format("%s_%d_trace", mTestCaseName, iteration);
+            mThread = new Thread(new SystraceTracker(mTraceTime, outputFile));
+            mThread.start();
+        }
+    }
+
+    /**
+     * Wait for the tracing thread to exit
+     */
+    protected void endTrace() {
+        if (mThread != null) {
+            try {
+                mThread.join();
+            } catch (InterruptedException e) {
+                Log.e(TAG, "wait for the trace thread to exit exception:", e);
+            }
+        }
+    }
+
+    /**
      * Expects a file from the command line via conf param or default following format each on its
      * own line. <code>
      *    key=Value
@@ -147,6 +282,38 @@
      */
     protected void recordResults(String testCaseName, int iteration) {
         long refreshPeriod = SurfaceFlingerHelper.getRefreshPeriod();
+        // if the raw directory doesn't exit, create the directory
+        File rawDataDir = new File(RAW_DATA_DIR);
+        try {
+            if (!rawDataDir.exists()) {
+                if (!rawDataDir.mkdir()) {
+                    log(String.format("create directory %s failed, you can manually create " +
+                            "it and start the test again", rawDataDir));
+                }
+            }
+        } catch (SecurityException e) {
+            Log.e(TAG, "create directory failed: ", e);
+        }
+        String rawFileName = String.format("%s/%s_%d.txt", RAW_DATA_DIR, testCaseName, iteration);
+        // write results into a file
+        BufferedWriter fw = null;
+        try {
+            fw = new BufferedWriter(new FileWriter(new File(rawFileName), false));
+            fw.write(SurfaceFlingerHelper.getFrameBufferData());
+        } catch (IOException e) {
+            Log.e(TAG, "failed to write to file", e);
+            return;
+        } finally {
+            try {
+                if (fw != null) {
+                    fw.close();
+                }
+            }
+            catch (IOException e) {
+                    Log.e(TAG, "close file failed.", e);
+            }
+        }
+
         // get jankiness count
         int jankinessCount = SurfaceFlingerHelper.getVsyncJankiness();
         // get frame rate
@@ -302,5 +469,4 @@
    protected int getIteration(){
        return mIteration;
    }
-
 }
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/platform/SurfaceFlingerHelper.java b/uiautomator_test_libraries/src/com/android/uiautomator/platform/SurfaceFlingerHelper.java
index 1fcdafd..fe69ea9 100644
--- a/uiautomator_test_libraries/src/com/android/uiautomator/platform/SurfaceFlingerHelper.java
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/platform/SurfaceFlingerHelper.java
@@ -37,8 +37,8 @@
     private static String TAG = "SurfaceFlingerHelper";
     private static int BUFFER_SIZE = 128;
     private static int BUFFER_NUMBER = 3;
-    private static String CLEAR_BUFFER_CMD = "dumpsys SurfaceFlinger --latency-clear %s";
-    private static String FRAME_LATENCY_CMD = "dumpsys SurfaceFlinger --latency %s";
+    private static String CLEAR_BUFFER_CMD = "dumpsys SurfaceFlinger --latency-clear";
+    private static String FRAME_LATENCY_CMD = "dumpsys SurfaceFlinger --latency";
     private final static String RAW_DATA_DIR = "UiJankinessRawData";
     private final static String LOCAL_TMP_DIR = "/data/local/tmp/";
     /* If the latency between two frames is greater than this number, it it treated as a pause
@@ -66,6 +66,8 @@
     /* Normalized data */
     private static double[] mNormalizedDelta2Vsync = new double[BUFFER_SIZE];
     private static int[] mRoundNormalizedDelta2Vsync = new int[BUFFER_SIZE];
+    // Symbol of unfinished frame time */
+    public final static String PENDING_FENCE_TIME = new Long(Long.MAX_VALUE).toString();
 
     /**
      * Run clear buffer command and clear the saved frame buffer results
@@ -87,20 +89,21 @@
 
         Process p = null;
         BufferedReader resultReader = null;
-        String command = String.format(CLEAR_BUFFER_CMD, windowName);
+        String command = CLEAR_BUFFER_CMD;
+        if (windowName != null) {
+            command = String.format("%s %s", CLEAR_BUFFER_CMD, windowName);
+        }
         try {
             p = Runtime.getRuntime().exec(command);
             int status = p.waitFor();
             if (status != 0) {
-                System.err.println(String.format("Run shell command: %s, status: %s",
+                Log.e(TAG, String.format("Run shell command: %s, status: %s",
                         command, status));
             }
         } catch (IOException e) {
-            System.err.println("// Exception from command " + command + ":");
-            System.err.println(e.toString());
+            Log.e(TAG, "// Exception from command " + command + ":", e);
         } catch (InterruptedException e) {
-            System.err.println("// Interrupted while waiting for the command to finish. ");
-            System.err.println(e.toString());
+            Log.e(TAG, "// Interrupted while waiting for the command to finish. ", e);
         } finally {
             try {
                 if (resultReader != null) {
@@ -110,80 +113,81 @@
                     p.destroy();
                 }
             } catch (IOException e) {
-                System.err.println(e.toString());
+                Log.e(TAG, "exception " + e);
             }
         }
     }
 
     /**
-     * Run frame latency command to get the raw data, save raw data on the disk
+     * Run frame latency command without ignoring pending fence time
      *
-     * @param windowName
-     * @return
+     * @param windowName the window name which SurfaceFlinger will acquire frame time for
      */
-    public static boolean dumpFrameLatency(String windowName, String fileName, int index) {
-        BufferedWriter fw = null;
+    public static boolean dumpFrameLatency(String windowName) {
+        return dumpFrameLatency(windowName, false);
+    }
+
+    /**
+     * Run frame latency command to get frame time
+     *
+     * @param windowName the window name which SurfaceFlinger will get frame time for
+     * @param ignorePendingFenceTime flag to process frames with pending fence time
+     *                              set true to ignore pending fence time
+     *                              set false to fail the test if pending fence time is not allowed
+     */
+    public static boolean dumpFrameLatency(String windowName, boolean ignorePendingFenceTime) {
         Process p = null;
         BufferedReader resultReader = null;
-        String command = String.format(FRAME_LATENCY_CMD, windowName);
-        // if the raw directory doesn't exit, create the directory
-        File rawDataDir = new File(LOCAL_TMP_DIR, RAW_DATA_DIR);
-        try {
-            if (!rawDataDir.exists()) {
-                if (!rawDataDir.mkdir()) {
-                    log(String.format("create directory %s failed, you can manually create " +
-                            "it and start the test again", rawDataDir));
-                    return false;
-                }
-            }
-        } catch (SecurityException e) {
-            System.err.println(e.toString());
+        String command = FRAME_LATENCY_CMD;
+        if (windowName != null) {
+            command = String.format("%s %s", FRAME_LATENCY_CMD, windowName);
         }
-        String rawFileName = String.format("%s/%s_%d.txt", RAW_DATA_DIR, fileName, index);
-
+        log("dump frame latency command: " + command);
         try {
             p = Runtime.getRuntime().exec(command);
             int status = p.waitFor();
             if (status != 0) {
-                System.err.println(String.format("Run shell command: %s, status: %s",
-                        command, status));
+                Log.e(TAG, String.format("Run shell command: %s, status: %s",command, status));
             }
             resultReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
-            fw = new BufferedWriter(new FileWriter(new File(
-                    LOCAL_TMP_DIR, rawFileName), false));
-            // The first line will always show the refresh period
             String line = resultReader.readLine();
-            Log.v("testing", "output line = " + line);
-            fw.write(line);
-            fw.write("\n");
             mRefreshPeriod = Long.parseLong(line.trim());
-            //log("reading refresh period: " + mRefreshPeriod);
+            log("reading refresh period: " + mRefreshPeriod);
             if (mRefreshPeriod < 0) {
                 return false;
             }
-
+            boolean dataInvalidFlag = false;
             while((line = resultReader.readLine()) != null) {
-                fw.write(line);
-                fw.write("\n");
-
-                // remove the last line which is empty
+                // remove lines which are empty
                 if (line.trim().isEmpty()) {
                     break;
                 }
                 String[] bufferValues = line.split("\\s+");
                 if (bufferValues[0].trim().compareTo("0") == 0) {
                     continue;
+                } else if (bufferValues[1].trim().compareTo(PENDING_FENCE_TIME) == 0 ) {
+                    if (ignorePendingFenceTime) {
+                        log("ignore pending fence time");
+                        dataInvalidFlag = true;
+                    } else {
+                        log("the data contains unfinished frame time, please allow the animation"
+                            + " to finish in the test before calling dumpFrameLatency.");
+                        return false;
+                    }
                 }
+                // store raw data which could have both valid and invalid data
                 List<String> delayArray = Arrays.asList(bufferValues);
                 mFrameBufferData.add(delayArray);
-                ++mFrameLatencySampleSize;
+                if (!dataInvalidFlag) {
+                    // only count frames which have valid time
+                    ++mFrameLatencySampleSize;
+                }
             }
             log("frame latency sample size: " + mFrameLatencySampleSize);
         } catch (InterruptedException e) {
-            System.err.println("// Exception from command " + command + ":");
-            System.err.println(e.toString());
+            Log.e(TAG, "// Exception from command " + command + ":", e);
         } catch (IOException e) {
-            log("Open file error: " + e.toString());
+            Log.e(TAG, "Open file error: ", e);
             return false;
         }
         finally {
@@ -191,14 +195,11 @@
                 if (resultReader != null) {
                     resultReader.close();
                 }
-                if (fw != null) {
-                    fw.close();
-                }
                 if (p != null) {
                     p.destroy();
                 }
             } catch (IOException e) {
-                System.err.println(e.toString());
+                Log.e(TAG, "io exception: ", e);
             }
         }
         return true;
@@ -219,12 +220,12 @@
         }
         String rawData = String.format("%d\n", mRefreshPeriod);
         List<String> tempList = new ArrayList<String>(BUFFER_NUMBER);
-        for (int i = 0; i < mFrameLatencySampleSize; i++) {
+        for (int i = 0; i < mFrameBufferData.size(); i++) {
             tempList = mFrameBufferData.get(i);
             for (int j = 0; j < BUFFER_NUMBER; j++) {
                 rawData += String.format("%s", tempList.get(j));
-                if (j < BUFFER_NUMBER -1) {
-                    rawData += " ";
+                if (j < BUFFER_NUMBER - 1) {
+                    rawData += "\t";
                 } else {
                     rawData += "\n";
                 }
@@ -243,13 +244,12 @@
             return null;
         }
         if (mDeltaVsync[0] < 0 ) {
-            mDeltaVsync = new long[BUFFER_SIZE];
             // keep a record of the max DeltaVsync
             mMaxDeltaVsync = 0;
             // get the first frame vsync time
             long preVsyncTime = Long.parseLong(mFrameBufferData.get(0).get(1));
-            for (int i = 1; i < mFrameLatencySampleSize; i++) {
-                long curVsyncTime = Long.parseLong(mFrameBufferData.get(i).get(1));
+            for (int i = 0; i < mFrameLatencySampleSize - 1; i++) {
+                long curVsyncTime = Long.parseLong(mFrameBufferData.get(i + 1).get(1));
                 mDeltaVsync[i] = curVsyncTime - preVsyncTime;
                 preVsyncTime = curVsyncTime;
                 if (mMaxDeltaVsync < mDeltaVsync[i]) {
@@ -273,9 +273,9 @@
             getDeltaVsync();
         }
         if (mDelta2Vsync[0] < 0) {
-            mDelta2Vsync = new long[BUFFER_SIZE];
-            for (int i = 1; i < mFrameLatencySampleSize; i++) {
-                mDelta2Vsync[i] = mDeltaVsync[i] - mDeltaVsync[i - 1];
+            int numDeltaVsync = mFrameLatencySampleSize - 1;
+            for (int i = 0; i < numDeltaVsync - 1; i++) {
+                mDelta2Vsync[i] = mDeltaVsync[i + 1] - mDeltaVsync[i];
             }
         }
         return mDelta2Vsync;
@@ -294,8 +294,7 @@
             getDelta2Vsync();
         }
         if (mNormalizedDelta2Vsync[0] < 0) {
-            mNormalizedDelta2Vsync = new double[BUFFER_SIZE];
-            for (int i = 0; i < mFrameLatencySampleSize; i++) {
+            for (int i = 0; i < mFrameLatencySampleSize - 2; i++) {
                 mNormalizedDelta2Vsync[i] = (double)mDelta2Vsync[i] /mRefreshPeriod;
             }
         }
@@ -311,7 +310,7 @@
             getNormalizedDelta2Vsync();
         }
 
-        for (int i = 0; i < mFrameLatencySampleSize; i++) {
+        for (int i = 0; i < mFrameLatencySampleSize - 2; i++) {
              int value = (int)Math.round(Math.max(mNormalizedDelta2Vsync[i], 0.0));
              mRoundNormalizedDelta2Vsync[i] = value;
         }
@@ -330,7 +329,7 @@
             getRoundNormalizedDelta2Vsync();
         }
         int numberJankiness = 0;
-        for (int i = 0; i < mFrameLatencySampleSize; i++) {
+        for (int i = 0; i < mFrameLatencySampleSize - 2; i++) {
             int value = mRoundNormalizedDelta2Vsync[i];
             // ignore the latency which is too long
             if (value > 0 && value < PAUSE_LATENCY) {
@@ -376,8 +375,7 @@
         log("write raw data and process data into file: " + rawAndProcDataFileName);
         BufferedWriter fw = null;
         try {
-            fw = new BufferedWriter(new FileWriter(new File(
-                    LOCAL_TMP_DIR, rawAndProcDataFileName), false));
+            fw = new BufferedWriter(new FileWriter(new File(rawAndProcDataFileName), false));
             // Show the number of jankiness first:
             fw.write(String.format("Jankiness count: %d\n", getVsyncJankiness()));
             fw.write(String.format("Max accumulated frames: %d\n", getMaxDeltaVsync()));
@@ -408,7 +406,7 @@
                 }
             }
             catch (IOException e) {
-                System.err.println(e.toString());
+                Log.e(TAG, "close file exception: ", e);
             }
         }
     }
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/platform/TestWatchers.java b/uiautomator_test_libraries/src/com/android/uiautomator/platform/TestWatchers.java
new file mode 100644
index 0000000..5cab83a
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/platform/TestWatchers.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.uiautomator.platform;
+
+import com.android.uiautomator.common.UiWatchers;
+
+public class TestWatchers extends UiWatchers {
+    private String TAG = "TestWatchers";
+
+    @Override
+    public void onAnrDetected(String errorText) {
+        // The ANR dialog is still open now and upon returning from here
+        // it will automatically get closed. See UiWatchers or implement
+        // your handlers directly.
+        super.onAnrDetected("ANR:" + errorText);
+    }
+
+    @Override
+    public void onCrashDetected(String errorText) {
+        // what do we need to do here?
+        // The Crash dialog is still open now and upon returning from here
+        // it will automatically get closed. See UiWatchers or implement
+        // your handlers directly.
+        super.onCrashDetected("CRASH:" + errorText);
+    }
+}