camera cts: testFlashTurnOff(v2): Check on->off->on transitions.

Check on -> off -> on transitions for torch and also allow a certain
number of FLASH_STATE_PARTIAL states an transition edges.

Bug: 137224452

Test: runtest -x CaptureRequestTest.java -m testFlashTurnOff on various
      Pixel devices.

Change-Id: I41f676efa7ca8215881348c37b982b0f80f16ee7
Signed-off-by: Jayant Chowdhary <jchowdhary@google.com>
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 2dbe878..2a46cf7 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -86,6 +86,9 @@
     private static final int NUM_RESULTS_WAIT_TIMEOUT = 100;
     private static final int NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY = 8;
     private static final int NUM_FRAMES_WAITED_FOR_TORCH = 100;
+    private static final int NUM_PARTIAL_FRAMES_PFC = 2;
+    private static final int NUM_PARTIAL_FRAMES_NPFC = 6;
+
     private static final int NUM_TEST_FOCUS_DISTANCES = 10;
     private static final int NUM_FOCUS_DISTANCES_REPEAT = 3;
     // 5 percent error margin for calibrated device
@@ -525,7 +528,6 @@
                 flashTurnOffTest(listener,
                         /* initiaAeControl */CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH,
                         /* offAeControl */CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
-
                 flashTurnOffTest(listener,
                         /* initiaAeControl */CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH,
                         /* offAeControl */CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
@@ -1479,17 +1481,123 @@
         // Test that the flash actually turned on continuously.
         mCollector.expectEquals("Flash state result must be FIRED", CaptureResult.FLASH_STATE_FIRED,
                 result.get(CaptureResult.FLASH_STATE));
-
+        mSession.stopRepeating();
         // Turn off the torch
         requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, flashOffAeControl);
         // TODO: jchowdhary@, b/130323585, this line can be removed.
         requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+        int numAllowedTransitionStates = NUM_PARTIAL_FRAMES_NPFC;
+        if (mStaticInfo.isPerFrameControlSupported()) {
+           numAllowedTransitionStates = NUM_PARTIAL_FRAMES_PFC;
+
+        }
+        // We submit 2 * numAllowedTransitionStates + 1 requests since we have two torch mode
+        // transitions. The additional request is to check for at least 1 expected (FIRED / READY)
+        // state.
+        int numTorchTestSamples =  2 * numAllowedTransitionStates  + 1;
         CaptureRequest flashOffRequest = requestBuilder.build();
-        mSession.setRepeatingRequest(flashOffRequest, listener, mHandler);
-        waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_TORCH);
-        result = listener.getCaptureResultForRequest(flashOffRequest, NUM_RESULTS_WAIT_TIMEOUT);
-        mCollector.expectEquals("Flash state result must be READY", CaptureResult.FLASH_STATE_READY,
-                result.get(CaptureResult.FLASH_STATE));
+        int flashModeOffRequests = captureRequestsSynchronizedBurst(flashOffRequest,
+                numTorchTestSamples, listener, mHandler);
+        // Turn it on again.
+        requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
+        // We need to have CONTROL_AE_MODE be either CONTROL_AE_MODE_ON or CONTROL_AE_MODE_OFF to
+        // turn the torch on again.
+        requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
+        CaptureRequest flashModeTorchRequest = requestBuilder.build();
+        int flashModeTorchRequests = captureRequestsSynchronizedBurst(flashModeTorchRequest,
+                numTorchTestSamples, listener, mHandler);
+
+        CaptureResult[] torchStateResults =
+                new CaptureResult[flashModeTorchRequests + flashModeOffRequests];
+        Arrays.fill(torchStateResults, null);
+        int i = 0;
+        for (; i < flashModeOffRequests; i++) {
+            torchStateResults[i] =
+                    listener.getCaptureResultForRequest(flashOffRequest, NUM_RESULTS_WAIT_TIMEOUT);
+            mCollector.expectNotEquals("Result for flashModeOff request null",
+                    torchStateResults[i], null);
+        }
+        for (int j = i; j < torchStateResults.length; j++) {
+            torchStateResults[j] =
+                    listener.getCaptureResultForRequest(flashModeTorchRequest,
+                            NUM_RESULTS_WAIT_TIMEOUT);
+            mCollector.expectNotEquals("Result for flashModeTorch request null",
+                    torchStateResults[j], null);
+        }
+        checkTorchStates(torchStateResults, numAllowedTransitionStates, flashModeOffRequests,
+                flashModeTorchRequests);
+    }
+
+    // We check that torch states appear in the order expected. We don't necessarily know how many
+    // times each state might appear, however we make sure that the states do not appear out of
+    // order.
+    private void checkTorchTransitionStates(CaptureResult []torchStateResults, int beg, int end,
+            List<Integer> stateOrder, boolean isTurningOff) {
+        Integer flashState;
+        Integer curIndex = 0;
+        for (int i = beg; i <= end; i++) {
+            flashState = torchStateResults[i].get(CaptureResult.FLASH_STATE);
+            int index = stateOrder.indexOf(flashState);
+            mCollector.expectNotEquals("Invalid state " + flashState + " not in expected list" +
+                    stateOrder, index, -1);
+            mCollector.expectGreaterOrEqual("state " + flashState  + " index " + index +
+                    " is expected to be >= " + curIndex,
+                    curIndex, index);
+            curIndex = index;
+        }
+    }
+
+    private void checkTorchStates(CaptureResult []torchResults, int numAllowedTransitionStates,
+            int numTorchOffSamples, int numTorchOnSamples) {
+        // We test for flash states from request:
+        // Request:       O(0) O(1) O(2) O(n)....O(nOFF) T(0) T(1) T(2) ....T(n) .... T(nON)
+        // Valid Result : P/R  P/R  P/R  R R R...P/R P/R   P/F  P/F  P/F      F         F
+        // For the FLASH_STATE_OFF requests, once FLASH_STATE READY has been seen, for the
+        // transition states while switching the torch off, it must not transition to
+        // FLASH_STATE_PARTIAL again till the next transition period which turns the torch on.
+        // P - FLASH_STATE_PARTIAL
+        // R - FLASH_STATE_READY
+        // F - FLASH_STATE_FIRED
+        // O(k) - kth FLASH_MODE_OFF request
+        // T(k) - kth FLASH_MODE_TORCH request
+        // nOFF - number of torch off samples
+        // nON - number of torch on samples
+        Integer flashState;
+        // Check on -> off transition states
+        List<Integer> onToOffStateOrderList = new ArrayList<Integer>();
+        onToOffStateOrderList.add(CaptureRequest.FLASH_STATE_PARTIAL);
+        onToOffStateOrderList.add(CaptureRequest.FLASH_STATE_READY);
+        checkTorchTransitionStates(torchResults, 0, numAllowedTransitionStates,
+                onToOffStateOrderList, true);
+        // The next frames (before transition) must have its flash state as FLASH_STATE_READY
+        for (int i = numAllowedTransitionStates + 1;
+                i < numTorchOffSamples - numAllowedTransitionStates; i++) {
+            flashState = torchResults[numAllowedTransitionStates].get(CaptureResult.FLASH_STATE);
+            mCollector.expectEquals("flash state result must be READY",
+                    CaptureResult.FLASH_STATE_READY, flashState);
+        }
+        // check off -> on transition states, before the FLASH_MODE_TORCH request was sent
+        List<Integer> offToOnPreStateOrderList = new ArrayList<Integer>();
+        offToOnPreStateOrderList.add(CaptureRequest.FLASH_STATE_READY);
+        offToOnPreStateOrderList.add(CaptureRequest.FLASH_STATE_PARTIAL);
+        checkTorchTransitionStates(torchResults,
+                numTorchOffSamples - numAllowedTransitionStates, numTorchOffSamples - 1,
+                offToOnPreStateOrderList, false);
+        // check off -> on transition states
+        List<Integer> offToOnPostStateOrderList = new ArrayList<Integer>();
+        offToOnPostStateOrderList.add(CaptureRequest.FLASH_STATE_PARTIAL);
+        offToOnPostStateOrderList.add(CaptureRequest.FLASH_STATE_FIRED);
+        checkTorchTransitionStates(torchResults,
+                numTorchOffSamples, numTorchOffSamples + numAllowedTransitionStates,
+                offToOnPostStateOrderList, false);
+        // check on states after off -> on transition
+        // The next frames must have its flash state as FLASH_STATE_FIRED
+        for (int i = numTorchOffSamples + numAllowedTransitionStates + 1;
+                i < torchResults.length - 1; i++) {
+            flashState = torchResults[i].get(CaptureResult.FLASH_STATE);
+            mCollector.expectEquals("flash state result must be FIRED for frame " + i,
+                    CaptureRequest.FLASH_STATE_FIRED, flashState);
+        }
     }
 
     /**
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
index 1f8b792..5a95d62 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
@@ -385,6 +385,31 @@
     }
 
     /**
+     * Submit a burst of the same capture request, then submit additional captures in order to
+     * ensure that the camera will be synchronized.
+     *
+     * <p>
+     * The additional capture count is determined by android.sync.maxLatency (or
+     * a fixed {@value #NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY}) captures if maxLatency is unknown).
+     * </p>
+     *
+     * <p>Returns the number of captures that were submitted (at least 1), which is useful
+     * with {@link #waitForNumResults}.</p>
+     *
+     * @param request capture request to forward to {@link CameraDevice#capture}
+     * @param listener request listener to forward to {@link CameraDevice#capture}
+     * @param handler handler to forward to {@link CameraDevice#capture}
+     *
+     * @return the number of captures that were submitted
+     *
+     * @throws CameraAccessException if capturing failed
+     */
+    protected int captureRequestsSynchronizedBurst(
+            CaptureRequest request, int count, CaptureCallback listener, Handler handler)
+                    throws CameraAccessException {
+        return captureRequestsSynchronizedImpl(request, count, listener, handler, true);
+    }
+    /**
      * Submit a capture once, then submit additional captures in order to ensure that
      * the camera will be synchronized.
      *
@@ -407,7 +432,7 @@
     protected int captureRequestsSynchronized(
             CaptureRequest request, CaptureCallback listener, Handler handler)
                     throws CameraAccessException {
-        return captureRequestsSynchronized(request, /*count*/1, listener, handler);
+        return captureRequestsSynchronizedImpl(request, /*count*/1, listener, handler, false);
     }
 
     /**
@@ -435,24 +460,7 @@
     protected int captureRequestsSynchronized(
             CaptureRequest request, int count, CaptureCallback listener, Handler handler)
                     throws CameraAccessException {
-        if (count < 1) {
-            throw new IllegalArgumentException("count must be positive");
-        }
-
-        int maxLatency = mStaticInfo.getSyncMaxLatency();
-        if (maxLatency == CameraMetadata.SYNC_MAX_LATENCY_UNKNOWN) {
-            maxLatency = NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY;
-        }
-
-        assertTrue("maxLatency is non-negative", maxLatency >= 0);
-
-        int numCaptures = maxLatency + count;
-
-        for (int i = 0; i < numCaptures; ++i) {
-            mSession.capture(request, listener, handler);
-        }
-
-        return numCaptures;
+        return captureRequestsSynchronizedImpl(request, count, listener, handler, false);
     }
 
     /**
@@ -878,4 +886,34 @@
     protected Range<Integer> getSuitableFpsRangeForDuration(String cameraId, long frameDuration) {
         return CameraTestUtils.getSuitableFpsRangeForDuration(cameraId, frameDuration, mStaticInfo);
     }
+
+    private int captureRequestsSynchronizedImpl(
+            CaptureRequest request, int count, CaptureCallback listener, Handler handler,
+            boolean isBurst) throws CameraAccessException {
+        if (count < 1) {
+            throw new IllegalArgumentException("count must be positive");
+        }
+
+        int maxLatency = mStaticInfo.getSyncMaxLatency();
+        if (maxLatency == CameraMetadata.SYNC_MAX_LATENCY_UNKNOWN) {
+            maxLatency = NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY;
+        }
+
+        assertTrue("maxLatency is non-negative", maxLatency >= 0);
+
+        int numCaptures = maxLatency + count;
+        ArrayList<CaptureRequest> burstCaptureRequests = new ArrayList<>();
+        for (int i = 0; i < numCaptures; ++i) {
+            if (isBurst) {
+                burstCaptureRequests.add(request);
+            } else {
+                mSession.capture(request, listener, handler);
+            }
+        }
+        if (isBurst) {
+            mSession.captureBurst(burstCaptureRequests, listener, handler);
+        }
+
+        return numCaptures;
+    }
 }