Camera2 Tests: Add multiple stream tests

Add a test to test multiple stream configuration: one preview stream, one small
size YUV burst capture stream, and one large size YUV burst capture stream.

Also cleanup the code.

Bug 10325342

Change-Id: I676484cc0034055caa8f2a8788100ed34b4cefbb
diff --git a/tests/camera2/CameraMultiStreamTests.cpp b/tests/camera2/CameraMultiStreamTests.cpp
new file mode 100644
index 0000000..679ff4e
--- /dev/null
+++ b/tests/camera2/CameraMultiStreamTests.cpp
@@ -0,0 +1,651 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#define LOG_TAG "CameraMultiStreamTest"
+//#define LOG_NDEBUG 0
+#include "CameraStreamFixture.h"
+#include "TestExtensions.h"
+
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+#include <utils/StrongPointer.h>
+#include <common/CameraDeviceBase.h>
+#include <hardware/hardware.h>
+#include <hardware/camera2.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/Surface.h>
+
+#define DEFAULT_FRAME_DURATION 33000000LL // 33ms
+#define CAMERA_HEAP_COUNT       1
+#define CAMERA_EXPOSURE_FORMAT CAMERA_STREAM_AUTO_CPU_FORMAT
+#define CAMERA_DISPLAY_FORMAT HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED
+#define CAMERA_MULTI_STREAM_DEBUGGING  0
+#define CAMERA_FRAME_TIMEOUT    1000000000LL // nsecs (1 secs)
+#define PREVIEW_RENDERING_TIME_INTERVAL 200000 // in unit of us, 200ms
+/* constants for display */
+#define DISPLAY_BUFFER_HEIGHT 1024
+#define DISPLAY_BUFFER_WIDTH 1024
+#define DISPLAY_BUFFER_FORMAT PIXEL_FORMAT_RGB_888
+
+// This test intends to test large preview size but less than 1080p.
+#define PREVIEW_WIDTH_CAP   1920
+#define PREVIEW_HEIGHT_CAP  1080
+// This test intends to test small metering burst size that is less than 640x480
+#define METERING_WIDTH_CAP  640
+#define METERING_HEIGHT_CAP 480
+
+#define EXP_WAIT_MULTIPLIER 2
+
+namespace android {
+namespace camera2 {
+namespace tests {
+
+static const CameraStreamParams DEFAULT_STREAM_PARAMETERS = {
+    /*mFormat*/     CAMERA_EXPOSURE_FORMAT,
+    /*mHeapCount*/  CAMERA_HEAP_COUNT
+};
+
+static const CameraStreamParams DISPLAY_STREAM_PARAMETERS = {
+    /*mFormat*/     CAMERA_DISPLAY_FORMAT,
+    /*mHeapCount*/  CAMERA_HEAP_COUNT
+};
+
+class CameraMultiStreamTest
+    : public ::testing::Test,
+      public CameraStreamFixture {
+
+public:
+    CameraMultiStreamTest() : CameraStreamFixture(DEFAULT_STREAM_PARAMETERS) {
+        TEST_EXTENSION_FORKING_CONSTRUCTOR;
+
+        if (HasFatalFailure()) {
+            return;
+        }
+        /**
+         * Don't create default stream, each test is in charge of creating
+         * its own streams.
+         */
+    }
+
+    ~CameraMultiStreamTest() {
+        TEST_EXTENSION_FORKING_DESTRUCTOR;
+    }
+
+    sp<SurfaceComposerClient> mComposerClient;
+    sp<SurfaceControl> mSurfaceControl;
+
+    void CreateOnScreenSurface(sp<ANativeWindow>& surface) {
+        mComposerClient = new SurfaceComposerClient;
+        ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
+
+        mSurfaceControl = mComposerClient->createSurface(
+                String8("CameraMultiStreamTest StreamingImage Surface"),
+                DISPLAY_BUFFER_HEIGHT, DISPLAY_BUFFER_WIDTH,
+                DISPLAY_BUFFER_FORMAT, 0);
+
+        ASSERT_NE((void*)NULL, mSurfaceControl.get());
+        ASSERT_TRUE(mSurfaceControl->isValid());
+
+        SurfaceComposerClient::openGlobalTransaction();
+        ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(0x7FFFFFFF));
+        ASSERT_EQ(NO_ERROR, mSurfaceControl->show());
+        SurfaceComposerClient::closeGlobalTransaction();
+
+        surface = mSurfaceControl->getSurface();
+
+        ASSERT_NE((void*)NULL, surface.get());
+    }
+
+    struct Size {
+        int32_t width;
+        int32_t height;
+    };
+
+    // Select minimal size by number of pixels.
+    void GetMinSize(const int32_t* data, size_t count,
+            Size* min, int32_t* idx) {
+        ASSERT_NE((int32_t*)NULL, data);
+        int32_t minIdx = 0;
+        int32_t minSize = INT_MAX, tempSize;
+        for (size_t i = 0; i < count; i+=2) {
+            tempSize = data[i] * data[i+1];
+            if (minSize > tempSize) {
+                minSize = tempSize;
+                minIdx = i;
+            }
+        }
+        min->width = data[minIdx];
+        min->height = data[minIdx + 1];
+        *idx = minIdx;
+    }
+
+    // Select maximal size by number of pixels.
+    void GetMaxSize(const int32_t* data, size_t count,
+            Size* max, int32_t* idx) {
+        ASSERT_NE((int32_t*)NULL, data);
+        int32_t maxIdx = 0;
+        int32_t maxSize = INT_MIN, tempSize;
+        for (size_t i = 0; i < count; i+=2) {
+            tempSize = data[i] * data[i+1];
+            if (maxSize < tempSize) {
+                maxSize = tempSize;
+                maxIdx = i;
+            }
+        }
+        max->width = data[maxIdx];
+        max->height = data[maxIdx + 1];
+        *idx = maxIdx;
+    }
+
+    // Cap size by number of pixels.
+    Size CapSize(Size cap, Size input) {
+        if (input.width * input.height > cap.width * cap.height) {
+            return cap;
+        }
+        return input;
+    }
+
+    struct CameraStream : public RefBase {
+
+    public:
+        /**
+         * Only initialize the variables here, do the ASSERT check in
+         * SetUp function. To make this stream useful, the SetUp must
+         * be called before using it.
+         */
+        CameraStream(
+                int width,
+                int height,
+                const sp<CameraDeviceBase>& device,
+                CameraStreamParams param, sp<ANativeWindow> surface,
+                bool useCpuConsumer)
+            : mDevice(device),
+              mWidth(width),
+              mHeight(height) {
+            mFormat = param.mFormat;
+            if (useCpuConsumer) {
+                sp<BufferQueue> bq = new BufferQueue();
+                mCpuConsumer = new CpuConsumer(bq, param.mHeapCount);
+                mCpuConsumer->setName(String8(
+                        "CameraMultiStreamTest::mCpuConsumer"));
+                mNativeWindow = new Surface(bq);
+            } else {
+                // Render the stream to screen.
+                mCpuConsumer = NULL;
+                mNativeWindow = surface;
+            }
+
+            mFrameListener = new FrameListener();
+            if (mCpuConsumer != 0) {
+                mCpuConsumer->setFrameAvailableListener(mFrameListener);
+            }
+        }
+
+        /**
+         * Finally create camera stream, and do the ASSERT check, since we
+         * can not do it in ctor.
+         */
+        void SetUp() {
+            ASSERT_EQ(OK,
+                mDevice->createStream(mNativeWindow,
+                    mWidth, mHeight, mFormat, /*size (for jpegs)*/0,
+                    &mStreamId));
+
+            ASSERT_NE(-1, mStreamId);
+        }
+
+        int GetStreamId() { return mStreamId; }
+        sp<CpuConsumer> GetConsumer() { return mCpuConsumer; }
+        sp<FrameListener> GetFrameListener() { return mFrameListener; }
+
+    protected:
+        ~CameraStream() {
+            if (mDevice.get()) {
+                mDevice->waitUntilDrained();
+                mDevice->deleteStream(mStreamId);
+            }
+            // Clear producer before consumer.
+            mNativeWindow.clear();
+            mCpuConsumer.clear();
+        }
+
+    private:
+        sp<FrameListener>       mFrameListener;
+        sp<CpuConsumer>         mCpuConsumer;
+        sp<ANativeWindow>       mNativeWindow;
+        sp<CameraDeviceBase>    mDevice;
+        int                     mStreamId;
+        int                     mWidth;
+        int                     mHeight;
+        int                     mFormat;
+    };
+
+    int64_t GetExposureValue(const CameraMetadata& metaData) {
+        camera_metadata_ro_entry_t entry =
+                metaData.find(ANDROID_SENSOR_EXPOSURE_TIME);
+        EXPECT_EQ(1u, entry.count);
+        if (entry.count == 1) {
+            return entry.data.i64[0];
+        }
+        return -1;
+    }
+
+    int32_t GetSensitivity(const CameraMetadata& metaData) {
+        camera_metadata_ro_entry_t entry =
+                metaData.find(ANDROID_SENSOR_SENSITIVITY);
+        EXPECT_EQ(1u, entry.count);
+        if (entry.count == 1) {
+            return entry.data.i32[0];
+        }
+        return -1;
+    }
+
+    int64_t GetFrameDuration(const CameraMetadata& metaData) {
+        camera_metadata_ro_entry_t entry =
+                metaData.find(ANDROID_SENSOR_FRAME_DURATION);
+        EXPECT_EQ(1u, entry.count);
+        if (entry.count == 1) {
+            return entry.data.i64[0];
+        }
+        return -1;
+    }
+
+    void CreateRequests(CameraMetadata& previewRequest,
+            CameraMetadata& meteringRequest,
+            CameraMetadata& captureRequest,
+            int previewStreamId,
+            int meteringStreamId,
+            int captureStreamId) {
+    int32_t requestId = 1;
+    Vector<uint8_t> previewStreamIds;
+    previewStreamIds.push(previewStreamId);
+    ASSERT_EQ(OK, mDevice->createDefaultRequest(CAMERA2_TEMPLATE_PREVIEW,
+            &previewRequest));
+    ASSERT_EQ(OK, previewRequest.update(ANDROID_REQUEST_OUTPUT_STREAMS,
+            previewStreamIds));
+    ASSERT_EQ(OK, previewRequest.update(ANDROID_REQUEST_ID,
+            &requestId, 1));
+
+    // Create metering request, manual settings
+    // Manual control: Disable 3A, noise reduction, edge sharping
+    uint8_t cmOff = static_cast<uint8_t>(ANDROID_CONTROL_MODE_OFF);
+    uint8_t nrOff = static_cast<uint8_t>(ANDROID_NOISE_REDUCTION_MODE_OFF);
+    uint8_t sharpOff = static_cast<uint8_t>(ANDROID_EDGE_MODE_OFF);
+    Vector<uint8_t> meteringStreamIds;
+    meteringStreamIds.push(meteringStreamId);
+    ASSERT_EQ(OK, mDevice->createDefaultRequest(
+            CAMERA2_TEMPLATE_PREVIEW,
+            &meteringRequest));
+    ASSERT_EQ(OK, meteringRequest.update(
+            ANDROID_REQUEST_OUTPUT_STREAMS,
+            meteringStreamIds));
+    ASSERT_EQ(OK, meteringRequest.update(
+            ANDROID_REQUEST_ID,
+            &requestId, 1));
+    ASSERT_EQ(OK, meteringRequest.update(
+            ANDROID_CONTROL_MODE,
+            &cmOff, 1));
+    ASSERT_EQ(OK, meteringRequest.update(
+            ANDROID_NOISE_REDUCTION_MODE,
+            &nrOff, 1));
+    ASSERT_EQ(OK, meteringRequest.update(
+            ANDROID_EDGE_MODE,
+            &sharpOff, 1));
+
+    // Create capture request, manual settings
+    requestId++;
+    Vector<uint8_t> captureStreamIds;
+    captureStreamIds.push(captureStreamId);
+    ASSERT_EQ(OK, mDevice->createDefaultRequest(
+            CAMERA2_TEMPLATE_PREVIEW,
+            &captureRequest));
+    ASSERT_EQ(OK, captureRequest.update(
+            ANDROID_REQUEST_OUTPUT_STREAMS,
+            captureStreamIds));
+    ASSERT_EQ(OK, captureRequest.update(
+            ANDROID_REQUEST_ID,
+            &requestId, 1));
+    ASSERT_EQ(OK, captureRequest.update(
+            ANDROID_CONTROL_MODE,
+            &cmOff, 1));
+    ASSERT_EQ(OK, captureRequest.update(
+            ANDROID_NOISE_REDUCTION_MODE,
+            &nrOff, 1));
+    ASSERT_EQ(OK, captureRequest.update(
+            ANDROID_EDGE_MODE,
+            &sharpOff, 1));
+    }
+
+    sp<CameraStream> CreateStream(
+            int width,
+            int height,
+            const sp<CameraDeviceBase>& device,
+            CameraStreamParams param = DEFAULT_STREAM_PARAMETERS,
+            sp<ANativeWindow> surface = NULL,
+            bool useCpuConsumer = true) {
+        param.mFormat = MapAutoFormat(param.mFormat);
+        return new CameraStream(width, height, device,
+                param, surface, useCpuConsumer);
+    }
+
+    void CaptureBurst(CameraMetadata& request, size_t requestCount,
+            const Vector<int64_t>& exposures,
+            const Vector<int32_t>& sensitivities,
+            const sp<CameraStream>& stream,
+            int64_t minFrameDuration) {
+        ASSERT_EQ(OK, request.update(ANDROID_SENSOR_FRAME_DURATION,
+                &minFrameDuration, 1));
+        // Submit a series of requests with the specified exposure/gain values.
+        for (size_t i = 0; i < requestCount; i++) {
+            ASSERT_EQ(OK, request.update(ANDROID_SENSOR_EXPOSURE_TIME,
+                    &exposures[i], 1));
+            ASSERT_EQ(OK, request.update(ANDROID_SENSOR_SENSITIVITY,
+                    &sensitivities[i], 1));
+            ASSERT_EQ(OK, mDevice->capture(request));
+            ALOGV("Submitting capture %d with exposure %lld, sensitivity %d",
+                    i, exposures[i], sensitivities[i]);
+            if (CAMERA_MULTI_STREAM_DEBUGGING) {
+                request.dump(STDOUT_FILENO);
+            }
+        }
+        // Get capture burst results.
+        Vector<nsecs_t> captureBurstTimes;
+        sp<CpuConsumer> consumer = stream->GetConsumer();
+        sp<FrameListener> listener = stream->GetFrameListener();
+
+        // Set wait limit based on expected frame duration.
+        int64_t waitLimit = CAMERA_FRAME_TIMEOUT;
+        for (size_t i = 0; i < requestCount; i++) {
+            ALOGV("Reading request result %d", i);
+
+            /**
+             * Raise the timeout to be at least twice as long as the exposure
+             * time. to avoid a false positive when the timeout is too short.
+             */
+            if ((exposures[i] * EXP_WAIT_MULTIPLIER) > waitLimit) {
+                waitLimit = exposures[i] * EXP_WAIT_MULTIPLIER;
+            }
+
+            ASSERT_EQ(OK, mDevice->waitForNextFrame(waitLimit));
+            CameraMetadata frameMetadata;
+            ASSERT_EQ(OK, mDevice->getNextFrame(&frameMetadata));
+            ALOGV("Got capture burst result for request %d", i);
+            // Validate capture result
+            if (CAMERA_MULTI_STREAM_DEBUGGING) {
+                frameMetadata.dump(STDOUT_FILENO);
+            }
+
+            // TODO: Need revisit it to figure out an accurate margin.
+            EXPECT_EQ(sensitivities[i], GetSensitivity(frameMetadata));
+            EXPECT_EQ(exposures[i], GetExposureValue(frameMetadata));
+
+            ASSERT_EQ(OK, listener->waitForFrame(waitLimit));
+            captureBurstTimes.push_back(systemTime());
+            CpuConsumer::LockedBuffer imgBuffer;
+            ASSERT_EQ(OK, consumer->lockNextBuffer(&imgBuffer));
+            ALOGV("Got capture buffer for request %d", i);
+
+            /**
+             * TODO: Validate capture buffer. Current brightness calculation
+             * is too slow, it also doesn't account for saturation effects,
+             * which is quite common since we are going over a significant
+             * range of EVs. we need figure out some reliable way to validate
+             * buffer data.
+             */
+
+            ASSERT_EQ(OK, consumer->unlockBuffer(imgBuffer));
+            if (i > 0) {
+                nsecs_t timeDelta =
+                        captureBurstTimes[i] - captureBurstTimes[i-1];
+                EXPECT_GE(timeDelta, exposures[i]);
+            }
+        }
+    }
+
+    /**
+     * Intentionally shadow default CreateStream function from base class,
+     * because we don't want any test in this class to use the default
+     * stream creation function.
+     */
+    void CreateStream() {
+    }
+};
+
+/**
+ * This test adds multiple stream use case test, basically, test 3
+ * streams:
+ *
+ * 1. Preview stream, with large size that is no bigger than 1080p
+ * we render this stream to display and vary the exposure time for
+ * for certain amount of time for visualization purpose.
+ *
+ * 2. Metering stream, with small size that is no bigger than VGA size.
+ * a burst is issued for different exposure times and analog gains
+ * (or analog gain implemented sensitivities) then check if the capture
+ * result metadata matches the request.
+ *
+ * 3. Capture stream, this is basically similar as meterting stream, but
+ * has large size, which is the largest supported JPEG capture size.
+ *
+ * This multiple stream test is to test if HAL supports:
+ *
+ * 1. Multiple streams like above, HAL should support at least 3 streams
+ * concurrently: one preview stream, 2 other YUV stream.
+ *
+ * 2. Manual control(gain/exposure) of mutiple burst capture.
+ */
+TEST_F(CameraMultiStreamTest, MultiBurst) {
+
+    TEST_EXTENSION_FORKING_INIT;
+
+    camera_metadata_ro_entry availableProcessedSizes =
+        GetStaticEntry(ANDROID_SCALER_AVAILABLE_PROCESSED_SIZES);
+    ASSERT_EQ(0u, availableProcessedSizes.count % 2);
+    ASSERT_GE(availableProcessedSizes.count, 2u);
+    camera_metadata_ro_entry availableProcessedMinFrameDurations =
+        GetStaticEntry(ANDROID_SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS);
+    EXPECT_EQ(availableProcessedSizes.count,
+        availableProcessedMinFrameDurations.count * 2);
+
+    camera_metadata_ro_entry availableJpegSizes =
+        GetStaticEntry(ANDROID_SCALER_AVAILABLE_JPEG_SIZES);
+    ASSERT_EQ(0u, availableJpegSizes.count % 2);
+    ASSERT_GE(availableJpegSizes.count, 2u);
+
+    // Find the right sizes for preview, metering, and capture streams
+    // assumes at least 2 entries in availableProcessedSizes.
+    int64_t minFrameDuration = DEFAULT_FRAME_DURATION;
+    Size processedMinSize, processedMaxSize, jpegMaxSize;
+    const int32_t* data = availableProcessedSizes.data.i32;
+    size_t count = availableProcessedSizes.count;
+
+    int32_t minIdx, maxIdx;
+    GetMinSize(data, count, &processedMinSize, &minIdx);
+    GetMaxSize(data, count, &processedMaxSize, &maxIdx);
+    ALOGV("Found processed max size: %dx%d, min size = %dx%d",
+            processedMaxSize.width, processedMaxSize.height,
+            processedMinSize.width, processedMinSize.height);
+
+    if (availableProcessedSizes.count ==
+        availableProcessedMinFrameDurations.count * 2) {
+        minFrameDuration =
+            availableProcessedMinFrameDurations.data.i64[maxIdx / 2];
+    }
+
+    EXPECT_GT(minFrameDuration, 0);
+
+    if (minFrameDuration <= 0) {
+        minFrameDuration = DEFAULT_FRAME_DURATION;
+    }
+
+    ALOGV("targeted minimal frame duration is: %lldns", minFrameDuration);
+
+    data = &(availableJpegSizes.data.i32[0]);
+    count = availableJpegSizes.count;
+    GetMaxSize(data, count, &jpegMaxSize, &maxIdx);
+    ALOGV("Found Jpeg size max idx = %d", maxIdx);
+
+    // Max Jpeg size should be available in processed sizes. Use it for
+    // YUV capture anyway.
+    EXPECT_EQ(processedMaxSize.width, jpegMaxSize.width);
+    EXPECT_EQ(processedMaxSize.height, jpegMaxSize.height);
+
+    // Cap preview size.
+    Size previewLimit = { PREVIEW_WIDTH_CAP, PREVIEW_HEIGHT_CAP };
+    // FIXME: need make sure the previewLimit is supported by HAL.
+    Size previewSize = CapSize(previewLimit, processedMaxSize);
+    // Cap Metering size.
+    Size meteringLimit = { METERING_WIDTH_CAP, METERING_HEIGHT_CAP };
+    // Cap metering size to VGA (VGA is mandatory by CDD)
+    Size meteringSize = CapSize(meteringLimit, processedMinSize);
+    // Capture stream should be the max size of jpeg sizes.
+    ALOGV("preview size: %dx%d, metering size: %dx%d, capture size: %dx%d",
+            previewSize.width, previewSize.height,
+            meteringSize.width, meteringSize.height,
+            jpegMaxSize.width, jpegMaxSize.height);
+
+    // Create streams
+    // Preview stream: small resolution, render on the screen.
+    sp<CameraStream> previewStream;
+    {
+        sp<ANativeWindow> surface;
+        ASSERT_NO_FATAL_FAILURE(CreateOnScreenSurface(/*out*/surface));
+        previewStream = CreateStream(
+                previewSize.width,
+                previewSize.height,
+                mDevice,
+                DISPLAY_STREAM_PARAMETERS,
+                surface,
+                false);
+        ASSERT_NE((void*)NULL, previewStream.get());
+        ASSERT_NO_FATAL_FAILURE(previewStream->SetUp());
+    }
+    // Metering burst stream: small resolution yuv stream
+    sp<CameraStream> meteringStream =
+            CreateStream(
+                    meteringSize.width,
+                    meteringSize.height,
+                    mDevice);
+    ASSERT_NE((void*)NULL, meteringStream.get());
+    ASSERT_NO_FATAL_FAILURE(meteringStream->SetUp());
+    // Capture burst stream: full resolution yuv stream
+    sp<CameraStream> captureStream =
+            CreateStream(
+                    jpegMaxSize.width,
+                    jpegMaxSize.height,
+                    mDevice);
+    ASSERT_NE((void*)NULL, captureStream.get());
+    ASSERT_NO_FATAL_FAILURE(captureStream->SetUp());
+
+    // Create Preview request.
+    CameraMetadata previewRequest, meteringRequest, captureRequest;
+    ASSERT_NO_FATAL_FAILURE(CreateRequests(previewRequest, meteringRequest,
+            captureRequest, previewStream->GetStreamId(),
+            meteringStream->GetStreamId(), captureStream->GetStreamId()));
+
+    // Start preview
+    if (CAMERA_MULTI_STREAM_DEBUGGING) {
+        previewRequest.dump(STDOUT_FILENO);
+    }
+
+    // Generate exposure and sensitivity lists
+    camera_metadata_ro_entry exposureTimeRange =
+        GetStaticEntry(ANDROID_SENSOR_INFO_EXPOSURE_TIME_RANGE);
+    ASSERT_EQ(exposureTimeRange.count, 2u);
+    int64_t minExp = exposureTimeRange.data.i64[0];
+    int64_t maxExp = exposureTimeRange.data.i64[1];
+    ASSERT_GT(maxExp, minExp);
+
+    camera_metadata_ro_entry sensivityRange =
+        GetStaticEntry(ANDROID_SENSOR_INFO_SENSITIVITY_RANGE);
+    ASSERT_EQ(2u, sensivityRange.count);
+    int32_t minSensitivity = sensivityRange.data.i32[0];
+    int32_t maxSensitivity = sensivityRange.data.i32[1];
+    camera_metadata_ro_entry maxAnalogSenEntry =
+            GetStaticEntry(ANDROID_SENSOR_MAX_ANALOG_SENSITIVITY);
+    EXPECT_EQ(1u, maxAnalogSenEntry.count);
+    int32_t maxAnalogSensitivity = maxAnalogSenEntry.data.i32[0];
+    EXPECT_LE(maxAnalogSensitivity, maxSensitivity);
+    // Only test the sensitivity implemented by analog gain.
+    if (maxAnalogSensitivity > maxSensitivity) {
+        // Fallback to maxSensitity
+        maxAnalogSensitivity = maxSensitivity;
+    }
+
+    // sensitivity list, only include the sensitivities that are implemented
+    // purely by analog gain if possible.
+    Vector<int32_t> sensitivities;
+    Vector<int64_t> exposures;
+    count = (maxAnalogSensitivity - minSensitivity + 99) / 100;
+    sensitivities.push_back(minSensitivity);
+    for (size_t i = 1; i < count; i++) {
+        sensitivities.push_back(minSensitivity + i * 100);
+    }
+    sensitivities.push_back(maxAnalogSensitivity);
+    ALOGV("Sensitivity Range: min=%d, max=%d", minSensitivity,
+            maxAnalogSensitivity);
+    int64_t exp = minExp;
+    while (exp < maxExp) {
+        exposures.push_back(exp);
+        exp *= 2;
+    }
+    // Sweep the exposure value for preview, just for visual inspection purpose.
+    uint8_t cmOff = static_cast<uint8_t>(ANDROID_CONTROL_MODE_OFF);
+    for (size_t i = 0; i < exposures.size(); i++) {
+        ASSERT_EQ(OK, previewRequest.update(
+                ANDROID_CONTROL_MODE,
+                &cmOff, 1));
+        ASSERT_EQ(OK, previewRequest.update(
+                ANDROID_SENSOR_EXPOSURE_TIME,
+                &exposures[i], 1));
+        ALOGV("Submitting preview request %d with exposure %lld",
+                i, exposures[i]);
+
+        ASSERT_EQ(OK, mDevice->setStreamingRequest(previewRequest));
+
+        // Let preview run 200ms on screen for each exposure time.
+        usleep(PREVIEW_RENDERING_TIME_INTERVAL);
+    }
+
+    size_t requestCount = sensitivities.size();
+    if (requestCount > exposures.size()) {
+        requestCount = exposures.size();
+    }
+
+    /**
+     * Submit metering request, set default frame duration to minimal possible
+     * value, we want the capture to run as fast as possible. HAL should adjust
+     * the frame duration to minimal necessary value to support the requested
+     * exposure value if exposure is larger than frame duration.
+     */
+    CaptureBurst(meteringRequest, requestCount, exposures, sensitivities,
+            meteringStream, minFrameDuration);
+
+    /**
+     * Submit capture request, set default frame duration to minimal possible
+     * value, we want the capture to run as fast as possible. HAL should adjust
+     * the frame duration to minimal necessary value to support the requested
+     * exposure value if exposure is larger than frame duration.
+     */
+    CaptureBurst(captureRequest, requestCount, exposures, sensitivities,
+            captureStream, minFrameDuration);
+
+    ASSERT_EQ(OK, mDevice->clearStreamingRequest());
+}
+
+}
+}
+}