1 /* 2 * Copyright (C) 2013 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.cts.verifier.camera.fov; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.graphics.Color; 26 import android.hardware.Camera; 27 import android.hardware.Camera.PictureCallback; 28 import android.hardware.Camera.ShutterCallback; 29 import android.os.Bundle; 30 import android.os.PowerManager; 31 import android.os.PowerManager.WakeLock; 32 import android.util.Log; 33 import android.view.SurfaceHolder; 34 import android.view.SurfaceView; 35 import android.view.View; 36 import android.view.View.OnClickListener; 37 import android.widget.AdapterView; 38 import android.widget.AdapterView.OnItemSelectedListener; 39 import android.widget.ArrayAdapter; 40 import android.widget.Button; 41 import android.widget.Spinner; 42 import android.widget.TextView; 43 import android.widget.Toast; 44 45 import com.android.cts.verifier.R; 46 import com.android.cts.verifier.TestResult; 47 48 import java.io.File; 49 import java.io.FileOutputStream; 50 import java.io.IOException; 51 import java.util.ArrayList; 52 import java.util.List; 53 54 /** 55 * An activity for showing the camera preview and taking a picture. 56 */ 57 public class PhotoCaptureActivity extends Activity 58 implements PictureCallback, SurfaceHolder.Callback { 59 private static final String TAG = PhotoCaptureActivity.class.getSimpleName(); 60 private static final int FOV_REQUEST_CODE = 1006; 61 private static final String PICTURE_FILENAME = "photo.jpg"; 62 private static float mReportedFovDegrees = 0; 63 private float mReportedFovPrePictureTaken = -1; 64 65 private SurfaceView mPreview; 66 private SurfaceHolder mSurfaceHolder; 67 private Spinner mResolutionSpinner; 68 private List<SelectableResolution> mSupportedResolutions; 69 private ArrayAdapter<SelectableResolution> mAdapter; 70 71 private int mCameraId; 72 private Camera mCamera; 73 private Size mSurfaceSize; 74 private boolean mCameraInitialized = false; 75 private boolean mPreviewActive = false; 76 private int mResolutionSpinnerIndex = -1; 77 private WakeLock mWakeLock; 78 private long shutterStartTime; 79 80 private ArrayList<Integer> mPreviewSizeCamerasToProcess = new ArrayList<Integer>(); 81 82 private Dialog mActiveDialog; 83 84 /** 85 * Selected preview size per camera. If null, preview size should be 86 * automatically detected. 87 */ 88 private Size[] mPreviewSizes = null; 89 getPictureFile(Context context)90 public static File getPictureFile(Context context) { 91 return new File(context.getExternalCacheDir(), PICTURE_FILENAME); 92 } 93 getReportedFovDegrees()94 public static float getReportedFovDegrees() { 95 return mReportedFovDegrees; 96 } 97 98 @Override onCreate(Bundle savedInstanceState)99 protected void onCreate(Bundle savedInstanceState) { 100 super.onCreate(savedInstanceState); 101 setContentView(R.layout.camera_fov_calibration_photo_capture); 102 103 mPreview = (SurfaceView) findViewById(R.id.camera_fov_camera_preview); 104 mSurfaceHolder = mPreview.getHolder(); 105 mSurfaceHolder.addCallback(this); 106 107 // This is required for older versions of Android hardware. 108 mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 109 110 TextView textView = (TextView) findViewById(R.id.camera_fov_tap_to_take_photo); 111 textView.setTextColor(Color.WHITE); 112 113 Button setupButton = (Button) findViewById(R.id.camera_fov_settings_button); 114 setupButton.setOnClickListener(new OnClickListener() { 115 116 @Override 117 public void onClick(View v) { 118 startActivity(new Intent( 119 PhotoCaptureActivity.this, CalibrationPreferenceActivity.class)); 120 } 121 }); 122 123 Button changePreviewSizeButton = (Button) findViewById( 124 R.id.camera_fov_change_preview_size_button); 125 changePreviewSizeButton.setOnClickListener(new OnClickListener() { 126 @Override 127 public void onClick(View v) { 128 // Stop camera until preview sizes have been obtained. 129 if (mCamera != null) { 130 mCamera.stopPreview(); 131 mCamera.release(); 132 mCamera = null; 133 } 134 135 mPreviewSizeCamerasToProcess.clear(); 136 mPreviewSizes = new Size[Camera.getNumberOfCameras()]; 137 for (int cameraId = 0; cameraId < Camera.getNumberOfCameras(); ++cameraId) { 138 mPreviewSizeCamerasToProcess.add(cameraId); 139 } 140 showNextDialogToChoosePreviewSize(); 141 } 142 }); 143 144 View previewView = findViewById(R.id.camera_fov_preview_overlay); 145 previewView.setOnClickListener(new OnClickListener() { 146 @Override 147 public void onClick(View v) { 148 shutterStartTime = System.currentTimeMillis(); 149 150 mCamera.takePicture(new ShutterCallback() { 151 @Override 152 public void onShutter() { 153 long dT = System.currentTimeMillis() - shutterStartTime; 154 Log.d("CTS", "Shutter Lag: " + dT); 155 } 156 }, null, PhotoCaptureActivity.this); 157 } 158 }); 159 160 mResolutionSpinner = (Spinner) findViewById(R.id.camera_fov_resolution_selector); 161 mResolutionSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { 162 @Override 163 public void onItemSelected( 164 AdapterView<?> parent, View view, int position, long id) { 165 if (mSupportedResolutions != null) { 166 SelectableResolution resolution = mSupportedResolutions.get(position); 167 168 switchToCamera(resolution.cameraId, false); 169 170 Camera.Parameters params = mCamera.getParameters(); 171 params.setPictureSize(resolution.width, resolution.height); 172 mCamera.setParameters(params); 173 174 // It should be guaranteed that the FOV is correctly updated after setParameters(). 175 mReportedFovPrePictureTaken = mCamera.getParameters().getHorizontalViewAngle(); 176 177 mResolutionSpinnerIndex = position; 178 initializeCamera(); 179 } 180 } 181 182 @Override 183 public void onNothingSelected(AdapterView<?> arg0) {} 184 }); 185 } 186 187 @Override onResume()188 protected void onResume() { 189 super.onResume(); 190 // Keep the device from going to sleep. 191 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 192 mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); 193 mWakeLock.acquire(); 194 195 if (mSupportedResolutions == null) { 196 mSupportedResolutions = new ArrayList<SelectableResolution>(); 197 int numCameras = Camera.getNumberOfCameras(); 198 for (int cameraId = 0; cameraId < numCameras; ++cameraId) { 199 Camera camera = Camera.open(cameraId); 200 201 // Get the supported picture sizes and fill the spinner. 202 List<Camera.Size> supportedSizes = 203 camera.getParameters().getSupportedPictureSizes(); 204 for (Camera.Size size : supportedSizes) { 205 mSupportedResolutions.add( 206 new SelectableResolution(cameraId, size.width, size.height)); 207 } 208 camera.release(); 209 } 210 } 211 212 // Find the first untested entry. 213 for (mResolutionSpinnerIndex = 0; 214 mResolutionSpinnerIndex < mSupportedResolutions.size(); 215 mResolutionSpinnerIndex++) { 216 if (!mSupportedResolutions.get(mResolutionSpinnerIndex).tested) { 217 break; 218 } 219 } 220 221 mAdapter = new ArrayAdapter<SelectableResolution>( 222 this, android.R.layout.simple_spinner_dropdown_item, 223 mSupportedResolutions); 224 mResolutionSpinner.setAdapter(mAdapter); 225 226 mResolutionSpinner.setSelection(mResolutionSpinnerIndex); 227 setResult(RESULT_CANCELED); 228 } 229 230 @Override onPause()231 public void onPause() { 232 if (mCamera != null) { 233 if (mPreviewActive) { 234 mCamera.stopPreview(); 235 } 236 237 mCamera.release(); 238 mCamera = null; 239 } 240 mPreviewActive = false; 241 mWakeLock.release(); 242 super.onPause(); 243 } 244 245 @Override onPictureTaken(byte[] data, Camera camera)246 public void onPictureTaken(byte[] data, Camera camera) { 247 File pictureFile = getPictureFile(this); 248 Camera.Parameters params = mCamera.getParameters(); 249 mReportedFovDegrees = params.getHorizontalViewAngle(); 250 251 // Show error if FOV does not match the value reported before takePicture(). 252 if (mReportedFovPrePictureTaken != mReportedFovDegrees) { 253 mSupportedResolutions.get(mResolutionSpinnerIndex).tested = true; 254 mSupportedResolutions.get(mResolutionSpinnerIndex).passed = false; 255 256 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); 257 dialogBuilder.setTitle(R.string.camera_fov_reported_fov_problem); 258 dialogBuilder.setNeutralButton( 259 android.R.string.ok, new DialogInterface.OnClickListener() { 260 @Override 261 public void onClick(DialogInterface dialog, int which) { 262 if (mActiveDialog != null) { 263 mActiveDialog.dismiss(); 264 mActiveDialog = null; 265 initializeCamera(); 266 } 267 } 268 }); 269 270 String message = getResources().getString(R.string.camera_fov_reported_fov_problem_message); 271 dialogBuilder.setMessage(String.format(message, mReportedFovPrePictureTaken, mReportedFovDegrees)); 272 mActiveDialog = dialogBuilder.show(); 273 return; 274 } 275 276 try { 277 FileOutputStream fos = new FileOutputStream(pictureFile); 278 fos.write(data); 279 fos.close(); 280 Log.d(TAG, "File saved to " + pictureFile.getAbsolutePath()); 281 282 // Start activity which will use the taken picture to determine the 283 // FOV. 284 startActivityForResult(new Intent(this, DetermineFovActivity.class), 285 FOV_REQUEST_CODE + mResolutionSpinnerIndex, null); 286 } catch (IOException e) { 287 Log.e(TAG, "Could not save picture file.", e); 288 Toast.makeText(this, "Could not save picture file: " + e.getMessage(), 289 Toast.LENGTH_LONG).show(); 290 return; 291 } 292 } 293 294 @Override onActivityResult(int requestCode, int resultCode, Intent data)295 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 296 if (resultCode != RESULT_OK) { 297 return; 298 } 299 int testIndex = requestCode - FOV_REQUEST_CODE; 300 SelectableResolution res = mSupportedResolutions.get(testIndex); 301 res.tested = true; 302 float reportedFOV = CtsTestHelper.getReportedFOV(data); 303 float measuredFOV = CtsTestHelper.getMeasuredFOV(data); 304 res.measuredFOV = measuredFOV; 305 if (CtsTestHelper.isResultPassed(reportedFOV, measuredFOV)) { 306 res.passed = true; 307 } 308 309 boolean allTested = true; 310 for (int i = 0; i < mSupportedResolutions.size(); i++) { 311 if (!mSupportedResolutions.get(i).tested) { 312 allTested = false; 313 break; 314 } 315 } 316 if (!allTested) { 317 mAdapter.notifyDataSetChanged(); 318 return; 319 } 320 321 boolean allPassed = true; 322 for (int i = 0; i < mSupportedResolutions.size(); i++) { 323 if (!mSupportedResolutions.get(i).passed) { 324 allPassed = false; 325 break; 326 } 327 } 328 if (allPassed) { 329 TestResult.setPassedResult(this, getClass().getName(), 330 CtsTestHelper.getTestDetails(mSupportedResolutions)); 331 } else { 332 TestResult.setFailedResult(this, getClass().getName(), 333 CtsTestHelper.getTestDetails(mSupportedResolutions)); 334 } 335 finish(); 336 } 337 338 @Override surfaceChanged( SurfaceHolder holder, int format, int width, int height)339 public void surfaceChanged( 340 SurfaceHolder holder, int format, int width, int height) { 341 mSurfaceSize = new Size(width, height); 342 initializeCamera(); 343 } 344 345 @Override surfaceCreated(SurfaceHolder holder)346 public void surfaceCreated(SurfaceHolder holder) { 347 // Nothing to do. 348 } 349 350 @Override surfaceDestroyed(SurfaceHolder holder)351 public void surfaceDestroyed(SurfaceHolder holder) { 352 // Nothing to do. 353 } 354 showNextDialogToChoosePreviewSize()355 private void showNextDialogToChoosePreviewSize() { 356 final int cameraId = mPreviewSizeCamerasToProcess.remove(0); 357 358 Camera camera = Camera.open(cameraId); 359 final List<Camera.Size> sizes = camera.getParameters() 360 .getSupportedPreviewSizes(); 361 String[] choices = new String[sizes.size()]; 362 for (int i = 0; i < sizes.size(); ++i) { 363 Camera.Size size = sizes.get(i); 364 choices[i] = size.width + " x " + size.height; 365 } 366 367 final AlertDialog.Builder builder = new AlertDialog.Builder(this); 368 String dialogTitle = String.format( 369 getResources().getString(R.string.camera_fov_choose_preview_size_for_camera), 370 cameraId); 371 builder.setTitle( 372 dialogTitle). 373 setOnCancelListener(new DialogInterface.OnCancelListener() { 374 @Override 375 public void onCancel(DialogInterface arg0) { 376 // User cancelled preview size selection. 377 mPreviewSizes = null; 378 switchToCamera(mCameraId, true); 379 } 380 }). 381 setSingleChoiceItems(choices, 0, new DialogInterface.OnClickListener() { 382 @Override 383 public void onClick(DialogInterface dialog, int which) { 384 Camera.Size size = sizes.get(which); 385 mPreviewSizes[cameraId] = new Size( 386 size.width, size.height); 387 dialog.dismiss(); 388 389 if (mPreviewSizeCamerasToProcess.isEmpty()) { 390 // We're done, re-initialize camera. 391 switchToCamera(mCameraId, true); 392 } else { 393 // Process other cameras. 394 showNextDialogToChoosePreviewSize(); 395 } 396 } 397 }).create().show(); 398 camera.release(); 399 } 400 initializeCamera()401 private void initializeCamera() { 402 if (mCamera == null || mSurfaceHolder.getSurface() == null) { 403 return; 404 } 405 406 try { 407 mCamera.setPreviewDisplay(mSurfaceHolder); 408 } catch (Throwable t) { 409 Log.e("TAG", "Could not set preview display", t); 410 Toast.makeText(this, t.getMessage(), Toast.LENGTH_LONG).show(); 411 return; 412 } 413 Camera.Parameters params = setCameraParams(mCamera); 414 415 // Either use chosen preview size for current camera or automatically 416 // choose preview size based on view dimensions. 417 Size selectedPreviewSize = (mPreviewSizes != null) ? mPreviewSizes[mCameraId] : 418 getBestPreviewSize(mSurfaceSize.width, mSurfaceSize.height, params); 419 if (selectedPreviewSize != null) { 420 params.setPreviewSize(selectedPreviewSize.width, selectedPreviewSize.height); 421 mCamera.setParameters(params); 422 mCameraInitialized = true; 423 } 424 startPreview(); 425 } 426 startPreview()427 private void startPreview() { 428 if (mCameraInitialized && mCamera != null) { 429 mCamera.startPreview(); 430 mPreviewActive = true; 431 } 432 } 433 switchToCamera(int cameraId, boolean initializeCamera)434 private void switchToCamera(int cameraId, boolean initializeCamera) { 435 if (mCamera != null) { 436 mCamera.stopPreview(); 437 mCamera.release(); 438 } 439 mCameraId = cameraId; 440 mCamera = Camera.open(cameraId); 441 442 if (initializeCamera){ 443 initializeCamera(); 444 } 445 } 446 447 /** 448 * Get the best supported focus mode. 449 * 450 * @param camera - Android camera object. 451 * @return the best supported focus mode. 452 */ getFocusMode(Camera camera)453 private static String getFocusMode(Camera camera) { 454 List<String> modes = camera.getParameters().getSupportedFocusModes(); 455 if (modes != null) { 456 if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) { 457 Log.v(TAG, "Using Focus mode infinity"); 458 return Camera.Parameters.FOCUS_MODE_INFINITY; 459 } 460 if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) { 461 Log.v(TAG, "Using Focus mode fixed"); 462 return Camera.Parameters.FOCUS_MODE_FIXED; 463 } 464 } 465 Log.v(TAG, "Using Focus mode auto."); 466 return Camera.Parameters.FOCUS_MODE_AUTO; 467 } 468 469 /** 470 * Set the common camera parameters on the given camera and returns the 471 * parameter object for further modification, if needed. 472 */ setCameraParams(Camera camera)473 private static Camera.Parameters setCameraParams(Camera camera) { 474 // The picture size is taken and set from the spinner selection 475 // callback. 476 Camera.Parameters params = camera.getParameters(); 477 params.setJpegThumbnailSize(0, 0); 478 params.setJpegQuality(100); 479 params.setFocusMode(getFocusMode(camera)); 480 params.setZoom(0); 481 return params; 482 } 483 getBestPreviewSize( int width, int height, Camera.Parameters parameters)484 private Size getBestPreviewSize( 485 int width, int height, Camera.Parameters parameters) { 486 Size result = null; 487 488 for (Camera.Size size : parameters.getSupportedPreviewSizes()) { 489 if (size.width <= width && size.height <= height) { 490 if (result == null) { 491 result = new Size(size.width, size.height); 492 } else { 493 int resultArea = result.width * result.height; 494 int newArea = size.width * size.height; 495 496 if (newArea > resultArea) { 497 result = new Size(size.width, size.height); 498 } 499 } 500 } 501 } 502 return result; 503 } 504 } 505