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