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