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 package com.android.cts.verifier.camera.video;
17 
18 import android.app.AlertDialog;
19 import android.content.Context;
20 import android.content.DialogInterface;
21 import android.graphics.Matrix;
22 import android.graphics.SurfaceTexture;
23 import android.hardware.Camera;
24 import android.hardware.Camera.CameraInfo;
25 import android.hardware.Camera.Size;
26 import android.hardware.camera2.CameraAccessException;
27 import android.hardware.camera2.CameraCharacteristics;
28 import android.hardware.camera2.CameraManager;
29 import android.media.CamcorderProfile;
30 import android.media.MediaPlayer;
31 import android.media.MediaRecorder;
32 import android.os.Bundle;
33 import android.os.Environment;
34 import android.os.Handler;
35 import android.util.Log;
36 import android.view.Surface;
37 import android.view.TextureView;
38 import android.view.View;
39 import android.widget.AdapterView;
40 import android.widget.ArrayAdapter;
41 import android.widget.Button;
42 import android.widget.ImageButton;
43 import android.widget.Spinner;
44 import android.widget.TextView;
45 import android.widget.Toast;
46 import android.widget.VideoView;
47 
48 import com.android.cts.verifier.PassFailButtons;
49 import com.android.cts.verifier.R;
50 
51 import java.io.File;
52 import java.io.IOException;
53 import java.text.SimpleDateFormat;
54 import java.util.ArrayList;
55 import java.util.Comparator;
56 import java.util.Date;
57 import java.util.List;
58 import java.util.TreeSet;
59 
60 
61 /**
62  * Tests for manual verification of camera video capture
63  */
64 public class CameraVideoActivity extends PassFailButtons.Activity
65         implements TextureView.SurfaceTextureListener {
66 
67     private static final String TAG = "CtsCameraVideo";
68     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
69     private static final int MEDIA_TYPE_IMAGE = 1;
70     private static final int MEDIA_TYPE_VIDEO = 2;
71     private static final int VIDEO_LENGTH = 3000; // in ms
72 
73     private TextureView mPreviewView;
74     private SurfaceTexture mPreviewTexture;
75     private int mPreviewTexWidth;
76     private int mPreviewTexHeight;
77     private int mPreviewRotation;
78     private int mVideoRotation;
79 
80     private VideoView mPlaybackView;
81 
82     private Spinner mCameraSpinner;
83     private Spinner mResolutionSpinner;
84 
85     private int mCurrentCameraId = -1;
86     private Camera mCamera;
87     private boolean mIsExternalCamera;
88 
89     private MediaRecorder mMediaRecorder;
90 
91     private List<Size> mPreviewSizes;
92     private Size mNextPreviewSize;
93     private Size mPreviewSize;
94     private List<Integer> mVideoSizeIds;
95     private int mCurrentVideoSizeId;
96 
97     private boolean isRecording = false;
98     private boolean isPlayingBack = false;
99     private Button captureButton;
100     private ImageButton mPassButton;
101     private ImageButton mFailButton;
102 
103     private TextView mStatusLabel;
104 
105     private TreeSet<String> mTestedCombinations = new TreeSet<String>();
106     private TreeSet<String> mUntestedCombinations = new TreeSet<String>();
107 
108     private File outputVideoFile;
109 
110     /**
111      * @see #MEDIA_TYPE_IMAGE
112      * @see #MEDIA_TYPE_VIDEO
113      */
getOutputMediaFile(int type)114     private static File getOutputMediaFile(int type) {
115         // Question: why do I need to comment this to get it working?
116         // Logcat says "external storage not ready"
117         // if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
118         //     Log.e(TAG, "external storage not ready");
119         //     return null;
120         // }
121 
122         File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
123                 Environment.DIRECTORY_MOVIES), TAG);
124 
125         if (!mediaStorageDir.exists()) {
126             if (!mediaStorageDir.mkdirs()) {
127                 Log.d(TAG, "failed to create directory");
128                 return null;
129             }
130         }
131 
132         String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
133         File mediaFile;
134         if (type == MEDIA_TYPE_IMAGE) {
135             mediaFile = new File(mediaStorageDir.getPath() + File.separator +
136                     "IMG_" + timeStamp + ".jpg");
137         } else if (type == MEDIA_TYPE_VIDEO) {
138             mediaFile = new File(mediaStorageDir.getPath() + File.separator +
139                     "VID_" + timeStamp + ".mp4");
140             if (VERBOSE) {
141                 Log.v(TAG, "getOutputMediaFile: output file " + mediaFile.getPath());
142             }
143         } else {
144             return null;
145         }
146 
147         return mediaFile;
148     }
149 
150     private static final int BIT_RATE_720P = 8000000;
151     private static final int BIT_RATE_MIN = 64000;
152     private static final int BIT_RATE_MAX = BIT_RATE_720P;
153 
getVideoBitRate(Camera.Size sz)154     private int getVideoBitRate(Camera.Size sz) {
155         int rate = BIT_RATE_720P;
156         float scaleFactor = sz.height * sz.width / (float)(1280 * 720);
157         rate = (int)(rate * scaleFactor);
158 
159         // Clamp to the MIN, MAX range.
160         return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate));
161     }
162 
prepareVideoRecorder()163     private boolean prepareVideoRecorder() {
164 
165         mMediaRecorder = new MediaRecorder();
166 
167         // Step 1: unlock and set camera to MediaRecorder
168         mCamera.unlock();
169         mMediaRecorder.setCamera(mCamera);
170 
171         // Step 2: set sources
172         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
173         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
174 
175         // Step 3: set a CamcorderProfile
176         if (mIsExternalCamera) {
177             Camera.Size recordSize = null;
178             switch (mCurrentVideoSizeId) {
179                 case CamcorderProfile.QUALITY_QCIF:
180                     recordSize = mCamera.new Size(176, 144);
181                 break;
182                 case CamcorderProfile.QUALITY_QVGA:
183                     recordSize = mCamera.new Size(320, 240);
184                 break;
185                 case CamcorderProfile.QUALITY_CIF:
186                     recordSize = mCamera.new Size(352, 288);
187                 break;
188                 case CamcorderProfile.QUALITY_480P:
189                     recordSize = mCamera.new Size(720, 480);
190                 break;
191                 case CamcorderProfile.QUALITY_720P:
192                     recordSize = mCamera.new Size(1280, 720);
193                 break;
194                 default:
195                     String msg = "Unknown CamcorderProfile: " + mCurrentVideoSizeId;
196                     Log.e(TAG, msg);
197                     releaseMediaRecorder();
198                     throw new AssertionError(msg);
199             }
200 
201             mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
202             mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
203             mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
204             mMediaRecorder.setVideoEncodingBitRate(getVideoBitRate(recordSize));
205             mMediaRecorder.setVideoSize(recordSize.width, recordSize.height);
206         } else {
207             mMediaRecorder.setProfile(CamcorderProfile.get(mCurrentCameraId, mCurrentVideoSizeId));
208         }
209 
210         // Step 4: set output file
211         outputVideoFile = getOutputMediaFile(MEDIA_TYPE_VIDEO);
212         mMediaRecorder.setOutputFile(outputVideoFile.toString());
213 
214         // Step 5: set preview output
215         // This is not necessary since preview has been taken care of
216 
217         // Step 6: set orientation hint
218         mMediaRecorder.setOrientationHint(mVideoRotation);
219 
220         // Step 7: prepare configured MediaRecorder
221         try {
222             mMediaRecorder.prepare();
223         } catch (IOException e) {
224             Log.e(TAG, "IOException preparing MediaRecorder: ", e);
225             releaseMediaRecorder();
226             throw new AssertionError(e);
227         }
228 
229         mMediaRecorder.setOnErrorListener(
230                 new MediaRecorder.OnErrorListener() {
231                     @Override
232                     public void onError(MediaRecorder mr, int what, int extra) {
233                         if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
234                             Log.e(TAG, "unknown error in media recorder, error: " + extra);
235                         } else {
236                             Log.e(TAG, "media recorder server died, error: " + extra);
237                         }
238 
239                         failTest("Media recorder error.");
240                     }
241                 });
242 
243         if (VERBOSE) {
244             Log.v(TAG, "prepareVideoRecorder: prepared configured MediaRecorder");
245         }
246 
247         return true;
248     }
249 
250     @Override
onCreate(Bundle savedInstanceState)251     public void onCreate(Bundle savedInstanceState) {
252         super.onCreate(savedInstanceState);
253 
254         setContentView(R.layout.camera_video);
255         setPassFailButtonClickListeners();
256         setInfoResources(R.string.camera_video, R.string.video_info, /*viewId*/-1);
257 
258         mPreviewView = (TextureView) findViewById(R.id.video_capture);
259         mPlaybackView = (VideoView) findViewById(R.id.video_playback);
260         mPlaybackView.setOnCompletionListener(mPlaybackViewListener);
261 
262         captureButton = (Button) findViewById(R.id.record_button);
263         mPassButton = (ImageButton) findViewById(R.id.pass_button);
264         mFailButton = (ImageButton) findViewById(R.id.fail_button);
265         mPassButton.setEnabled(false);
266         mFailButton.setEnabled(true);
267 
268         mPreviewView.setSurfaceTextureListener(this);
269 
270         int numCameras = Camera.getNumberOfCameras();
271         String[] cameraNames = new String[numCameras];
272         for (int i = 0; i < numCameras; i++) {
273             cameraNames[i] = "Camera " + i;
274             mUntestedCombinations.add("All combinations for Camera " + i + "\n");
275         }
276         if (VERBOSE) {
277             Log.v(TAG, "onCreate: number of cameras=" + numCameras);
278         }
279         mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection);
280         mCameraSpinner.setAdapter(
281             new ArrayAdapter<String>(
282                 this, R.layout.cf_format_list_item, cameraNames));
283         mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
284 
285         mResolutionSpinner = (Spinner) findViewById(R.id.resolution_selection);
286         mResolutionSpinner.setOnItemSelectedListener(mResolutionSelectedListener);
287 
288         mStatusLabel = (TextView) findViewById(R.id.status_label);
289     }
290 
291     @Override
onResume()292     public void onResume() {
293         super.onResume();
294 
295         setUpCamera(mCameraSpinner.getSelectedItemPosition());
296         if (VERBOSE) {
297             Log.v(TAG, "onResume: camera has been setup");
298         }
299 
300         setUpCaptureButton();
301         if (VERBOSE) {
302             Log.v(TAG, "onResume: captureButton has been setup");
303         }
304 
305     }
306 
307     @Override
onPause()308     public void onPause() {
309         super.onPause();
310 
311         releaseMediaRecorder();
312         shutdownCamera();
313         mPreviewTexture = null;
314     }
315 
316     private MediaPlayer.OnCompletionListener mPlaybackViewListener =
317             new MediaPlayer.OnCompletionListener() {
318 
319                 @Override
320                 public void onCompletion(MediaPlayer mp) {
321                     isPlayingBack = false;
322                     mPlaybackView.stopPlayback();
323                     captureButton.setEnabled(true);
324                     mStatusLabel.setText(getResources().getString(R.string.status_ready));
325                 }
326 
327     };
328 
releaseMediaRecorder()329     private void releaseMediaRecorder() {
330         if (mMediaRecorder != null) {
331             mMediaRecorder.reset();
332             mMediaRecorder.release();
333             mMediaRecorder = null;
334             mCamera.lock(); // check here, lock camera for later use
335         }
336     }
337 
338     @Override
getTestDetails()339     public String getTestDetails() {
340         StringBuilder reportBuilder = new StringBuilder();
341         reportBuilder.append("Tested combinations:\n");
342         for (String combination : mTestedCombinations) {
343             reportBuilder.append(combination);
344         }
345         reportBuilder.append("Untested combinations:\n");
346         for (String combination : mUntestedCombinations) {
347             reportBuilder.append(combination);
348         }
349         return reportBuilder.toString();
350     }
351 
352     @Override
onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)353     public void onSurfaceTextureAvailable(SurfaceTexture surface,
354             int width, int height) {
355         mPreviewTexture = surface;
356         mPreviewTexWidth = width;
357         mPreviewTexHeight = height;
358         if (mCamera != null) {
359             startPreview();
360         }
361     }
362 
363     @Override
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)364     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
365         // Ignored, Camera does all the work for us
366     }
367 
368     @Override
onSurfaceTextureDestroyed(SurfaceTexture surface)369     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
370         return true;
371     }
372 
373 
374     @Override
onSurfaceTextureUpdated(SurfaceTexture surface)375     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
376         // Invoked every time there's a new Camera preview frame
377     }
378 
379     private AdapterView.OnItemSelectedListener mCameraSpinnerListener =
380             new AdapterView.OnItemSelectedListener() {
381                 @Override
382                 public void onItemSelected(AdapterView<?> parent,
383                         View view, int pos, long id) {
384                     if (mCurrentCameraId != pos) {
385                         setUpCamera(pos);
386                     }
387                 }
388 
389                 @Override
390                 public void onNothingSelected(AdapterView<?> parent) {
391                     // Intentionally left blank
392                 }
393 
394             };
395 
396     private AdapterView.OnItemSelectedListener mResolutionSelectedListener =
397             new AdapterView.OnItemSelectedListener() {
398                 @Override
399                 public void onItemSelected(AdapterView<?> parent,
400                         View view, int position, long id) {
401                     if (mVideoSizeIds.get(position) != mCurrentVideoSizeId) {
402                         mCurrentVideoSizeId = mVideoSizeIds.get(position);
403                         if (VERBOSE) {
404                             Log.v(TAG, "onItemSelected: mCurrentVideoSizeId = " +
405                                     mCurrentVideoSizeId);
406                         }
407                         mNextPreviewSize = matchPreviewRecordSize();
408                         if (VERBOSE) {
409                             Log.v(TAG, "onItemSelected: setting preview size "
410                                     + mNextPreviewSize.width + "x" + mNextPreviewSize.height);
411                         }
412 
413                         startPreview();
414                         if (VERBOSE) {
415                             Log.v(TAG, "onItemSelected: started new preview");
416                         }
417                     }
418                 }
419 
420                 @Override
421                 public void onNothingSelected(AdapterView<?> parent) {
422                     // Intentionally left blank
423                 }
424 
425             };
426 
427 
setUpCaptureButton()428     private void setUpCaptureButton() {
429         captureButton.setOnClickListener (
430                 new View.OnClickListener() {
431                     @Override
432                     public void onClick(View V) {
433                         if ((!isRecording) && (!isPlayingBack)) {
434                             if (prepareVideoRecorder()) {
435                                 mMediaRecorder.start();
436                                 if (VERBOSE) {
437                                     Log.v(TAG, "onClick: started mMediaRecorder");
438                                 }
439                                 isRecording = true;
440                                 captureButton.setEnabled(false);
441                                 mStatusLabel.setText(getResources()
442                                         .getString(R.string.status_recording));
443                             } else {
444                                 releaseMediaRecorder();
445                                 Log.e(TAG, "media recorder cannot be set up");
446                                 failTest("Unable to set up media recorder.");
447                             }
448                             Handler h = new Handler();
449                             Runnable mDelayedPreview = new Runnable() {
450                                 @Override
451                                 public void run() {
452                                     mMediaRecorder.stop();
453                                     releaseMediaRecorder();
454 
455                                     mPlaybackView.setVideoPath(outputVideoFile.getPath());
456                                     mPlaybackView.start();
457                                     isRecording = false;
458                                     isPlayingBack = true;
459                                     mStatusLabel.setText(getResources()
460                                             .getString(R.string.status_playback));
461                                     String combination = "Camera " + mCurrentCameraId + ", " +
462                                             mCurrentVideoSizeId + "\n";
463                                     mUntestedCombinations.remove(combination);
464                                     mTestedCombinations.add(combination);
465 
466                                     if (mUntestedCombinations.isEmpty()) {
467                                         mPassButton.setEnabled(true);
468                                         if (VERBOSE) {
469                                             Log.v(TAG, "run: test success");
470                                         }
471                                     }
472                                 }
473                             };
474                             h.postDelayed(mDelayedPreview, VIDEO_LENGTH);
475                         }
476 
477                     }
478                 }
479         );
480     }
481 
482     private class VideoSizeNamePair {
483         private int sizeId;
484         private String sizeName;
485 
VideoSizeNamePair(int id, String name)486         public VideoSizeNamePair(int id, String name) {
487             sizeId = id;
488             sizeName = name;
489         }
490 
getSizeId()491         public int getSizeId() {
492             return sizeId;
493         }
494 
getSizeName()495         public String getSizeName() {
496             return sizeName;
497         }
498     }
499 
getVideoSizeNamePairs(int cameraId)500     private ArrayList<VideoSizeNamePair> getVideoSizeNamePairs(int cameraId) {
501         int[] qualityArray = {
502                 CamcorderProfile.QUALITY_LOW,
503                 CamcorderProfile.QUALITY_HIGH,
504                 CamcorderProfile.QUALITY_QCIF,  // 176x144
505                 CamcorderProfile.QUALITY_QVGA,  // 320x240
506                 CamcorderProfile.QUALITY_CIF,   // 352x288
507                 CamcorderProfile.QUALITY_480P,  // 720x480
508                 CamcorderProfile.QUALITY_720P,  // 1280x720
509                 CamcorderProfile.QUALITY_1080P, // 1920x1080 or 1920x1088
510                 CamcorderProfile.QUALITY_2160P
511         };
512 
513         final Camera.Size skip = mCamera.new Size(-1, -1);
514         Camera.Size[] videoSizeArray = {
515                 skip,
516                 skip,
517                 mCamera.new Size(176, 144),
518                 mCamera.new Size(320, 240),
519                 mCamera.new Size(352, 288),
520                 mCamera.new Size(720, 480),
521                 mCamera.new Size(1280, 720),
522                 skip,
523                 skip
524         };
525 
526         String[] nameArray = {
527                 "LOW",
528                 "HIGH",
529                 "QCIF",
530                 "QVGA",
531                 "CIF",
532                 "480P",
533                 "720P",
534                 "1080P",
535                 "2160P"
536         };
537 
538         ArrayList<VideoSizeNamePair> availableSizes =
539                 new ArrayList<VideoSizeNamePair> ();
540 
541         Camera.Parameters p = mCamera.getParameters();
542         List<Camera.Size> supportedVideoSizes = p.getSupportedVideoSizes();
543         for (int i = 0; i < qualityArray.length; i++) {
544             if (mIsExternalCamera) {
545                 Camera.Size videoSz = videoSizeArray[i];
546                 if (videoSz.equals(skip)) {
547                     continue;
548                 }
549                 if (supportedVideoSizes.contains(videoSz)) {
550                     VideoSizeNamePair pair = new VideoSizeNamePair(qualityArray[i], nameArray[i]);
551                     availableSizes.add(pair);
552                 }
553             } else {
554                 if (CamcorderProfile.hasProfile(cameraId, qualityArray[i])) {
555                     VideoSizeNamePair pair = new VideoSizeNamePair(qualityArray[i], nameArray[i]);
556                     availableSizes.add(pair);
557                 }
558             }
559         }
560         return availableSizes;
561     }
562 
563     static class ResolutionQuality {
564         private int videoSizeId;
565         private int width;
566         private int height;
567 
ResolutionQuality()568         public ResolutionQuality() {
569             // intentionally left blank
570         }
ResolutionQuality(int newSizeId, int newWidth, int newHeight)571         public ResolutionQuality(int newSizeId, int newWidth, int newHeight) {
572             videoSizeId = newSizeId;
573             width = newWidth;
574             height = newHeight;
575         }
576     }
577 
findRecordSize(int cameraId)578     private Size findRecordSize(int cameraId) {
579         int[] possibleQuality = {
580                 CamcorderProfile.QUALITY_LOW,
581                 CamcorderProfile.QUALITY_HIGH,
582                 CamcorderProfile.QUALITY_QCIF,
583                 CamcorderProfile.QUALITY_QVGA,
584                 CamcorderProfile.QUALITY_CIF,
585                 CamcorderProfile.QUALITY_480P,
586                 CamcorderProfile.QUALITY_720P,
587                 CamcorderProfile.QUALITY_1080P,
588                 CamcorderProfile.QUALITY_2160P
589         };
590 
591         final Camera.Size skip = mCamera.new Size(-1, -1);
592         Camera.Size[] videoSizeArray = {
593                 skip,
594                 skip,
595                 mCamera.new Size(176, 144),
596                 mCamera.new Size(320, 240),
597                 mCamera.new Size(352, 288),
598                 mCamera.new Size(720, 480),
599                 mCamera.new Size(1280, 720),
600                 skip,
601                 skip
602         };
603 
604         ArrayList<ResolutionQuality> qualityList = new ArrayList<ResolutionQuality>();
605         Camera.Parameters p = mCamera.getParameters();
606         List<Camera.Size> supportedVideoSizes = p.getSupportedVideoSizes();
607         for (int i = 0; i < possibleQuality.length; i++) {
608             if (mIsExternalCamera) {
609                 Camera.Size videoSz = videoSizeArray[i];
610                 if (videoSz.equals(skip)) {
611                     continue;
612                 }
613                 if (supportedVideoSizes.contains(videoSz)) {
614                     qualityList.add(new ResolutionQuality(possibleQuality[i],
615                             videoSz.width, videoSz.height));
616                 }
617             } else {
618                 if (CamcorderProfile.hasProfile(cameraId, possibleQuality[i])) {
619                     CamcorderProfile profile = CamcorderProfile.get(cameraId, possibleQuality[i]);
620                     qualityList.add(new ResolutionQuality(possibleQuality[i],
621                             profile.videoFrameWidth, profile.videoFrameHeight));
622                 }
623             }
624         }
625 
626         Size recordSize = null;
627         for (int i = 0; i < qualityList.size(); i++) {
628             if (mCurrentVideoSizeId == qualityList.get(i).videoSizeId) {
629                 recordSize = mCamera.new Size(qualityList.get(i).width,
630                         qualityList.get(i).height);
631                 break;
632             }
633         }
634 
635         if (recordSize == null) {
636             Log.e(TAG, "findRecordSize: did not find a match");
637             failTest("Cannot find video size");
638         }
639         return recordSize;
640     }
641 
642     // Match preview size with current recording size mCurrentVideoSizeId
matchPreviewRecordSize()643     private Size matchPreviewRecordSize() {
644         Size recordSize = findRecordSize(mCurrentCameraId);
645 
646         Size matchedSize = null;
647         // First try to find exact match in size
648         for (int i = 0; i < mPreviewSizes.size(); i++) {
649             if (mPreviewSizes.get(i).equals(recordSize)) {
650                 matchedSize = mCamera.new Size(recordSize.width, recordSize.height);
651                 break;
652             }
653         }
654         // Second try to find same ratio in size
655         if (matchedSize == null) {
656             for (int i = mPreviewSizes.size() - 1; i >= 0; i--) {
657                 if (mPreviewSizes.get(i).width * recordSize.height ==
658                         mPreviewSizes.get(i).height * recordSize.width) {
659                     matchedSize = mCamera.new Size(mPreviewSizes.get(i).width,
660                             mPreviewSizes.get(i).height);
661                     break;
662                 }
663             }
664         }
665         //Third try to find one with similar if not the same apect ratio
666         if (matchedSize == null) {
667             for (int i = mPreviewSizes.size() - 1; i >= 0; i--) {
668                 if (Math.abs((float)mPreviewSizes.get(i).width * recordSize.height /
669                         mPreviewSizes.get(i).height / recordSize.width - 1) < 0.12) {
670                     matchedSize = mCamera.new Size(mPreviewSizes.get(i).width,
671                             mPreviewSizes.get(i).height);
672                     break;
673                 }
674             }
675         }
676         // Last resort, just use the first preview size
677         if (matchedSize == null) {
678             matchedSize = mCamera.new Size(mPreviewSizes.get(0).width,
679                     mPreviewSizes.get(0).height);
680         }
681 
682         if (VERBOSE) {
683             Log.v(TAG, "matchPreviewRecordSize " + matchedSize.width + "x" + matchedSize.height);
684         }
685 
686         return matchedSize;
687     }
688 
setUpCamera(int id)689     private void setUpCamera(int id) {
690         shutdownCamera();
691 
692         mCurrentCameraId = id;
693         try {
694             mCamera = Camera.open(id);
695         }
696         catch (Exception e) {
697             Log.e(TAG, "camera is not available", e);
698             failTest("camera not available" + e.getMessage());
699             return;
700         }
701         mIsExternalCamera = isExternalCamera(id);
702 
703         Camera.Parameters p = mCamera.getParameters();
704         if (VERBOSE) {
705             Log.v(TAG, "setUpCamera: setUpCamera got camera parameters");
706         }
707 
708         // Get preview resolutions
709         List<Size> unsortedSizes = p.getSupportedPreviewSizes();
710 
711         class SizeCompare implements Comparator<Size> {
712             @Override
713             public int compare(Size lhs, Size rhs) {
714                 if (lhs.width < rhs.width) return -1;
715                 if (lhs.width > rhs.width) return 1;
716                 if (lhs.height < rhs.height) return -1;
717                 if (lhs.height > rhs.height) return 1;
718                 return 0;
719             }
720         };
721 
722         SizeCompare s = new SizeCompare();
723         TreeSet<Size> sortedResolutions = new TreeSet<Size>(s);
724         sortedResolutions.addAll(unsortedSizes);
725 
726         mPreviewSizes = new ArrayList<Size>(sortedResolutions);
727 
728         ArrayList<VideoSizeNamePair> availableVideoSizes = getVideoSizeNamePairs(id);
729         String[] availableVideoSizeNames = new String[availableVideoSizes.size()];
730         mVideoSizeIds = new ArrayList<Integer>();
731         for (int i = 0; i < availableVideoSizes.size(); i++) {
732             availableVideoSizeNames[i] = availableVideoSizes.get(i).getSizeName();
733             mVideoSizeIds.add(availableVideoSizes.get(i).getSizeId());
734         }
735 
736         mResolutionSpinner.setAdapter(
737             new ArrayAdapter<String>(
738                 this, R.layout.cf_format_list_item, availableVideoSizeNames));
739 
740         // Update untested
741         mUntestedCombinations.remove("All combinations for Camera " + id + "\n");
742         for (int videoSizeId: mVideoSizeIds) {
743             String combination = "Camera " + id + ", " + videoSizeId + "\n";
744             if (!mTestedCombinations.contains(combination)) {
745                 mUntestedCombinations.add(combination);
746             }
747         }
748 
749         // Set initial values
750         mCurrentVideoSizeId = mVideoSizeIds.get(0);
751         mNextPreviewSize = matchPreviewRecordSize();
752         mResolutionSpinner.setSelection(0);
753 
754         // Set up correct display orientation
755         CameraInfo info = new CameraInfo();
756         Camera.getCameraInfo(id, info);
757         int rotation = getWindowManager().getDefaultDisplay().getRotation();
758         int degrees = 0;
759         switch (rotation) {
760             case Surface.ROTATION_0: degrees = 0; break;
761             case Surface.ROTATION_90: degrees = 90; break;
762             case Surface.ROTATION_180: degrees = 180; break;
763             case Surface.ROTATION_270: degrees = 270; break;
764         }
765 
766         if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
767             mVideoRotation = (info.orientation + degrees) % 360;
768             mPreviewRotation = (360 - mVideoRotation) % 360;  // compensate the mirror
769         } else {  // back-facing
770             mVideoRotation = (info.orientation - degrees + 360) % 360;
771             mPreviewRotation = mVideoRotation;
772         }
773         if (mPreviewRotation != 0 && mPreviewRotation != 180) {
774             Log.w(TAG,
775                 "Display orientation correction is not 0 or 180, as expected!");
776         }
777 
778         mCamera.setDisplayOrientation(mPreviewRotation);
779 
780         // Start up preview if display is ready
781         if (mPreviewTexture != null) {
782             startPreview();
783         }
784     }
785 
shutdownCamera()786     private void shutdownCamera() {
787         if (mCamera != null) {
788             mCamera.setPreviewCallback(null);
789             mCamera.stopPreview();
790             mCamera.release();
791             mCamera = null;
792         }
793     }
794 
795     /**
796      * starts capturing and drawing frames on screen
797      */
startPreview()798     private void startPreview() {
799 
800         mCamera.stopPreview();
801 
802         Matrix transform = new Matrix();
803         float widthRatio = mNextPreviewSize.width / (float)mPreviewTexWidth;
804         float heightRatio = mNextPreviewSize.height / (float)mPreviewTexHeight;
805         if (VERBOSE) {
806             Log.v(TAG, "startPreview: widthRatio=" + widthRatio + " " + "heightRatio=" +
807                     heightRatio);
808         }
809 
810         if (heightRatio < widthRatio) {
811             transform.setScale(1, heightRatio / widthRatio);
812             transform.postTranslate(0,
813                     mPreviewTexHeight * (1 - heightRatio / widthRatio) / 2);
814             if (VERBOSE) {
815                 Log.v(TAG, "startPreview: shrink vertical by " + heightRatio / widthRatio);
816             }
817         } else {
818             transform.setScale(widthRatio / heightRatio, 1);
819             transform.postTranslate(mPreviewTexWidth * (1 - widthRatio / heightRatio) / 2, 0);
820             if (VERBOSE) {
821                 Log.v(TAG, "startPreview: shrink horizontal by " + widthRatio / heightRatio);
822             }
823         }
824 
825         mPreviewView.setTransform(transform);
826 
827         mPreviewSize = mNextPreviewSize;
828 
829         Camera.Parameters p = mCamera.getParameters();
830         p.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
831         mCamera.setParameters(p);
832 
833         try {
834             mCamera.setPreviewTexture(mPreviewTexture);
835             if (mPreviewTexture == null) {
836                 Log.e(TAG, "preview texture is null.");
837             }
838             if (VERBOSE) {
839                 Log.v(TAG, "startPreview: set preview texture in startPreview");
840             }
841             mCamera.startPreview();
842             if (VERBOSE) {
843                 Log.v(TAG, "startPreview: started preview in startPreview");
844             }
845         } catch (IOException ioe) {
846             Log.e(TAG, "Unable to start up preview", ioe);
847             // Show a dialog box to tell user test failed
848             failTest("Unable to start preview.");
849         }
850     }
851 
failTest(String failMessage)852     private void failTest(String failMessage) {
853         DialogInterface.OnClickListener dialogClickListener =
854                 new DialogInterface.OnClickListener() {
855                     @Override
856                     public void onClick(DialogInterface dialog, int which) {
857                         switch (which) {
858                             case DialogInterface.BUTTON_POSITIVE:
859                                 setTestResultAndFinish(/* passed */false);
860                                 break;
861                             case DialogInterface.BUTTON_NEGATIVE:
862                                 break;
863                         }
864                     }
865                 };
866 
867         AlertDialog.Builder builder = new AlertDialog.Builder(CameraVideoActivity.this);
868         builder.setMessage(getString(R.string.dialog_fail_test) + ". " + failMessage)
869                 .setPositiveButton(R.string.fail_quit, dialogClickListener)
870                 .setNegativeButton(R.string.cancel, dialogClickListener)
871                 .show();
872     }
873 
isExternalCamera(int cameraId)874     private boolean isExternalCamera(int cameraId) {
875         CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
876         try {
877             String cameraIdStr = manager.getCameraIdList()[cameraId];
878             CameraCharacteristics characteristics =
879                     manager.getCameraCharacteristics(cameraIdStr);
880 
881             if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
882                             CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL) {
883                 // External camera doesn't support FOV informations
884                 return true;
885             }
886         } catch (CameraAccessException e) {
887             Toast.makeText(this, "Could not access camera " + cameraId +
888                     ": " + e.getMessage(), Toast.LENGTH_LONG).show();
889         }
890         return false;
891     }
892 
893 }
894