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 package com.android.cts.verifier.camera.analyzer; 17 18 import com.android.cts.verifier.PassFailButtons; 19 import com.android.cts.verifier.R; 20 21 import android.app.Activity; 22 import android.graphics.Bitmap; 23 import android.graphics.BitmapFactory; 24 import android.graphics.Color; 25 import android.graphics.ImageFormat; 26 import android.hardware.Camera; 27 import android.hardware.Camera.CameraInfo; 28 import android.hardware.Camera.Size; 29 import android.os.AsyncTask; 30 import android.os.Bundle; 31 import android.text.Html; 32 import android.text.method.ScrollingMovementMethod; 33 import android.util.Log; 34 import android.view.LayoutInflater; 35 import android.view.Menu; 36 import android.view.MenuInflater; 37 import android.view.MenuItem; 38 import android.view.SurfaceHolder; 39 import android.view.SurfaceView; 40 import android.view.View; 41 import android.view.ViewGroup; 42 import android.widget.AdapterView; 43 import android.widget.ArrayAdapter; 44 import android.widget.ImageView; 45 import android.widget.ListView; 46 import android.widget.TextView; 47 import android.widget.Button; 48 import android.os.PowerManager; 49 import android.os.PowerManager.WakeLock; 50 import android.content.Context; 51 52 import java.io.IOException; 53 import java.lang.Thread; 54 import java.util.List; 55 56 /** 57 * Controls the UI activities of the camera quality test app. It is created 58 * as soon as the app started. Users can launch different quality tests with 59 * the buttons in the UI. This class will manage the threading for different 60 * tests. Also it will provide debug output or debug text results for tests. 61 */ 62 public class CameraAnalyzerActivity extends PassFailButtons.Activity { 63 64 private static final String TAG = "CameraAnalyzer"; 65 private SurfaceView mCameraView; 66 private ImageView mResultView; 67 private Button mFindCheckerButton; 68 private Button mExposureCompensationButton; 69 private Button mWhiteBalanceButton; 70 private Button mAutoLockButton; 71 private Button mMeteringButton; 72 private ListView mTestList; 73 private TwoColumnAdapter mAdapter; 74 75 private Camera mCamera; 76 private int mCameraIdx = 0; 77 private boolean mIsCameraOpen = false; 78 79 private PowerManager mPowerManager; 80 private PowerManager.WakeLock mWakeLock; 81 82 private boolean mProcessingPicture = false; 83 private boolean mTestInProgress = false; 84 private final Object mProcessingTest = new Object(); 85 private CameraTests mCurrentTest = null; 86 87 private long mCheckerCenterAddress; 88 private long mCheckerRadiusAddress; 89 90 private String mResultReport = ""; 91 static final String[] TESTS = new String[] {"Test1", "Test2"}; 92 93 @Override 94 public void onCreate(Bundle savedInstanceState) { 95 super.onCreate(savedInstanceState); 96 setContentView(R.layout.ca_main); 97 setPassFailButtonClickListeners(); 98 setInfoResources(R.string.camera_analyzer, R.string.ca_info, -1); 99 100 mFindCheckerButton = (Button) findViewById(R.id.findcheckerboardbutton); 101 mExposureCompensationButton = (Button) findViewById(R.id.exposurecompensationbutton); 102 mWhiteBalanceButton = (Button) findViewById(R.id.whitebalancebutton); 103 mAutoLockButton = (Button) findViewById(R.id.lockbutton); 104 mMeteringButton = (Button) findViewById(R.id.meteringbutton); 105 mCameraView = (SurfaceView) findViewById(R.id.cameraview); 106 mResultView = (ImageView) findViewById(R.id.resultview); 107 mTestList = (ListView) findViewById(R.id.ca_tests); 108 mAdapter = new TwoColumnAdapter(this); 109 110 // Initialize the list view. 111 initializeAdapter(); 112 mTestList.setAdapter(mAdapter); 113 mTestList.setOnItemClickListener(mListListener); 114 115 mFindCheckerButton.setOnClickListener(mFindCheckerListener); 116 mExposureCompensationButton.setOnClickListener(mExposureCompensationListener); 117 mWhiteBalanceButton.setOnClickListener(mWhiteBalanceListener); 118 mAutoLockButton.setOnClickListener(mAutoLockListener); 119 mMeteringButton.setOnClickListener(mMeteringListener); 120 mCameraView.getHolder().addCallback(mSurfaceChangeListener); 121 122 // Disables all test buttons except the color checker test one. 123 // They will be enabled after the color checker is located. 124 mExposureCompensationButton.setEnabled(false); 125 mWhiteBalanceButton.setEnabled(false); 126 mAutoLockButton.setEnabled(false); 127 mMeteringButton.setEnabled(false); 128 } 129 130 @Override 131 public void onResume() { 132 super.onResume(); 133 134 openCamera(mCameraIdx); 135 Camera.Parameters params = mCamera.getParameters(); 136 params.setPictureFormat(ImageFormat.JPEG); 137 params.setPictureSize(640, 480); 138 mCamera.setParameters(params); 139 Log.v(TAG, "Set resolution to 640*480"); 140 } 141 142 @Override 143 public void onPause() { 144 super.onPause(); 145 CameraTests.getCamera().release(); 146 mIsCameraOpen = false; 147 } 148 149 @Override 150 public boolean onCreateOptionsMenu(Menu menu) { 151 MenuInflater inflater = getMenuInflater(); 152 inflater.inflate(R.menu.ca_menu, menu); 153 154 Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); 155 int cameraCount = Camera.getNumberOfCameras(); 156 for (int camIdx = 0; camIdx < cameraCount; ++camIdx) { 157 MenuItem cameraMenuItem = menu.add(0, camIdx, Menu.NONE, 158 String.format("Open Camera %d", camIdx)); 159 } 160 return true; 161 } 162 163 @Override 164 public boolean onOptionsItemSelected(MenuItem item) { 165 if (item.getItemId() != mCameraIdx) { 166 mCameraIdx = item.getItemId(); 167 new SwitchCameraTask().execute(mCameraIdx); 168 } 169 return false; 170 } 171 172 private class SwitchCameraTask extends AsyncTask<Integer, Void, Void> { 173 @Override 174 protected Void doInBackground(Integer... camIdx) { 175 if (mTestInProgress) { 176 synchronized (mProcessingTest) { 177 try{ 178 Log.v(TAG, "Waiting for test to finish"); 179 mProcessingTest.wait(); 180 } catch (InterruptedException e){ 181 Log.v(TAG, "test wait fails!"); 182 } 183 } 184 } 185 186 openCamera((int)camIdx[0]); 187 return null; 188 } 189 } 190 191 private synchronized void openCamera(int camIdx) { 192 if (mIsCameraOpen) { 193 CameraTests.getCamera().release(); 194 Log.v(TAG, "Releasing the cameratests camera"); 195 } 196 try { 197 mCamera = Camera.open(camIdx); 198 mIsCameraOpen = true; 199 } catch (RuntimeException e) { 200 throw new RuntimeException("Failed to open the camera", e); 201 } 202 203 try { 204 mCamera.setPreviewDisplay(mCameraView.getHolder()); 205 } catch (IOException e) { 206 throw new RuntimeException("Unable to connect camera to display: " + e); 207 } 208 mCamera.startPreview(); 209 CameraTests.setCamera(mCamera); 210 211 ColorCheckerTest.getSingletonTest().updateCamera(); 212 WhiteBalanceTest.getSingletonTest().updateCamera(); 213 ExposureCompensationTest.getSingletonTest().updateCamera(); 214 MeteringTest.getSingletonTest().updateCamera(); 215 AutoLockTest.getSingletonTest().updateCamera(); 216 } 217 218 public Camera getCameraInstance() { 219 return mCamera; 220 } 221 222 public void disableAll() { 223 mExposureCompensationButton.setEnabled(false); 224 mWhiteBalanceButton.setEnabled(false); 225 mAutoLockButton.setEnabled(false); 226 mMeteringButton.setEnabled(false); 227 mFindCheckerButton.setEnabled(false); 228 } 229 230 public void enableAll() { 231 mExposureCompensationButton.setEnabled(true); 232 mWhiteBalanceButton.setEnabled(true); 233 mAutoLockButton.setEnabled(true); 234 mMeteringButton.setEnabled(true); 235 mFindCheckerButton.setEnabled(true); 236 } 237 238 /** 239 * Provides an abstraction for the Camera tests. The camera tests will 240 * run in the background and the results will be shown in the UI thread 241 * after the tests are processed. 242 */ 243 private class DebugOutputProcessingTask extends AsyncTask<Integer, 244 Integer, 245 Integer> { 246 @Override 247 protected Integer doInBackground(Integer... cameraTestIndex) { 248 Log.v(TAG, "Do in Background started!"); 249 250 mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); 251 mWakeLock = mPowerManager.newWakeLock( 252 PowerManager.SCREEN_DIM_WAKE_LOCK, "CameraQualityTest"); 253 mWakeLock.acquire(); 254 255 mTestInProgress = true; 256 257 // Processes the camera tests one by one and publishes their 258 // debug output or debug text results after each test is done. 259 mCurrentTest.run((int)cameraTestIndex[0]); 260 publishProgress(cameraTestIndex); 261 262 Log.v(TAG, "Do in Background thread returns!"); 263 return cameraTestIndex[0]; 264 } 265 266 @Override 267 protected void onProgressUpdate(Integer... cameraTestIndex) { 268 Log.v(TAG, "Prepare to get debug output!"); 269 270 // Copies the debug output image or text results to the UI. 271 mResultView.setImageBitmap(mCurrentTest.getDebugOutput()); 272 mResultReport += (mCurrentTest.getTestName() + mCurrentTest.getDebugText()); 273 mAdapter.notifyDataSetChanged(); 274 } 275 276 @Override 277 protected void onPostExecute(Integer cameraTestIndex) { 278 279 // If the test is to find the color checker, copy the memory 280 // address of the found color checker centers and radius to the 281 // CameraTests class' static fields. 282 if (mCurrentTest.copyCheckerAddress()) { 283 mCheckerCenterAddress = CameraTests.getCheckerCenter(); 284 mCheckerRadiusAddress = CameraTests.getCheckerRadius(); 285 } 286 287 if (mCurrentTest.copyCheckerAddress() || 288 !mCurrentTest.getTestName().contains("Color Checker")) { 289 // Enables the button of all other tests after the color checker 290 // is found. Now the user can start all other available tests. 291 enableAll(); 292 } 293 294 mWakeLock.release(); 295 mTestInProgress = false; 296 synchronized (mProcessingTest) { 297 mProcessingTest.notifyAll(); 298 } 299 300 } 301 } 302 303 // Creates and runs a new test to find color checker in the captured image. 304 // It is invoked when users press the Find color checker button in the UI. 305 private View.OnClickListener mFindCheckerListener = new View.OnClickListener() { 306 @Override 307 public void onClick(View v) { 308 Log.v(TAG, "Running new color checker finding tests!"); 309 ColorCheckerTest colorCheckerTest = ColorCheckerTest.getSingletonTest(); 310 311 mCurrentTest = colorCheckerTest; 312 initializeAdapter(); 313 } 314 }; 315 316 // Creates and runs a new test to test the exposure compensation function. 317 // It is invoked when users press the Exposure Compensation Test button 318 // in the UI. 319 private View.OnClickListener mExposureCompensationListener = new View.OnClickListener() { 320 @Override 321 public void onClick(View v) { 322 Log.v(TAG, "Running new exposure compensation tests!"); 323 324 ExposureCompensationTest exposureCompensationTest = 325 ExposureCompensationTest.getSingletonTest(); 326 327 mCurrentTest = exposureCompensationTest; 328 initializeAdapter(); 329 330 // Loads the memory address of the checker centers and radius 331 // from the this class and set the two values for the new test. 332 ExposureCompensationTest.setCheckerAddress(mCheckerCenterAddress, 333 mCheckerRadiusAddress); 334 } 335 }; 336 337 // Creates and runs a new test to test the white balance function. 338 // It is invoked when users press the White Balance Test button in the UI. 339 private View.OnClickListener mWhiteBalanceListener = new View.OnClickListener() { 340 @Override 341 public void onClick(View v) { 342 Log.v(TAG, "Running new white balance tests!"); 343 344 WhiteBalanceTest whiteBalanceTest = WhiteBalanceTest.getSingletonTest(); 345 346 mCurrentTest = whiteBalanceTest; 347 initializeAdapter(); 348 349 // Loads the memory address of the checker centers and radius 350 // from the this class and set the two values for the new test. 351 WhiteBalanceTest.setCheckerAddress(mCheckerCenterAddress, mCheckerRadiusAddress); 352 } 353 }; 354 355 // Creates and runs a new test to test the camera metering function. 356 // It is invoked when users press the Metering Test button in the UI. 357 private View.OnClickListener mMeteringListener = new View.OnClickListener() { 358 @Override 359 public void onClick(View v) { 360 Log.v(TAG, "Running new metering tests!"); 361 362 MeteringTest meteringTest = MeteringTest.getSingletonTest(); 363 364 mCurrentTest = meteringTest; 365 initializeAdapter(); 366 367 // Loads the memory address of the checker centers and radius 368 // from the this class and set the two values for the new test. 369 MeteringTest.setCheckerAddress(mCheckerCenterAddress, mCheckerRadiusAddress); 370 } 371 }; 372 373 // Creates and runs new tests to test the camera auto exposure lock. 374 // It is invoked when users press the AWB and AE Lock Test button 375 // in the UI. 376 private View.OnClickListener mAutoLockListener = new View.OnClickListener() { 377 @Override 378 public void onClick(View v) { 379 Log.v(TAG, "Running New auto exposure/wb lock tests!"); 380 381 // Loads the memory address of the checker centers and radius 382 // from the this class and set the two values for the new test. 383 AutoLockTest.setCheckerAddress(mCheckerCenterAddress, mCheckerRadiusAddress); 384 385 // Construct all base case test scenearios for the Auto Lock test. 386 // Detailed documentation on each test can be found in native code. 387 AutoLockTest autoLockTest = AutoLockTest.getSingletonTest(); 388 autoLockTest.setActivity(CameraAnalyzerActivity.this); 389 390 mCurrentTest = autoLockTest; 391 initializeAdapter(); 392 393 } 394 }; 395 396 // Creates a list listner that launches the experiment with the user's click 397 private AdapterView.OnItemClickListener mListListener = new AdapterView.OnItemClickListener() { 398 @Override 399 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 400 Log.v(TAG, String.format("Item %d selected!", position)); 401 if (!mTestInProgress) { 402 DebugOutputProcessingTask captureTask = new DebugOutputProcessingTask(); 403 disableAll(); 404 captureTask.execute(position); 405 } 406 } 407 }; 408 409 private SurfaceHolder.Callback mSurfaceChangeListener = 410 new SurfaceHolder.Callback() { 411 412 // Sets the aspect ratio of the camera preview to 4:3 413 @Override 414 public void surfaceChanged(SurfaceHolder holder, 415 int format, 416 int width, 417 int height) { 418 int x = mCameraView.getWidth(); 419 int y = mCameraView.getHeight(); 420 Log.v(TAG, String.format("Measures are %d, %d", x, y)); 421 422 if ( x > 4.0 / 3.0 * y) { 423 android.view.ViewGroup.LayoutParams lp = mCameraView.getLayoutParams(); 424 lp.height = y; 425 lp.width = (int)(4.0 / 3.0 * lp.height); 426 Log.v(TAG, String.format("params are %d, %d", lp.width, lp.height)); 427 mCameraView.setLayoutParams(lp); 428 } 429 430 try { 431 mCamera.setPreviewDisplay(mCameraView.getHolder()); 432 } catch (IOException e) { 433 throw new RuntimeException("Unable to connect camera to display: " + e); 434 } 435 CameraTests.setCameraView(mCameraView); 436 mCamera.startPreview(); 437 } 438 439 @Override 440 public void surfaceCreated(SurfaceHolder holder) {} 441 442 @Override 443 public void surfaceDestroyed(SurfaceHolder holder) {} 444 }; 445 446 @Override 447 public String getTestDetails() { 448 return mResultReport; 449 } 450 451 class TwoColumnAdapter extends ArrayAdapter<String> { 452 TwoColumnAdapter(Context context) { 453 super(context, R.layout.ca_row); 454 } 455 456 @Override 457 public View getView(int position, View convertView, ViewGroup parent) { 458 LayoutInflater inflater = getLayoutInflater(); 459 View row = inflater.inflate(R.layout.ca_row, parent, false); 460 ImageView iconField = (ImageView) row.findViewById(R.id.caTestIcon); 461 TextView nameField = (TextView) row.findViewById(R.id.caTestName); 462 TextView resultField = (TextView) row.findViewById(R.id.caTestResult); 463 if (mCurrentTest != null) { 464 nameField.setText(mCurrentTest.getTestName(position)); 465 int result = mCurrentTest.getResult(position); 466 switch (result) { 467 case CameraTests.CAMERA_TEST_SUCCESS: 468 resultField.setText("Success"); 469 iconField.setBackgroundColor(Color.rgb(0x99,0xCC,00)); 470 resultField.setTextColor(Color.rgb(0x99,0xCC,00)); 471 break; 472 case CameraTests.CAMERA_TEST_FAILURE: 473 resultField.setText("Failed!"); 474 iconField.setBackgroundColor(Color.rgb(0xFF,0x44,0x44)); 475 resultField.setTextColor(Color.rgb(0xFF,0x44,0x44)); 476 break; 477 case CameraTests.CAMERA_TEST_NOT_RUN: 478 resultField.setText("Tap to run"); 479 iconField.setBackgroundColor(Color.rgb(0x00,0x99,0xCC)); 480 resultField.setTextColor(Color.rgb(0x33,0xB5,0xE5)); 481 break; 482 } 483 } 484 return row; 485 } 486 } 487 488 private void initializeAdapter() { 489 mAdapter.clear(); 490 if (mCurrentTest != null) { 491 for (int i = 0; i < mCurrentTest.getNumTests(); ++i) { 492 mAdapter.add(mCurrentTest.getTestName(i)); 493 } 494 } 495 } 496 497 public int getCameraIdx() { 498 return mCameraIdx; 499 } 500 } 501