Merge "Add ability to receive command line args in JUnit3 tests."
diff --git a/uiautomator/api/current.txt b/uiautomator/api/current.txt
index 609c1b3..664fbbc 100644
--- a/uiautomator/api/current.txt
+++ b/uiautomator/api/current.txt
@@ -11,6 +11,7 @@
   public class UiDevice {
     method public void clearLastTraversedText();
     method public boolean click(int, int);
+    method public boolean drag(int, int, int, int, int);
     method public void dumpWindowHierarchy(java.lang.String);
     method public void freezeRotation() throws android.os.RemoteException;
     method public deprecated java.lang.String getCurrentActivityName();
@@ -67,6 +68,8 @@
     method public boolean clickAndWaitForNewWindow(long) throws com.android.uiautomator.core.UiObjectNotFoundException;
     method public boolean clickBottomRight() throws com.android.uiautomator.core.UiObjectNotFoundException;
     method public boolean clickTopLeft() throws com.android.uiautomator.core.UiObjectNotFoundException;
+    method public boolean dragTo(com.android.uiautomator.core.UiObject, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
+    method public boolean dragTo(int, int, int) throws com.android.uiautomator.core.UiObjectNotFoundException;
     method public boolean exists();
     method protected android.view.accessibility.AccessibilityNodeInfo findAccessibilityNodeInfo(long);
     method public android.graphics.Rect getBounds() throws com.android.uiautomator.core.UiObjectNotFoundException;
diff --git a/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java b/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
index 7deb3a6..a00f2ea 100644
--- a/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
@@ -424,6 +424,20 @@
      * @return true if the swipe executed successfully
      */
     public boolean swipe(int downX, int downY, int upX, int upY, int steps) {
+        return swipe(downX, downY, upX, upY, steps, false /*drag*/);
+    }
+
+    /**
+     * Handle swipes/drags in any direction.
+     * @param downX
+     * @param downY
+     * @param upX
+     * @param upY
+     * @param steps
+     * @param drag when true, the swipe becomes a drag swipe
+     * @return true if the swipe executed successfully
+     */
+    public boolean swipe(int downX, int downY, int upX, int upY, int steps, boolean drag) {
         boolean ret = false;
         int swipeSteps = steps;
         double xStep = 0;
@@ -438,6 +452,8 @@
 
         // first touch starts exactly at the point requested
         ret = touchDown(downX, downY);
+        if (drag)
+            SystemClock.sleep(mUiAutomatorBridge.getSystemLongPressTime());
         for(int i = 1; i < swipeSteps; i++) {
             ret &= touchMove(downX + (int)(xStep * i), downY + (int)(yStep * i));
             if(ret == false)
@@ -448,6 +464,8 @@
             // a preset delay.
             SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
         }
+        if (drag)
+            SystemClock.sleep(REGULAR_CLICK_LENGTH);
         ret &= touchUp(upX, upY);
         return(ret);
     }
diff --git a/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
index 2305e3a..4d8016e 100644
--- a/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -398,7 +398,26 @@
     public boolean swipe(int startX, int startY, int endX, int endY, int steps) {
         Tracer.trace(startX, startY, endX, endY, steps);
         return mUiAutomationBridge.getInteractionController()
-                .scrollSwipe(startX, startY, endX, endY, steps);
+                .swipe(startX, startY, endX, endY, steps);
+    }
+
+    /**
+     * Performs a swipe from one coordinate to another using the number of steps
+     * to determine smoothness and speed. Each step execution is throttled to 5ms
+     * per step. So for a 100 steps, the swipe will take about 1/2 second to complete.
+     *
+     * @param startX
+     * @param startY
+     * @param endX
+     * @param endY
+     * @param steps is the number of move steps sent to the system
+     * @return false if the operation fails or the coordinates are invalid
+     * @since API Level 18
+     */
+    public boolean drag(int startX, int startY, int endX, int endY, int steps) {
+        Tracer.trace(startX, startY, endX, endY, steps);
+        return mUiAutomationBridge.getInteractionController()
+                .swipe(startX, startY, endX, endY, steps, true);
     }
 
     /**
diff --git a/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java b/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java
index 2594888..8e82a98 100644
--- a/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java
+++ b/uiautomator/library/core-src/com/android/uiautomator/core/UiObject.java
@@ -188,6 +188,42 @@
     }
 
     /**
+     * Performs a drag of this object to a destination UiObject. Note that the number of steps
+     * used can influence the drag speed and varying speeds may impact the results. Consider
+     * evaluating different speeds when testing this method.
+     *
+     * @param destObj
+     * @param steps usually 40 steps. More or less to change the speed.
+     * @return true of successful
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public boolean dragTo(UiObject destObj, int steps) throws UiObjectNotFoundException {
+        Rect srcRect = getVisibleBounds();
+        Rect dstRect = destObj.getVisibleBounds();
+        return getInteractionController().swipe(srcRect.centerX(), srcRect.centerY(),
+                dstRect.centerX(), dstRect.centerY(), steps, true);
+    }
+
+    /**
+     * Performs a drag of this object to arbitrary coordinates. Note that the number of steps
+     * used will influence the drag speed and varying speeds may impact the results. Consider
+     * evaluating different speeds when testing this method.
+     *
+     * @param destX
+     * @param destY
+     * @param steps
+     * @return true of successful
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public boolean dragTo(int destX, int destY, int steps) throws UiObjectNotFoundException {
+        Rect srcRect = getVisibleBounds();
+        return getInteractionController().swipe(srcRect.centerX(), srcRect.centerY(), destX, destY,
+                steps, true);
+    }
+
+    /**
      * Perform the action on the UI element that is represented by this UiObject. Also see
      * {@link UiScrollable#scrollToBeginning(int)}, {@link UiScrollable#scrollToEnd(int)},
      * {@link UiScrollable#scrollBackward()}, {@link UiScrollable#scrollForward()}.
diff --git a/uiautomator_test_libraries/Android.mk b/uiautomator_test_libraries/Android.mk
new file mode 100644
index 0000000..f975016
--- /dev/null
+++ b/uiautomator_test_libraries/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2012 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.
+#
+local_target_dir := $(TARGET_OUT_DATA)/local/tmp
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := com.android.uiautomator.platform.common
+
+LOCAL_SDK_VERSION := 16
+
+LOCAL_JAVA_LIBRARIES := uiautomator_sdk_v$(LOCAL_SDK_VERSION)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(local_target_dir)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/AppHelperBase.java b/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/AppHelperBase.java
new file mode 100644
index 0000000..e3d9b61
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/AppHelperBase.java
@@ -0,0 +1,96 @@
+/*
+ * 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.helpers;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+/**
+ * Base app helper class intended for all app helper to extend.
+ * This class provides common APIs that are expected to be present across
+ * all app helpers.
+ */
+public abstract class AppHelperBase {
+
+    /*
+     * App helpers should provide methods for accessing various UI widgets.
+     * Assume the app has an Action Bar, the helper should provide something similar to
+     * SomeAppHelper.ActionBar.getRefreshButton(). Methods like this help the tests check the
+     * state of the targeted widget as well as clicking it if need be. These types of methods are
+     * referred to as object getters. If there are different components, consider creating internal
+     * name spaces as in the .ActionBar example for better context.
+     *
+     * Adding basic units of functionality APIs is also very helpful to test.
+     * Consider the Alarm clock application as an example. It would be helpful if its helper
+     * provided basic functionality such as, setAlarm(Date) and deleteAlarm(Date). Such basic
+     * and key functionality helper methods, will abstract the tests from the UI implementation and
+     * make tests more reliable.
+     */
+
+    /**
+     * Launches the application.
+     *
+     * This is typically performed by executing a shell command to launch the application
+     * via Intent. It is possible to launch the application by automating the Launcher
+     * views and finding the target app icon to launch, however, this is prone to failure if
+     * the Launcher UI implementation differ from one platform to another.
+     */
+    abstract public void open();
+
+    /**
+     * Checks if the application is in foreground.
+     *
+     * This is typically performed by verifying the current package name of the foreground
+     * application. See UiDevice.getCurrentPackageName()
+     * @return true if open, else false.
+     */
+    abstract public boolean isOpen();
+
+
+    /**
+     * Helper to execute a shell command.
+     * @param command
+     */
+    protected void runShellCommand(String command) {
+        Process p = null;
+        BufferedReader resultReader = null;
+        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));
+            }
+        } catch (IOException e) {
+            System.err.println("// Exception from command " + command + ":");
+            System.err.println(e.toString());
+        } catch (InterruptedException e) {
+            System.err.println("// Interrupted while waiting for the command to finish. ");
+            System.err.println(e.toString());
+        } finally {
+            try {
+                if (resultReader != null) {
+                    resultReader.close();
+                }
+                if (p != null) {
+                    p.destroy();
+                }
+            } catch (IOException e) {
+                System.err.println(e.toString());
+            }
+        }
+    }
+}
diff --git a/uiautomator_test_libraries/src/com/android/uiautomator/platform/JankTestBase.java b/uiautomator_test_libraries/src/com/android/uiautomator/platform/JankTestBase.java
new file mode 100644
index 0000000..42e4db1
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/platform/JankTestBase.java
@@ -0,0 +1,306 @@
+/*
+ * 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 android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+
+import com.android.uiautomator.core.UiDevice;
+import com.android.uiautomator.testrunner.UiAutomatorTestCase;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Helper class for jankiness test.
+ * All the jank test must extend the JankBaseHelper
+ */
+public class JankTestBase extends UiAutomatorTestCase {
+    protected UiDevice mDevice;
+    protected BufferedWriter mWriter = null;
+    protected BufferedWriter mStatusWriter = null;
+    protected int mIteration = 1; // default iteration is set 1
+    protected Bundle mParams;
+    protected String mTestCaseName;
+    protected int mSuccessTestRuns = 0;
+
+    private final static String TAG = "BasePerformance";
+    // holds all params for the derived tests
+    private final static String PROPERTY_FILE_NAME = "UiJankinessTests.conf";
+    private final static String PARAM_CONFIG = "conf";
+    private final static String LOCAL_TMP_DIR = "/data/local/tmp/";
+    // File that hold the test results
+    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 static int SUCCESS_THRESHOLD = 80;
+    private static boolean DEBUG = false;
+
+    /* 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;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDevice = UiDevice.getInstance();
+
+        mWriter = new BufferedWriter(new FileWriter(new File(OUTPUT_FILE_NAME), true));
+        mStatusWriter = new BufferedWriter(new FileWriter(new File(STATUS_FILE_NAME), true));
+
+        mParams = getParams();
+        if (mParams != null && !mParams.isEmpty()) {
+            log("mParams is not empty, get properties.");
+            String mIterationStr;
+            if ((mIterationStr = getPropertyString(mParams, "iteration")) != null) {
+                mIteration = Integer.valueOf(mIterationStr);
+            }
+        }
+        jankinessArray = new int[mIteration];
+        frameRateArray = new double[mIteration];
+        maxDeltaVsyncArray = new int[mIteration];
+        mTestCaseName = this.getName();
+
+        mSuccessTestRuns = 0;
+        mDevice.pressHome();
+    }
+
+    /**
+     * Expects a file from the command line via conf param or default following format each on its
+     * own line. <code>
+     *    key=Value
+     *    Browser_URL1=cnn.com
+     *    Browser_URL2=google.com
+     *    Camera_ShutterDelay=1000
+     *    etc...
+     * </code>
+     * @param Bundle params
+     * @param key
+     * @return the value of the property else defaultValue
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    protected String getPropertyString(Bundle params, String key)
+            throws FileNotFoundException, IOException {
+        Properties prop = new Properties();
+        prop.load(new FileInputStream(new File(LOCAL_TMP_DIR,
+                params.getString(PARAM_CONFIG, PROPERTY_FILE_NAME))));
+        String value = prop.getProperty(key);
+        if (value != null && !value.isEmpty())
+            return value;
+        return null;
+    }
+
+    /**
+     * Expects a file from the command line via conf param or default following format each on its
+     * own line. <code>
+     *    key=Value
+     *    Browser_URL1=cnn.com
+     *    Browser_URL2=google.com
+     *    Camera_ShutterDelay=1000
+     *    etc...
+     * </code>
+     * @param Bundle params
+     * @param key
+     * @return the value of the property else defaultValue
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    protected long getPropertyLong(Bundle params, String key)
+            throws FileNotFoundException, IOException {
+        Properties prop = new Properties();
+        prop.load(new FileInputStream(new File(LOCAL_TMP_DIR,
+                params.getString(PARAM_CONFIG, PROPERTY_FILE_NAME))));
+        String value = prop.getProperty(key);
+        if (value != null && !value.trim().isEmpty())
+            return Long.valueOf(value.trim());
+        return 0;
+    }
+
+    /**
+     * Process the raw data, calculate jankiness, frame rate and max accumulated frames number
+     * @param testCaseName
+     * @param iteration
+     */
+    protected void recordResults(String testCaseName, int iteration) {
+        long refreshPeriod = SurfaceFlingerHelper.getRefreshPeriod();
+        // get jankiness count
+        int jankinessCount = SurfaceFlingerHelper.getVsyncJankiness();
+        // get frame rate
+        double frameRate = SurfaceFlingerHelper.getFrameRate();
+        // get max accumulated frames
+        int maxDeltaVsync = SurfaceFlingerHelper.getMaxDeltaVsync();
+
+        // only record data when they are valid
+        if (jankinessCount >=0 && frameRate > 0) {
+            jankinessArray[iteration] = jankinessCount;
+            frameRateArray[iteration] = frameRate;
+            maxDeltaVsyncArray[iteration] = maxDeltaVsync;
+            mSuccessTestRuns++;
+        }
+        String msg = String.format("%s, iteration %d\n" +
+                "refresh period: %d\n" +
+                "jankiness count: %d\n" +
+                "frame rate: %f\n" +
+                "max accumulated frames: %d\n",
+                testCaseName, iteration, refreshPeriod,
+                jankinessCount, frameRate, maxDeltaVsync);
+        log(msg);
+        if (DEBUG) {
+            SurfaceFlingerHelper.printData(testCaseName, iteration);
+        }
+    }
+
+    /**
+     * Process data from all test iterations, and save to disk
+     * @param testCaseName
+     */
+    protected void saveResults(String testCaseName) {
+        // write test status into status file
+        try {
+            mStatusWriter.write(String.format("%s: %d success runs out of %d iterations\n",
+                    testCaseName, mSuccessTestRuns, mIteration));
+        } catch (IOException e) {
+            log("failed to write output for test case " + testCaseName);
+        }
+
+        // if successful test runs is less than the threshold, no results will be saved.
+        if (mSuccessTestRuns * 100 / mIteration < SUCCESS_THRESHOLD) {
+            log(String.format("In %s, # of successful test runs out of %s iterations: %d ",
+                    testCaseName, mIteration, mSuccessTestRuns));
+            log(String.format("threshold is %d%%", SUCCESS_THRESHOLD));
+            return;
+        }
+
+        if (DEBUG) {
+            print(jankinessArray, "jankiness array");
+            print(frameRateArray, "frame rate array");
+            print(maxDeltaVsyncArray, "max delta vsync array");
+        }
+        double avgJankinessCount = getAverage(jankinessArray);
+        int maxJankinessCount = getMaxValue(jankinessArray);
+        double avgFrameRate = getAverage(frameRateArray);
+        double avgMaxDeltaVsync = getAverage(maxDeltaVsyncArray);
+
+        String avgMsg = String.format("%s\n" +
+                "average number of jankiness: %f\n" +
+                "max number of jankiness: %d\n" +
+                "average frame rate: %f\n" +
+                "average of max accumulated frames: %f\n",
+                testCaseName, avgJankinessCount, maxJankinessCount, avgFrameRate, avgMaxDeltaVsync);
+        log(avgMsg);
+
+        try {
+            mWriter.write(avgMsg);
+        } catch (IOException e) {
+            log("failed to write output for test case " + testCaseName);
+        }
+    }
+
+    // return the max value in an integer array
+    private int getMaxValue(int[] intArray) {
+        int index = 0;
+        int max = intArray[index];
+        for (int i  = 1; i < intArray.length; i++) {
+            if (max < intArray[i]) {
+                max = intArray[i];
+            }
+        }
+        return max;
+    }
+
+    private double getAverage(int[] intArray) {
+        int mean = 0;
+        int numberTests = 0;
+        for (int i = 0; i < intArray.length; i++) {
+            // in case in some iteration, test fails, no data points is collected
+            if (intArray[i] >= 0) {
+                mean += intArray[i];
+                ++numberTests;
+            }
+        }
+        return (double)mean/numberTests;
+    }
+
+    private double getAverage(double[] doubleArray) {
+        double mean = 0;
+        int numberTests = 0;
+        for (int i = 0; i < doubleArray.length; i++) {
+            // in case in some iteration, test fails, no data points is collected
+            if (doubleArray[i] >= 0) {
+                mean += doubleArray[i];
+                ++numberTests;
+            }
+        }
+        return mean/numberTests;
+    }
+
+    private void print(int[] intArray, String arrayName) {
+        log("start to print array for " + arrayName);
+        for (int i = 0; i < intArray.length; i++) {
+            log(String.format("%d: %d", i, intArray[i]));
+        }
+    }
+
+    private void print(double[] doubleArray, String arrayName) {
+        log("start to print array for " + arrayName);
+        for (int i = 0; i < doubleArray.length; i++) {
+            log(String.format("%d: %f", i, doubleArray[i]));
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (mWriter != null) {
+            mWriter.close();
+        }
+        if (mStatusWriter != null) {
+            mStatusWriter.close();
+        }
+    }
+
+   private void log(String message) {
+       Log.v(TAG, message);
+   }
+
+   /**
+    * Set the total number of test iteration
+    * @param iteration
+    */
+   protected void setIteration(int iteration){
+       mIteration = iteration;
+   }
+
+   /**
+    * Get the total number of test iteration
+    * @return iteration
+    */
+   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
new file mode 100644
index 0000000..1fcdafd
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/platform/SurfaceFlingerHelper.java
@@ -0,0 +1,419 @@
+/*
+ * 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 android.os.Environment;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.ArrayList;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.BufferedWriter;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.lang.Math;
+
+/*
+ * Tools to measure jankiness through SurfaceFlinger
+ */
+public class SurfaceFlingerHelper {
+    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 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
+     * not a jankiness */
+    private final static int PAUSE_LATENCY = 20;
+
+    /* An array list which includes the raw buffer information from frame latency tool */
+    private static List<List<String>> mFrameBufferData = new ArrayList<List<String>>(BUFFER_SIZE);
+
+    /* Record the refresh period returned from driver */
+    private static long mRefreshPeriod = -1;
+
+    /* Record the size of frame latency */
+    private static int mFrameLatencySampleSize = 0;
+
+    /* An integer array which includes delta vsync */
+    private static long[] mDeltaVsync = new long[BUFFER_SIZE];
+
+    /* Integer array for delta of delta vsync */
+    private static long[] mDelta2Vsync = new long[BUFFER_SIZE];
+
+    /* the maximum delta vsync number */
+    private static long mMaxDeltaVsync;
+
+    /* Normalized data */
+    private static double[] mNormalizedDelta2Vsync = new double[BUFFER_SIZE];
+    private static int[] mRoundNormalizedDelta2Vsync = new int[BUFFER_SIZE];
+
+    /**
+     * Run clear buffer command and clear the saved frame buffer results
+     *
+     * @param windowName the window name that the buffer will be cleared
+     */
+    public static void clearBuffer(String windowName) {
+        // clear results
+        if (mFrameBufferData != null) {
+            mFrameBufferData.clear();
+        }
+        Arrays.fill(mDeltaVsync, -1);
+        Arrays.fill(mDelta2Vsync, -1);
+        Arrays.fill(mNormalizedDelta2Vsync, -1.0);
+        Arrays.fill(mRoundNormalizedDelta2Vsync, -1);
+        mRefreshPeriod = -1;
+        mFrameLatencySampleSize = 0;
+        mMaxDeltaVsync = 0;
+
+        Process p = null;
+        BufferedReader resultReader = null;
+        String command = String.format(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",
+                        command, status));
+            }
+        } catch (IOException e) {
+            System.err.println("// Exception from command " + command + ":");
+            System.err.println(e.toString());
+        } catch (InterruptedException e) {
+            System.err.println("// Interrupted while waiting for the command to finish. ");
+            System.err.println(e.toString());
+        } finally {
+            try {
+                if (resultReader != null) {
+                    resultReader.close();
+                }
+                if (p != null) {
+                    p.destroy();
+                }
+            } catch (IOException e) {
+                System.err.println(e.toString());
+            }
+        }
+    }
+
+    /**
+     * Run frame latency command to get the raw data, save raw data on the disk
+     *
+     * @param windowName
+     * @return
+     */
+    public static boolean dumpFrameLatency(String windowName, String fileName, int index) {
+        BufferedWriter fw = null;
+        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 rawFileName = String.format("%s/%s_%d.txt", RAW_DATA_DIR, fileName, index);
+
+        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));
+            }
+            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);
+            if (mRefreshPeriod < 0) {
+                return false;
+            }
+
+            while((line = resultReader.readLine()) != null) {
+                fw.write(line);
+                fw.write("\n");
+
+                // remove the last line which is empty
+                if (line.trim().isEmpty()) {
+                    break;
+                }
+                String[] bufferValues = line.split("\\s+");
+                if (bufferValues[0].trim().compareTo("0") == 0) {
+                    continue;
+                }
+                List<String> delayArray = Arrays.asList(bufferValues);
+                mFrameBufferData.add(delayArray);
+                ++mFrameLatencySampleSize;
+            }
+            log("frame latency sample size: " + mFrameLatencySampleSize);
+        } catch (InterruptedException e) {
+            System.err.println("// Exception from command " + command + ":");
+            System.err.println(e.toString());
+        } catch (IOException e) {
+            log("Open file error: " + e.toString());
+            return false;
+        }
+        finally {
+            try {
+                if (resultReader != null) {
+                    resultReader.close();
+                }
+                if (fw != null) {
+                    fw.close();
+                }
+                if (p != null) {
+                    p.destroy();
+                }
+            } catch (IOException e) {
+                System.err.println(e.toString());
+            }
+        }
+        return true;
+    }
+
+    public static long getRefreshPeriod() {
+        if (mRefreshPeriod < 0) {
+            // Haven't dump the frame latency yet
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving the refresh period");
+        }
+        return mRefreshPeriod;
+    }
+
+    public static String getFrameBufferData() {
+        if (mFrameBufferData.get(0) == null) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame buffer data");
+            return null;
+        }
+        String rawData = String.format("%d\n", mRefreshPeriod);
+        List<String> tempList = new ArrayList<String>(BUFFER_NUMBER);
+        for (int i = 0; i < mFrameLatencySampleSize; 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 += " ";
+                } else {
+                    rawData += "\n";
+                }
+            }
+        }
+        return rawData;
+    }
+
+    /**
+     * Calculate delta(vsync)
+     * @return
+     */
+    public static long[] getDeltaVsync() {
+        if (mRefreshPeriod < 0) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame latency");
+            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));
+                mDeltaVsync[i] = curVsyncTime - preVsyncTime;
+                preVsyncTime = curVsyncTime;
+                if (mMaxDeltaVsync < mDeltaVsync[i]) {
+                    mMaxDeltaVsync = mDeltaVsync[i];
+                }
+            }
+        }
+        return mDeltaVsync;
+    }
+
+    /**
+     * Calculate difference between delta vsync
+     * @return
+     */
+    public static long[] getDelta2Vsync() {
+        if (mRefreshPeriod < 0) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame latency");
+            return null;
+        }
+        if (mDeltaVsync[0] < 0) {
+            getDeltaVsync();
+        }
+        if (mDelta2Vsync[0] < 0) {
+            mDelta2Vsync = new long[BUFFER_SIZE];
+            for (int i = 1; i < mFrameLatencySampleSize; i++) {
+                mDelta2Vsync[i] = mDeltaVsync[i] - mDeltaVsync[i - 1];
+            }
+        }
+        return mDelta2Vsync;
+    }
+
+    /**
+     * normalized delta(delta(vsync)) by refresh period
+     * @return
+     */
+    public static double[] getNormalizedDelta2Vsync() {
+        if (mRefreshPeriod < 0) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame latency");
+            return null;
+        }
+        if (mDelta2Vsync[0] < 0) {
+            getDelta2Vsync();
+        }
+        if (mNormalizedDelta2Vsync[0] < 0) {
+            mNormalizedDelta2Vsync = new double[BUFFER_SIZE];
+            for (int i = 0; i < mFrameLatencySampleSize; i++) {
+                mNormalizedDelta2Vsync[i] = (double)mDelta2Vsync[i] /mRefreshPeriod;
+            }
+        }
+        return mNormalizedDelta2Vsync;
+    }
+
+    public static int[] getRoundNormalizedDelta2Vsync() {
+        if (mRefreshPeriod < 0) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" for number of jankiness.");
+            return null;
+        }
+        if (mNormalizedDelta2Vsync[0] < 0) {
+            getNormalizedDelta2Vsync();
+        }
+
+        for (int i = 0; i < mFrameLatencySampleSize; i++) {
+             int value = (int)Math.round(Math.max(mNormalizedDelta2Vsync[i], 0.0));
+             mRoundNormalizedDelta2Vsync[i] = value;
+        }
+        return mRoundNormalizedDelta2Vsync;
+    }
+
+    /*
+     * Get number of jankiness using Vsync time difference
+     */
+    public static int getVsyncJankiness() {
+        if (mRefreshPeriod < 0) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" for number of jankiness.");
+            return -1;
+        }
+        if (mRoundNormalizedDelta2Vsync[0] < 0) {
+            getRoundNormalizedDelta2Vsync();
+        }
+        int numberJankiness = 0;
+        for (int i = 0; i < mFrameLatencySampleSize; i++) {
+            int value = mRoundNormalizedDelta2Vsync[i];
+            // ignore the latency which is too long
+            if (value > 0 && value < PAUSE_LATENCY) {
+                numberJankiness++;
+            }
+        }
+        return numberJankiness;
+    }
+
+    /* Track the maximum delta which shows the accumulating time
+     * before animation starts */
+    public static int getMaxDeltaVsync() {
+        return Math.round((float)mMaxDeltaVsync /mRefreshPeriod);
+    }
+
+    /**
+     * Calculate frame rate
+     * @return
+     */
+    public static double getFrameRate() {
+        if (mRefreshPeriod < 0) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before calcuating average frame rate");
+            return -1.0;
+        }
+        if (mFrameBufferData.get(0) == null) {
+            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame buffer data");
+            return -1.0;
+        }
+        long startTime = Long.parseLong(mFrameBufferData.get(0).get(1));
+        long endTime =  Long.parseLong(mFrameBufferData.get(mFrameLatencySampleSize - 1).get(1));
+        long totalDuration = endTime - startTime;
+        return (double)((mFrameLatencySampleSize - 1) * Math.pow(10, 9))/totalDuration;
+    }
+
+    /**
+     * Print raw data and processed results into file <testcasename_[iteration]_processed.txt>
+     * @param fileName
+     * @param index
+     */
+    public static void printData(String fileName, int index) {
+        String rawAndProcDataFileName = String.format("%s/%s_%d_processed.txt", RAW_DATA_DIR,
+                fileName, index);
+        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));
+            // 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()));
+            fw.write(String.format("Frame rate is: %f\n", getFrameRate()));
+
+            // refresh period
+            fw.write(String.valueOf(mRefreshPeriod));
+            fw.write("\n");
+            fw.write("app\tvsync\tset\tdelta(vsync)\tdelta^2(vsync)\t" +
+                    "delta^2(vsync)/refreshPeriod\t normalized delta^2(vsync)\n");
+
+            for (int i = 0; i < mFrameLatencySampleSize; i++) {
+                // write raw data
+                List<String> rawData = mFrameBufferData.get(i);
+                String line = String.format("%s\t%s\t%s\t%d\t%d\t%f\t%d\n",
+                        rawData.get(0), rawData.get(1), rawData.get(2),
+                        mDeltaVsync[i], mDelta2Vsync[i],
+                        mNormalizedDelta2Vsync[i], mRoundNormalizedDelta2Vsync[i]);
+                fw.write(line);
+            }
+        } catch (IOException e) {
+            log("Open file error: " + e.toString());
+        } finally {
+            try {
+                if (fw != null) {
+                    fw.flush();
+                    fw.close();
+                }
+            }
+            catch (IOException e) {
+                System.err.println(e.toString());
+            }
+        }
+    }
+
+    private static void log(String msg) {
+        Log.v(TAG, msg);
+    }
+}