Merge "camera: Add support for individual physical camera requests"
diff --git a/camera/device/3.2/default/CameraDeviceSession.cpp b/camera/device/3.2/default/CameraDeviceSession.cpp
index 631404e..31b4739 100644
--- a/camera/device/3.2/default/CameraDeviceSession.cpp
+++ b/camera/device/3.2/default/CameraDeviceSession.cpp
@@ -333,11 +333,10 @@
     mResultMetadataQueue = q;
 }
 
-void CameraDeviceSession::ResultBatcher::registerBatch(
-        const hidl_vec<CaptureRequest>& requests) {
+void CameraDeviceSession::ResultBatcher::registerBatch(uint32_t frameNumber, uint32_t batchSize) {
     auto batch = std::make_shared<InflightBatch>();
-    batch->mFirstFrame = requests[0].frameNumber;
-    batch->mBatchSize = requests.size();
+    batch->mFirstFrame = frameNumber;
+    batch->mBatchSize = batchSize;
     batch->mLastFrame = batch->mFirstFrame + batch->mBatchSize - 1;
     batch->mNumPartialResults = mNumPartialResults;
     for (int id : mStreamsToBatch) {
@@ -1010,7 +1009,7 @@
     }
 
     if (s == Status::OK && requests.size() > 1) {
-        mResultBatcher.registerBatch(requests);
+        mResultBatcher.registerBatch(requests[0].frameNumber, requests.size());
     }
 
     _hidl_cb(s, numRequestProcessed);
@@ -1111,6 +1110,7 @@
             halRequest.settings = settingsOverride.getAndLock();
         }
     }
+    halRequest.num_physcam_settings = 0;
 
     ATRACE_ASYNC_BEGIN("frame capture", request.frameNumber);
     ATRACE_BEGIN("camera3->process_capture_request");
diff --git a/camera/device/3.2/default/CameraDeviceSession.h b/camera/device/3.2/default/CameraDeviceSession.h
index c5a63c8..0048ef4 100644
--- a/camera/device/3.2/default/CameraDeviceSession.h
+++ b/camera/device/3.2/default/CameraDeviceSession.h
@@ -184,7 +184,7 @@
         void setBatchedStreams(const std::vector<int>& streamsToBatch);
         void setResultMetadataQueue(std::shared_ptr<ResultMetadataQueue> q);
 
-        void registerBatch(const hidl_vec<CaptureRequest>& requests);
+        void registerBatch(uint32_t frameNumber, uint32_t batchSize);
         void notify(NotifyMsg& msg);
         void processCaptureResult(CaptureResult& result);
 
diff --git a/camera/device/3.4/ICameraDeviceSession.hal b/camera/device/3.4/ICameraDeviceSession.hal
index 71a4b34..4ce749d 100644
--- a/camera/device/3.4/ICameraDeviceSession.hal
+++ b/camera/device/3.4/ICameraDeviceSession.hal
@@ -20,7 +20,6 @@
 import @3.3::ICameraDeviceSession;
 import @3.3::HalStreamConfiguration;
 import @3.2::BufferCache;
-import @3.2::CaptureRequest;
 
 /**
  * Camera device active session interface.
@@ -72,4 +71,38 @@
     configureStreams_3_4(@3.4::StreamConfiguration requestedConfiguration)
             generates (Status status,
                        @3.4::HalStreamConfiguration halConfiguration);
+
+    /**
+     * processCaptureRequest_3_4:
+     *
+     * Identical to @3.2::ICameraDeviceSession.processCaptureRequest, except that:
+     *
+     * - The capture request can include individual settings for physical camera devices
+     *   backing a logical multi-camera.
+     *
+     * @return status Status code for the operation, one of:
+     *     OK:
+     *         On a successful start to processing the capture request
+     *     ILLEGAL_ARGUMENT:
+     *         If the input is malformed (the settings are empty when not
+     *         allowed, the physical camera settings are invalid, there are 0
+     *         output buffers, etc) and capture processing
+     *         cannot start. Failures during request processing must be
+     *         handled by calling ICameraDeviceCallback::notify(). In case of
+     *         this error, the framework retains responsibility for the
+     *         stream buffers' fences and the buffer handles; the HAL must not
+     *         close the fences or return these buffers with
+     *         ICameraDeviceCallback::processCaptureResult().
+     *     INTERNAL_ERROR:
+     *         If the camera device has encountered a serious error. After this
+     *         error is returned, only the close() method can be successfully
+     *         called by the framework.
+     * @return numRequestProcessed Number of requests successfully processed by
+     *     camera HAL. When status is OK, this must be equal to the size of
+     *     requests. When the call fails, this number is the number of requests
+     *     that HAL processed successfully before HAL runs into an error.
+     *
+     */
+    processCaptureRequest_3_4(vec<CaptureRequest> requests, vec<BufferCache> cachesToRemove)
+            generates (Status status, uint32_t numRequestProcessed);
 };
diff --git a/camera/device/3.4/default/CameraDeviceSession.cpp b/camera/device/3.4/default/CameraDeviceSession.cpp
index b74fd57..c8d33eb 100644
--- a/camera/device/3.4/default/CameraDeviceSession.cpp
+++ b/camera/device/3.4/default/CameraDeviceSession.cpp
@@ -201,9 +201,9 @@
 }
 
 Return<void> CameraDeviceSession::processCaptureRequest_3_4(
-        const hidl_vec<CaptureRequest>& requests,
+        const hidl_vec<V3_4::CaptureRequest>& requests,
         const hidl_vec<V3_2::BufferCache>& cachesToRemove,
-        ICameraDeviceSession::processCaptureRequest_cb _hidl_cb)  {
+        ICameraDeviceSession::processCaptureRequest_3_4_cb _hidl_cb)  {
     updateBufferCaches(cachesToRemove);
 
     uint32_t numRequestProcessed = 0;
@@ -216,14 +216,14 @@
     }
 
     if (s == Status::OK && requests.size() > 1) {
-        mResultBatcher.registerBatch(requests);
+        mResultBatcher.registerBatch(requests[0].v3_2.frameNumber, requests.size());
     }
 
     _hidl_cb(s, numRequestProcessed);
     return Void();
 }
 
-Status CameraDeviceSession::processOneCaptureRequest_3_4(const CaptureRequest& request)  {
+Status CameraDeviceSession::processOneCaptureRequest_3_4(const V3_4::CaptureRequest& request)  {
     Status status = initStatus();
     if (status != Status::OK) {
         ALOGE("%s: camera init failed or disconnected", __FUNCTION__);
@@ -231,15 +231,15 @@
     }
 
     camera3_capture_request_t halRequest;
-    halRequest.frame_number = request.frameNumber;
+    halRequest.frame_number = request.v3_2.frameNumber;
 
     bool converted = true;
     V3_2::CameraMetadata settingsFmq;  // settings from FMQ
-    if (request.fmqSettingsSize > 0) {
+    if (request.v3_2.fmqSettingsSize > 0) {
         // non-blocking read; client must write metadata before calling
         // processOneCaptureRequest
-        settingsFmq.resize(request.fmqSettingsSize);
-        bool read = mRequestMetadataQueue->read(settingsFmq.data(), request.fmqSettingsSize);
+        settingsFmq.resize(request.v3_2.fmqSettingsSize);
+        bool read = mRequestMetadataQueue->read(settingsFmq.data(), request.v3_2.fmqSettingsSize);
         if (read) {
             converted = V3_2::implementation::convertFromHidl(settingsFmq, &halRequest.settings);
         } else {
@@ -247,7 +247,8 @@
             converted = false;
         }
     } else {
-        converted = V3_2::implementation::convertFromHidl(request.settings, &halRequest.settings);
+        converted = V3_2::implementation::convertFromHidl(request.v3_2.settings,
+                &halRequest.settings);
     }
 
     if (!converted) {
@@ -263,9 +264,9 @@
 
     hidl_vec<buffer_handle_t*> allBufPtrs;
     hidl_vec<int> allFences;
-    bool hasInputBuf = (request.inputBuffer.streamId != -1 &&
-            request.inputBuffer.bufferId != 0);
-    size_t numOutputBufs = request.outputBuffers.size();
+    bool hasInputBuf = (request.v3_2.inputBuffer.streamId != -1 &&
+            request.v3_2.inputBuffer.bufferId != 0);
+    size_t numOutputBufs = request.v3_2.outputBuffers.size();
     size_t numBufs = numOutputBufs + (hasInputBuf ? 1 : 0);
 
     if (numOutputBufs == 0) {
@@ -273,7 +274,7 @@
         return Status::ILLEGAL_ARGUMENT;
     }
 
-    status = importRequest(request, allBufPtrs, allFences);
+    status = importRequest(request.v3_2, allBufPtrs, allFences);
     if (status != Status::OK) {
         return status;
     }
@@ -285,12 +286,12 @@
     {
         Mutex::Autolock _l(mInflightLock);
         if (hasInputBuf) {
-            auto streamId = request.inputBuffer.streamId;
-            auto key = std::make_pair(request.inputBuffer.streamId, request.frameNumber);
+            auto streamId = request.v3_2.inputBuffer.streamId;
+            auto key = std::make_pair(request.v3_2.inputBuffer.streamId, request.v3_2.frameNumber);
             auto& bufCache = mInflightBuffers[key] = camera3_stream_buffer_t{};
             convertFromHidl(
-                    allBufPtrs[numOutputBufs], request.inputBuffer.status,
-                    &mStreamMap[request.inputBuffer.streamId], allFences[numOutputBufs],
+                    allBufPtrs[numOutputBufs], request.v3_2.inputBuffer.status,
+                    &mStreamMap[request.v3_2.inputBuffer.streamId], allFences[numOutputBufs],
                     &bufCache);
             bufCache.stream->physical_camera_id = mPhysicalCameraIdMap[streamId].c_str();
             halRequest.input_buffer = &bufCache;
@@ -300,11 +301,11 @@
 
         halRequest.num_output_buffers = numOutputBufs;
         for (size_t i = 0; i < numOutputBufs; i++) {
-            auto streamId = request.outputBuffers[i].streamId;
-            auto key = std::make_pair(streamId, request.frameNumber);
+            auto streamId = request.v3_2.outputBuffers[i].streamId;
+            auto key = std::make_pair(streamId, request.v3_2.frameNumber);
             auto& bufCache = mInflightBuffers[key] = camera3_stream_buffer_t{};
             convertFromHidl(
-                    allBufPtrs[i], request.outputBuffers[i].status,
+                    allBufPtrs[i], request.v3_2.outputBuffers[i].status,
                     &mStreamMap[streamId], allFences[i],
                     &bufCache);
             bufCache.stream->physical_camera_id = mPhysicalCameraIdMap[streamId].c_str();
@@ -322,7 +323,47 @@
         }
     }
 
-    ATRACE_ASYNC_BEGIN("frame capture", request.frameNumber);
+    std::vector<const char *> physicalCameraIds;
+    std::vector<const camera_metadata_t *> physicalCameraSettings;
+    std::vector<V3_2::CameraMetadata> physicalFmq;
+    size_t settingsCount = request.physicalCameraSettings.size();
+    if (settingsCount > 0) {
+        physicalCameraIds.reserve(settingsCount);
+        physicalCameraSettings.reserve(settingsCount);
+        physicalFmq.reserve(settingsCount);
+
+        for (size_t i = 0; i < settingsCount; i++) {
+            uint64_t settingsSize = request.physicalCameraSettings[i].fmqSettingsSize;
+            const camera_metadata_t *settings;
+            if (settingsSize > 0) {
+                physicalFmq.push_back(V3_2::CameraMetadata(settingsSize));
+                bool read = mRequestMetadataQueue->read(physicalFmq[i].data(), settingsSize);
+                if (read) {
+                    converted = V3_2::implementation::convertFromHidl(physicalFmq[i], &settings);
+                    physicalCameraSettings.push_back(settings);
+                } else {
+                    ALOGE("%s: physical camera settings metadata couldn't be read from fmq!",
+                            __FUNCTION__);
+                    converted = false;
+                }
+            } else {
+                converted = V3_2::implementation::convertFromHidl(
+                        request.physicalCameraSettings[i].settings, &settings);
+                physicalCameraSettings.push_back(settings);
+            }
+
+            if (!converted) {
+                ALOGE("%s: physical camera settings metadata is corrupt!", __FUNCTION__);
+                return Status::ILLEGAL_ARGUMENT;
+            }
+            physicalCameraIds.push_back(request.physicalCameraSettings[i].physicalCameraId.c_str());
+        }
+    }
+    halRequest.num_physcam_settings = settingsCount;
+    halRequest.physcam_id = physicalCameraIds.data();
+    halRequest.physcam_settings = physicalCameraSettings.data();
+
+    ATRACE_ASYNC_BEGIN("frame capture", request.v3_2.frameNumber);
     ATRACE_BEGIN("camera3->process_capture_request");
     status_t ret = mDevice->ops->process_capture_request(mDevice, &halRequest);
     ATRACE_END();
@@ -335,17 +376,23 @@
 
         cleanupInflightFences(allFences, numBufs);
         if (hasInputBuf) {
-            auto key = std::make_pair(request.inputBuffer.streamId, request.frameNumber);
+            auto key = std::make_pair(request.v3_2.inputBuffer.streamId, request.v3_2.frameNumber);
             mInflightBuffers.erase(key);
         }
         for (size_t i = 0; i < numOutputBufs; i++) {
-            auto key = std::make_pair(request.outputBuffers[i].streamId, request.frameNumber);
+            auto key = std::make_pair(request.v3_2.outputBuffers[i].streamId,
+                    request.v3_2.frameNumber);
             mInflightBuffers.erase(key);
         }
         if (aeCancelTriggerNeeded) {
-            mInflightAETriggerOverrides.erase(request.frameNumber);
+            mInflightAETriggerOverrides.erase(request.v3_2.frameNumber);
         }
-        return Status::INTERNAL_ERROR;
+
+        if (ret == BAD_VALUE) {
+            return Status::ILLEGAL_ARGUMENT;
+        } else {
+            return Status::INTERNAL_ERROR;
+        }
     }
 
     mFirstRequest = false;
diff --git a/camera/device/3.4/default/include/device_v3_4_impl/CameraDeviceSession.h b/camera/device/3.4/default/include/device_v3_4_impl/CameraDeviceSession.h
index 83b3afe..fbde083 100644
--- a/camera/device/3.4/default/include/device_v3_4_impl/CameraDeviceSession.h
+++ b/camera/device/3.4/default/include/device_v3_4_impl/CameraDeviceSession.h
@@ -85,10 +85,10 @@
     void postProcessConfigurationLocked_3_4(const StreamConfiguration& requestedConfiguration);
 
     Return<void> processCaptureRequest_3_4(
-            const hidl_vec<CaptureRequest>& requests,
+            const hidl_vec<V3_4::CaptureRequest>& requests,
             const hidl_vec<V3_2::BufferCache>& cachesToRemove,
-            ICameraDeviceSession::processCaptureRequest_cb _hidl_cb);
-    Status processOneCaptureRequest_3_4(const CaptureRequest& request);
+            ICameraDeviceSession::processCaptureRequest_3_4_cb _hidl_cb);
+    Status processOneCaptureRequest_3_4(const V3_4::CaptureRequest& request);
 
     std::map<int, std::string> mPhysicalCameraIdMap;
 private:
@@ -109,10 +109,16 @@
             return mParent->configureStreams(requestedConfiguration, _hidl_cb);
         }
 
+        virtual Return<void> processCaptureRequest_3_4(const hidl_vec<V3_4::CaptureRequest>& requests,
+                const hidl_vec<V3_2::BufferCache>& cachesToRemove,
+                ICameraDeviceSession::processCaptureRequest_3_4_cb _hidl_cb) override {
+            return mParent->processCaptureRequest_3_4(requests, cachesToRemove, _hidl_cb);
+        }
+
         virtual Return<void> processCaptureRequest(const hidl_vec<V3_2::CaptureRequest>& requests,
                 const hidl_vec<V3_2::BufferCache>& cachesToRemove,
                 V3_3::ICameraDeviceSession::processCaptureRequest_cb _hidl_cb) override {
-            return mParent->processCaptureRequest_3_4(requests, cachesToRemove, _hidl_cb);
+            return mParent->processCaptureRequest(requests, cachesToRemove, _hidl_cb);
         }
 
         virtual Return<void> getCaptureRequestMetadataQueue(
diff --git a/camera/device/3.4/types.hal b/camera/device/3.4/types.hal
index acad093..77e855f 100644
--- a/camera/device/3.4/types.hal
+++ b/camera/device/3.4/types.hal
@@ -21,6 +21,7 @@
 import @3.2::Stream;
 import @3.3::HalStream;
 import @3.2::CameraMetadata;
+import @3.2::CaptureRequest;
 
 /**
  * Stream:
@@ -133,3 +134,65 @@
 struct HalStreamConfiguration {
     vec<HalStream> streams;
 };
+
+/**
+ * PhysicalCameraSetting:
+ *
+ * Individual camera settings for logical camera backed by multiple physical devices.
+ * Clients are allowed to pass separate settings for each physical device.
+ */
+struct PhysicalCameraSetting {
+    /**
+     * If non-zero, read settings from request queue instead
+     * (see ICameraDeviceSession.getCaptureRequestMetadataQueue).
+     * If zero, read settings from .settings field.
+     */
+    uint64_t fmqSettingsSize;
+
+    /**
+     * Contains the physical device camera id. Any settings passed by client here
+     * should be applied for this physical device. In case the physical id is invalid
+     * Hal should fail the process request and return Status::ILLEGAL_ARGUMENT.
+     */
+    string physicalCameraId;
+
+    /**
+     * If fmqSettingsSize is zero, the settings buffer contains the capture and
+     * processing parameters for the physical device with id 'phyCamId'.
+     * In case the individual settings are empty or missing, Hal should fail the
+     * request and return Status::ILLEGAL_ARGUMENT.
+     */
+    CameraMetadata settings;
+};
+
+/**
+ * CaptureRequest:
+ *
+ * A single request for image capture/buffer reprocessing, sent to the Camera
+ * HAL device by the framework in processCaptureRequest().
+ *
+ * The request contains the settings to be used for this capture, and the set of
+ * output buffers to write the resulting image data in. It may optionally
+ * contain an input buffer, in which case the request is for reprocessing that
+ * input buffer instead of capturing a new image with the camera sensor. The
+ * capture is identified by the frameNumber.
+ *
+ * In response, the camera HAL device must send a CaptureResult
+ * structure asynchronously to the framework, using the processCaptureResult()
+ * callback.
+ *
+ * Identical to @3.2::CaptureRequest, except that it contains @3.4::physCamSettings vector.
+ *
+ */
+struct CaptureRequest {
+    /**
+     * The definition of CaptureRequest from prior version.
+     */
+    @3.2::CaptureRequest v3_2;
+
+    /**
+     * A vector containing individual camera settings for logical camera backed by multiple physical
+     * devices. In case the vector is empty, Hal should use the settings field in 'v3_2'.
+     */
+    vec<PhysicalCameraSetting> physicalCameraSettings;
+};
diff --git a/camera/metadata/3.3/types.hal b/camera/metadata/3.3/types.hal
index 3182905..d2a5886 100644
--- a/camera/metadata/3.3/types.hal
+++ b/camera/metadata/3.3/types.hal
@@ -83,6 +83,13 @@
      */
     ANDROID_REQUEST_AVAILABLE_SESSION_KEYS = android.hardware.camera.metadata@3.2::CameraMetadataTag:ANDROID_REQUEST_END,
 
+    /** android.request.availablePhysicalCameraRequestKeys [static, int32[], hidden]
+     *
+     * <p>A subset of the available request keys that can be overriden for
+     * physical devices backing a logical multi-camera.</p>
+     */
+    ANDROID_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS,
+
     ANDROID_REQUEST_END_3_3,
 
     /** android.statistics.oisDataMode [dynamic, enum, public]
diff --git a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
index eb15766..4652efd 100644
--- a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
+++ b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
@@ -537,6 +537,8 @@
         Return<void> notify(const hidl_vec<NotifyMsg>& msgs) override;
 
      private:
+        bool processCaptureResultLocked(const CaptureResult& results);
+
         CameraHidlTest *mParent;               // Parent object
     };
 
@@ -637,6 +639,9 @@
             std::vector<AvailableStream> &outputStreams,
             const AvailableStream *threshold = nullptr);
     static Status isConstrainedModeAvailable(camera_metadata_t *staticMeta);
+    static Status isLogicalMultiCamera(camera_metadata_t *staticMeta);
+    static Status getPhysicalCameraIds(camera_metadata_t *staticMeta,
+            std::vector<std::string> *physicalIds/*out*/);
     static Status pickConstrainedModeSize(camera_metadata_t *staticMeta,
             AvailableStream &hfrStream);
     static Status isZSLModeAvailable(camera_metadata_t *staticMeta);
@@ -848,121 +853,7 @@
     bool notify = false;
     std::unique_lock<std::mutex> l(mParent->mLock);
     for (size_t i = 0 ; i < results.size(); i++) {
-        uint32_t frameNumber = results[i].frameNumber;
-
-        if ((results[i].result.size() == 0) &&
-                (results[i].outputBuffers.size() == 0) &&
-                (results[i].inputBuffer.buffer == nullptr) &&
-                (results[i].fmqResultSize == 0)) {
-            ALOGE("%s: No result data provided by HAL for frame %d result count: %d",
-                  __func__, frameNumber, (int) results[i].fmqResultSize);
-            ADD_FAILURE();
-            break;
-        }
-
-        ssize_t idx = mParent->mInflightMap.indexOfKey(frameNumber);
-        if (::android::NAME_NOT_FOUND == idx) {
-            ALOGE("%s: Unexpected frame number! received: %u",
-                  __func__, frameNumber);
-            ADD_FAILURE();
-            break;
-        }
-
-        bool isPartialResult = false;
-        bool hasInputBufferInRequest = false;
-        InFlightRequest *request = mParent->mInflightMap.editValueAt(idx);
-        ::android::hardware::camera::device::V3_2::CameraMetadata resultMetadata;
-        size_t resultSize = 0;
-        if (results[i].fmqResultSize > 0) {
-            resultMetadata.resize(results[i].fmqResultSize);
-            if (request->resultQueue == nullptr) {
-                ADD_FAILURE();
-                break;
-            }
-            if (!request->resultQueue->read(resultMetadata.data(),
-                    results[i].fmqResultSize)) {
-                ALOGE("%s: Frame %d: Cannot read camera metadata from fmq,"
-                        "size = %" PRIu64, __func__, frameNumber,
-                        results[i].fmqResultSize);
-                ADD_FAILURE();
-                break;
-            }
-            resultSize = resultMetadata.size();
-        } else if (results[i].result.size() > 0) {
-            resultMetadata.setToExternal(const_cast<uint8_t *>(
-                    results[i].result.data()), results[i].result.size());
-            resultSize = resultMetadata.size();
-        }
-
-        if (!request->usePartialResult && (resultSize > 0) &&
-                (results[i].partialResult != 1)) {
-            ALOGE("%s: Result is malformed for frame %d: partial_result %u "
-                    "must be 1  if partial result is not supported", __func__,
-                    frameNumber, results[i].partialResult);
-            ADD_FAILURE();
-            break;
-        }
-
-        if (results[i].partialResult != 0) {
-            request->partialResultCount = results[i].partialResult;
-        }
-
-        // Check if this result carries only partial metadata
-        if (request->usePartialResult && (resultSize > 0)) {
-            if ((results[i].partialResult > request->numPartialResults) ||
-                    (results[i].partialResult < 1)) {
-                ALOGE("%s: Result is malformed for frame %d: partial_result %u"
-                        " must be  in the range of [1, %d] when metadata is "
-                        "included in the result", __func__, frameNumber,
-                        results[i].partialResult, request->numPartialResults);
-                ADD_FAILURE();
-                break;
-            }
-            request->collectedResult.append(
-                    reinterpret_cast<const camera_metadata_t*>(
-                            resultMetadata.data()));
-
-            isPartialResult =
-                    (results[i].partialResult < request->numPartialResults);
-        }
-
-        hasInputBufferInRequest = request->hasInputBuffer;
-
-        // Did we get the (final) result metadata for this capture?
-        if ((resultSize > 0) && !isPartialResult) {
-            if (request->haveResultMetadata) {
-                ALOGE("%s: Called multiple times with metadata for frame %d",
-                      __func__, frameNumber);
-                ADD_FAILURE();
-                break;
-            }
-            request->haveResultMetadata = true;
-            request->collectedResult.sort();
-        }
-
-        uint32_t numBuffersReturned = results[i].outputBuffers.size();
-        if (results[i].inputBuffer.buffer != nullptr) {
-            if (hasInputBufferInRequest) {
-                numBuffersReturned += 1;
-            } else {
-                ALOGW("%s: Input buffer should be NULL if there is no input"
-                        " buffer sent in the request", __func__);
-            }
-        }
-        request->numBuffersLeft -= numBuffersReturned;
-        if (request->numBuffersLeft < 0) {
-            ALOGE("%s: Too many buffers returned for frame %d", __func__,
-                    frameNumber);
-            ADD_FAILURE();
-            break;
-        }
-
-        request->resultOutputBuffers.appendArray(results[i].outputBuffers.data(),
-                results[i].outputBuffers.size());
-        // If shutter event is received notify the pending threads.
-        if (request->shutterTimestamp != 0) {
-            notify = true;
-        }
+        notify = processCaptureResultLocked(results[i]);
     }
 
     l.unlock();
@@ -973,6 +864,126 @@
     return Void();
 }
 
+bool CameraHidlTest::DeviceCb::processCaptureResultLocked(const CaptureResult& results) {
+    bool notify = false;
+    uint32_t frameNumber = results.frameNumber;
+
+    if ((results.result.size() == 0) &&
+            (results.outputBuffers.size() == 0) &&
+            (results.inputBuffer.buffer == nullptr) &&
+            (results.fmqResultSize == 0)) {
+        ALOGE("%s: No result data provided by HAL for frame %d result count: %d",
+                __func__, frameNumber, (int) results.fmqResultSize);
+        ADD_FAILURE();
+        return notify;
+    }
+
+    ssize_t idx = mParent->mInflightMap.indexOfKey(frameNumber);
+    if (::android::NAME_NOT_FOUND == idx) {
+        ALOGE("%s: Unexpected frame number! received: %u",
+                __func__, frameNumber);
+        ADD_FAILURE();
+        return notify;
+    }
+
+    bool isPartialResult = false;
+    bool hasInputBufferInRequest = false;
+    InFlightRequest *request = mParent->mInflightMap.editValueAt(idx);
+    ::android::hardware::camera::device::V3_2::CameraMetadata resultMetadata;
+    size_t resultSize = 0;
+    if (results.fmqResultSize > 0) {
+        resultMetadata.resize(results.fmqResultSize);
+        if (request->resultQueue == nullptr) {
+            ADD_FAILURE();
+            return notify;
+        }
+        if (!request->resultQueue->read(resultMetadata.data(),
+                    results.fmqResultSize)) {
+            ALOGE("%s: Frame %d: Cannot read camera metadata from fmq,"
+                    "size = %" PRIu64, __func__, frameNumber,
+                    results.fmqResultSize);
+            ADD_FAILURE();
+            return notify;
+        }
+        resultSize = resultMetadata.size();
+    } else if (results.result.size() > 0) {
+        resultMetadata.setToExternal(const_cast<uint8_t *>(
+                    results.result.data()), results.result.size());
+        resultSize = resultMetadata.size();
+    }
+
+    if (!request->usePartialResult && (resultSize > 0) &&
+            (results.partialResult != 1)) {
+        ALOGE("%s: Result is malformed for frame %d: partial_result %u "
+                "must be 1  if partial result is not supported", __func__,
+                frameNumber, results.partialResult);
+        ADD_FAILURE();
+        return notify;
+    }
+
+    if (results.partialResult != 0) {
+        request->partialResultCount = results.partialResult;
+    }
+
+    // Check if this result carries only partial metadata
+    if (request->usePartialResult && (resultSize > 0)) {
+        if ((results.partialResult > request->numPartialResults) ||
+                (results.partialResult < 1)) {
+            ALOGE("%s: Result is malformed for frame %d: partial_result %u"
+                    " must be  in the range of [1, %d] when metadata is "
+                    "included in the result", __func__, frameNumber,
+                    results.partialResult, request->numPartialResults);
+            ADD_FAILURE();
+            return notify;
+        }
+        request->collectedResult.append(
+                reinterpret_cast<const camera_metadata_t*>(
+                    resultMetadata.data()));
+
+        isPartialResult =
+            (results.partialResult < request->numPartialResults);
+    }
+
+    hasInputBufferInRequest = request->hasInputBuffer;
+
+    // Did we get the (final) result metadata for this capture?
+    if ((resultSize > 0) && !isPartialResult) {
+        if (request->haveResultMetadata) {
+            ALOGE("%s: Called multiple times with metadata for frame %d",
+                    __func__, frameNumber);
+            ADD_FAILURE();
+            return notify;
+        }
+        request->haveResultMetadata = true;
+        request->collectedResult.sort();
+    }
+
+    uint32_t numBuffersReturned = results.outputBuffers.size();
+    if (results.inputBuffer.buffer != nullptr) {
+        if (hasInputBufferInRequest) {
+            numBuffersReturned += 1;
+        } else {
+            ALOGW("%s: Input buffer should be NULL if there is no input"
+                    " buffer sent in the request", __func__);
+        }
+    }
+    request->numBuffersLeft -= numBuffersReturned;
+    if (request->numBuffersLeft < 0) {
+        ALOGE("%s: Too many buffers returned for frame %d", __func__,
+                frameNumber);
+        ADD_FAILURE();
+        return notify;
+    }
+
+    request->resultOutputBuffers.appendArray(results.outputBuffers.data(),
+            results.outputBuffers.size());
+    // If shutter event is received notify the pending threads.
+    if (request->shutterTimestamp != 0) {
+        notify = true;
+    }
+    return notify;
+}
+
 Return<void> CameraHidlTest::DeviceCb::notify(
         const hidl_vec<NotifyMsg>& messages) {
     std::lock_guard<std::mutex> l(mParent->mLock);
@@ -3289,6 +3300,171 @@
     }
 }
 
+// Generate and verify a multi-camera capture request
+TEST_F(CameraHidlTest, processMultiCaptureRequestPreview) {
+    hidl_vec<hidl_string> cameraDeviceNames = getCameraDeviceNames(mProvider);
+    AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight,
+                                        static_cast<int32_t>(PixelFormat::IMPLEMENTATION_DEFINED)};
+    uint64_t bufferId = 1;
+    uint32_t frameNumber = 1;
+    ::android::hardware::hidl_vec<uint8_t> settings;
+    ::android::hardware::hidl_vec<uint8_t> emptySettings;
+    hidl_string invalidPhysicalId = "-1";
+
+    for (const auto& name : cameraDeviceNames) {
+        int deviceVersion = getCameraDeviceVersion(name, mProviderType);
+        if (deviceVersion < CAMERA_DEVICE_API_VERSION_3_4) {
+            continue;
+        }
+        camera_metadata_t* staticMeta;
+        Return<void> ret;
+        sp<ICameraDeviceSession> session;
+        openEmptyDeviceSession(name, mProvider, &session /*out*/, &staticMeta /*out*/);
+
+        Status rc = isLogicalMultiCamera(staticMeta);
+        if (Status::METHOD_NOT_SUPPORTED == rc) {
+            ret = session->close();
+            ASSERT_TRUE(ret.isOk());
+            continue;
+        }
+        std::vector<std::string> physicalIds;
+        rc = getPhysicalCameraIds(staticMeta, &physicalIds);
+        ASSERT_TRUE(Status::OK == rc);
+        ASSERT_TRUE(physicalIds.size() > 1);
+
+        free_camera_metadata(staticMeta);
+        ret = session->close();
+        ASSERT_TRUE(ret.isOk());
+
+        V3_2::Stream previewStream;
+        HalStreamConfiguration halStreamConfig;
+        bool supportsPartialResults = false;
+        uint32_t partialResultCount = 0;
+        configurePreviewStream(name, deviceVersion, mProvider, &previewThreshold, &session /*out*/,
+                &previewStream /*out*/, &halStreamConfig /*out*/,
+                &supportsPartialResults /*out*/,
+                &partialResultCount /*out*/);
+        sp<device::V3_3::ICameraDeviceSession> session3_3;
+        sp<device::V3_4::ICameraDeviceSession> session3_4;
+        castSession(session, deviceVersion, &session3_3, &session3_4);
+        ASSERT_NE(session3_4, nullptr);
+
+        std::shared_ptr<ResultMetadataQueue> resultQueue;
+        auto resultQueueRet =
+            session->getCaptureResultMetadataQueue(
+                [&resultQueue](const auto& descriptor) {
+                    resultQueue = std::make_shared<ResultMetadataQueue>(
+                            descriptor);
+                    if (!resultQueue->isValid() ||
+                            resultQueue->availableToWrite() <= 0) {
+                        ALOGE("%s: HAL returns empty result metadata fmq,"
+                                " not use it", __func__);
+                        resultQueue = nullptr;
+                        // Don't use the queue onwards.
+                    }
+                });
+        ASSERT_TRUE(resultQueueRet.isOk());
+
+        InFlightRequest inflightReq = {1, false, supportsPartialResults,
+                                       partialResultCount, resultQueue};
+
+        RequestTemplate reqTemplate = RequestTemplate::PREVIEW;
+        ret = session->constructDefaultRequestSettings(reqTemplate,
+                                                       [&](auto status, const auto& req) {
+                                                           ASSERT_EQ(Status::OK, status);
+                                                           settings = req;
+                                                       });
+        ASSERT_TRUE(ret.isOk());
+
+        sp<GraphicBuffer> gb = new GraphicBuffer(
+            previewStream.width, previewStream.height,
+            static_cast<int32_t>(halStreamConfig.streams[0].overrideFormat), 1,
+            android_convertGralloc1To0Usage(halStreamConfig.streams[0].producerUsage,
+                                            halStreamConfig.streams[0].consumerUsage));
+        ASSERT_NE(nullptr, gb.get());
+        ::android::hardware::hidl_vec<StreamBuffer> outputBuffers;
+        outputBuffers.resize(1);
+        outputBuffers[0] = {halStreamConfig.streams[0].id,
+                                     bufferId,
+                                     hidl_handle(gb->getNativeBuffer()->handle),
+                                     BufferStatus::OK,
+                                     nullptr,
+                                     nullptr};
+
+        StreamBuffer emptyInputBuffer = {-1, 0, nullptr, BufferStatus::ERROR, nullptr,
+                                         nullptr};
+        hidl_vec<V3_4::PhysicalCameraSetting> camSettings;
+        camSettings.resize(2);
+        camSettings[0] = {0, hidl_string(physicalIds[0]), settings};
+        camSettings[1] = {0, hidl_string(physicalIds[1]), settings};
+        V3_4::CaptureRequest request = {{frameNumber, 0 /* fmqSettingsSize */, settings,
+                                  emptyInputBuffer, outputBuffers}, camSettings};
+
+        {
+            std::unique_lock<std::mutex> l(mLock);
+            mInflightMap.clear();
+            mInflightMap.add(frameNumber, &inflightReq);
+        }
+
+        Status stat = Status::INTERNAL_ERROR;
+        uint32_t numRequestProcessed = 0;
+        hidl_vec<BufferCache> cachesToRemove;
+        Return<void> returnStatus = session3_4->processCaptureRequest_3_4(
+            {request}, cachesToRemove, [&stat, &numRequestProcessed](auto s, uint32_t n) {
+                stat = s;
+                numRequestProcessed = n;
+            });
+        ASSERT_TRUE(returnStatus.isOk());
+        ASSERT_EQ(Status::OK, stat);
+        ASSERT_EQ(numRequestProcessed, 1u);
+
+        {
+            std::unique_lock<std::mutex> l(mLock);
+            while (!inflightReq.errorCodeValid &&
+                   ((0 < inflightReq.numBuffersLeft) ||
+                           (!inflightReq.haveResultMetadata))) {
+                auto timeout = std::chrono::system_clock::now() +
+                               std::chrono::seconds(kStreamBufferTimeoutSec);
+                ASSERT_NE(std::cv_status::timeout,
+                        mResultCondition.wait_until(l, timeout));
+            }
+
+            ASSERT_FALSE(inflightReq.errorCodeValid);
+            ASSERT_NE(inflightReq.resultOutputBuffers.size(), 0u);
+            ASSERT_EQ(halStreamConfig.streams[0].id,
+                    inflightReq.resultOutputBuffers[0].streamId);
+        }
+
+        // Empty physical camera settings should fail process requests
+        camSettings[1] = {0, hidl_string(physicalIds[1]), emptySettings};
+        frameNumber++;
+        request = {{frameNumber, 0 /* fmqSettingsSize */, settings,
+            emptyInputBuffer, outputBuffers}, camSettings};
+        returnStatus = session3_4->processCaptureRequest_3_4(
+            {request}, cachesToRemove, [&stat, &numRequestProcessed](auto s, uint32_t n) {
+                stat = s;
+                numRequestProcessed = n;
+            });
+        ASSERT_TRUE(returnStatus.isOk());
+        ASSERT_EQ(Status::ILLEGAL_ARGUMENT, stat);
+
+        // Invalid physical camera id should fail process requests
+        camSettings[1] = {0, invalidPhysicalId, settings};
+        request = {{frameNumber, 0 /* fmqSettingsSize */, settings,
+            emptyInputBuffer, outputBuffers}, camSettings};
+        returnStatus = session3_4->processCaptureRequest_3_4(
+            {request}, cachesToRemove, [&stat, &numRequestProcessed](auto s, uint32_t n) {
+                stat = s;
+                numRequestProcessed = n;
+            });
+        ASSERT_TRUE(returnStatus.isOk());
+        ASSERT_EQ(Status::ILLEGAL_ARGUMENT, stat);
+
+        ret = session->close();
+        ASSERT_TRUE(ret.isOk());
+    }
+}
+
 // Test whether an incorrect capture request with missing settings will
 // be reported correctly.
 TEST_F(CameraHidlTest, processCaptureRequestInvalidSinglePreview) {
@@ -3636,6 +3812,59 @@
     return Status::OK;
 }
 
+// Check if the camera device has logical multi-camera capability.
+Status CameraHidlTest::isLogicalMultiCamera(camera_metadata_t *staticMeta) {
+    Status ret = Status::METHOD_NOT_SUPPORTED;
+    if (nullptr == staticMeta) {
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    camera_metadata_ro_entry entry;
+    int rc = find_camera_metadata_ro_entry(staticMeta,
+            ANDROID_REQUEST_AVAILABLE_CAPABILITIES, &entry);
+    if (0 != rc) {
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    for (size_t i = 0; i < entry.count; i++) {
+        if (ANDROID_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA == entry.data.u8[i]) {
+            ret = Status::OK;
+            break;
+        }
+    }
+
+    return ret;
+}
+
+// Generate a list of physical camera ids backing a logical multi-camera.
+Status CameraHidlTest::getPhysicalCameraIds(camera_metadata_t *staticMeta,
+        std::vector<std::string> *physicalIds) {
+    if ((nullptr == staticMeta) || (nullptr == physicalIds)) {
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    camera_metadata_ro_entry entry;
+    int rc = find_camera_metadata_ro_entry(staticMeta, ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS,
+            &entry);
+    if (0 != rc) {
+        return Status::ILLEGAL_ARGUMENT;
+    }
+
+    const uint8_t* ids = entry.data.u8;
+    size_t start = 0;
+    for (size_t i = 0; i < entry.count; i++) {
+        if (ids[i] == '\0') {
+            if (start != i) {
+                std::string currentId(reinterpret_cast<const char *> (ids + start));
+                physicalIds->push_back(currentId);
+            }
+            start = i + 1;
+        }
+    }
+
+    return Status::OK;
+}
+
 // Check if constrained mode is supported by using the static
 // camera characteristics.
 Status CameraHidlTest::isConstrainedModeAvailable(camera_metadata_t *staticMeta) {