• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package com.android.cts.verifier.camera.orientation;
15 
16 import android.content.Intent;
17 import android.graphics.Bitmap;
18 import android.graphics.BitmapFactory;
19 import android.graphics.ImageFormat;
20 import android.graphics.Matrix;
21 import android.graphics.RectF;
22 import android.hardware.Camera;
23 import android.hardware.Camera.CameraInfo;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.util.Log;
27 import android.view.Surface;
28 import android.view.SurfaceHolder;
29 import android.view.SurfaceView;
30 import android.view.View;
31 import android.view.View.OnClickListener;
32 import android.widget.Button;
33 import android.widget.ImageButton;
34 import android.widget.ImageView;
35 import android.widget.LinearLayout.LayoutParams;
36 import android.widget.TextView;
37 
38 import com.android.cts.verifier.PassFailButtons;
39 import com.android.cts.verifier.R;
40 import com.android.cts.verifier.TestResult;
41 
42 import java.io.IOException;
43 import java.util.ArrayList;
44 import java.util.Comparator;
45 import java.util.List;
46 import java.util.TreeSet;
47 
48 /**
49  * Tests for manual verification of the CDD-required camera output formats
50  * for preview callbacks
51  */
52 public class CameraOrientationActivity extends PassFailButtons.Activity
53 implements OnClickListener, SurfaceHolder.Callback {
54 
55     private static final String TAG = "CameraOrientation";
56     private static final int STATE_OFF = 0;
57     private static final int STATE_PREVIEW = 1;
58     private static final int STATE_CAPTURE = 2;
59     private static final int NUM_ORIENTATIONS = 4;
60     private static final String STAGE_INDEX_EXTRA = "stageIndex";
61     private static final int VGA_WIDTH = 640;
62     private static final int VGA_HEIGHT = 480;
63     private static final double ASPECT_TOLERANCE = 0.1;
64 
65     private ImageButton mPassButton;
66     private ImageButton mFailButton;
67     private Button mTakePictureButton;
68 
69     private SurfaceView mCameraView;
70     private ImageView mFormatView;
71     private SurfaceHolder mSurfaceHolder;
72     private Camera mCamera;
73     private List<Camera.Size> mPreviewSizes;
74     private List<Camera.Size> mPictureSizes;
75     private Camera.Size mOptimalPreviewSize;
76     private Camera.Size mOptimalPictureSize;
77     private List<Integer> mPreviewOrientations;
78     private int mNextPreviewOrientation;
79     private int mNumCameras;
80     private int mCurrentCameraId = -1;
81     private int mState = STATE_OFF;
82     private boolean mSizeAdjusted;
83 
84     private int mPreviewRotation;
85     private int mPictureRotation;
86 
87     private StringBuilder mReportBuilder = new StringBuilder();
88     private final TreeSet<String> mTestedCombinations = new TreeSet<String>();
89     private final TreeSet<String> mUntestedCombinations = new TreeSet<String>();
90 
91     @Override
onCreate(Bundle savedInstanceState)92     public void onCreate(Bundle savedInstanceState) {
93         super.onCreate(savedInstanceState);
94 
95         setContentView(R.layout.co_main);
96         setPassFailButtonClickListeners();
97         setInfoResources(R.string.camera_orientation, R.string.co_info, -1);
98         mNumCameras = Camera.getNumberOfCameras();
99 
100         mPassButton         = (ImageButton) findViewById(R.id.pass_button);
101         mFailButton         = (ImageButton) findViewById(R.id.fail_button);
102         mTakePictureButton  = (Button) findViewById(R.id.take_picture_button);
103         mFormatView         = (ImageView) findViewById(R.id.format_view);
104         mCameraView         = (SurfaceView) findViewById(R.id.camera_view);
105 
106         mFormatView.setOnClickListener(this);
107         mCameraView.setOnClickListener(this);
108         mTakePictureButton.setOnClickListener(this);
109 
110         mSurfaceHolder = mCameraView.getHolder();
111         mSurfaceHolder.addCallback(this);
112 
113         mPreviewOrientations = new ArrayList<Integer>();
114         mPreviewOrientations.add(0);
115         mPreviewOrientations.add(90);
116         mPreviewOrientations.add(180);
117         mPreviewOrientations.add(270);
118 
119         // This activity is reused multiple times
120         // to test each camera/orientation combination
121         final int stageIndex = getIntent().getIntExtra(STAGE_INDEX_EXTRA, 0);
122         Settings settings = getSettings(stageIndex);
123 
124         // Hitting the pass button goes to the next test activity.
125         // Only the last one uses the PassFailButtons click callback function,
126         // which gracefully terminates the activity.
127         if (stageIndex + 1 < mNumCameras * NUM_ORIENTATIONS) {
128             setPassButtonGoesToNextStage(stageIndex);
129         }
130 
131         String[] availableOrientations = new String[NUM_ORIENTATIONS];
132         for (int i=0; i<availableOrientations.length; i++) {
133             // append degree symbol
134             availableOrientations[i] = Integer.toString(i * 90) + "\u00b0";
135         }
136 
137         resetButtons();
138 
139         // Set initial values
140         mSizeAdjusted = false;
141         mCurrentCameraId = settings.mCameraId;
142         TextView cameraLabel = (TextView) findViewById(R.id.camera_text);
143         cameraLabel.setText(
144                 getString(R.string.co_camera_label)
145                 + " " + (mCurrentCameraId+1) + " of " + mNumCameras);
146 
147         mNextPreviewOrientation = settings.mOrientation;
148         TextView orientationLabel =
149                 (TextView) findViewById(R.id.orientation_text);
150         orientationLabel.setText(
151                 getString(R.string.co_orientation_label)
152                 + " "
153                 + Integer.toString(mNextPreviewOrientation+1)
154                 + " of "
155                 + Integer.toString(NUM_ORIENTATIONS)
156                 + ": "
157                 + mPreviewOrientations.get(mNextPreviewOrientation) + "\u00b0"
158                 );
159 
160         TextView instructionLabel =
161                 (TextView) findViewById(R.id.instruction_text);
162         instructionLabel.setText(R.string.co_instruction_text_photo_label);
163 
164         mTakePictureButton.setEnabled(false);
165         setUpCamera(mCurrentCameraId);
166     }
167 
168     @Override
onResume()169     public void onResume() {
170         super.onResume();
171         setUpCamera(mCurrentCameraId);
172     }
173 
174     @Override
onPause()175     public void onPause() {
176         super.onPause();
177         shutdownCamera();
178     }
179 
180     @Override
getTestDetails()181     public String getTestDetails() {
182         return mReportBuilder.toString();
183     }
184 
setUpCamera(int id)185     private void setUpCamera(int id) {
186         shutdownCamera();
187 
188         Log.v(TAG, "Setting up Camera " + id);
189         mCurrentCameraId = id;
190 
191         try {
192             mCamera = Camera.open(id);
193         } catch (Exception e) {
194             Log.e(TAG, "Error opening camera");
195         }
196 
197         Camera.Parameters p = mCamera.getParameters();
198 
199         class SizeCompare implements Comparator<Camera.Size> {
200             @Override
201             public int compare(Camera.Size lhs, Camera.Size rhs) {
202                 if (lhs.width < rhs.width) return -1;
203                 if (lhs.width > rhs.width) return 1;
204                 if (lhs.height < rhs.height) return -1;
205                 if (lhs.height > rhs.height) return 1;
206                 return 0;
207             }
208         }
209         SizeCompare s = new SizeCompare();
210         TreeSet<Camera.Size> sortedResolutions = new TreeSet<Camera.Size>(s);
211 
212         // Get preview resolutions
213         List<Camera.Size> unsortedSizes = p.getSupportedPreviewSizes();
214         sortedResolutions.addAll(unsortedSizes);
215         mPreviewSizes = new ArrayList<Camera.Size>(sortedResolutions);
216 
217         // Get picture resolutions
218         unsortedSizes = p.getSupportedPictureSizes();
219         sortedResolutions.clear();
220         sortedResolutions.addAll(unsortedSizes);
221         mPictureSizes = new ArrayList<Camera.Size>(sortedResolutions);
222 
223         startPreview();
224     }
225 
shutdownCamera()226     private void shutdownCamera() {
227         if (mCamera != null) {
228             mCamera.setPreviewCallback(null);
229             mCamera.stopPreview();
230             mCamera.release();
231             mCamera = null;
232             mState = STATE_OFF;
233         }
234     }
235 
startPreview()236     private void startPreview() {
237         if (mState != STATE_OFF) {
238             // Stop for a while to drain callbacks
239             mCamera.setPreviewCallback(null);
240             mCamera.stopPreview();
241             mState = STATE_OFF;
242             Handler h = new Handler();
243             Runnable mDelayedPreview = new Runnable() {
244                 @Override
245                 public void run() {
246                     startPreview();
247                 }
248             };
249             h.postDelayed(mDelayedPreview, 300);
250             return;
251         }
252 
253         mCamera.setPreviewCallback(mPreviewCallback);
254 
255         try {
256             mCamera.setPreviewDisplay(mCameraView.getHolder());
257         } catch (IOException ioe) {
258             Log.e(TAG, "Unable to connect camera to display");
259         }
260 
261         Camera.Parameters p = mCamera.getParameters();
262         Log.v(TAG, "Initializing picture format");
263         p.setPictureFormat(ImageFormat.JPEG);
264         mOptimalPictureSize = getOptimalSize(mPictureSizes, VGA_WIDTH, VGA_HEIGHT);
265         Log.v(TAG, "Initializing picture size to "
266                 + mOptimalPictureSize.width + "x" + mOptimalPictureSize.height);
267         p.setPictureSize(mOptimalPictureSize.width, mOptimalPictureSize.height);
268         mOptimalPreviewSize = getOptimalSize(mPreviewSizes, VGA_WIDTH, VGA_HEIGHT);
269 
270         if (!aspectWithinTolerance((double) mOptimalPreviewSize.width / mOptimalPreviewSize.height,
271                 (double) mOptimalPictureSize.width / mOptimalPictureSize.height)) {
272             Log.v(TAG, "Preview and picture aspect ratios do not match, overriding...");
273             mOptimalPreviewSize = getOptimalSize(mPreviewSizes, mOptimalPictureSize.width,
274                                                  mOptimalPictureSize.height);
275         }
276 
277         Log.v(TAG, "Initializing preview size to "
278                     + mOptimalPreviewSize.width + "x" + mOptimalPreviewSize.height);
279         p.setPreviewSize(mOptimalPreviewSize.width, mOptimalPreviewSize.height);
280 
281         Log.v(TAG, "Setting camera parameters");
282         mCamera.setParameters(p);
283         Log.v(TAG, "Setting color filter");
284         mFormatView.setColorFilter(null);
285         Log.v(TAG, "Starting preview");
286         try {
287             mCamera.startPreview();
288         } catch (Exception e) {
289             Log.d(TAG, "Cannot start preview", e);
290         }
291 
292         // set preview orientation
293         int degrees = mPreviewOrientations.get(mNextPreviewOrientation);
294 
295         CameraInfo info = new CameraInfo();
296         Camera.getCameraInfo(mCurrentCameraId, info);
297         int displayRotation = getWindowManager().getDefaultDisplay().getRotation();
298         switch (displayRotation) {
299             case Surface.ROTATION_90: degrees += 90; break;
300             case Surface.ROTATION_180: degrees += 180; break;
301             case Surface.ROTATION_270: degrees += 270; break;
302         }
303         degrees %= 360;
304 
305         if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
306             mPictureRotation = (info.orientation + degrees) % 360;
307             mPreviewRotation = (360 - mPictureRotation) % 360;  // compensate the mirror
308         } else {  // back-facing
309             mPictureRotation = (info.orientation - degrees + 360) % 360;
310             mPreviewRotation = mPictureRotation;
311         }
312 
313         mCamera.setDisplayOrientation(mPreviewRotation);
314 
315         if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
316             TextView cameraExtraLabel =
317                     (TextView) findViewById(R.id.instruction_extra_text);
318             cameraExtraLabel.setText(
319                     getString(R.string.co_instruction_text_extra_label));
320         }
321 
322         mState = STATE_PREVIEW;
323     }
324 
325     @Override
onClick(View view)326     public void onClick(View view) {
327         Log.v(TAG, "Click detected");
328 
329         if (view == mFormatView || view == mTakePictureButton) {
330             if(mState == STATE_PREVIEW) {
331                 mTakePictureButton.setEnabled(false);
332                 Log.v(TAG, "Taking picture");
333                 mCamera.takePicture(null, null, null, mCameraCallback);
334                 mState = STATE_CAPTURE;
335             }
336         }
337 
338         if(view == mPassButton || view == mFailButton) {
339             final int stageIndex =
340                     getIntent().getIntExtra(STAGE_INDEX_EXTRA, 0);
341             String[] cameraNames = new String[mNumCameras];
342             int counter = 0;
343             for (int i = 0; i < mNumCameras; i++) {
344                 cameraNames[i] = "Camera " + i;
345 
346                 for(int j = 0; j < mPreviewOrientations.size(); j++) {
347                     String combination = cameraNames[i] + ", "
348                             + mPreviewOrientations.get(j)
349                             + "\u00b0"
350                             + "\n";
351 
352                     if(counter < stageIndex) {
353                         // test already passed, or else wouldn't have made
354                         // it to current stageIndex
355                         mTestedCombinations.add(combination);
356                     }
357 
358                     if(counter == stageIndex) {
359                         // current test configuration
360                         if(view == mPassButton) {
361                             mTestedCombinations.add(combination);
362                         }
363                         else if(view == mFailButton) {
364                             mUntestedCombinations.add(combination);
365                         }
366                     }
367 
368                     if(counter > stageIndex) {
369                         // test not passed yet, since haven't made it to
370                         // stageIndex
371                         mUntestedCombinations.add(combination);
372                     }
373 
374                     counter++;
375                 }
376             }
377 
378             mReportBuilder = new StringBuilder();
379             mReportBuilder.append("Passed combinations:\n");
380             for (String combination : mTestedCombinations) {
381                 mReportBuilder.append(combination);
382             }
383             mReportBuilder.append("Failed/untested combinations:\n");
384             for (String combination : mUntestedCombinations) {
385                 mReportBuilder.append(combination);
386             }
387 
388             if(view == mPassButton) {
389                 TestResult.setPassedResult(this, "CameraOrientationActivity",
390                         getTestDetails());
391             }
392             if(view == mFailButton) {
393                 TestResult.setFailedResult(this, "CameraOrientationActivity",
394                         getTestDetails());
395             }
396 
397             // restart activity to test next orientation
398             Intent intent = new Intent(CameraOrientationActivity.this,
399                     CameraOrientationActivity.class);
400             intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
401                     | Intent.FLAG_ACTIVITY_FORWARD_RESULT);
402             intent.putExtra(STAGE_INDEX_EXTRA, stageIndex + 1);
403             startActivity(intent);
404         }
405     }
406 
resetButtons()407     private void resetButtons() {
408         enablePassFailButtons(false);
409     }
410 
enablePassFailButtons(boolean enable)411     private void enablePassFailButtons(boolean enable) {
412         mPassButton.setEnabled(enable);
413         mFailButton.setEnabled(enable);
414     }
415 
aspectWithinTolerance(double ratio, double targetRatio)416     private boolean aspectWithinTolerance(double ratio, double targetRatio) {
417         return Math.abs(ratio - targetRatio) <= ASPECT_TOLERANCE;
418     }
419 
420     // find a supported size with ratio less than tolerance threshold, and
421     // which is closest to height and width of given dimensions without
422     // being larger than either of given dimensions
getOptimalSize(List<Camera.Size> sizes, int w, int h)423     private Camera.Size getOptimalSize(List<Camera.Size> sizes, int w,
424             int h) {
425         double targetRatio = (double) w / (double) h;
426         if (sizes == null) return null;
427 
428         Camera.Size optimalSize = null;
429         int minDiff = Integer.MAX_VALUE;
430         int curDiff;
431 
432         int targetHeight = h;
433         int targetWidth = w;
434 
435         boolean aspectRatio = true;
436         boolean maintainCeiling = true;
437         while(true) {
438             for (Camera.Size size : sizes) {
439                 if(aspectRatio) {
440                     double ratio = (double) size.width / size.height;
441                     if (!aspectWithinTolerance(ratio, targetRatio)) {
442                         continue;
443                     }
444                 }
445                 curDiff = Math.abs(size.height - targetHeight) +
446                         Math.abs(size.width - targetWidth);
447                 if (maintainCeiling && curDiff < minDiff
448                         && size.height <= targetHeight
449                         && size.width <= targetWidth) {
450                     optimalSize = size;
451                     minDiff = curDiff;
452                 } else if (maintainCeiling == false
453                                && curDiff < minDiff) {
454                     //try to get as close as possible
455                     optimalSize = size;
456                     minDiff = curDiff;
457                 }
458             }
459             if (optimalSize == null && aspectRatio == true) {
460                 // Cannot find a match, so repeat search and
461                 // ignore aspect ratio requirement
462                 aspectRatio = false;
463             } else if (maintainCeiling == true) {
464                 //Camera resolutions are greater than ceiling provided
465                 //lets try to get as close as we can
466                 maintainCeiling = false;
467             } else {
468                 break;
469             }
470         }
471 
472         return optimalSize;
473     }
474 
475     @Override
surfaceChanged(SurfaceHolder holder, int format, int width, int height)476     public void surfaceChanged(SurfaceHolder holder, int format, int width,
477             int height) {
478         startPreview();
479     }
480 
setTestedConfiguration(int cameraId, int orientation)481     private void setTestedConfiguration(int cameraId, int orientation) {
482         String combination = "Camera " + cameraId + ", "
483                 + orientation
484                 + "\u00b0"
485                 + "\n";
486         if (!mTestedCombinations.contains(combination)) {
487             mTestedCombinations.add(combination);
488             mUntestedCombinations.remove(combination);
489         }
490     }
491 
492     @Override
surfaceCreated(SurfaceHolder holder)493     public void surfaceCreated(SurfaceHolder holder) {
494         // Auto-generated method stub
495     }
496 
497     @Override
surfaceDestroyed(SurfaceHolder holder)498     public void surfaceDestroyed(SurfaceHolder holder) {
499         // Auto-generated method stub
500     }
501 
502     private final Camera.PreviewCallback mPreviewCallback =
503             new Camera.PreviewCallback() {
504         @Override
505         public void onPreviewFrame(byte[] data, Camera camera) {
506             // adjust camera preview to match output image's aspect ratio
507             if (!mSizeAdjusted && mState == STATE_PREVIEW) {
508                 int viewWidth = mFormatView.getMeasuredWidth();
509                 int viewHeight = mFormatView.getMeasuredHeight();
510 
511                 if (viewWidth == 0 || viewHeight == 0) {
512                     return;
513                 }
514 
515                 Matrix m = new Matrix(Matrix.IDENTITY_MATRIX);
516                 RectF previewRect;
517                 if (mPreviewRotation == 0 || mPreviewRotation == 180) {
518                     previewRect = new RectF(0, 0, mOptimalPreviewSize.width,
519                             mOptimalPreviewSize.height);
520                 } else {
521                     previewRect = new RectF(0, 0, mOptimalPreviewSize.height,
522                             mOptimalPreviewSize.width);
523                 }
524                 m.setRectToRect(previewRect,
525                                 new RectF(0, 0, viewWidth, viewHeight),
526                                 Matrix.ScaleToFit.CENTER);
527                 RectF dstRect = new RectF();
528                 m.mapRect(dstRect, previewRect);
529 
530                 LayoutParams layoutParams =
531                         new LayoutParams((int) dstRect.width(), (int) dstRect.height());
532                 mCameraView.setLayoutParams(layoutParams);
533                 mSizeAdjusted = true;
534                 mTakePictureButton.setEnabled(true);
535             }
536         }
537     };
538 
539     private final Camera.PictureCallback mCameraCallback =
540             new Camera.PictureCallback() {
541         @Override
542         public void onPictureTaken(byte[] data, Camera mCamera) {
543             if (data != null) {
544                 Bitmap inputImage;
545                 inputImage = BitmapFactory.decodeByteArray(data, 0, data.length);
546 
547                 android.hardware.Camera.CameraInfo info =
548                         new android.hardware.Camera.CameraInfo();
549                 android.hardware.Camera.getCameraInfo(mCurrentCameraId, info);
550                 float mirrorX[];
551                 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
552                     // mirror the image along vertical axis
553                     mirrorX = new float[] {-1, 0, 0, 0, 1, 1, 0, 0, 1};
554                 } else {
555                     // leave image the same via identity matrix
556                     mirrorX = new float[] {1, 0, 0, 0, 1, 0, 0, 0, 1};
557                 }
558 
559                 // use matrix to transform the image
560                 Matrix matrixMirrorX = new Matrix();
561                 matrixMirrorX.setValues(mirrorX);
562                 Matrix mat = new Matrix();
563                 mat.postRotate(mPictureRotation);
564                 mat.postConcat(matrixMirrorX);
565 
566                 Bitmap inputImageAdjusted = Bitmap.createBitmap(inputImage,
567                         0,
568                         0,
569                         inputImage.getWidth(),
570                         inputImage.getHeight(),
571                         mat,
572                         true);
573                 mFormatView.setImageBitmap(inputImageAdjusted);
574 
575                 Log.v(TAG, "Output image set");
576                 enablePassFailButtons(true);
577 
578                 TextView instructionLabel =
579                         (TextView) findViewById(R.id.instruction_text);
580                 instructionLabel.setText(
581                         R.string.co_instruction_text_passfail_label);
582             }
583 
584             startPreview();
585         }
586     };
587 
setPassButtonGoesToNextStage(final int stageIndex)588     private void setPassButtonGoesToNextStage(final int stageIndex) {
589         findViewById(R.id.pass_button).setOnClickListener(this);
590     }
591 
getSettings(int stageIndex)592     private Settings getSettings(int stageIndex) {
593         int curCameraId = stageIndex / NUM_ORIENTATIONS;
594         int curOrientation = stageIndex % NUM_ORIENTATIONS;
595         return new Settings(stageIndex, curCameraId, curOrientation);
596     }
597 
598     // Bundle of settings for testing a particular
599     // camera/orientation combination
600     class Settings {
601         int mCameraId;
602         int mOrientation;
603 
Settings(int stageIndex, int cameraId, int orientation)604         Settings(int stageIndex, int cameraId, int orientation) {
605             mCameraId = cameraId;
606             mOrientation = orientation;
607         }
608     }
609 }
610