blob: 1ed47230c6fc755c30e0425a5a39387734eb5561 [file] [log] [blame]
/*
* 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.test.hwuicompare;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.TreeSet;
import org.json.JSONException;
import org.json.JSONObject;
import android.os.Bundle;
import android.os.Environment;
import android.os.Trace;
import android.util.Log;
import android.widget.ImageView;
import android.widget.Toast;
public class AutomaticActivity extends CompareActivity {
private static final String LOG_TAG = "AutomaticActivity";
private static final float ERROR_DISPLAY_THRESHOLD = 0.01f;
protected static final boolean DRAW_BITMAPS = false;
/**
* Threshold of error change required to consider a test regressed/improved
*/
private static final float ERROR_CHANGE_THRESHOLD = 0.001f;
private static final float[] ERROR_CUTOFFS = {
0, 0.005f, 0.01f, 0.02f, 0.05f, 0.1f, 0.25f, 0.5f, 1f, 2f
};
private final float[] mErrorRates = new float[ERROR_CUTOFFS.length];
private float mTotalTests = 0;
private float mTotalError = 0;
private int mTestsRegressed = 0;
private int mTestsImproved = 0;
private ImageView mSoftwareImageView = null;
private ImageView mHardwareImageView = null;
public abstract static class FinalCallback {
abstract void report(String name, float value);
void complete() {};
}
private final ArrayList<FinalCallback> mFinalCallbacks = new ArrayList<FinalCallback>();
Runnable mRunnable = new Runnable() {
@Override
public void run() {
loadBitmaps();
if (mSoftwareBitmap == null || mHardwareBitmap == null) {
Log.e(LOG_TAG, "bitmap is null");
return;
}
if (DRAW_BITMAPS) {
mSoftwareImageView.setImageBitmap(mSoftwareBitmap);
mHardwareImageView.setImageBitmap(mHardwareBitmap);
}
Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, "calculateError");
float error = mErrorCalculator.calcErrorRS(mSoftwareBitmap, mHardwareBitmap);
Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
final String[] modifierNames = DisplayModifier.getLastAppliedModifications();
handleError(modifierNames, error);
if (DisplayModifier.step()) {
finishTest();
} else {
mHardwareView.invalidate();
if (DRAW_BITMAPS) {
mSoftwareImageView.invalidate();
mHardwareImageView.invalidate();
}
}
mHandler.removeCallbacks(mRunnable);
}
};
@Override
protected void onPause() {
super.onPause();
mHandler.removeCallbacks(mRunnable);
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.automatic_layout);
mSoftwareImageView = (ImageView) findViewById(R.id.software_image_view);
mHardwareImageView = (ImageView) findViewById(R.id.hardware_image_view);
onCreateCommon(mRunnable);
beginTest();
}
private static class TestResult {
TestResult(String label, float error) {
mLabel = label;
mTotalError = error;
mCount = 1;
}
public void addInto(float error) {
mTotalError += error;
mCount++;
}
public float getAverage() {
return mTotalError / mCount;
}
final String mLabel;
float mTotalError;
int mCount;
}
JSONObject mOutputJson = null;
JSONObject mInputJson = null;
final HashMap<String, TestResult> mModifierResults = new HashMap<String, TestResult>();
final HashMap<String, TestResult> mIndividualResults = new HashMap<String, TestResult>();
final HashMap<String, TestResult> mModifierDiffResults = new HashMap<String, TestResult>();
final HashMap<String, TestResult> mIndividualDiffResults = new HashMap<String, TestResult>();
private void beginTest() {
mFinalCallbacks.add(new FinalCallback() {
@Override
void report(String name, float value) {
Log.d(LOG_TAG, name + " " + value);
};
});
File inputFile = new File(Environment.getExternalStorageDirectory(),
"CanvasCompareInput.json");
if (inputFile.exists() && inputFile.canRead() && inputFile.length() > 0) {
try {
FileInputStream inputStream = new FileInputStream(inputFile);
Log.d(LOG_TAG, "Parsing input file...");
StringBuffer content = new StringBuffer((int)inputFile.length());
byte[] buffer = new byte[1024];
while (inputStream.read(buffer) != -1) {
content.append(new String(buffer));
}
mInputJson = new JSONObject(content.toString());
inputStream.close();
Log.d(LOG_TAG, "Parsed input file with " + mInputJson.length() + " entries");
} catch (JSONException e) {
Log.e(LOG_TAG, "error parsing input json", e);
} catch (IOException e) {
Log.e(LOG_TAG, "error reading input json from sd", e);
}
}
mOutputJson = new JSONObject();
}
private static void logTestResultHash(String label, HashMap<String, TestResult> map) {
Log.d(LOG_TAG, "---------------");
Log.d(LOG_TAG, label + ":");
Log.d(LOG_TAG, "---------------");
TreeSet<TestResult> set = new TreeSet<TestResult>(new Comparator<TestResult>() {
@Override
public int compare(TestResult lhs, TestResult rhs) {
if (lhs == rhs) return 0; // don't need to worry about complex equality
int cmp = Float.compare(lhs.getAverage(), rhs.getAverage());
if (cmp != 0) {
return cmp;
}
return lhs.mLabel.compareTo(rhs.mLabel);
}
});
for (TestResult t : map.values()) {
set.add(t);
}
for (TestResult t : set.descendingSet()) {
if (Math.abs(t.getAverage()) > ERROR_DISPLAY_THRESHOLD) {
Log.d(LOG_TAG, String.format("%2.4f : %s", t.getAverage(), t.mLabel));
}
}
Log.d(LOG_TAG, "");
}
private void finishTest() {
for (FinalCallback c : mFinalCallbacks) {
c.report("averageError", (mTotalError / mTotalTests));
for (int i = 1; i < ERROR_CUTOFFS.length; i++) {
c.report(String.format("tests with error over %1.3f", ERROR_CUTOFFS[i]),
mErrorRates[i]);
}
if (mInputJson != null) {
c.report("tests regressed", mTestsRegressed);
c.report("tests improved", mTestsImproved);
}
c.complete();
}
try {
if (mOutputJson != null) {
String outputString = mOutputJson.toString(4);
File outputFile = new File(Environment.getExternalStorageDirectory(),
"CanvasCompareOutput.json");
FileOutputStream outputStream = new FileOutputStream(outputFile);
outputStream.write(outputString.getBytes());
outputStream.close();
Log.d(LOG_TAG, "Saved output file with " + mOutputJson.length() + " entries");
}
} catch (JSONException e) {
Log.e(LOG_TAG, "error during JSON stringify", e);
} catch (IOException e) {
Log.e(LOG_TAG, "error storing JSON output on sd", e);
}
logTestResultHash("Modifier change vs previous", mModifierDiffResults);
logTestResultHash("Invidual test change vs previous", mIndividualDiffResults);
logTestResultHash("Modifier average test results", mModifierResults);
logTestResultHash("Individual test results", mIndividualResults);
Toast.makeText(getApplicationContext(), "done!", Toast.LENGTH_SHORT).show();
finish();
}
/**
* Inserts the error value into all TestResult objects, associated with each of its modifiers
*/
private static void addForAllModifiers(String fullName, float error, String[] modifierNames,
HashMap<String, TestResult> modifierResults) {
for (String modifierName : modifierNames) {
TestResult r = modifierResults.get(fullName);
if (r == null) {
modifierResults.put(modifierName, new TestResult(modifierName, error));
} else {
r.addInto(error);
}
}
}
private void handleError(final String[] modifierNames, final float error) {
String fullName = "";
for (String s : modifierNames) {
fullName = fullName.concat("." + s);
}
fullName = fullName.substring(1);
float deltaError = 0;
if (mInputJson != null) {
try {
deltaError = error - (float)mInputJson.getDouble(fullName);
} catch (JSONException e) {
Log.w(LOG_TAG, "Warning: unable to read from input json", e);
}
if (deltaError > ERROR_CHANGE_THRESHOLD) mTestsRegressed++;
if (deltaError < -ERROR_CHANGE_THRESHOLD) mTestsImproved++;
mIndividualDiffResults.put(fullName, new TestResult(fullName, deltaError));
addForAllModifiers(fullName, deltaError, modifierNames, mModifierDiffResults);
}
mIndividualResults.put(fullName, new TestResult(fullName, error));
addForAllModifiers(fullName, error, modifierNames, mModifierResults);
try {
if (mOutputJson != null) {
mOutputJson.put(fullName, error);
}
} catch (JSONException e) {
Log.e(LOG_TAG, "exception during JSON recording", e);
mOutputJson = null;
}
for (int i = 0; i < ERROR_CUTOFFS.length; i++) {
if (error <= ERROR_CUTOFFS[i]) break;
mErrorRates[i]++;
}
mTotalError += error;
mTotalTests++;
}
@Override
protected boolean forceRecreateBitmaps() {
// disable, unless needed for drawing into imageviews
return DRAW_BITMAPS;
}
// FOR TESTING
public void setFinalCallback(FinalCallback c) {
mFinalCallbacks.add(c);
}
}