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.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 
     90     public static File getPictureFile(Context context) {
     91         return new File(context.getExternalCacheDir(), PICTURE_FILENAME);
     92     }
     93 
     94     public static float getReportedFovDegrees() {
     95         return mReportedFovDegrees;
     96     }
     97 
     98     @Override
     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
    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
    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
    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
    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
    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
    346     public void surfaceCreated(SurfaceHolder holder) {
    347         // Nothing to do.
    348     }
    349 
    350     @Override
    351     public void surfaceDestroyed(SurfaceHolder holder) {
    352         // Nothing to do.
    353     }
    354 
    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 
    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 
    427     private void startPreview() {
    428         if (mCameraInitialized && mCamera != null) {
    429             mCamera.startPreview();
    430             mPreviewActive = true;
    431         }
    432     }
    433 
    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      */
    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      */
    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 
    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