1 /*
2  * Copyright (C) 2012 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.formats;
17 
18 import android.app.AlertDialog;
19 import android.graphics.Bitmap;
20 import android.graphics.Color;
21 import android.graphics.ColorMatrix;
22 import android.graphics.ColorMatrixColorFilter;
23 import android.graphics.ImageFormat;
24 import android.graphics.Matrix;
25 import android.graphics.Rect;
26 import android.graphics.SurfaceTexture;
27 import android.hardware.Camera;
28 import android.hardware.Camera.CameraInfo;
29 import android.os.AsyncTask;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.util.Log;
33 import android.util.SparseArray;
34 import android.view.Menu;
35 import android.view.MenuItem;
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.ImageView;
44 import android.widget.Spinner;
45 import android.widget.Toast;
46 
47 import com.android.cts.verifier.PassFailButtons;
48 import com.android.cts.verifier.R;
49 
50 import java.io.IOException;
51 import java.util.ArrayList;
52 import java.util.Comparator;
53 import java.util.HashSet;
54 import java.util.List;
55 import java.util.Optional;
56 import java.util.TreeSet;
57 
58 /**
59  * Tests for manual verification of the CDD-required camera output formats
60  * for preview callbacks
61  */
62 public class CameraFormatsActivity extends PassFailButtons.Activity
63         implements TextureView.SurfaceTextureListener, Camera.PreviewCallback {
64 
65     private static final String TAG = "CameraFormats";
66 
67     private TextureView mPreviewView;
68     private SurfaceTexture mPreviewTexture;
69     private int mPreviewTexWidth;
70     private int mPreviewTexHeight;
71     private int mPreviewRotation;
72 
73     private ImageView mFormatView;
74 
75     private Spinner mCameraSpinner;
76     private Spinner mFormatSpinner;
77     private Spinner mResolutionSpinner;
78 
79     private int mCurrentCameraId = -1;
80     private Camera mCamera;
81 
82     private List<Camera.Size> mPreviewSizes;
83     private Camera.Size mNextPreviewSize;
84     private Camera.Size mPreviewSize;
85     private List<Integer> mPreviewFormats;
86     private int mNextPreviewFormat;
87     private int mPreviewFormat;
88     private SparseArray<String> mPreviewFormatNames;
89 
90     private ColorMatrixColorFilter mYuv2RgbFilter;
91 
92     private Bitmap mCallbackBitmap;
93     private int[] mRgbData;
94     private int mRgbWidth;
95     private int mRgbHeight;
96 
97     private static final int STATE_OFF = 0;
98     private static final int STATE_PREVIEW = 1;
99     private static final int STATE_NO_CALLBACKS = 2;
100     private int mState = STATE_OFF;
101     private boolean mProcessInProgress = false;
102     private boolean mProcessingFirstFrame = false;
103 
104     private final TreeSet<CameraCombination> mTestedCombinations = new TreeSet<>(COMPARATOR);
105     private final TreeSet<CameraCombination> mUntestedCombinations = new TreeSet<>(COMPARATOR);
106 
107     private int mAllCombinationsSize = 0;
108 
109     // Menu to show the test progress
110     private static final int MENU_ID_PROGRESS = Menu.FIRST + 1;
111 
112     private class CameraCombination {
113         private final int mCameraIndex;
114         private final int mResolutionIndex;
115         private final int mFormatIndex;
116         private final int mResolutionWidth;
117         private final int mResolutionHeight;
118         private final String mFormatName;
119 
CameraCombination(int cameraIndex, int resolutionIndex, int formatIndex, int resolutionWidth, int resolutionHeight, String formatName)120         private CameraCombination(int cameraIndex, int resolutionIndex, int formatIndex,
121             int resolutionWidth, int resolutionHeight, String formatName) {
122             this.mCameraIndex = cameraIndex;
123             this.mResolutionIndex = resolutionIndex;
124             this.mFormatIndex = formatIndex;
125             this.mResolutionWidth = resolutionWidth;
126             this.mResolutionHeight = resolutionHeight;
127             this.mFormatName = formatName;
128         }
129 
130         @Override
toString()131         public String toString() {
132             return String.format("Camera %d, %dx%d, %s",
133                 mCameraIndex, mResolutionWidth, mResolutionHeight, mFormatName);
134         }
135     }
136 
137     private static final Comparator<CameraCombination> COMPARATOR =
138         Comparator.<CameraCombination, Integer>comparing(c -> c.mCameraIndex)
139             .thenComparing(c -> c.mResolutionIndex)
140             .thenComparing(c -> c.mFormatIndex);
141 
142     @Override
onCreate(Bundle savedInstanceState)143     public void onCreate(Bundle savedInstanceState) {
144         super.onCreate(savedInstanceState);
145 
146         setContentView(R.layout.cf_main);
147 
148         mAllCombinationsSize = calcAllCombinationsSize();
149 
150         // disable "Pass" button until all combinations are tested
151         setPassButtonEnabled(false);
152 
153         setPassFailButtonClickListeners();
154         setInfoResources(R.string.camera_format, R.string.cf_info, -1);
155 
156         mPreviewView = (TextureView) findViewById(R.id.preview_view);
157         mFormatView = (ImageView) findViewById(R.id.format_view);
158 
159         mPreviewView.setSurfaceTextureListener(this);
160 
161         int numCameras = Camera.getNumberOfCameras();
162         String[] cameraNames = new String[numCameras];
163         for (int i = 0; i < numCameras; i++) {
164             cameraNames[i] = "Camera " + i;
165         }
166         mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection);
167         mCameraSpinner.setAdapter(
168             new ArrayAdapter<String>(
169                 this, R.layout.camera_list_item, cameraNames));
170         mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
171 
172         mFormatSpinner = (Spinner) findViewById(R.id.format_selection);
173         mFormatSpinner.setOnItemSelectedListener(mFormatSelectedListener);
174 
175         mResolutionSpinner = (Spinner) findViewById(R.id.resolution_selection);
176         mResolutionSpinner.setOnItemSelectedListener(mResolutionSelectedListener);
177 
178         // Must be kept in sync with android.graphics.ImageFormat manually
179         mPreviewFormatNames = new SparseArray(7);
180         mPreviewFormatNames.append(ImageFormat.JPEG, "JPEG");
181         mPreviewFormatNames.append(ImageFormat.NV16, "NV16");
182         mPreviewFormatNames.append(ImageFormat.NV21, "NV21");
183         mPreviewFormatNames.append(ImageFormat.RGB_565, "RGB_565");
184         mPreviewFormatNames.append(ImageFormat.UNKNOWN, "UNKNOWN");
185         mPreviewFormatNames.append(ImageFormat.YUY2, "YUY2");
186         mPreviewFormatNames.append(ImageFormat.YV12, "YV12");
187 
188         // Need YUV->RGB conversion in many cases
189 
190         ColorMatrix y2r = new ColorMatrix();
191         y2r.setYUV2RGB();
192         float[] yuvOffset = new float[] {
193             1.f, 0.f, 0.f, 0.f, 0.f,
194             0.f, 1.f, 0.f, 0.f, -128.f,
195             0.f, 0.f, 1.f, 0.f, -128.f,
196             0.f, 0.f, 0.f, 1.f, 0.f
197         };
198 
199         ColorMatrix yOffset = new ColorMatrix(yuvOffset);
200 
201         ColorMatrix yTotal = new ColorMatrix();
202         yTotal.setConcat(y2r, yOffset);
203 
204         mYuv2RgbFilter = new ColorMatrixColorFilter(yTotal);
205 
206         Button mNextButton = findViewById(R.id.next_button);
207         mNextButton.setOnClickListener(v -> {
208                 setUntestedCombination();
209                 startPreview();
210         });
211     }
212 
213     /**
214      * Set an untested combination of resolution and format for the current camera.
215      * Triggered by next button click.
216      */
setUntestedCombination()217     private void setUntestedCombination() {
218         Optional<CameraCombination> combination = mUntestedCombinations.stream().filter(
219             c -> c.mCameraIndex == mCurrentCameraId).findFirst();
220         if (!combination.isPresent()) {
221             Toast.makeText(this, "All Camera " + mCurrentCameraId + " tests are done.",
222                 Toast.LENGTH_SHORT).show();
223             return;
224         }
225 
226         // There is untested combination for the current camera, set the next untested combination.
227         int mNextResolutionIndex = combination.get().mResolutionIndex;
228         int mNextFormatIndex = combination.get().mFormatIndex;
229 
230         mNextPreviewSize = mPreviewSizes.get(mNextResolutionIndex);
231         mResolutionSpinner.setSelection(mNextResolutionIndex);
232         mNextPreviewFormat = mPreviewFormats.get(mNextFormatIndex);
233         mFormatSpinner.setSelection(mNextFormatIndex);
234     }
235 
236     @Override
onCreateOptionsMenu(Menu menu)237     public boolean onCreateOptionsMenu(Menu menu) {
238         menu.add(Menu.NONE, MENU_ID_PROGRESS, Menu.NONE, "Current Progress");
239         return super.onCreateOptionsMenu(menu);
240     }
241 
242     @Override
onOptionsItemSelected(MenuItem item)243     public boolean onOptionsItemSelected(MenuItem item) {
244         boolean ret = true;
245         switch (item.getItemId()) {
246             case MENU_ID_PROGRESS:
247                 showCombinationsDialog();
248                 ret = true;
249                 break;
250             default:
251                 ret = super.onOptionsItemSelected(item);
252                 break;
253         }
254         return ret;
255     }
256 
showCombinationsDialog()257     private void showCombinationsDialog() {
258         AlertDialog.Builder builder =
259                 new AlertDialog.Builder(CameraFormatsActivity.this);
260         builder.setMessage(getTestDetails())
261                 .setTitle("Current Progress")
262                 .setPositiveButton("OK", null);
263         builder.show();
264     }
265 
266     @Override
onResume()267     public void onResume() {
268         super.onResume();
269 
270         setUpCamera(mCameraSpinner.getSelectedItemPosition());
271     }
272 
273     @Override
onPause()274     public void onPause() {
275         super.onPause();
276 
277         shutdownCamera();
278         mPreviewTexture = null;
279     }
280 
281     @Override
getTestDetails()282     public String getTestDetails() {
283         StringBuilder reportBuilder = new StringBuilder();
284         reportBuilder.append("Tested combinations:\n");
285         for (CameraCombination combination: mTestedCombinations) {
286             reportBuilder.append(combination);
287             reportBuilder.append("\n");
288         }
289 
290         reportBuilder.append("Untested combinations:\n");
291         for (CameraCombination combination: mUntestedCombinations) {
292             reportBuilder.append(combination);
293             reportBuilder.append("\n");
294         }
295         return reportBuilder.toString();
296     }
297 
onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)298     public void onSurfaceTextureAvailable(SurfaceTexture surface,
299             int width, int height) {
300         mPreviewTexture = surface;
301         if (mFormatView.getMeasuredWidth() != width
302                 || mFormatView.getMeasuredHeight() != height) {
303             mPreviewTexWidth = mFormatView.getMeasuredWidth();
304             mPreviewTexHeight = mFormatView.getMeasuredHeight();
305         } else {
306             mPreviewTexWidth = width;
307             mPreviewTexHeight = height;
308         }
309 
310         if (mCamera != null) {
311             startPreview();
312         }
313     }
314 
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)315     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
316         // Ignored, Camera does all the work for us
317     }
318 
onSurfaceTextureDestroyed(SurfaceTexture surface)319     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
320         return true;
321     }
322 
onSurfaceTextureUpdated(SurfaceTexture surface)323     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
324         // Invoked every time there's a new Camera preview frame
325     }
326 
327     private AdapterView.OnItemSelectedListener mCameraSpinnerListener =
328             new AdapterView.OnItemSelectedListener() {
329                 public void onItemSelected(AdapterView<?> parent,
330                         View view, int pos, long id) {
331                     if (mCurrentCameraId != pos) {
332                         setUpCamera(pos);
333                     }
334                 }
335 
336                 public void onNothingSelected(AdapterView parent) {
337 
338                 }
339 
340             };
341 
342     private AdapterView.OnItemSelectedListener mResolutionSelectedListener =
343             new AdapterView.OnItemSelectedListener() {
344                 public void onItemSelected(AdapterView<?> parent,
345                         View view, int position, long id) {
346                     if (mPreviewSizes.get(position) != mPreviewSize) {
347                         mNextPreviewSize = mPreviewSizes.get(position);
348                         startPreview();
349                     }
350                 }
351 
352                 public void onNothingSelected(AdapterView parent) {
353 
354                 }
355 
356             };
357 
358 
359     private AdapterView.OnItemSelectedListener mFormatSelectedListener =
360             new AdapterView.OnItemSelectedListener() {
361                 public void onItemSelected(AdapterView<?> parent,
362                         View view, int position, long id) {
363                     if (mPreviewFormats.get(position) != mNextPreviewFormat) {
364                         mNextPreviewFormat = mPreviewFormats.get(position);
365                         startPreview();
366                     }
367                 }
368 
369                 public void onNothingSelected(AdapterView parent) {
370 
371                 }
372 
373             };
374 
setUpCamera(int id)375     private void setUpCamera(int id) {
376         shutdownCamera();
377 
378         mCurrentCameraId = id;
379         mCamera = Camera.open(id);
380         Camera.Parameters p = mCamera.getParameters();
381 
382         // Get preview resolutions
383 
384         List<Camera.Size> unsortedSizes = p.getSupportedPreviewSizes();
385 
386         class SizeCompare implements Comparator<Camera.Size> {
387             public int compare(Camera.Size lhs, Camera.Size rhs) {
388                 if (lhs.width < rhs.width) return -1;
389                 if (lhs.width > rhs.width) return 1;
390                 if (lhs.height < rhs.height) return -1;
391                 if (lhs.height > rhs.height) return 1;
392                 return 0;
393             }
394         }
395 
396         SizeCompare s = new SizeCompare();
397         TreeSet<Camera.Size> sortedResolutions = new TreeSet<Camera.Size>(s);
398         sortedResolutions.addAll(unsortedSizes);
399 
400         mPreviewSizes = new ArrayList<Camera.Size>(sortedResolutions);
401 
402         String[] availableSizeNames = new String[mPreviewSizes.size()];
403         for (int i = 0; i < mPreviewSizes.size(); i++) {
404             availableSizeNames[i] =
405                     Integer.toString(mPreviewSizes.get(i).width) + " x " +
406                     Integer.toString(mPreviewSizes.get(i).height);
407         }
408         mResolutionSpinner.setAdapter(
409             new ArrayAdapter<String>(
410                 this, R.layout.camera_list_item, availableSizeNames));
411 
412         // Get preview formats, removing duplicates
413 
414         HashSet<Integer> formatSet = new HashSet<>(p.getSupportedPreviewFormats());
415         mPreviewFormats = new ArrayList<Integer>(formatSet);
416 
417         String[] availableFormatNames = new String[mPreviewFormats.size()];
418         for (int i = 0; i < mPreviewFormats.size(); i++) {
419             availableFormatNames[i] =
420                     mPreviewFormatNames.get(mPreviewFormats.get(i));
421         }
422         mFormatSpinner.setAdapter(
423             new ArrayAdapter<String>(
424                 this, R.layout.camera_list_item, availableFormatNames));
425 
426         // Update untested entries
427 
428         for (int resolutionIndex = 0; resolutionIndex < mPreviewSizes.size(); resolutionIndex++) {
429             for (int formatIndex = 0; formatIndex < mPreviewFormats.size(); formatIndex++) {
430                 CameraCombination combination = new CameraCombination(
431                     id, resolutionIndex, formatIndex,
432                     mPreviewSizes.get(resolutionIndex).width,
433                     mPreviewSizes.get(resolutionIndex).height,
434                     mPreviewFormatNames.get(mPreviewFormats.get(formatIndex)));
435 
436                 if (!mTestedCombinations.contains(combination)) {
437                     mUntestedCombinations.add(combination);
438                 }
439             }
440         }
441 
442         // Set initial values
443 
444         mNextPreviewSize = mPreviewSizes.get(0);
445         mResolutionSpinner.setSelection(0);
446 
447         mNextPreviewFormat = mPreviewFormats.get(0);
448         mFormatSpinner.setSelection(0);
449 
450 
451         // Set up correct display orientation
452 
453         CameraInfo info =
454             new CameraInfo();
455         Camera.getCameraInfo(id, info);
456         int rotation = getWindowManager().getDefaultDisplay().getRotation();
457         int degrees = 0;
458         switch (rotation) {
459             case Surface.ROTATION_0: degrees = 0; break;
460             case Surface.ROTATION_90: degrees = 90; break;
461             case Surface.ROTATION_180: degrees = 180; break;
462             case Surface.ROTATION_270: degrees = 270; break;
463         }
464 
465         if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
466             mPreviewRotation = (info.orientation + degrees) % 360;
467             mPreviewRotation = (360 - mPreviewRotation) % 360;  // compensate the mirror
468         } else {  // back-facing
469             mPreviewRotation = (info.orientation - degrees + 360) % 360;
470         }
471 
472         mCamera.setDisplayOrientation(mPreviewRotation);
473 
474         // Start up preview if display is ready
475 
476         if (mPreviewTexture != null) {
477             startPreview();
478         }
479 
480     }
481 
shutdownCamera()482     private void shutdownCamera() {
483         if (mCamera != null) {
484             mCamera.setPreviewCallback(null);
485             mCamera.stopPreview();
486             mCamera.release();
487             mCamera = null;
488             mState = STATE_OFF;
489         }
490     }
491 
492     /**
493      * Rotate and scale the matrix to be applied to the preview or format view, such that no
494      * stretching of the image occurs. To achieve this, the image is centered in the SurfaceTexture
495      * with black bars filling the excess space.
496      */
concatPreviewTransform(Matrix transform)497     private void concatPreviewTransform(Matrix transform) {
498         float widthRatio = mNextPreviewSize.width / (float) mPreviewTexWidth;
499         float heightRatio = mNextPreviewSize.height / (float) mPreviewTexHeight;
500         float scaledWidth = (float) mPreviewTexWidth;
501         float scaledHeight = (float) mPreviewTexHeight;
502 
503         if (heightRatio < widthRatio) {
504             scaledHeight = mPreviewTexHeight * (heightRatio / widthRatio);
505             transform.postScale(1, heightRatio / widthRatio);
506             transform.postTranslate(0,
507                     mPreviewTexHeight * (1 - heightRatio / widthRatio) / 2);
508         } else {
509             scaledWidth = mPreviewTexWidth * (widthRatio / heightRatio);
510             transform.postScale(widthRatio / heightRatio, 1);
511             transform.postTranslate(mPreviewTexWidth * (1 - widthRatio / heightRatio) / 2, 0);
512         }
513 
514         if (mPreviewRotation == 90 || mPreviewRotation == 270) {
515             float scaledAspect = scaledWidth / scaledHeight;
516             float previewAspect = (float) mNextPreviewSize.width / (float) mNextPreviewSize.height;
517             transform.postScale(1.0f, scaledAspect * previewAspect,
518                                 (float) mPreviewTexWidth / 2, (float) mPreviewTexHeight / 2);
519         }
520     }
521 
startPreview()522     private void startPreview() {
523         if (mState != STATE_OFF) {
524             // Stop for a while to drain callbacks
525             mCamera.setPreviewCallback(null);
526             mCamera.stopPreview();
527             mState = STATE_OFF;
528             Handler h = new Handler();
529             Runnable mDelayedPreview = new Runnable() {
530                 public void run() {
531                     startPreview();
532                 }
533             };
534             h.postDelayed(mDelayedPreview, 300);
535             return;
536         }
537         mState = STATE_PREVIEW;
538 
539         Matrix transform = new Matrix();
540         concatPreviewTransform(transform);
541         mPreviewView.setTransform(transform);
542 
543         mPreviewFormat = mNextPreviewFormat;
544         mPreviewSize   = mNextPreviewSize;
545 
546         Camera.Parameters p = mCamera.getParameters();
547         p.setPreviewFormat(mPreviewFormat);
548         p.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
549         mCamera.setParameters(p);
550 
551         mCamera.setPreviewCallback(this);
552         switch (mPreviewFormat) {
553             case ImageFormat.NV16:
554             case ImageFormat.NV21:
555             case ImageFormat.YUY2:
556             case ImageFormat.YV12:
557                 mFormatView.setColorFilter(mYuv2RgbFilter);
558                 break;
559             default:
560                 mFormatView.setColorFilter(null);
561                 break;
562         }
563 
564         // Filter out currently untestable formats
565         switch (mPreviewFormat) {
566             case ImageFormat.NV16:
567             case ImageFormat.RGB_565:
568             case ImageFormat.UNKNOWN:
569             case ImageFormat.JPEG:
570                 AlertDialog.Builder builder =
571                         new AlertDialog.Builder(CameraFormatsActivity.this);
572                 builder.setMessage("Unsupported format " +
573                         mPreviewFormatNames.get(mPreviewFormat) +
574                         "; consider this combination as pass. ")
575                         .setTitle("Missing test" )
576                         .setNeutralButton("Back", null);
577                 builder.show();
578                 mState = STATE_NO_CALLBACKS;
579                 mCamera.setPreviewCallback(null);
580                 break;
581             default:
582                 // supported
583                 break;
584         }
585 
586         mProcessingFirstFrame = true;
587         try {
588             mCamera.setPreviewTexture(mPreviewTexture);
589             mCamera.startPreview();
590         } catch (IOException ioe) {
591             // Something bad happened
592             Log.e(TAG, "Unable to start up preview");
593         }
594     }
595 
596     private class ProcessPreviewDataTask extends AsyncTask<byte[], Void, Boolean> {
doInBackground(byte[]... datas)597         protected Boolean doInBackground(byte[]... datas) {
598             byte[] data = datas[0];
599             try {
600                 if (mRgbData == null ||
601                         mPreviewSize.width != mRgbWidth ||
602                         mPreviewSize.height != mRgbHeight) {
603 
604                     mRgbData = new int[mPreviewSize.width * mPreviewSize.height * 4];
605                     mRgbWidth = mPreviewSize.width;
606                     mRgbHeight = mPreviewSize.height;
607                 }
608                 switch(mPreviewFormat) {
609                     case ImageFormat.NV21:
610                         convertFromNV21(data, mRgbData);
611                         break;
612                     case ImageFormat.YV12:
613                         convertFromYV12(data, mRgbData);
614                         break;
615                     case ImageFormat.YUY2:
616                         convertFromYUY2(data, mRgbData);
617                         break;
618                     case ImageFormat.NV16:
619                     case ImageFormat.RGB_565:
620                     case ImageFormat.UNKNOWN:
621                     case ImageFormat.JPEG:
622                     default:
623                         convertFromUnknown(data, mRgbData);
624                         break;
625                 }
626 
627                 if (mCallbackBitmap == null ||
628                         mRgbWidth != mCallbackBitmap.getWidth() ||
629                         mRgbHeight != mCallbackBitmap.getHeight() ) {
630                     mCallbackBitmap =
631                             Bitmap.createBitmap(
632                                 mRgbWidth, mRgbHeight,
633                                 Bitmap.Config.ARGB_8888);
634                 }
635                 mCallbackBitmap.setPixels(mRgbData, 0, mRgbWidth,
636                         0, 0, mRgbWidth, mRgbHeight);
637             } catch (OutOfMemoryError o) {
638                 Log.e(TAG, "Out of memory trying to process preview data");
639                 return false;
640             }
641             return true;
642         }
643 
onPostExecute(Boolean result)644         protected void onPostExecute(Boolean result) {
645             if (result) {
646                 mFormatView.setImageBitmap(mCallbackBitmap);
647 
648                 CameraInfo info = new CameraInfo();
649                 Camera.getCameraInfo(mCurrentCameraId, info);
650 
651                 int rotation = mPreviewRotation;
652                 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
653                     rotation = (360 - rotation) % 360;  // de-compensate the mirror
654                 }
655 
656                 if (rotation != 0 && rotation != 180) {
657                     Matrix transform = new Matrix();
658                     mFormatView.setScaleType(ImageView.ScaleType.MATRIX);
659                     Rect viewRect = mFormatView.getDrawable().getBounds();
660                     transform.postTranslate(-viewRect.width() / 2, -viewRect.height() / 2);
661                     transform.postRotate(rotation);
662                     transform.postTranslate(viewRect.height() / 2, viewRect.width() / 2);
663                     transform.postScale(
664                             mPreviewView.getMeasuredWidth() / (float) viewRect.height(),
665                             mPreviewView.getMeasuredHeight() / (float) viewRect.width());
666                     concatPreviewTransform(transform);
667                     mFormatView.setImageMatrix(transform);
668                 } else {
669                     mFormatView.setScaleType(ImageView.ScaleType.FIT_CENTER);
670                 }
671 
672                 if (mProcessingFirstFrame) {
673                     mProcessingFirstFrame = false;
674 
675                     CameraCombination combination = new CameraCombination(
676                         mCurrentCameraId,
677                         mResolutionSpinner.getSelectedItemPosition(),
678                         mFormatSpinner.getSelectedItemPosition(),
679                         mPreviewSizes.get(mResolutionSpinner.getSelectedItemPosition()).width,
680                         mPreviewSizes.get(mResolutionSpinner.getSelectedItemPosition()).height,
681                         mPreviewFormatNames.get(
682                             mPreviewFormats.get(mFormatSpinner.getSelectedItemPosition())));
683 
684                     mUntestedCombinations.remove(combination);
685                     mTestedCombinations.add(combination);
686 
687                     displayToast(combination.toString());
688 
689                     if (mTestedCombinations.size() == mAllCombinationsSize) {
690                         setPassButtonEnabled(true);
691                     }
692                 }
693             }
694             mProcessInProgress = false;
695         }
696 
697     }
698 
setPassButtonEnabled(boolean enabled)699     private void setPassButtonEnabled(boolean enabled) {
700         ImageButton pass_button = (ImageButton) findViewById(R.id.pass_button);
701         pass_button.setEnabled(enabled);
702     }
703 
calcAllCombinationsSize()704     private int calcAllCombinationsSize() {
705         int allCombinationsSize = 0;
706         int numCameras = Camera.getNumberOfCameras();
707 
708         for (int i = 0; i<numCameras; i++) {
709             // must release a Camera object before a new Camera object is created
710             shutdownCamera();
711 
712             mCamera = Camera.open(i);
713             Camera.Parameters p = mCamera.getParameters();
714 
715             HashSet<Integer> formatSet = new HashSet<>(p.getSupportedPreviewFormats());
716 
717             allCombinationsSize +=
718                     p.getSupportedPreviewSizes().size() *   // resolutions
719                     formatSet.size();  // unique formats
720         }
721 
722         return allCombinationsSize;
723     }
724 
displayToast(String combination)725     private void displayToast(String combination) {
726         Toast.makeText(this, "\"" + combination + "\"\n" + " has been tested.", Toast.LENGTH_SHORT)
727             .show();
728     }
729 
onPreviewFrame(byte[] data, Camera camera)730     public void onPreviewFrame(byte[] data, Camera camera) {
731         if (mProcessInProgress || mState != STATE_PREVIEW) return;
732 
733         int expectedBytes;
734         switch (mPreviewFormat) {
735             case ImageFormat.YV12:
736                 // YV12 may have stride != width.
737                 int w = mPreviewSize.width;
738                 int h = mPreviewSize.height;
739                 int yStride = (int)Math.ceil(w / 16.0) * 16;
740                 int uvStride = (int)Math.ceil(yStride / 2 / 16.0) * 16;
741                 int ySize = yStride * h;
742                 int uvSize = uvStride * h / 2;
743                 expectedBytes = ySize + uvSize * 2;
744                 break;
745             case ImageFormat.NV21:
746             case ImageFormat.YUY2:
747             default:
748                 expectedBytes = mPreviewSize.width * mPreviewSize.height *
749                         ImageFormat.getBitsPerPixel(mPreviewFormat) / 8;
750                 break;
751         }
752         if (expectedBytes != data.length) {
753             AlertDialog.Builder builder =
754                     new AlertDialog.Builder(CameraFormatsActivity.this);
755             builder.setMessage("Mismatched size of buffer! Expected " +
756                     expectedBytes + ", but got " +
757                     data.length + " bytes instead!")
758                     .setTitle("Error trying to use format "
759                             + mPreviewFormatNames.get(mPreviewFormat))
760                     .setNeutralButton("Back", null);
761 
762             builder.show();
763 
764             mState = STATE_NO_CALLBACKS;
765             mCamera.setPreviewCallback(null);
766             return;
767         }
768 
769         mProcessInProgress = true;
770         new ProcessPreviewDataTask().execute(data);
771     }
772 
convertFromUnknown(byte[] data, int[] rgbData)773     private void convertFromUnknown(byte[] data, int[] rgbData) {
774         int w = mPreviewSize.width;
775         int h = mPreviewSize.height;
776         // RGBA output
777         int rgbInc = 1;
778         if (mPreviewRotation == 180) {
779             rgbInc = -1;
780         }
781         int index = 0;
782         for (int y = 0; y < h; y++) {
783             int rgbIndex = y * w;
784             if (mPreviewRotation == 180) {
785                 rgbIndex = w * (h - y) - 1;
786             }
787             for (int x = 0; x < mPreviewSize.width/3; x++) {
788                 int r = data[index + 0] & 0xFF;
789                 int g = data[index + 1] & 0xFF;
790                 int b = data[index + 2] & 0xFF;
791                 rgbData[rgbIndex] = Color.rgb(r,g,b);
792                 rgbIndex += rgbInc;
793                 index += 3;
794             }
795         }
796     }
797 
798     // NV21 is a semi-planar 4:2:0 format, in the order YVU, which means we have:
799     // a W x H-size 1-byte-per-pixel Y plane, then
800     // a W/2 x H/2-size 2-byte-per-pixel plane, where each pixel has V then U.
convertFromNV21(byte[] data, int rgbData[])801     private void convertFromNV21(byte[] data, int rgbData[]) {
802         int w = mPreviewSize.width;
803         int h = mPreviewSize.height;
804         // RGBA output
805         int rgbIndex = 0;
806         int rgbInc = 1;
807         if (mPreviewRotation == 180) {
808             rgbIndex = h * w - 1;
809             rgbInc = -1;
810         }
811         int yIndex = 0;
812         int uvRowIndex = w*h;
813         int uvRowInc = 0;
814         for (int y = 0; y < h; y++) {
815             int uvInc = 0;
816             int vIndex = uvRowIndex;
817             int uIndex = uvRowIndex + 1;
818 
819             uvRowIndex += uvRowInc * w;
820             uvRowInc = (uvRowInc + 1) & 0x1;
821 
822             for (int x = 0; x < w; x++) {
823                 int yv = data[yIndex] & 0xFF;
824                 int uv = data[uIndex] & 0xFF;
825                 int vv = data[vIndex] & 0xFF;
826                 rgbData[rgbIndex] =
827                         Color.rgb(yv, uv, vv);
828 
829                 rgbIndex += rgbInc;
830                 yIndex += 1;
831                 uIndex += uvInc;
832                 vIndex += uvInc;
833                 uvInc = (uvInc + 2) & 0x2;
834             }
835         }
836     }
837 
838     // YV12 is a planar 4:2:0 format, in the order YVU, which means we have:
839     // a W x H-size 1-byte-per-pixel Y plane, then
840     // a W/2 x H/2-size 1-byte-per-pixel V plane, then
841     // a W/2 x H/2-size 1-byte-per-pixel U plane
842     // The stride may not be equal to width, since it has to be a multiple of
843     // 16 pixels for both the Y and UV planes.
convertFromYV12(byte[] data, int rgbData[])844     private void convertFromYV12(byte[] data, int rgbData[]) {
845         int w = mPreviewSize.width;
846         int h = mPreviewSize.height;
847         // RGBA output
848         int rgbIndex = 0;
849         int rgbInc = 1;
850         if (mPreviewRotation == 180) {
851             rgbIndex = h * w - 1;
852             rgbInc = -1;
853         }
854 
855         int yStride = (int)Math.ceil(w / 16.0) * 16;
856         int uvStride = (int)Math.ceil(yStride/2/16.0) * 16;
857         int ySize = yStride * h;
858         int uvSize = uvStride * h / 2;
859 
860         int uRowIndex = ySize + uvSize;
861         int vRowIndex = ySize;
862 
863         int uv_w = w/2;
864         for (int y = 0; y < h; y++) {
865             int yIndex = yStride * y;
866             int uIndex = uRowIndex;
867             int vIndex = vRowIndex;
868 
869             if ( (y & 0x1) == 1) {
870                 uRowIndex += uvStride;
871                 vRowIndex += uvStride;
872             }
873 
874             int uv = 0, vv = 0;
875             for (int x = 0; x < w; x++) {
876                 if ( (x & 0x1)  == 0) {
877                     uv = data[uIndex] & 0xFF;
878                     vv = data[vIndex] & 0xFF;
879                     uIndex++;
880                     vIndex++;
881                 }
882                 int yv = data[yIndex] & 0xFF;
883                 rgbData[rgbIndex] =
884                         Color.rgb(yv, uv, vv);
885 
886                 rgbIndex += rgbInc;
887                 yIndex += 1;
888             }
889         }
890     }
891 
892     // YUY2 is an interleaved 4:2:2 format: YU,YV,YU,YV
convertFromYUY2(byte[] data, int[] rgbData)893     private void convertFromYUY2(byte[] data, int[] rgbData) {
894         int w = mPreviewSize.width;
895         int h = mPreviewSize.height;
896         // RGBA output
897         int yIndex = 0;
898         int uIndex = 1;
899         int vIndex = 3;
900         int rgbIndex = 0;
901         int rgbInc = 1;
902         if (mPreviewRotation == 180) {
903             rgbIndex = h * w - 1;
904             rgbInc = -1;
905         }
906 
907         for (int y = 0; y < h; y++) {
908             for (int x = 0; x < w; x++) {
909                 int yv = data[yIndex] & 0xFF;
910                 int uv = data[uIndex] & 0xFF;
911                 int vv = data[vIndex] & 0xFF;
912                 rgbData[rgbIndex] = Color.rgb(yv,uv,vv);
913                 rgbIndex += rgbInc;
914                 yIndex += 2;
915                 if ( (x & 0x1) == 1 ) {
916                     uIndex += 4;
917                     vIndex += 4;
918                 }
919             }
920         }
921     }
922 
923 }
924