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 com.android.cts.verifier.PassFailButtons;
19 import com.android.cts.verifier.R;
20 
21 import android.app.AlertDialog;
22 import android.graphics.Bitmap;
23 import android.graphics.Color;
24 import android.graphics.ColorMatrix;
25 import android.graphics.ColorMatrixColorFilter;
26 import android.graphics.ImageFormat;
27 import android.graphics.Matrix;
28 import android.graphics.SurfaceTexture;
29 import android.hardware.Camera;
30 import android.hardware.Camera.CameraInfo;
31 import android.os.AsyncTask;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.util.Log;
35 import android.util.SparseArray;
36 import android.view.Menu;
37 import android.view.MenuItem;
38 import android.view.View;
39 import android.view.Surface;
40 import android.view.TextureView;
41 import android.widget.AdapterView;
42 import android.widget.ArrayAdapter;
43 import android.widget.Button;
44 import android.widget.ImageButton;
45 import android.widget.ImageView;
46 import android.widget.Spinner;
47 import android.widget.Toast;
48 
49 import java.io.IOException;
50 import java.lang.Math;
51 import java.util.ArrayList;
52 import java.util.HashSet;
53 import java.util.Comparator;
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.cf_format_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.cf_format_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.cf_format_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         if (mPreviewRotation != 0 && mPreviewRotation != 180) {
472             Log.w(TAG,
473                 "Display orientation correction is not 0 or 180, as expected!");
474         }
475 
476         mCamera.setDisplayOrientation(mPreviewRotation);
477 
478         // Start up preview if display is ready
479 
480         if (mPreviewTexture != null) {
481             startPreview();
482         }
483 
484     }
485 
shutdownCamera()486     private void shutdownCamera() {
487         if (mCamera != null) {
488             mCamera.setPreviewCallback(null);
489             mCamera.stopPreview();
490             mCamera.release();
491             mCamera = null;
492             mState = STATE_OFF;
493         }
494     }
495 
startPreview()496     private void startPreview() {
497         if (mState != STATE_OFF) {
498             // Stop for a while to drain callbacks
499             mCamera.setPreviewCallback(null);
500             mCamera.stopPreview();
501             mState = STATE_OFF;
502             Handler h = new Handler();
503             Runnable mDelayedPreview = new Runnable() {
504                 public void run() {
505                     startPreview();
506                 }
507             };
508             h.postDelayed(mDelayedPreview, 300);
509             return;
510         }
511         mState = STATE_PREVIEW;
512 
513         Matrix transform = new Matrix();
514         float widthRatio = mNextPreviewSize.width / (float)mPreviewTexWidth;
515         float heightRatio = mNextPreviewSize.height / (float)mPreviewTexHeight;
516 
517         if (heightRatio < widthRatio) {
518             transform.setScale(1, heightRatio/widthRatio);
519             transform.postTranslate(0,
520                 mPreviewTexHeight * (1 - heightRatio/widthRatio)/2);
521         } else {
522             transform.setScale(widthRatio/heightRatio, 1);
523             transform.postTranslate(mPreviewTexWidth * (1 - widthRatio/heightRatio)/2,
524             0);
525         }
526 
527         mPreviewView.setTransform(transform);
528 
529         mPreviewFormat = mNextPreviewFormat;
530         mPreviewSize   = mNextPreviewSize;
531 
532         Camera.Parameters p = mCamera.getParameters();
533         p.setPreviewFormat(mPreviewFormat);
534         p.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
535         mCamera.setParameters(p);
536 
537         mCamera.setPreviewCallback(this);
538         switch (mPreviewFormat) {
539             case ImageFormat.NV16:
540             case ImageFormat.NV21:
541             case ImageFormat.YUY2:
542             case ImageFormat.YV12:
543                 mFormatView.setColorFilter(mYuv2RgbFilter);
544                 break;
545             default:
546                 mFormatView.setColorFilter(null);
547                 break;
548         }
549 
550         // Filter out currently untestable formats
551         switch (mPreviewFormat) {
552             case ImageFormat.NV16:
553             case ImageFormat.RGB_565:
554             case ImageFormat.UNKNOWN:
555             case ImageFormat.JPEG:
556                 AlertDialog.Builder builder =
557                         new AlertDialog.Builder(CameraFormatsActivity.this);
558                 builder.setMessage("Unsupported format " +
559                         mPreviewFormatNames.get(mPreviewFormat) +
560                         "; consider this combination as pass. ")
561                         .setTitle("Missing test" )
562                         .setNeutralButton("Back", null);
563                 builder.show();
564                 mState = STATE_NO_CALLBACKS;
565                 mCamera.setPreviewCallback(null);
566                 break;
567             default:
568                 // supported
569                 break;
570         }
571 
572         mProcessingFirstFrame = true;
573         try {
574             mCamera.setPreviewTexture(mPreviewTexture);
575             mCamera.startPreview();
576         } catch (IOException ioe) {
577             // Something bad happened
578             Log.e(TAG, "Unable to start up preview");
579         }
580     }
581 
582     private class ProcessPreviewDataTask extends AsyncTask<byte[], Void, Boolean> {
doInBackground(byte[]... datas)583         protected Boolean doInBackground(byte[]... datas) {
584             byte[] data = datas[0];
585             try {
586                 if (mRgbData == null ||
587                         mPreviewSize.width != mRgbWidth ||
588                         mPreviewSize.height != mRgbHeight) {
589 
590                     mRgbData = new int[mPreviewSize.width * mPreviewSize.height * 4];
591                     mRgbWidth = mPreviewSize.width;
592                     mRgbHeight = mPreviewSize.height;
593                 }
594                 switch(mPreviewFormat) {
595                     case ImageFormat.NV21:
596                         convertFromNV21(data, mRgbData);
597                         break;
598                     case ImageFormat.YV12:
599                         convertFromYV12(data, mRgbData);
600                         break;
601                     case ImageFormat.YUY2:
602                         convertFromYUY2(data, mRgbData);
603                         break;
604                     case ImageFormat.NV16:
605                     case ImageFormat.RGB_565:
606                     case ImageFormat.UNKNOWN:
607                     case ImageFormat.JPEG:
608                     default:
609                         convertFromUnknown(data, mRgbData);
610                         break;
611                 }
612 
613                 if (mCallbackBitmap == null ||
614                         mRgbWidth != mCallbackBitmap.getWidth() ||
615                         mRgbHeight != mCallbackBitmap.getHeight() ) {
616                     mCallbackBitmap =
617                             Bitmap.createBitmap(
618                                 mRgbWidth, mRgbHeight,
619                                 Bitmap.Config.ARGB_8888);
620                 }
621                 mCallbackBitmap.setPixels(mRgbData, 0, mRgbWidth,
622                         0, 0, mRgbWidth, mRgbHeight);
623             } catch (OutOfMemoryError o) {
624                 Log.e(TAG, "Out of memory trying to process preview data");
625                 return false;
626             }
627             return true;
628         }
629 
onPostExecute(Boolean result)630         protected void onPostExecute(Boolean result) {
631             if (result) {
632                 mFormatView.setImageBitmap(mCallbackBitmap);
633                 if (mProcessingFirstFrame) {
634                     mProcessingFirstFrame = false;
635 
636                     CameraCombination combination = new CameraCombination(
637                         mCurrentCameraId,
638                         mResolutionSpinner.getSelectedItemPosition(),
639                         mFormatSpinner.getSelectedItemPosition(),
640                         mPreviewSizes.get(mResolutionSpinner.getSelectedItemPosition()).width,
641                         mPreviewSizes.get(mResolutionSpinner.getSelectedItemPosition()).height,
642                         mPreviewFormatNames.get(
643                             mPreviewFormats.get(mFormatSpinner.getSelectedItemPosition())));
644 
645                     mUntestedCombinations.remove(combination);
646                     mTestedCombinations.add(combination);
647 
648                     displayToast(combination.toString());
649 
650                     if (mTestedCombinations.size() == mAllCombinationsSize) {
651                         setPassButtonEnabled(true);
652                     }
653                 }
654             }
655             mProcessInProgress = false;
656         }
657 
658     }
659 
setPassButtonEnabled(boolean enabled)660     private void setPassButtonEnabled(boolean enabled) {
661         ImageButton pass_button = (ImageButton) findViewById(R.id.pass_button);
662         pass_button.setEnabled(enabled);
663     }
664 
calcAllCombinationsSize()665     private int calcAllCombinationsSize() {
666         int allCombinationsSize = 0;
667         int numCameras = Camera.getNumberOfCameras();
668 
669         for (int i = 0; i<numCameras; i++) {
670             // must release a Camera object before a new Camera object is created
671             shutdownCamera();
672 
673             mCamera = Camera.open(i);
674             Camera.Parameters p = mCamera.getParameters();
675 
676             HashSet<Integer> formatSet = new HashSet<>(p.getSupportedPreviewFormats());
677 
678             allCombinationsSize +=
679                     p.getSupportedPreviewSizes().size() *   // resolutions
680                     formatSet.size();  // unique formats
681         }
682 
683         return allCombinationsSize;
684     }
685 
displayToast(String combination)686     private void displayToast(String combination) {
687         Toast.makeText(this, "\"" + combination + "\"\n" + " has been tested.", Toast.LENGTH_SHORT)
688             .show();
689     }
690 
onPreviewFrame(byte[] data, Camera camera)691     public void onPreviewFrame(byte[] data, Camera camera) {
692         if (mProcessInProgress || mState != STATE_PREVIEW) return;
693 
694         int expectedBytes;
695         switch (mPreviewFormat) {
696             case ImageFormat.YV12:
697                 // YV12 may have stride != width.
698                 int w = mPreviewSize.width;
699                 int h = mPreviewSize.height;
700                 int yStride = (int)Math.ceil(w / 16.0) * 16;
701                 int uvStride = (int)Math.ceil(yStride / 2 / 16.0) * 16;
702                 int ySize = yStride * h;
703                 int uvSize = uvStride * h / 2;
704                 expectedBytes = ySize + uvSize * 2;
705                 break;
706             case ImageFormat.NV21:
707             case ImageFormat.YUY2:
708             default:
709                 expectedBytes = mPreviewSize.width * mPreviewSize.height *
710                         ImageFormat.getBitsPerPixel(mPreviewFormat) / 8;
711                 break;
712         }
713         if (expectedBytes != data.length) {
714             AlertDialog.Builder builder =
715                     new AlertDialog.Builder(CameraFormatsActivity.this);
716             builder.setMessage("Mismatched size of buffer! Expected " +
717                     expectedBytes + ", but got " +
718                     data.length + " bytes instead!")
719                     .setTitle("Error trying to use format "
720                             + mPreviewFormatNames.get(mPreviewFormat))
721                     .setNeutralButton("Back", null);
722 
723             builder.show();
724 
725             mState = STATE_NO_CALLBACKS;
726             mCamera.setPreviewCallback(null);
727             return;
728         }
729 
730         mProcessInProgress = true;
731         new ProcessPreviewDataTask().execute(data);
732     }
733 
convertFromUnknown(byte[] data, int[] rgbData)734     private void convertFromUnknown(byte[] data, int[] rgbData) {
735         int w = mPreviewSize.width;
736         int h = mPreviewSize.height;
737         // RGBA output
738         int rgbInc = 1;
739         if (mPreviewRotation == 180) {
740             rgbInc = -1;
741         }
742         int index = 0;
743         for (int y = 0; y < h; y++) {
744             int rgbIndex = y * w;
745             if (mPreviewRotation == 180) {
746                 rgbIndex = w * (h - y) - 1;
747             }
748             for (int x = 0; x < mPreviewSize.width/3; x++) {
749                 int r = data[index + 0] & 0xFF;
750                 int g = data[index + 1] & 0xFF;
751                 int b = data[index + 2] & 0xFF;
752                 rgbData[rgbIndex] = Color.rgb(r,g,b);
753                 rgbIndex += rgbInc;
754                 index += 3;
755             }
756         }
757     }
758 
759     // NV21 is a semi-planar 4:2:0 format, in the order YVU, which means we have:
760     // a W x H-size 1-byte-per-pixel Y plane, then
761     // a W/2 x H/2-size 2-byte-per-pixel plane, where each pixel has V then U.
convertFromNV21(byte[] data, int rgbData[])762     private void convertFromNV21(byte[] data, int rgbData[]) {
763         int w = mPreviewSize.width;
764         int h = mPreviewSize.height;
765         // RGBA output
766         int rgbIndex = 0;
767         int rgbInc = 1;
768         if (mPreviewRotation == 180) {
769             rgbIndex = h * w - 1;
770             rgbInc = -1;
771         }
772         int yIndex = 0;
773         int uvRowIndex = w*h;
774         int uvRowInc = 0;
775         for (int y = 0; y < h; y++) {
776             int uvInc = 0;
777             int vIndex = uvRowIndex;
778             int uIndex = uvRowIndex + 1;
779 
780             uvRowIndex += uvRowInc * w;
781             uvRowInc = (uvRowInc + 1) & 0x1;
782 
783             for (int x = 0; x < w; x++) {
784                 int yv = data[yIndex] & 0xFF;
785                 int uv = data[uIndex] & 0xFF;
786                 int vv = data[vIndex] & 0xFF;
787                 rgbData[rgbIndex] =
788                         Color.rgb(yv, uv, vv);
789 
790                 rgbIndex += rgbInc;
791                 yIndex += 1;
792                 uIndex += uvInc;
793                 vIndex += uvInc;
794                 uvInc = (uvInc + 2) & 0x2;
795             }
796         }
797     }
798 
799     // YV12 is a planar 4:2:0 format, in the order YVU, which means we have:
800     // a W x H-size 1-byte-per-pixel Y plane, then
801     // a W/2 x H/2-size 1-byte-per-pixel V plane, then
802     // a W/2 x H/2-size 1-byte-per-pixel U plane
803     // The stride may not be equal to width, since it has to be a multiple of
804     // 16 pixels for both the Y and UV planes.
convertFromYV12(byte[] data, int rgbData[])805     private void convertFromYV12(byte[] data, int rgbData[]) {
806         int w = mPreviewSize.width;
807         int h = mPreviewSize.height;
808         // RGBA output
809         int rgbIndex = 0;
810         int rgbInc = 1;
811         if (mPreviewRotation == 180) {
812             rgbIndex = h * w - 1;
813             rgbInc = -1;
814         }
815 
816         int yStride = (int)Math.ceil(w / 16.0) * 16;
817         int uvStride = (int)Math.ceil(yStride/2/16.0) * 16;
818         int ySize = yStride * h;
819         int uvSize = uvStride * h / 2;
820 
821         int uRowIndex = ySize + uvSize;
822         int vRowIndex = ySize;
823 
824         int uv_w = w/2;
825         for (int y = 0; y < h; y++) {
826             int yIndex = yStride * y;
827             int uIndex = uRowIndex;
828             int vIndex = vRowIndex;
829 
830             if ( (y & 0x1) == 1) {
831                 uRowIndex += uvStride;
832                 vRowIndex += uvStride;
833             }
834 
835             int uv = 0, vv = 0;
836             for (int x = 0; x < w; x++) {
837                 if ( (x & 0x1)  == 0) {
838                     uv = data[uIndex] & 0xFF;
839                     vv = data[vIndex] & 0xFF;
840                     uIndex++;
841                     vIndex++;
842                 }
843                 int yv = data[yIndex] & 0xFF;
844                 rgbData[rgbIndex] =
845                         Color.rgb(yv, uv, vv);
846 
847                 rgbIndex += rgbInc;
848                 yIndex += 1;
849             }
850         }
851     }
852 
853     // YUY2 is an interleaved 4:2:2 format: YU,YV,YU,YV
convertFromYUY2(byte[] data, int[] rgbData)854     private void convertFromYUY2(byte[] data, int[] rgbData) {
855         int w = mPreviewSize.width;
856         int h = mPreviewSize.height;
857         // RGBA output
858         int yIndex = 0;
859         int uIndex = 1;
860         int vIndex = 3;
861         int rgbIndex = 0;
862         int rgbInc = 1;
863         if (mPreviewRotation == 180) {
864             rgbIndex = h * w - 1;
865             rgbInc = -1;
866         }
867 
868         for (int y = 0; y < h; y++) {
869             for (int x = 0; x < w; x++) {
870                 int yv = data[yIndex] & 0xFF;
871                 int uv = data[uIndex] & 0xFF;
872                 int vv = data[vIndex] & 0xFF;
873                 rgbData[rgbIndex] = Color.rgb(yv,uv,vv);
874                 rgbIndex += rgbInc;
875                 yIndex += 2;
876                 if ( (x & 0x1) == 1 ) {
877                     uIndex += 4;
878                     vIndex += 4;
879                 }
880             }
881         }
882     }
883 
884 }
885