am ff3ae2fb: am 0aac9989: Move the uiautomator test libraries to frameworks/testing to solve the unbundle configure issue.
# Via Android Git Automerger (1) and Xia Wang (1)
* commit 'ff3ae2fbec12b4f1b7947f999d3a20e62c53a5b3':
Move the uiautomator test libraries to frameworks/testing to solve the unbundle configure issue.
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..9bd474e
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/common/helpers/AppHelperBase.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+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..1e554bd
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/platform/JankTestBase.java
@@ -0,0 +1,306 @@
+/*
+ * 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.
+ */
+
+package com.android.uiautomation.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..f072d95
--- /dev/null
+++ b/uiautomator_test_libraries/src/com/android/uiautomator/platform/SurfaceFlingerHelper.java
@@ -0,0 +1,419 @@
+/*
+ * 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.
+ */
+
+package com.android.uiautomation.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);
+ }
+}