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