am e412f43c: am a5cb4567: Camera2: Add CaptureResult test

* commit 'e412f43c95d11fdb7254f327fb5d81ae909d56de':
  Camera2: Add CaptureResult test
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCaptureResultTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCaptureResultTest.java
new file mode 100644
index 0000000..6a708e3
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraCaptureResultTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.Size;
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import android.media.ImageReader;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.ex.camera2.blocking.BlockingStateListener;
+import static com.android.ex.camera2.blocking.BlockingStateListener.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class CameraCaptureResultTest extends AndroidTestCase {
+    private static final String TAG = "CameraCaptureResultTest";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private CameraManager mCameraManager;
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+    private ImageReader mImageReader;
+    private Surface mSurface;
+    private BlockingStateListener mCameraListener;
+
+    private static final int MAX_NUM_IMAGES = 5;
+    private static final int NUM_FRAMES_VERIFIED = 300;
+    private static final long WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
+
+    // List that includes all public keys from CaptureResult
+    List<CameraMetadata.Key<?>> mAllKeys;
+
+    // List tracking the failed test keys.
+    List<CameraMetadata.Key<?>> mFailedKeys = new ArrayList<CameraMetadata.Key<?>>();
+
+    @Override
+    public void setContext(Context context) {
+        mAllKeys = getAllCaptureResultKeys();
+        super.setContext(context);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+        assertNotNull("Can't connect to camera manager", mCameraManager);
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mCameraListener = new BlockingStateListener();
+        mFailedKeys.clear();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mHandlerThread.quitSafely();
+        super.tearDown();
+    }
+
+    public void testCameraCaptureResultAllKeys() throws Exception {
+        /**
+         * Hardcode a key waiver list for the keys we want to skip the sanity check.
+         * FIXME: We need get ride of this list, see bug 11116270.
+         */
+        List<CameraMetadata.Key<?>> waiverkeys = new ArrayList<CameraMetadata.Key<?>>();
+        waiverkeys.add(CaptureResult.EDGE_MODE);
+        waiverkeys.add(CaptureResult.JPEG_GPS_COORDINATES);
+        waiverkeys.add(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
+        waiverkeys.add(CaptureResult.JPEG_GPS_TIMESTAMP);
+        waiverkeys.add(CaptureResult.JPEG_ORIENTATION);
+        waiverkeys.add(CaptureResult.JPEG_QUALITY);
+        waiverkeys.add(CaptureResult.JPEG_THUMBNAIL_QUALITY);
+        waiverkeys.add(CaptureResult.JPEG_THUMBNAIL_SIZE);
+        waiverkeys.add(CaptureResult.SENSOR_TEMPERATURE);
+        waiverkeys.add(CaptureResult.TONEMAP_CURVE_BLUE);
+        waiverkeys.add(CaptureResult.TONEMAP_CURVE_GREEN);
+        waiverkeys.add(CaptureResult.TONEMAP_CURVE_RED);
+        waiverkeys.add(CaptureResult.TONEMAP_MODE);
+        waiverkeys.add(CaptureResult.STATISTICS_PREDICTED_COLOR_GAINS);
+        waiverkeys.add(CaptureResult.STATISTICS_PREDICTED_COLOR_TRANSFORM);
+        waiverkeys.add(CaptureResult.STATISTICS_SCENE_FLICKER);
+
+        String[] ids = mCameraManager.getCameraIdList();
+        for (int i = 0; i < ids.length; i++) {
+            CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+            assertNotNull("CameraCharacteristics shouldn't be null", props);
+            Integer hwLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+            if (hwLevel != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL) {
+                continue;
+            }
+            // TODO: check for LIMITED keys
+
+            CameraDevice camera = null;
+            try {
+                Size[] sizes = CameraTestUtils.getSupportedSizeForFormat(
+                        ImageFormat.YUV_420_888, ids[i], mCameraManager);
+                CameraTestUtils.assertArrayNotEmpty(sizes, "Available sizes shouldn't be empty");
+                createDefaultSurface(sizes[0]);
+
+                if (VERBOSE) {
+                    Log.v(TAG, "Testing camera " + ids[i] + "for size " + sizes[0].toString());
+                }
+
+                camera = CameraTestUtils.openCamera(
+                        mCameraManager, ids[i], mCameraListener, mHandler);
+                assertNotNull(
+                        String.format("Failed to open camera device %s", ids[i]), camera);
+                mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
+
+                List<Surface> outputSurfaces = new ArrayList<Surface>(1);
+                outputSurfaces.add(mSurface);
+                camera.configureOutputs(outputSurfaces);
+                mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+                mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
+
+                CaptureRequest.Builder requestBuilder =
+                        camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+                assertNotNull("Failed to create capture request", requestBuilder);
+                requestBuilder.addTarget(mSurface);
+
+                // Enable face detection if supported
+                byte[] faceModes = props.get(
+                        CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES);
+                assertNotNull("Available face detection modes shouldn't be null", faceModes);
+                for (int m = 0; m < faceModes.length; m++) {
+                    if (faceModes[m] == CameraMetadata.STATISTICS_FACE_DETECT_MODE_FULL) {
+                        if (VERBOSE) {
+                            Log.v(TAG, "testCameraCaptureResultAllKeys - " +
+                                    "setting facedetection mode to full");
+                        }
+                        requestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE,
+                                (int)faceModes[m]);
+                    }
+                }
+
+                // Enable lensShading mode, it should be supported by full mode device.
+                requestBuilder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
+                        CameraMetadata.STATISTICS_LENS_SHADING_MAP_MODE_ON);
+
+                SimpleCaptureListener captureListener = new SimpleCaptureListener();
+                camera.setRepeatingRequest(requestBuilder.build(), captureListener, mHandler);
+
+                for (int m = 0; m < NUM_FRAMES_VERIFIED; m++) {
+                    if(VERBOSE) {
+                        Log.v(TAG, "Testing frame " + m);
+                    }
+                    validateCaptureResult(
+                            captureListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS),
+                            waiverkeys);
+                }
+
+                // Stop repeat, wait for captures to complete, and disconnect from surfaces
+                camera.configureOutputs(/*outputs*/ null);
+                mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+                mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS);
+                // Camera has disconnected, clear out the reader
+                mSurface.release();
+                mImageReader.close();
+            } finally {
+                if (camera != null) {
+                    camera.close();
+                }
+            }
+
+        }
+    }
+
+    private void validateCaptureResult(CaptureResult result,
+            List<CameraMetadata.Key<?>> skippedKeys) throws Exception {
+        for (CameraMetadata.Key<?> key : mAllKeys) {
+            if (!skippedKeys.contains(key) && result.get(key) == null) {
+                mFailedKeys.add(key);
+            }
+        }
+
+        StringBuffer failedKeyNames = new StringBuffer("Below Keys have null values:\n");
+        for (CameraMetadata.Key<?> key : mFailedKeys) {
+            failedKeyNames.append(key.getName() + "\n");
+        }
+
+        assertTrue("Some keys have null values, " + failedKeyNames.toString(),
+                mFailedKeys.isEmpty());
+    }
+
+    private static class SimpleCaptureListener extends CameraDevice.CaptureListener {
+        LinkedBlockingQueue<CaptureResult> mQueue = new LinkedBlockingQueue<CaptureResult>();
+
+        @Override
+        public void onCaptureStarted(CameraDevice camera, CaptureRequest request, long timestamp)
+        {
+        }
+
+        @Override
+        public void onCaptureCompleted(CameraDevice camera, CaptureRequest request,
+                CaptureResult result) {
+            try {
+                mQueue.put(result);
+            } catch (InterruptedException e) {
+                throw new UnsupportedOperationException(
+                        "Can't handle InterruptedException in onCaptureCompleted");
+            }
+        }
+
+        @Override
+        public void onCaptureFailed(CameraDevice camera, CaptureRequest request,
+                CaptureFailure failure) {
+        }
+
+        @Override
+        public void onCaptureSequenceCompleted(CameraDevice camera, int sequenceId,
+                int frameNumber) {
+        }
+
+        public CaptureResult getCaptureResult(long timeout) throws InterruptedException {
+            CaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+            assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result);
+            return result;
+        }
+    }
+
+    private void createDefaultSurface(Size sz) {
+        mImageReader =
+                ImageReader.newInstance(sz.getWidth(),
+                        sz.getHeight(),
+                        ImageFormat.YUV_420_888,
+                        MAX_NUM_IMAGES);
+        mImageReader.setOnImageAvailableListener(new ImageDropperListener(), mHandler);
+        mSurface = mImageReader.getSurface();
+    }
+
+    /**
+     * TODO: Use CameraCharacteristics.getAvailableCaptureResultKeys() once we can filter out
+     * @hide keys.
+     *
+     */
+
+    /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
+     * The key entries below this point are generated from metadata
+     * definitions in /system/media/camera/docs. Do not modify by hand or
+     * modify the comment blocks at the start or end.
+     *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/
+
+    private static List<CameraMetadata.Key<?>> getAllCaptureResultKeys() {
+        ArrayList<CameraMetadata.Key<?>> resultKeys = new ArrayList<CameraMetadata.Key<?>>();
+        resultKeys.add(CaptureResult.COLOR_CORRECTION_TRANSFORM);
+        resultKeys.add(CaptureResult.COLOR_CORRECTION_GAINS);
+        resultKeys.add(CaptureResult.CONTROL_AE_REGIONS);
+        resultKeys.add(CaptureResult.CONTROL_AF_MODE);
+        resultKeys.add(CaptureResult.CONTROL_AF_REGIONS);
+        resultKeys.add(CaptureResult.CONTROL_AWB_MODE);
+        resultKeys.add(CaptureResult.CONTROL_AWB_REGIONS);
+        resultKeys.add(CaptureResult.CONTROL_MODE);
+        resultKeys.add(CaptureResult.CONTROL_AE_STATE);
+        resultKeys.add(CaptureResult.CONTROL_AF_STATE);
+        resultKeys.add(CaptureResult.CONTROL_AWB_STATE);
+        resultKeys.add(CaptureResult.EDGE_MODE);
+        resultKeys.add(CaptureResult.FLASH_MODE);
+        resultKeys.add(CaptureResult.FLASH_STATE);
+        resultKeys.add(CaptureResult.JPEG_GPS_COORDINATES);
+        resultKeys.add(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
+        resultKeys.add(CaptureResult.JPEG_GPS_TIMESTAMP);
+        resultKeys.add(CaptureResult.JPEG_ORIENTATION);
+        resultKeys.add(CaptureResult.JPEG_QUALITY);
+        resultKeys.add(CaptureResult.JPEG_THUMBNAIL_QUALITY);
+        resultKeys.add(CaptureResult.JPEG_THUMBNAIL_SIZE);
+        resultKeys.add(CaptureResult.LENS_APERTURE);
+        resultKeys.add(CaptureResult.LENS_FILTER_DENSITY);
+        resultKeys.add(CaptureResult.LENS_FOCAL_LENGTH);
+        resultKeys.add(CaptureResult.LENS_FOCUS_DISTANCE);
+        resultKeys.add(CaptureResult.LENS_OPTICAL_STABILIZATION_MODE);
+        resultKeys.add(CaptureResult.LENS_FOCUS_RANGE);
+        resultKeys.add(CaptureResult.LENS_STATE);
+        resultKeys.add(CaptureResult.NOISE_REDUCTION_MODE);
+        resultKeys.add(CaptureResult.REQUEST_FRAME_COUNT);
+        resultKeys.add(CaptureResult.SCALER_CROP_REGION);
+        resultKeys.add(CaptureResult.SENSOR_EXPOSURE_TIME);
+        resultKeys.add(CaptureResult.SENSOR_FRAME_DURATION);
+        resultKeys.add(CaptureResult.SENSOR_SENSITIVITY);
+        resultKeys.add(CaptureResult.SENSOR_TIMESTAMP);
+        resultKeys.add(CaptureResult.SENSOR_TEMPERATURE);
+        resultKeys.add(CaptureResult.STATISTICS_FACE_DETECT_MODE);
+        resultKeys.add(CaptureResult.STATISTICS_FACE_IDS);
+        resultKeys.add(CaptureResult.STATISTICS_FACE_LANDMARKS);
+        resultKeys.add(CaptureResult.STATISTICS_FACE_RECTANGLES);
+        resultKeys.add(CaptureResult.STATISTICS_FACE_SCORES);
+        resultKeys.add(CaptureResult.STATISTICS_LENS_SHADING_MAP);
+        resultKeys.add(CaptureResult.STATISTICS_PREDICTED_COLOR_GAINS);
+        resultKeys.add(CaptureResult.STATISTICS_PREDICTED_COLOR_TRANSFORM);
+        resultKeys.add(CaptureResult.STATISTICS_SCENE_FLICKER);
+        resultKeys.add(CaptureResult.TONEMAP_CURVE_BLUE);
+        resultKeys.add(CaptureResult.TONEMAP_CURVE_GREEN);
+        resultKeys.add(CaptureResult.TONEMAP_CURVE_RED);
+        resultKeys.add(CaptureResult.TONEMAP_MODE);
+        resultKeys.add(CaptureResult.BLACK_LEVEL_LOCK);
+
+        // Add STATISTICS_FACES key separately here because it is not
+        // defined in metadata xml file.
+        resultKeys.add(CaptureResult.STATISTICS_FACES);
+
+        return resultKeys;
+    }
+
+    /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
+     * End generated code
+     *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
index a2d965c..7f10cb8 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -24,6 +24,7 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.Size;
 import android.media.Image;
+import android.media.ImageReader;
 import android.media.Image.Plane;
 import android.os.Handler;
 import android.util.Log;
@@ -52,6 +53,20 @@
     public static final int CAMERA_ACTIVE_TIMEOUT_MS = 500;
     public static final int CAMERA_BUSY_TIMEOUT_MS = 500;
 
+    public static class ImageDropperListener implements ImageReader.OnImageAvailableListener {
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            Image image = null;
+            try {
+                image = reader.acquireNextImage();
+            } finally {
+                if (image != null) {
+                    image.close();
+                }
+            }
+        }
+    }
+
     /**
      * Block until the camera is opened.
      *
@@ -214,21 +229,21 @@
         int format = image.getFormat();
         Plane[] planes = image.getPlanes();
         switch (format) {
-          case ImageFormat.YUV_420_888:
-          case ImageFormat.NV21:
-          case ImageFormat.YV12:
-              assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
-              break;
-          case ImageFormat.Y8:
-          case ImageFormat.Y16:
-              assertEquals("Y8/Y16 Image should have 1 plane", 1, planes.length);
-              break;
-          case ImageFormat.JPEG:
-              assertEquals("Jpeg Image should have one plane", 1, planes.length);
-              break;
-          default:
-              fail("Unsupported Image Format: " + format);
-      }
+            case ImageFormat.YUV_420_888:
+            case ImageFormat.NV21:
+            case ImageFormat.YV12:
+                assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
+                break;
+            case ImageFormat.Y8:
+            case ImageFormat.Y16:
+                assertEquals("Y8/Y16 Image should have 1 plane", 1, planes.length);
+                break;
+            case ImageFormat.JPEG:
+                assertEquals("Jpeg Image should have one plane", 1, planes.length);
+                break;
+            default:
+                fail("Unsupported Image Format: " + format);
+        }
     }
 
     public static void dumpFile(String fileName, byte[] data) {