Camera2: Add shading mode test

Change-Id: I6f8b006064cde1bd740741851b260cf977b1e685
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 3131ba2..3dd9325 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -17,6 +17,7 @@
 package android.hardware.camera2.cts;
 
 import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static android.hardware.camera2.CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE;
 import static android.hardware.camera2.CameraMetadata.*;
 
 import android.hardware.camera2.CameraDevice;
@@ -26,6 +27,8 @@
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.util.Log;
 
+import java.util.Arrays;
+
 /**
  * <p>
  * Basic test for camera CaptureRequest key controls.
@@ -42,6 +45,9 @@
     private static final long WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
     private static final long DEFAULT_EXP_TIME_NS = 30000000L; // 30ms
     private static final int DEFAULT_SENSITIVITY = 100; // 10ms
+    private static final int RGGB_COLOR_CHANNEL_COUNT = 4;
+    private static final int MAX_SHADING_MAP_SIZE = 64 * 64 * RGGB_COLOR_CHANNEL_COUNT;
+    private static final int MIN_SHADING_MAP_SIZE = 1 * 1 * RGGB_COLOR_CHANNEL_COUNT;
 
     @Override
     protected void setUp() throws Exception {
@@ -53,6 +59,17 @@
         super.tearDown();
     }
 
+    /**
+     * Test black level lock when exposure value change.
+     * <p>
+     * When {@link CaptureRequest#BLACK_LEVEL_LOCK} is true in a request, the
+     * camera device should lock the black level. When the exposure values are changed,
+     * the camera may require reset black level Since changes to certain capture
+     * parameters (such as exposure time) may require resetting of black level
+     * compensation. However, the black level must remain locked after exposure
+     * value changes (when requests have lock ON).
+     * </p>
+     */
     public void testBlackLevelLock() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
@@ -88,7 +105,70 @@
             } finally {
                 closeDevice();
             }
+        }
+    }
 
+    /**
+     * Basic lens shading map request test.
+     * <p>
+     * When {@link CaptureRequest#SHADING_MODE} is set to OFF, no lens shading correction will
+     * be applied by the camera device, and an identity lens shading map data
+     * will be provided if {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE} is ON.
+     * </p>
+     * <p>
+     * When {@link CaptureRequest#SHADING_MODE} is set to other modes, lens shading correction
+     * will be applied by the camera device. The lens shading map data can be
+     * requested by setting {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE} to ON.
+     * </p>
+     */
+    public void testLensShadingMap() throws Exception {
+        for (int i = 0; i < mCameraIds.length; i++) {
+            try {
+                openDevice(mCameraIds[i]);
+
+                if (!mStaticInfo.isHardwareLevelFull()) {
+                    continue;
+                }
+
+                SimpleCaptureListener listener = new SimpleCaptureListener();
+                CaptureRequest.Builder requestBuilder =
+                        mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+                // Shading map mode OFF, lensShadingMapMode ON, camera device
+                // should output unity maps.
+                requestBuilder.set(CaptureRequest.SHADING_MODE, SHADING_MODE_OFF);
+                requestBuilder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
+                        STATISTICS_LENS_SHADING_MAP_MODE_ON);
+
+                Size mapSz = mStaticInfo.getCharacteristics().get(LENS_INFO_SHADING_MAP_SIZE);
+                Size previewSz =
+                        getMaxPreviewSize(mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
+
+                startPreview(requestBuilder, previewSz, listener);
+
+                verifyShadingMap(listener, NUM_FRAMES_VERIFIED, mapSz, /*mapModeOn*/false);
+
+                // Shading map mode FAST, lensShadingMapMode ON, camera device
+                // should output valid maps.
+                requestBuilder.set(CaptureRequest.SHADING_MODE, SHADING_MODE_FAST);
+
+                startPreview(requestBuilder, previewSz, listener);
+
+                // Allow at most one lock OFF state as the exposure is changed once.
+                verifyShadingMap(listener, NUM_FRAMES_VERIFIED, mapSz, /*mapModeOn*/true);
+
+                // Shading map mode HIGH_QUALITY, lensShadingMapMode ON, camera device
+                // should output valid maps.
+                requestBuilder.set(CaptureRequest.SHADING_MODE, SHADING_MODE_HIGH_QUALITY);
+
+                startPreview(requestBuilder, previewSz, listener);
+
+                verifyShadingMap(listener, NUM_FRAMES_VERIFIED, mapSz, /*mapModeOn*/true);
+
+                stopPreview();
+            } finally {
+                closeDevice();
+            }
         }
     }
 
@@ -118,6 +198,46 @@
                 + maxLockOffCnt + " for camera " + mCamera.getId(), noLockCnt <= maxLockOffCnt);
     }
 
+    /**
+     * Verify shading map for different shading modes.
+     */
+    private void verifyShadingMap(SimpleCaptureListener listener, int numFramesVerified,
+            Size mapSize, boolean mapModeOn) throws Exception {
+        int numElementsInMap = mapSize.getWidth() * mapSize.getHeight() * RGGB_COLOR_CHANNEL_COUNT;
+        float[] unityMap = new float[numElementsInMap];
+        Arrays.fill(unityMap, 1.0f);
+
+        for (int i = 0; i < numFramesVerified; i++) {
+            CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+            float[] map = result.get(CaptureResult.STATISTICS_LENS_SHADING_MAP);
+            assertNotNull("Map must not be null", map);
+            assertTrue("Map size " + map.length + " must be " + numElementsInMap,
+                    map.length == numElementsInMap);
+            assertFalse(String.format(
+                    "Map size %d should be less than %d", numElementsInMap, MAX_SHADING_MAP_SIZE),
+                    numElementsInMap >= MAX_SHADING_MAP_SIZE);
+            assertFalse(String.format("Map size %d should be no less than %d", numElementsInMap,
+                    MIN_SHADING_MAP_SIZE), numElementsInMap < MIN_SHADING_MAP_SIZE);
+
+            if (mapModeOn) {
+                // Map mode is ON, expect to receive a map with all element >= 1.0f
+
+                int badValueCnt = 0;
+                // Detect the bad values of the map data.
+                for (int j = 0; j < numElementsInMap; j++) {
+                    if (Float.isNaN(map[j]) || map[j] < 1.0f) {
+                        badValueCnt++;
+                    }
+                }
+                assertEquals("Number of value in the map is " + badValueCnt + " out of "
+                        + numElementsInMap, /*expected*/0, /*actual*/badValueCnt);
+            } else {
+                // Map mode is OFF, expect to receive a unity map.
+                assertTrue("Result map " + Arrays.toString(map) + " must be an unity map",
+                        Arrays.equals(unityMap, map));
+            }
+        }
+    }
 
     //----------------------------------------------------------------
     //---------Below are common functions for all tests.--------------