• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.test.hwuicompare;
18 
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Comparator;
25 import java.util.HashMap;
26 import java.util.TreeSet;
27 
28 import org.json.JSONException;
29 import org.json.JSONObject;
30 
31 import android.os.Bundle;
32 import android.os.Environment;
33 import android.os.Trace;
34 import android.util.Log;
35 import android.widget.ImageView;
36 import android.widget.Toast;
37 
38 public class AutomaticActivity extends CompareActivity {
39     private static final String LOG_TAG = "AutomaticActivity";
40     private static final float ERROR_DISPLAY_THRESHOLD = 0.01f;
41     protected static final boolean DRAW_BITMAPS = false;
42 
43     /**
44      * Threshold of error change required to consider a test regressed/improved
45      */
46     private static final float ERROR_CHANGE_THRESHOLD = 0.001f;
47 
48     private static final float[] ERROR_CUTOFFS = {
49             0, 0.005f, 0.01f, 0.02f, 0.05f, 0.1f, 0.25f, 0.5f, 1f, 2f
50     };
51 
52     private final float[] mErrorRates = new float[ERROR_CUTOFFS.length];
53     private float mTotalTests = 0;
54     private float mTotalError = 0;
55     private int mTestsRegressed = 0;
56     private int mTestsImproved = 0;
57 
58     private ImageView mSoftwareImageView = null;
59     private ImageView mHardwareImageView = null;
60 
61 
62     public abstract static class FinalCallback {
report(String name, float value)63         abstract void report(String name, float value);
complete()64         void complete() {};
65     }
66 
67     private final ArrayList<FinalCallback> mFinalCallbacks = new ArrayList<FinalCallback>();
68 
69     Runnable mRunnable = new Runnable() {
70         @Override
71         public void run() {
72             loadBitmaps();
73             if (mSoftwareBitmap == null || mHardwareBitmap == null) {
74                 Log.e(LOG_TAG, "bitmap is null");
75                 return;
76             }
77 
78             if (DRAW_BITMAPS) {
79                 mSoftwareImageView.setImageBitmap(mSoftwareBitmap);
80                 mHardwareImageView.setImageBitmap(mHardwareBitmap);
81             }
82 
83             Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, "calculateError");
84             float error = mErrorCalculator.calcErrorRS(mSoftwareBitmap, mHardwareBitmap);
85             Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
86 
87             final String[] modifierNames = DisplayModifier.getLastAppliedModifications();
88             handleError(modifierNames, error);
89 
90             if (DisplayModifier.step()) {
91                 finishTest();
92             } else {
93                 mHardwareView.invalidate();
94                 if (DRAW_BITMAPS) {
95                     mSoftwareImageView.invalidate();
96                     mHardwareImageView.invalidate();
97                 }
98             }
99             mHandler.removeCallbacks(mRunnable);
100         }
101     };
102 
103     @Override
onPause()104     protected void onPause() {
105         super.onPause();
106         mHandler.removeCallbacks(mRunnable);
107     };
108 
109     @Override
onCreate(Bundle savedInstanceState)110     protected void onCreate(Bundle savedInstanceState) {
111         super.onCreate(savedInstanceState);
112         setContentView(R.layout.automatic_layout);
113 
114         mSoftwareImageView = findViewById(R.id.software_image_view);
115         mHardwareImageView = findViewById(R.id.hardware_image_view);
116 
117         onCreateCommon(mRunnable);
118         beginTest();
119     }
120 
121     private static class TestResult {
TestResult(String label, float error)122         TestResult(String label, float error) {
123             mLabel = label;
124             mTotalError = error;
125             mCount = 1;
126         }
addInto(float error)127         public void addInto(float error) {
128             mTotalError += error;
129             mCount++;
130         }
getAverage()131         public float getAverage() {
132             return mTotalError / mCount;
133         }
134         final String mLabel;
135         float mTotalError;
136         int mCount;
137     }
138 
139     JSONObject mOutputJson = null;
140     JSONObject mInputJson = null;
141     final HashMap<String, TestResult> mModifierResults = new HashMap<String, TestResult>();
142     final HashMap<String, TestResult> mIndividualResults = new HashMap<String, TestResult>();
143     final HashMap<String, TestResult> mModifierDiffResults = new HashMap<String, TestResult>();
144     final HashMap<String, TestResult> mIndividualDiffResults = new HashMap<String, TestResult>();
beginTest()145     private void beginTest() {
146         mFinalCallbacks.add(new FinalCallback() {
147             @Override
148             void report(String name, float value) {
149                 Log.d(LOG_TAG, name + " " + value);
150             };
151         });
152 
153         File inputFile = new File(Environment.getExternalStorageDirectory(),
154                 "CanvasCompareInput.json");
155         if (inputFile.exists() && inputFile.canRead() && inputFile.length() > 0) {
156             try {
157                 FileInputStream inputStream = new FileInputStream(inputFile);
158                 Log.d(LOG_TAG, "Parsing input file...");
159                 StringBuffer content = new StringBuffer((int)inputFile.length());
160                 byte[] buffer = new byte[1024];
161                 while (inputStream.read(buffer) != -1) {
162                     content.append(new String(buffer));
163                 }
164                 mInputJson = new JSONObject(content.toString());
165                 inputStream.close();
166                 Log.d(LOG_TAG, "Parsed input file with " + mInputJson.length() + " entries");
167             } catch (JSONException e) {
168                 Log.e(LOG_TAG, "error parsing input json", e);
169             } catch (IOException e) {
170                 Log.e(LOG_TAG, "error reading input json from sd", e);
171             }
172         }
173 
174         mOutputJson = new JSONObject();
175     }
176 
logTestResultHash(String label, HashMap<String, TestResult> map)177     private static void logTestResultHash(String label, HashMap<String, TestResult> map) {
178         Log.d(LOG_TAG, "---------------");
179         Log.d(LOG_TAG, label + ":");
180         Log.d(LOG_TAG, "---------------");
181         TreeSet<TestResult> set = new TreeSet<TestResult>(new Comparator<TestResult>() {
182             @Override
183             public int compare(TestResult lhs, TestResult rhs) {
184                 if (lhs == rhs) return 0; // don't need to worry about complex equality
185 
186                 int cmp = Float.compare(lhs.getAverage(), rhs.getAverage());
187                 if (cmp != 0) {
188                     return cmp;
189                 }
190                 return lhs.mLabel.compareTo(rhs.mLabel);
191             }
192         });
193 
194         for (TestResult t : map.values()) {
195             set.add(t);
196         }
197 
198         for (TestResult t : set.descendingSet()) {
199             if (Math.abs(t.getAverage()) > ERROR_DISPLAY_THRESHOLD) {
200                 Log.d(LOG_TAG, String.format("%2.4f : %s", t.getAverage(), t.mLabel));
201             }
202         }
203         Log.d(LOG_TAG, "");
204     }
205 
finishTest()206     private void finishTest() {
207         for (FinalCallback c : mFinalCallbacks) {
208             c.report("averageError", (mTotalError / mTotalTests));
209             for (int i = 1; i < ERROR_CUTOFFS.length; i++) {
210                 c.report(String.format("tests with error over %1.3f", ERROR_CUTOFFS[i]),
211                         mErrorRates[i]);
212             }
213             if (mInputJson != null) {
214                 c.report("tests regressed", mTestsRegressed);
215                 c.report("tests improved", mTestsImproved);
216             }
217             c.complete();
218         }
219 
220         try {
221             if (mOutputJson != null) {
222                 String outputString = mOutputJson.toString(4);
223                 File outputFile = new File(Environment.getExternalStorageDirectory(),
224                         "CanvasCompareOutput.json");
225                 FileOutputStream outputStream = new FileOutputStream(outputFile);
226                 outputStream.write(outputString.getBytes());
227                 outputStream.close();
228                 Log.d(LOG_TAG, "Saved output file with " + mOutputJson.length() + " entries");
229             }
230         } catch (JSONException e) {
231             Log.e(LOG_TAG, "error during JSON stringify", e);
232         } catch (IOException e) {
233             Log.e(LOG_TAG, "error storing JSON output on sd", e);
234         }
235 
236         logTestResultHash("Modifier change vs previous", mModifierDiffResults);
237         logTestResultHash("Invidual test change vs previous", mIndividualDiffResults);
238         logTestResultHash("Modifier average test results", mModifierResults);
239         logTestResultHash("Individual test results", mIndividualResults);
240 
241         Toast.makeText(getApplicationContext(), "done!", Toast.LENGTH_SHORT).show();
242         finish();
243     }
244 
245     /**
246      * Inserts the error value into all TestResult objects, associated with each of its modifiers
247      */
addForAllModifiers(String fullName, float error, String[] modifierNames, HashMap<String, TestResult> modifierResults)248     private static void addForAllModifiers(String fullName, float error, String[] modifierNames,
249             HashMap<String, TestResult> modifierResults) {
250         for (String modifierName : modifierNames) {
251             TestResult r = modifierResults.get(fullName);
252             if (r == null) {
253                 modifierResults.put(modifierName, new TestResult(modifierName, error));
254             } else {
255                 r.addInto(error);
256             }
257         }
258     }
259 
handleError(final String[] modifierNames, final float error)260     private void handleError(final String[] modifierNames, final float error) {
261         String fullName = "";
262         for (String s : modifierNames) {
263             fullName = fullName.concat("." + s);
264         }
265         fullName = fullName.substring(1);
266 
267         float deltaError = 0;
268         if (mInputJson != null) {
269             try {
270                 deltaError = error - (float)mInputJson.getDouble(fullName);
271             } catch (JSONException e) {
272                 Log.w(LOG_TAG, "Warning: unable to read from input json", e);
273             }
274             if (deltaError > ERROR_CHANGE_THRESHOLD) mTestsRegressed++;
275             if (deltaError < -ERROR_CHANGE_THRESHOLD) mTestsImproved++;
276             mIndividualDiffResults.put(fullName, new TestResult(fullName, deltaError));
277             addForAllModifiers(fullName, deltaError, modifierNames, mModifierDiffResults);
278         }
279 
280         mIndividualResults.put(fullName, new TestResult(fullName, error));
281         addForAllModifiers(fullName, error, modifierNames, mModifierResults);
282 
283         try {
284             if (mOutputJson != null) {
285                 mOutputJson.put(fullName, error);
286             }
287         } catch (JSONException e) {
288             Log.e(LOG_TAG, "exception during JSON recording", e);
289             mOutputJson = null;
290         }
291 
292         for (int i = 0; i < ERROR_CUTOFFS.length; i++) {
293             if (error <= ERROR_CUTOFFS[i]) break;
294             mErrorRates[i]++;
295         }
296         mTotalError += error;
297         mTotalTests++;
298     }
299 
300     @Override
forceRecreateBitmaps()301     protected boolean forceRecreateBitmaps() {
302         // disable, unless needed for drawing into imageviews
303         return DRAW_BITMAPS;
304     }
305 
306     // FOR TESTING
setFinalCallback(FinalCallback c)307     public void setFinalCallback(FinalCallback c) {
308         mFinalCallbacks.add(c);
309     }
310 }
311