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.content.res.Configuration;
26 import android.graphics.Color;
27 import android.graphics.Matrix;
28 import android.graphics.SurfaceTexture;
29 import android.hardware.Camera;
30 import android.hardware.Camera.PictureCallback;
31 import android.hardware.Camera.ShutterCallback;
32 import android.hardware.camera2.CameraAccessException;
33 import android.hardware.camera2.CameraCharacteristics;
34 import android.hardware.camera2.CameraManager;
35 import android.os.Bundle;
36 import android.os.PowerManager;
37 import android.os.PowerManager.WakeLock;
38 import android.util.Log;
39 import android.view.Surface;
40 import android.view.TextureView;
41 import android.view.View;
42 import android.view.View.OnClickListener;
43 import android.widget.AdapterView;
44 import android.widget.AdapterView.OnItemSelectedListener;
45 import android.widget.ArrayAdapter;
46 import android.widget.Button;
47 import android.widget.Spinner;
48 import android.widget.TextView;
49 import android.widget.Toast;
50 
51 import com.android.cts.verifier.R;
52 import com.android.cts.verifier.TestResult;
53 
54 import java.io.File;
55 import java.io.FileOutputStream;
56 import java.io.IOException;
57 import java.util.ArrayList;
58 import java.util.List;
59 
60 /**
61  * An activity for showing the camera preview and taking a picture.
62  */
63 public class PhotoCaptureActivity extends Activity
64         implements PictureCallback, TextureView.SurfaceTextureListener {
65     private static final String TAG = PhotoCaptureActivity.class.getSimpleName();
66     private static final int FOV_REQUEST_CODE = 1006;
67     private static final String PICTURE_FILENAME = "photo.jpg";
68     private static float mReportedFovDegrees = 0;
69     private float mReportedFovPrePictureTaken = -1;
70 
71     private TextureView mPreviewView;
72     private SurfaceTexture mPreviewTexture;
73     private int mPreviewTexWidth;
74     private int mPreviewTexHeight;
75 
76     private Spinner mResolutionSpinner;
77     private List<SelectableResolution> mSupportedResolutions;
78     private ArrayAdapter<SelectableResolution> mAdapter;
79 
80     private SelectableResolution mSelectedResolution;
81     private Camera mCamera;
82     private boolean mCameraInitialized = false;
83     private boolean mPreviewActive = false;
84     private boolean mTakingPicture = false;
85     private int mResolutionSpinnerIndex = -1;
86     private WakeLock mWakeLock;
87     private long shutterStartTime;
88     private int mPreviewOrientation;
89     private int mJpegOrientation;
90 
91     private ArrayList<Integer> mPreviewSizeCamerasToProcess = new ArrayList<Integer>();
92 
93     private Dialog mActiveDialog;
94 
95     /**
96      * Selected preview size per camera. If null, preview size should be
97      * automatically detected.
98      */
99     private Size[] mPreviewSizes = null;
100 
getPictureFile(Context context)101     public static File getPictureFile(Context context) {
102         return new File(context.getExternalCacheDir(), PICTURE_FILENAME);
103     }
104 
getReportedFovDegrees()105     public static float getReportedFovDegrees() {
106         return mReportedFovDegrees;
107     }
108 
109     @Override
onCreate(Bundle savedInstanceState)110     protected void onCreate(Bundle savedInstanceState) {
111         super.onCreate(savedInstanceState);
112         setContentView(R.layout.camera_fov_calibration_photo_capture);
113 
114         int cameraToBeTested = 0;
115         for (int cameraId = 0; cameraId < Camera.getNumberOfCameras(); ++cameraId) {
116             if (!isExternalCamera(cameraId)) {
117                 cameraToBeTested++;
118             }
119         }
120 
121         mPreviewView = (TextureView) findViewById(R.id.camera_fov_camera_preview);
122         mPreviewView.setSurfaceTextureListener(this);
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
189                     // setParameters().
190                     mReportedFovPrePictureTaken = getCameraFov(resolution.cameraId);
191 
192                     mResolutionSpinnerIndex = position;
193                     startPreview();
194                 }
195             }
196 
197             @Override
198             public void onNothingSelected(AdapterView<?> arg0) {}
199         });
200 
201         if (cameraToBeTested == 0) {
202             Log.i(TAG, "No cameras needs to be tested. Setting test pass.");
203             Toast.makeText(this, "No cameras needs to be tested. Test pass.",
204                     Toast.LENGTH_LONG).show();
205 
206             TestResult.setPassedResult(this, getClass().getName(),
207                     "All cameras are external, test skipped!");
208             finish();
209         }
210     }
211 
212     @Override
onResume()213     protected void onResume() {
214         super.onResume();
215         // Keep the device from going to sleep.
216         PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
217         mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
218         mWakeLock.acquire();
219 
220         if (mSupportedResolutions == null) {
221             mSupportedResolutions = new ArrayList<SelectableResolution>();
222             int numCameras = Camera.getNumberOfCameras();
223             for (int cameraId = 0; cameraId < numCameras; ++cameraId) {
224                 if (isExternalCamera(cameraId)) {
225                     continue;
226                 }
227 
228                 Camera camera = Camera.open(cameraId);
229 
230                 // Get the supported picture sizes and fill the spinner.
231                 List<Camera.Size> supportedSizes =
232                         camera.getParameters().getSupportedPictureSizes();
233                 for (Camera.Size size : supportedSizes) {
234                     mSupportedResolutions.add(
235                             new SelectableResolution(cameraId, size.width, size.height));
236                 }
237                 camera.release();
238             }
239         }
240 
241         // Find the first untested entry.
242         for (mResolutionSpinnerIndex = 0;
243                 mResolutionSpinnerIndex < mSupportedResolutions.size();
244                 mResolutionSpinnerIndex++) {
245             if (!mSupportedResolutions.get(mResolutionSpinnerIndex).tested) {
246                 break;
247             }
248         }
249 
250         mAdapter = new ArrayAdapter<SelectableResolution>(
251                 this, android.R.layout.simple_spinner_dropdown_item,
252                 mSupportedResolutions);
253         mResolutionSpinner.setAdapter(mAdapter);
254 
255         mResolutionSpinner.setSelection(mResolutionSpinnerIndex);
256         setResult(RESULT_CANCELED);
257     }
258 
259     @Override
onPause()260     public void onPause() {
261         if (mCamera != null) {
262             if (mPreviewActive) {
263                 mCamera.stopPreview();
264             }
265 
266             mCamera.release();
267             mCamera = null;
268         }
269         mPreviewActive = false;
270         mWakeLock.release();
271         super.onPause();
272     }
273 
274     @Override
onConfigurationChanged(Configuration newConfig)275     public void onConfigurationChanged(Configuration newConfig) {
276         super.onConfigurationChanged(newConfig);
277         this.recreate();
278     }
279 
280     @Override
onPictureTaken(byte[] data, Camera camera)281     public void onPictureTaken(byte[] data, Camera camera) {
282         File pictureFile = getPictureFile(this);
283 
284         mReportedFovDegrees = getCameraFov(mSelectedResolution.cameraId);
285 
286         // Show error if FOV does not match the value reported before takePicture().
287         if (mReportedFovPrePictureTaken != mReportedFovDegrees) {
288             mSupportedResolutions.get(mResolutionSpinnerIndex).tested = true;
289             mSupportedResolutions.get(mResolutionSpinnerIndex).passed = false;
290 
291             AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
292             dialogBuilder.setTitle(R.string.camera_fov_reported_fov_problem);
293             dialogBuilder.setNeutralButton(
294                     android.R.string.ok, new DialogInterface.OnClickListener() {
295                 @Override
296                 public void onClick(DialogInterface dialog, int which) {
297                     if (mActiveDialog != null) {
298                         mActiveDialog.dismiss();
299                         mActiveDialog = null;
300                         initializeCamera();
301                     }
302                 }
303             });
304 
305             String message  = getResources().getString(R.string.camera_fov_reported_fov_problem_message);
306             dialogBuilder.setMessage(String.format(message, mReportedFovPrePictureTaken, mReportedFovDegrees));
307             mActiveDialog = dialogBuilder.show();
308             mTakingPicture = false;
309             return;
310         }
311 
312         try {
313             FileOutputStream fos = new FileOutputStream(pictureFile);
314             fos.write(data);
315             fos.close();
316             Log.d(TAG, "File saved to " + pictureFile.getAbsolutePath());
317 
318             // Start activity which will use the taken picture to determine the
319             // FOV.
320             startActivityForResult(new Intent(this, DetermineFovActivity.class),
321                     FOV_REQUEST_CODE + mResolutionSpinnerIndex, null);
322         } catch (IOException e) {
323             Log.e(TAG, "Could not save picture file.", e);
324             Toast.makeText(this, "Could not save picture file: " + e.getMessage(),
325                     Toast.LENGTH_LONG).show();
326         }
327         mTakingPicture = false;
328     }
329 
330     @Override
onActivityResult(int requestCode, int resultCode, Intent data)331     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
332         if (resultCode != RESULT_OK) {
333             return;
334         }
335         int testIndex = requestCode - FOV_REQUEST_CODE;
336         SelectableResolution res = mSupportedResolutions.get(testIndex);
337         res.tested = true;
338         float reportedFOV = CtsTestHelper.getReportedFOV(data);
339         float measuredFOV = CtsTestHelper.getMeasuredFOV(data);
340         res.measuredFOV = measuredFOV;
341         if (CtsTestHelper.isResultPassed(reportedFOV, measuredFOV)) {
342             res.passed = true;
343         }
344 
345         boolean allTested = true;
346         for (int i = 0; i < mSupportedResolutions.size(); i++) {
347             if (!mSupportedResolutions.get(i).tested) {
348                 allTested = false;
349                 break;
350             }
351         }
352         if (!allTested) {
353             mAdapter.notifyDataSetChanged();
354             return;
355         }
356 
357         boolean allPassed = true;
358         for (int i = 0; i < mSupportedResolutions.size(); i++) {
359             if (!mSupportedResolutions.get(i).passed) {
360                 allPassed = false;
361                 break;
362             }
363         }
364         if (allPassed) {
365             TestResult.setPassedResult(this, getClass().getName(),
366                     CtsTestHelper.getTestDetails(mSupportedResolutions));
367         } else {
368             TestResult.setFailedResult(this, getClass().getName(),
369                     CtsTestHelper.getTestDetails(mSupportedResolutions));
370         }
371         finish();
372     }
373 
374     @Override
onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)375     public void onSurfaceTextureAvailable(SurfaceTexture surface,
376             int width, int height) {
377         mPreviewTexture = surface;
378         mPreviewTexWidth = width;
379         mPreviewTexHeight = height;
380         initializeCamera();
381     }
382 
383     @Override
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)384     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
385         // Ignored, Camera does all the work for us
386     }
387 
388     @Override
onSurfaceTextureDestroyed(SurfaceTexture surface)389     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
390         return true;
391     }
392 
393     @Override
onSurfaceTextureUpdated(SurfaceTexture surface)394     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
395         // Invoked every time there's a new Camera preview frame
396     }
397 
showNextDialogToChoosePreviewSize()398     private void showNextDialogToChoosePreviewSize() {
399         final int cameraId = mPreviewSizeCamerasToProcess.remove(0);
400 
401         Camera camera = Camera.open(cameraId);
402         final List<Camera.Size> sizes = camera.getParameters()
403                 .getSupportedPreviewSizes();
404         String[] choices = new String[sizes.size()];
405         for (int i = 0; i < sizes.size(); ++i) {
406             Camera.Size size = sizes.get(i);
407             choices[i] = size.width + " x " + size.height;
408         }
409 
410         final AlertDialog.Builder builder = new AlertDialog.Builder(this);
411         String dialogTitle = String.format(
412                 getResources().getString(R.string.camera_fov_choose_preview_size_for_camera),
413                 cameraId);
414         builder.setTitle(
415                 dialogTitle).
416                 setOnCancelListener(new DialogInterface.OnCancelListener() {
417                     @Override
418                     public void onCancel(DialogInterface arg0) {
419                         // User cancelled preview size selection.
420                         mPreviewSizes = null;
421                         switchToCamera(mSelectedResolution, true);
422                     }
423                 }).
424                 setSingleChoiceItems(choices, 0, new DialogInterface.OnClickListener() {
425                     @Override
426                     public void onClick(DialogInterface dialog, int which) {
427                         Camera.Size size = sizes.get(which);
428                         mPreviewSizes[cameraId] = new Size(
429                                 size.width, size.height);
430                         dialog.dismiss();
431 
432                         if (mPreviewSizeCamerasToProcess.isEmpty()) {
433                             // We're done, re-initialize camera.
434                             switchToCamera(mSelectedResolution, true);
435                         } else {
436                             // Process other cameras.
437                             showNextDialogToChoosePreviewSize();
438                         }
439                     }
440                 }).create().show();
441         camera.release();
442     }
443 
initializeCamera()444     private void initializeCamera() {
445         initializeCamera(true);
446     }
447 
setPreviewTransform(Size previewSize)448     private void setPreviewTransform(Size previewSize) {
449         int sensorRotation = mPreviewOrientation;
450         float selectedPreviewAspectRatio;
451         if (sensorRotation == 0 || sensorRotation == 180) {
452             selectedPreviewAspectRatio = (float) previewSize.width
453                 / (float) previewSize.height;
454         } else {
455             selectedPreviewAspectRatio = (float) previewSize.height
456                 / (float) previewSize.width;
457         }
458 
459         Matrix transform = new Matrix();
460         float viewAspectRatio = (float) mPreviewView.getMeasuredWidth()
461                 / (float) mPreviewView.getMeasuredHeight();
462         float scaleX = 1.0f, scaleY = 1.0f;
463         float translateX = 0, translateY = 0;
464         if (selectedPreviewAspectRatio > viewAspectRatio) {
465             scaleY = viewAspectRatio / selectedPreviewAspectRatio;
466             translateY = (float) mPreviewView.getMeasuredHeight() / 2
467                     - (float) mPreviewView.getMeasuredHeight() * scaleY / 2;
468         } else {
469             scaleX = selectedPreviewAspectRatio / viewAspectRatio;
470             translateX = (float) mPreviewView.getMeasuredWidth() / 2
471                     - (float) mPreviewView.getMeasuredWidth() * scaleX / 2;
472         }
473         transform.postScale(scaleX, scaleY);
474         transform.postTranslate(translateX, translateY);
475         mPreviewView.setTransform(transform);
476     }
477 
initializeCamera(boolean startPreviewAfterInit)478     private void initializeCamera(boolean startPreviewAfterInit) {
479         if (mCamera == null || mPreviewTexture == null) {
480             return;
481         }
482 
483         try {
484             mCamera.setPreviewTexture(mPreviewTexture);
485         } catch (Throwable t) {
486             Log.e(TAG, "Could not set preview texture", t);
487             Toast.makeText(this, t.getMessage(), Toast.LENGTH_LONG).show();
488             return;
489         }
490 
491         calculateOrientations(this, mSelectedResolution.cameraId, mCamera);
492         Camera.Parameters params = setCameraParams(mCamera);
493 
494         // Either use chosen preview size for current camera or automatically
495         // choose preview size based on view dimensions.
496         Size selectedPreviewSize = null;
497         if (mPreviewSizes != null) {
498             selectedPreviewSize = mPreviewSizes[mSelectedResolution.cameraId];
499         } else {
500             if (mPreviewOrientation == 0 || mPreviewOrientation == 180) {
501                 selectedPreviewSize = getBestPreviewSize(
502                         mPreviewTexWidth, mPreviewTexHeight, params);
503             } else {
504                 selectedPreviewSize = getBestPreviewSize(
505                         mPreviewTexHeight, mPreviewTexWidth, params);
506             }
507         }
508 
509         if (selectedPreviewSize != null) {
510             params.setPreviewSize(selectedPreviewSize.width, selectedPreviewSize.height);
511             mCamera.setParameters(params);
512             setPreviewTransform(selectedPreviewSize);
513             mCameraInitialized = true;
514         }
515 
516         if (startPreviewAfterInit) {
517             if (selectedPreviewSize == null) {
518                 Log.w(TAG, "Preview started without setting preview size");
519             }
520             startPreview();
521         }
522     }
523 
startPreview()524     private void startPreview() {
525         if (mCameraInitialized && mCamera != null) {
526             mCamera.setDisplayOrientation(mPreviewOrientation);
527             mCamera.startPreview();
528             mPreviewActive = true;
529         }
530     }
531 
switchToCamera(SelectableResolution resolution, boolean startPreview)532     private void switchToCamera(SelectableResolution resolution, boolean startPreview) {
533         if (mCamera != null) {
534             mCamera.stopPreview();
535             mCamera.release();
536         }
537 
538         mSelectedResolution = resolution;
539         mCamera = Camera.open(mSelectedResolution.cameraId);
540 
541         initializeCamera(startPreview);
542     }
543 
544     /**
545      * Get the best supported focus mode.
546      *
547      * @param camera - Android camera object.
548      * @return the best supported focus mode.
549      */
getFocusMode(Camera camera)550     private static String getFocusMode(Camera camera) {
551         List<String> modes = camera.getParameters().getSupportedFocusModes();
552         if (modes != null) {
553             if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
554                 Log.v(TAG, "Using Focus mode infinity");
555                 return Camera.Parameters.FOCUS_MODE_INFINITY;
556             }
557             if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {
558                 Log.v(TAG, "Using Focus mode fixed");
559                 return Camera.Parameters.FOCUS_MODE_FIXED;
560             }
561         }
562         Log.v(TAG, "Using Focus mode auto.");
563         return Camera.Parameters.FOCUS_MODE_AUTO;
564     }
565 
566     /**
567      * Set the common camera parameters on the given camera and returns the
568      * parameter object for further modification, if needed.
569      */
setCameraParams(Camera camera)570     private Camera.Parameters setCameraParams(Camera camera) {
571         // The picture size is taken and set from the spinner selection
572         // callback.
573         Camera.Parameters params = camera.getParameters();
574         params.setJpegThumbnailSize(0, 0);
575         params.setJpegQuality(100);
576         params.setRotation(mJpegOrientation);
577         params.setFocusMode(getFocusMode(camera));
578         params.setZoom(0);
579         params.setPictureSize(mSelectedResolution.width, mSelectedResolution.height);
580         return params;
581     }
582 
getBestPreviewSize( int width, int height, Camera.Parameters parameters)583     private Size getBestPreviewSize(
584             int width, int height, Camera.Parameters parameters) {
585         Size result = null;
586 
587         for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
588             if (size.width <= width && size.height <= height) {
589                 if (result == null) {
590                     result = new Size(size.width, size.height);
591                 } else {
592                     int resultArea = result.width * result.height;
593                     int newArea = size.width * size.height;
594 
595                     if (newArea > resultArea) {
596                         result = new Size(size.width, size.height);
597                     }
598                 }
599             }
600         }
601         return result;
602     }
603 
getDisplayRotation()604     private int getDisplayRotation() {
605         int displayRotation = getDisplay().getRotation();
606         int displayRotationDegrees = 0;
607         switch (displayRotation) {
608             case Surface.ROTATION_0: displayRotationDegrees = 0; break;
609             case Surface.ROTATION_90: displayRotationDegrees = 90; break;
610             case Surface.ROTATION_180: displayRotationDegrees = 180; break;
611             case Surface.ROTATION_270: displayRotationDegrees = 270; break;
612         }
613         return displayRotationDegrees;
614     }
615 
calculateOrientations(Activity activity, int cameraId, android.hardware.Camera camera)616     private void calculateOrientations(Activity activity,
617             int cameraId, android.hardware.Camera camera) {
618         android.hardware.Camera.CameraInfo info =
619                 new android.hardware.Camera.CameraInfo();
620         android.hardware.Camera.getCameraInfo(cameraId, info);
621 
622         int degrees = getDisplayRotation();
623         if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
624             mJpegOrientation = (info.orientation + degrees) % 360;
625             mPreviewOrientation = (360 - mJpegOrientation) % 360;  // compensate the mirror
626         } else {  // back-facing
627             mJpegOrientation = (info.orientation - degrees + 360) % 360;
628             mPreviewOrientation = mJpegOrientation;
629         }
630     }
631 
isExternalCamera(int cameraId)632     private boolean isExternalCamera(int cameraId) {
633         CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
634         try {
635             String cameraIdStr = manager.getCameraIdList()[cameraId];
636             CameraCharacteristics characteristics =
637                     manager.getCameraCharacteristics(cameraIdStr);
638 
639             if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
640                             CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL) {
641                 // External camera doesn't support FOV informations
642                 return true;
643             }
644         } catch (CameraAccessException e) {
645             Toast.makeText(this, "Could not access camera " + cameraId +
646                     ": " + e.getMessage(), Toast.LENGTH_LONG).show();
647         }
648         return false;
649     }
650 
getCameraFov(int cameraId)651     private float getCameraFov(int cameraId) {
652         if (mPreviewOrientation == 0 || mPreviewOrientation == 180) {
653             return mCamera.getParameters().getHorizontalViewAngle();
654         } else {
655             return mCamera.getParameters().getVerticalViewAngle();
656         }
657     }
658 }
659