Camera2: Add DEPTH_OUTPUT capability verification

Verify DEPTH_OUTPUT-capable devices have necessary
static characteristics included, and do some basic sanity checks of their values.

Also cross-check that DEPTH_OUTPUT is listed when the necessary
characteristics and outputs are supported

Bug: 20537722
Change-Id: Idd3cffce3d4ec578815631be687c68d7cce35745
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
index b752c07..dc499ba 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -748,10 +748,10 @@
         resultKeys.add(CaptureResult.LENS_OPTICAL_STABILIZATION_MODE);
         resultKeys.add(CaptureResult.LENS_POSE_ROTATION);
         resultKeys.add(CaptureResult.LENS_POSE_TRANSLATION);
-        resultKeys.add(CaptureResult.LENS_INTRINSIC_CALIBRATION);
-        resultKeys.add(CaptureResult.LENS_RADIAL_DISTORTION);
         resultKeys.add(CaptureResult.LENS_FOCUS_RANGE);
         resultKeys.add(CaptureResult.LENS_STATE);
+        resultKeys.add(CaptureResult.LENS_INTRINSIC_CALIBRATION);
+        resultKeys.add(CaptureResult.LENS_RADIAL_DISTORTION);
         resultKeys.add(CaptureResult.NOISE_REDUCTION_MODE);
         resultKeys.add(CaptureResult.REQUEST_PIPELINE_DEPTH);
         resultKeys.add(CaptureResult.SCALER_CROP_REGION);
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 80cd288..6d62067 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -738,6 +738,157 @@
     }
 
     /**
+     * Check depth output capability
+     */
+    public void testDepthOutputCharacteristics() {
+        int counter = 0;
+
+        for (CameraCharacteristics c : mCharacteristics) {
+            Log.i(TAG, "testDepthOutputCharacteristics: Testing camera ID " + mIds[counter]);
+
+            int[] capabilities = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            assertNotNull("android.request.availableCapabilities must never be null",
+                    capabilities);
+            boolean supportDepth = arrayContains(capabilities,
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT);
+            StreamConfigurationMap configs =
+                    c.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+            int[] outputFormats = configs.getOutputFormats();
+            boolean hasDepth16 = arrayContains(outputFormats, ImageFormat.DEPTH16);
+
+            Boolean depthIsExclusive = c.get(CameraCharacteristics.DEPTH_DEPTH_IS_EXCLUSIVE);
+
+            float[] poseRotation = c.get(CameraCharacteristics.LENS_POSE_ROTATION);
+            float[] poseTranslation = c.get(CameraCharacteristics.LENS_POSE_TRANSLATION);
+            float[] cameraIntrinsics = c.get(CameraCharacteristics.LENS_INTRINSIC_CALIBRATION);
+            float[] radialDistortion = c.get(CameraCharacteristics.LENS_RADIAL_DISTORTION);
+
+            if (supportDepth) {
+                mCollector.expectTrue("Supports DEPTH_OUTPUT but does not support DEPTH16",
+                        hasDepth16);
+                if (hasDepth16) {
+                    Size[] depthSizes = configs.getOutputSizes(ImageFormat.DEPTH16);
+                    mCollector.expectTrue("Supports DEPTH_OUTPUT but no sizes for DEPTH16 supported!",
+                            depthSizes != null && depthSizes.length > 0);
+                    if (depthSizes != null) {
+                        for (Size depthSize : depthSizes) {
+                            mCollector.expectTrue("All depth16 sizes must be positive",
+                                    depthSize.getWidth() > 0 && depthSize.getHeight() > 0);
+                            long minFrameDuration = configs.getOutputMinFrameDuration(
+                                    ImageFormat.DEPTH16, depthSize);
+                            mCollector.expectTrue("Non-negative min frame duration for depth size "
+                                    + depthSize + " expected, got " + minFrameDuration,
+                                    minFrameDuration >= 0);
+                            long stallDuration = configs.getOutputStallDuration(
+                                    ImageFormat.DEPTH16, depthSize);
+                            mCollector.expectTrue("Non-negative stall duration for depth size "
+                                    + depthSize + " expected, got " + stallDuration,
+                                    stallDuration >= 0);
+                        }
+                    }
+                }
+                if (arrayContains(outputFormats, ImageFormat.DEPTH_POINT_CLOUD)) {
+                    Size[] depthCloudSizes = configs.getOutputSizes(ImageFormat.DEPTH_POINT_CLOUD);
+                    mCollector.expectTrue("Supports DEPTH_POINT_CLOUD " +
+                            "but no sizes for DEPTH_POINT_CLOUD supported!",
+                            depthCloudSizes != null && depthCloudSizes.length > 0);
+                    if (depthCloudSizes != null) {
+                        for (Size depthCloudSize : depthCloudSizes) {
+                            mCollector.expectTrue("All depth point cloud sizes must be nonzero",
+                                    depthCloudSize.getWidth() > 0);
+                            mCollector.expectTrue("All depth point cloud sizes must be N x 1",
+                                    depthCloudSize.getHeight() == 1);
+                            long minFrameDuration = configs.getOutputMinFrameDuration(
+                                    ImageFormat.DEPTH_POINT_CLOUD, depthCloudSize);
+                            mCollector.expectTrue("Non-negative min frame duration for depth size "
+                                    + depthCloudSize + " expected, got " + minFrameDuration,
+                                    minFrameDuration >= 0);
+                            long stallDuration = configs.getOutputStallDuration(
+                                    ImageFormat.DEPTH_POINT_CLOUD, depthCloudSize);
+                            mCollector.expectTrue("Non-negative stall duration for depth size "
+                                    + depthCloudSize + " expected, got " + stallDuration,
+                                    stallDuration >= 0);
+                        }
+                    }
+                }
+
+                mCollector.expectTrue("Supports DEPTH_OUTPUT but DEPTH_IS_EXCLUSIVE is not defined",
+                        depthIsExclusive != null);
+
+                mCollector.expectTrue(
+                        "Supports DEPTH_OUTPUT but LENS_POSE_ROTATION not right size",
+                        poseRotation != null && poseRotation.length == 4);
+                mCollector.expectTrue(
+                        "Supports DEPTH_OUTPUT but LENS_POSE_TRANSLATION not right size",
+                        poseTranslation != null && poseTranslation.length == 3);
+                mCollector.expectTrue(
+                        "Supports DEPTH_OUTPUT but LENS_INTRINSIC_CALIBRATION not right size",
+                        cameraIntrinsics != null && cameraIntrinsics.length == 5);
+                mCollector.expectTrue(
+                        "Supports DEPTH_OUTPUT but LENS_RADIAL_DISTORTION not right size",
+                        radialDistortion != null && radialDistortion.length == 6);
+
+                if (poseRotation != null && poseRotation.length == 4) {
+                    float normSq =
+                        poseRotation[0] * poseRotation[0] +
+                        poseRotation[1] * poseRotation[1] +
+                        poseRotation[2] * poseRotation[2] +
+                        poseRotation[3] * poseRotation[3];
+                    mCollector.expectTrue(
+                            "LENS_POSE_ROTATION quarternion must be unit-length",
+                            0.9999f < normSq && normSq < 1.0001f);
+
+                    // TODO: Cross-validate orientation/facing and poseRotation
+                    Integer orientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);
+                    Integer facing = c.get(CameraCharacteristics.LENS_FACING);
+                }
+
+                if (poseTranslation != null && poseTranslation.length == 3) {
+                    float normSq =
+                        poseTranslation[0] * poseTranslation[0] +
+                        poseTranslation[1] * poseTranslation[1] +
+                        poseTranslation[2] * poseTranslation[2];
+                    mCollector.expectTrue("Pose translation is larger than 1 m",
+                            normSq < 1.f);
+                }
+
+                Rect precorrectionArray =
+                    c.get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
+                mCollector.expectTrue("Supports DEPTH_OUTPUT but does not have " +
+                        "precorrection active array defined", precorrectionArray != null);
+
+                if (cameraIntrinsics != null && precorrectionArray != null) {
+                    float fx = cameraIntrinsics[0];
+                    float fy = cameraIntrinsics[1];
+                    float cx = cameraIntrinsics[2];
+                    float cy = cameraIntrinsics[3];
+                    float s = cameraIntrinsics[4];
+                    mCollector.expectTrue("Optical center expected to be within precorrection array",
+                            0 <= cx && cx < precorrectionArray.width() &&
+                            0 <= cy && cy < precorrectionArray.height());
+
+                    // TODO: Verify focal lengths and skew are reasonable
+                }
+
+                if (radialDistortion != null) {
+                    // TODO: Verify radial distortion
+                }
+
+            } else {
+                boolean hasFields =
+                    hasDepth16 && (poseTranslation != null) &&
+                    (poseRotation != null) && (cameraIntrinsics != null) &&
+                    (radialDistortion != null) && (depthIsExclusive != null);
+
+                mCollector.expectTrue(
+                        "All necessary depth fields defined, but DEPTH_OUTPUT capability is not listed",
+                        !hasFields);
+            }
+        }
+    }
+
+    /**
      * Cross-check StreamConfigurationMap output
      */
     public void testStreamConfigurationMap() throws Exception {
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
index 4415ecc..3e3e81e 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -397,6 +397,9 @@
             case REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING:
                 // Tested in ExtendedCameraCharacteristicsTest
                 return;
+            case REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT:
+                // Tested in ExtendedCameracharacteristicsTest
+                return;
             default:
                 capabilityName = "Unknown";
                 assertTrue(String.format("Unknown capability set: %d", capability),