1 /* 2 * Copyright (C) 2011 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 android.holo.cts; 18 19 import com.android.cts.holo.R; 20 21 import android.app.Activity; 22 import android.content.Intent; 23 import android.graphics.Bitmap; 24 import android.graphics.Canvas; 25 import android.graphics.Color; 26 import android.holo.cts.LayoutAdapter.LayoutInfo; 27 import android.holo.cts.ThemeAdapter.ThemeInfo; 28 import android.os.AsyncTask; 29 import android.os.Bundle; 30 import android.util.Log; 31 import android.view.View; 32 import android.widget.Toast; 33 34 import java.io.FileNotFoundException; 35 import java.io.IOException; 36 import java.nio.ByteBuffer; 37 38 /** 39 * {@link Activity} that applies a theme, inflates a layout, and then either 40 * compares or generates a bitmap of the layout. 41 */ 42 public class LayoutTestActivity extends Activity { 43 44 private static final String TAG = LayoutTestActivity.class.getSimpleName(); 45 46 // Input extras 47 static final String EXTRA_THEME_INDEX = "themeIndex"; 48 static final String EXTRA_LAYOUT_INDEX = "layoutIndex"; 49 static final String EXTRA_TASK = "task"; 50 static final String EXTRA_LAYOUT_ADAPTER_MODE = "layoutAdapterMode"; 51 52 // Output extras 53 static final String EXTRA_BITMAP_NAME = "bitmapName"; 54 static final String EXTRA_MESSAGE = "message"; 55 static final String EXTRA_SUCCESS = "success"; 56 57 private View mTestView; 58 private String mBitmapName; 59 60 private ReferenceViewGroup mViewGroup; 61 62 @Override onCreate(Bundle savedInstanceState)63 protected void onCreate(Bundle savedInstanceState) { 64 super.onCreate(savedInstanceState); 65 66 int themeIndex = getIntent().getIntExtra(EXTRA_THEME_INDEX, -1); 67 int layoutIndex = getIntent().getIntExtra(EXTRA_LAYOUT_INDEX, -1); 68 int layoutMode = getIntent().getIntExtra(EXTRA_LAYOUT_ADAPTER_MODE, -1); 69 int task = getIntent().getIntExtra(EXTRA_TASK, -1); 70 71 ThemeAdapter themeAdapter = new ThemeAdapter(getLayoutInflater()); 72 LayoutAdapter layoutAdapter = new LayoutAdapter(getLayoutInflater(), layoutMode); 73 74 ThemeInfo themeInfo = themeAdapter.getItem(themeIndex); 75 LayoutInfo layoutInfo = layoutAdapter.getItem(layoutIndex); 76 mBitmapName = BitmapAssets.getBitmapName(themeInfo, layoutInfo); 77 78 setTheme(themeInfo.getTheme()); 79 setContentView(R.layout.holo_test); 80 81 if (layoutInfo.hasModifier()) { 82 layoutInfo.getModifier().prepare(); 83 } 84 85 // Inflate the view in our special view group that is fixed at a certain size 86 // and layout the inflated view with the fixed measurements... 87 mViewGroup = (ReferenceViewGroup) findViewById(R.id.reference_view_group); 88 mTestView = getLayoutInflater().inflate(layoutInfo.getLayout(), mViewGroup, false); 89 mViewGroup.addView(mTestView); 90 if (layoutInfo.hasModifier()) { 91 mTestView = layoutInfo.getModifier().modifyView(mTestView); 92 } 93 mViewGroup.measure(0, 0); 94 mViewGroup.layout(0, 0, mViewGroup.getMeasuredWidth(), mViewGroup.getMeasuredHeight()); 95 mTestView.setFocusable(false); 96 97 switch (task) { 98 case ThemeTestActivity.TASK_VIEW_LAYOUTS: 99 break; 100 101 case ThemeTestActivity.TASK_GENERATE_BITMAPS: 102 mTestView.postDelayed(new GenerateBitmapRunnable(), layoutInfo.getTimeoutMs()); 103 break; 104 105 case ThemeTestActivity.TASK_COMPARE_BITMAPS: 106 mTestView.postDelayed(new CompareBitmapRunnable(), layoutInfo.getTimeoutMs()); 107 break; 108 } 109 } 110 111 class GenerateBitmapRunnable implements Runnable { 112 @Override run()113 public void run() { 114 new GenerateBitmapTask().execute(); 115 } 116 } 117 118 class CompareBitmapRunnable implements Runnable { 119 @Override run()120 public void run() { 121 new CompareBitmapTask().execute(); 122 } 123 } 124 125 class GenerateBitmapTask extends AsyncTask<Void, Void, Boolean> { 126 127 private Bitmap mBitmap; 128 129 @Override onPreExecute()130 protected void onPreExecute() { 131 super.onPreExecute(); 132 mBitmap = getBitmap(); 133 } 134 135 @Override doInBackground(Void... avoid)136 protected Boolean doInBackground(Void... avoid) { 137 try { 138 return saveBitmap(mBitmap, BitmapAssets.TYPE_REFERENCE) != null; 139 } finally { 140 mBitmap.recycle(); 141 mBitmap = null; 142 } 143 } 144 145 @Override onPostExecute(Boolean success)146 protected void onPostExecute(Boolean success) { 147 String path = BitmapAssets.getBitmapPath(mBitmapName, 148 BitmapAssets.TYPE_REFERENCE).toString(); 149 String message = path != null 150 ? getString(R.string.generate_bitmap_success, path) 151 : getString(R.string.generate_bitmap_failure, path); 152 Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); 153 finishWithResult(success, message); 154 } 155 } 156 157 class CompareBitmapTask extends AsyncTask<Void, Void, String[]> { 158 private Bitmap mBitmap; 159 private Bitmap mReferenceBitmap; 160 private boolean mSame; 161 162 @Override onPreExecute()163 protected void onPreExecute() { 164 mBitmap = getBitmap(); 165 mReferenceBitmap = BitmapAssets.getBitmap(getApplicationContext(), mBitmapName); 166 } 167 168 /* Compares 2 bitmaps' width, height and pixels. 169 * 2 Bitmaps are consider the same if color value difference is less than 170 * or equal to +/-threshold 171 */ compareTo(Bitmap bitmap, Bitmap reference, int threshold)172 private boolean compareTo(Bitmap bitmap, Bitmap reference, int threshold) { 173 if (bitmap.getConfig() != reference.getConfig() || 174 bitmap.getWidth() != reference.getWidth() || 175 bitmap.getHeight() != reference.getHeight()) { 176 return false; 177 } 178 179 int w = bitmap.getWidth(); 180 int h = bitmap.getHeight(); 181 182 ByteBuffer buffer1 = ByteBuffer.allocate(bitmap.getByteCount()); 183 ByteBuffer buffer2 = ByteBuffer.allocate(reference.getByteCount()); 184 185 bitmap.copyPixelsToBuffer(buffer1); 186 reference.copyPixelsToBuffer(buffer2); 187 188 final int length = w*h; 189 for (int i = 0; i < length; i++) { 190 int pel1 = buffer1.getInt(i); 191 int pel2 = buffer2.getInt(i); 192 int dr = (pel1 & 0x000000FF) - (pel2 & 0x000000FF); 193 int dg = ((pel1 & 0x0000FF00) - (pel2 & 0x0000FF00)) >> 8; 194 int db = ((pel1 & 0x00FF0000) - (pel2 & 0x00FF0000)) >> 16; 195 196 if (Math.abs(db) > threshold || 197 Math.abs(dg) > threshold || 198 Math.abs(dr) > threshold) { 199 return false; 200 } 201 if (bitmap.hasAlpha()) { 202 int da = ((pel1 & 0xFF000000) - (pel2 & 0xFF000000)) >> 24; 203 if (Math.abs(da) > threshold) { 204 return false; 205 } 206 } 207 } 208 return true; 209 } 210 211 @Override doInBackground(Void... devoid)212 protected String[] doInBackground(Void... devoid) { 213 try { 214 final int threshold = 2; 215 mSame = compareTo(mBitmap, mReferenceBitmap, threshold); 216 if (!mSame) { 217 String[] paths = new String[2]; 218 paths[0] = saveDiffBitmap(mBitmap, mReferenceBitmap); 219 paths[1] = saveBitmap(mBitmap, BitmapAssets.TYPE_FAILED); 220 return paths; 221 } else { 222 return null; 223 } 224 } finally { 225 mBitmap.recycle(); 226 mBitmap = null; 227 } 228 } 229 saveDiffBitmap(Bitmap bitmap1, Bitmap bitmap2)230 private String saveDiffBitmap(Bitmap bitmap1, Bitmap bitmap2) { 231 int width = Math.max(bitmap1.getWidth(), bitmap2.getWidth()); 232 int height = Math.max(bitmap1.getHeight(), bitmap2.getHeight()); 233 Bitmap diff = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 234 235 try { 236 for (int i = 0; i < width; i++) { 237 for (int j = 0; j < height; j++) { 238 boolean inBounds1 = i < bitmap1.getWidth() && j < bitmap1.getHeight(); 239 boolean inBounds2 = i < bitmap2.getWidth() && j < bitmap2.getHeight(); 240 int color; 241 242 if (inBounds1 && inBounds2) { 243 int color1 = bitmap1.getPixel(i, j); 244 int color2 = bitmap2.getPixel(i, j); 245 color = color1 == color2 ? color1 : Color.RED; 246 } else if (inBounds1 && !inBounds2) { 247 color = Color.BLUE; 248 } else if (!inBounds1 && inBounds2) { 249 color = Color.GREEN; 250 } else { 251 color = Color.MAGENTA; 252 } 253 diff.setPixel(i, j, color); 254 } 255 } 256 257 return saveBitmap(diff, BitmapAssets.TYPE_DIFF); 258 } finally { 259 diff.recycle(); 260 } 261 } 262 263 @Override 264 protected void onPostExecute(String[] paths) { 265 String message = mSame 266 ? getString(R.string.comparison_success) 267 : getString(R.string.comparison_failure, paths[0], paths[1]); 268 finishWithResult(mSame, message); 269 } 270 } 271 272 private Bitmap getBitmap() { 273 Log.i(TAG, "Getting bitmap for " + mBitmapName); 274 Bitmap bitmap = Bitmap.createBitmap(mTestView.getWidth(), mTestView.getHeight(), 275 Bitmap.Config.ARGB_8888); 276 Canvas canvas = new Canvas(bitmap); 277 mTestView.draw(canvas); 278 return bitmap; 279 } 280 281 private String saveBitmap(Bitmap bitmap, int type) { 282 try { 283 Log.i(TAG, "Saving bitmap for " + mBitmapName); 284 return BitmapAssets.saveBitmap(bitmap, mBitmapName, type); 285 } catch (FileNotFoundException e) { 286 Log.e(TAG, "FileNotFoundException while saving " + mBitmapName, e); 287 return null; 288 } catch (IOException e) { 289 Log.e(TAG, "IOException while saving " + mBitmapName, e); 290 return null; 291 } 292 } 293 294 private void finishWithResult(boolean success, String message) { 295 Intent data = new Intent(); 296 data.putExtra(EXTRA_SUCCESS, success); 297 data.putExtra(EXTRA_MESSAGE, message); 298 data.putExtra(EXTRA_BITMAP_NAME, mBitmapName); 299 setResult(RESULT_OK, data); 300 finish(); 301 } 302 } 303