Camera: Detect session parameter updates and re-configure camera

Camera clients are allowed to change session parameters during
active session, however some camera devices might not be able
to apply them without additional stream configuration. To resolve
this, detect whether any of the available session parameter changes
in the queued requests and re-configure the camera before trying
to apply it.

Bug: 64450664
Test: Manual, Camera CTS
Change-Id: I005d6b7d6c6b27d4b5bac4b0d0809c7c019af9a4
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index d99fc1d..16d6999 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -194,8 +194,14 @@
 
     mTagMonitor.initialize(mVendorTagId);
 
+    Vector<int32_t> sessionParamKeys;
+    camera_metadata_entry_t sessionKeysEntry = mDeviceInfo.find(
+            ANDROID_REQUEST_AVAILABLE_SESSION_KEYS);
+    if (sessionKeysEntry.count > 0) {
+        sessionParamKeys.insertArrayAt(sessionKeysEntry.data.i32, 0, sessionKeysEntry.count);
+    }
     /** Start up request queue thread */
-    mRequestThread = new RequestThread(this, mStatusTracker, mInterface);
+    mRequestThread = new RequestThread(this, mStatusTracker, mInterface, sessionParamKeys);
     res = mRequestThread->run(String8::format("C3Dev-%s-ReqQueue", mId.string()).string());
     if (res != OK) {
         SET_ERR_L("Unable to start request queue thread: %s (%d)",
@@ -1104,7 +1110,7 @@
     if (mStatus == STATUS_UNCONFIGURED || mNeedConfig) {
         // This point should only be reached via API1 (API2 must explicitly call configureStreams)
         // so unilaterally select normal operating mode.
-        res = configureStreamsLocked(CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE, mSessionParams);
+        res = filterParamsAndConfigureLocked(request, CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE);
         // Stream configuration failed. Client might try other configuraitons.
         if (res != OK) {
             CLOGE("Can't set up streams: %s (%d)", strerror(-res), res);
@@ -1508,11 +1514,20 @@
     Mutex::Autolock il(mInterfaceLock);
     Mutex::Autolock l(mLock);
 
+    return filterParamsAndConfigureLocked(sessionParams, operatingMode);
+}
+
+status_t Camera3Device::filterParamsAndConfigureLocked(const CameraMetadata& sessionParams,
+        int operatingMode) {
     //Filter out any incoming session parameters
     const CameraMetadata params(sessionParams);
-    CameraMetadata filteredParams;
     camera_metadata_entry_t availableSessionKeys = mDeviceInfo.find(
             ANDROID_REQUEST_AVAILABLE_SESSION_KEYS);
+    CameraMetadata filteredParams(availableSessionKeys.count);
+    camera_metadata_t *meta = const_cast<camera_metadata_t *>(
+            filteredParams.getAndLock());
+    set_camera_metadata_vendor_id(meta, mVendorTagId);
+    filteredParams.unlock(meta);
     if (availableSessionKeys.count > 0) {
         for (size_t i = 0; i < availableSessionKeys.count; i++) {
             camera_metadata_ro_entry entry = params.find(
@@ -2203,10 +2218,47 @@
     // properly clean things up
     internalUpdateStatusLocked(STATUS_UNCONFIGURED);
     mNeedConfig = true;
+
+    res = mPreparerThread->resume();
+    if (res != OK) {
+        ALOGE("%s: Camera %s: Preparer thread failed to resume!", __FUNCTION__, mId.string());
+    }
+}
+
+bool Camera3Device::reconfigureCamera(const CameraMetadata& sessionParams) {
+    ATRACE_CALL();
+    bool ret = false;
+
+    Mutex::Autolock il(mInterfaceLock);
+    nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
+
+    Mutex::Autolock l(mLock);
+    auto rc = internalPauseAndWaitLocked(maxExpectedDuration);
+    if (rc == NO_ERROR) {
+        mNeedConfig = true;
+        rc = configureStreamsLocked(mOperatingMode, sessionParams, /*notifyRequestThread*/ false);
+        if (rc == NO_ERROR) {
+            ret = true;
+            mPauseStateNotify = false;
+            //Moving to active state while holding 'mLock' is important.
+            //There could be pending calls to 'create-/deleteStream' which
+            //will trigger another stream configuration while the already
+            //present streams end up with outstanding buffers that will
+            //not get drained.
+            internalUpdateStatusLocked(STATUS_ACTIVE);
+        } else {
+            setErrorStateLocked("%s: Failed to re-configure camera: %d",
+                    __FUNCTION__, rc);
+        }
+    } else {
+        ALOGE("%s: Failed to pause streaming: %d", __FUNCTION__, rc);
+    }
+
+    return ret;
 }
 
 status_t Camera3Device::configureStreamsLocked(int operatingMode,
-        const CameraMetadata& sessionParams) {
+        const CameraMetadata& sessionParams, bool notifyRequestThread) {
     ATRACE_CALL();
     status_t res;
 
@@ -2247,6 +2299,8 @@
     // Start configuring the streams
     ALOGV("%s: Camera %s: Starting stream configuration", __FUNCTION__, mId.string());
 
+    mPreparerThread->pause();
+
     camera3_stream_configuration config;
     config.operation_mode = mOperatingMode;
     config.num_streams = (mInputStream != NULL) + mOutputStreams.size();
@@ -2338,7 +2392,9 @@
 
     // Request thread needs to know to avoid using repeat-last-settings protocol
     // across configure_streams() calls
-    mRequestThread->configurationComplete(mIsConstrainedHighSpeedConfiguration);
+    if (notifyRequestThread) {
+        mRequestThread->configurationComplete(mIsConstrainedHighSpeedConfiguration, sessionParams);
+    }
 
     char value[PROPERTY_VALUE_MAX];
     property_get("camera.fifo.disable", value, "0");
@@ -2376,6 +2432,12 @@
     // tear down the deleted streams after configure streams.
     mDeletedStreams.clear();
 
+    auto rc = mPreparerThread->resume();
+    if (rc != OK) {
+        SET_ERR_L("%s: Camera %s: Preparer thread failed to resume!", __FUNCTION__, mId.string());
+        return rc;
+    }
+
     return OK;
 }
 
@@ -3747,7 +3809,7 @@
 
 Camera3Device::RequestThread::RequestThread(wp<Camera3Device> parent,
         sp<StatusTracker> statusTracker,
-        sp<HalInterface> interface) :
+        sp<HalInterface> interface, const Vector<int32_t>& sessionParamKeys) :
         Thread(/*canCallJava*/false),
         mParent(parent),
         mStatusTracker(statusTracker),
@@ -3764,7 +3826,9 @@
         mRepeatingLastFrameNumber(
             hardware::camera2::ICameraDeviceUser::NO_IN_FLIGHT_REPEATING_FRAMES),
         mPrepareVideoStream(false),
-        mRequestLatency(kRequestLatencyBinSize) {
+        mRequestLatency(kRequestLatencyBinSize),
+        mSessionParamKeys(sessionParamKeys),
+        mLatestSessionParams(sessionParamKeys.size()) {
     mStatusId = statusTracker->addComponent();
 }
 
@@ -3777,10 +3841,12 @@
     mListener = listener;
 }
 
-void Camera3Device::RequestThread::configurationComplete(bool isConstrainedHighSpeed) {
+void Camera3Device::RequestThread::configurationComplete(bool isConstrainedHighSpeed,
+        const CameraMetadata& sessionParams) {
     ATRACE_CALL();
     Mutex::Autolock l(mRequestLock);
     mReconfigured = true;
+    mLatestSessionParams = sessionParams;
     // Prepare video stream for high speed recording.
     mPrepareVideoStream = isConstrainedHighSpeed;
 }
@@ -4191,6 +4257,52 @@
     return maxExpectedDuration;
 }
 
+bool Camera3Device::RequestThread::updateSessionParameters(const CameraMetadata& settings) {
+    ATRACE_CALL();
+    bool updatesDetected = false;
+
+    for (auto tag : mSessionParamKeys) {
+        camera_metadata_ro_entry entry = settings.find(tag);
+        camera_metadata_entry lastEntry = mLatestSessionParams.find(tag);
+
+        if (entry.count > 0) {
+            bool isDifferent = false;
+            if (lastEntry.count > 0) {
+                // Have a last value, compare to see if changed
+                if (lastEntry.type == entry.type &&
+                        lastEntry.count == entry.count) {
+                    // Same type and count, compare values
+                    size_t bytesPerValue = camera_metadata_type_size[lastEntry.type];
+                    size_t entryBytes = bytesPerValue * lastEntry.count;
+                    int cmp = memcmp(entry.data.u8, lastEntry.data.u8, entryBytes);
+                    if (cmp != 0) {
+                        isDifferent = true;
+                    }
+                } else {
+                    // Count or type has changed
+                    isDifferent = true;
+                }
+            } else {
+                // No last entry, so always consider to be different
+                isDifferent = true;
+            }
+
+            if (isDifferent) {
+                ALOGV("%s: Session parameter tag id %d changed", __FUNCTION__, tag);
+                mLatestSessionParams.update(entry);
+                updatesDetected = true;
+            }
+        } else if (lastEntry.count > 0) {
+            // Value has been removed
+            ALOGV("%s: Session parameter tag id %d removed", __FUNCTION__, tag);
+            mLatestSessionParams.erase(tag);
+            updatesDetected = true;
+        }
+    }
+
+    return updatesDetected;
+}
+
 bool Camera3Device::RequestThread::threadLoop() {
     ATRACE_CALL();
     status_t res;
@@ -4217,6 +4329,49 @@
         latestRequestId = NAME_NOT_FOUND;
     }
 
+    // 'mNextRequests' will at this point contain either a set of HFR batched requests
+    //  or a single request from streaming or burst. In either case the first element
+    //  should contain the latest camera settings that we need to check for any session
+    //  parameter updates.
+    if (updateSessionParameters(mNextRequests[0].captureRequest->mSettings)) {
+        res = OK;
+
+        //Input stream buffers are already acquired at this point so an input stream
+        //will not be able to move to idle state unless we force it.
+        if (mNextRequests[0].captureRequest->mInputStream != nullptr) {
+            res = mNextRequests[0].captureRequest->mInputStream->forceToIdle();
+            if (res != OK) {
+                ALOGE("%s: Failed to force idle input stream: %d", __FUNCTION__, res);
+                cleanUpFailedRequests(/*sendRequestError*/ false);
+                return false;
+            }
+        }
+
+        if (res == OK) {
+            sp<StatusTracker> statusTracker = mStatusTracker.promote();
+            if (statusTracker != 0) {
+                statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
+
+                sp<Camera3Device> parent = mParent.promote();
+                if (parent != nullptr) {
+                    mReconfigured |= parent->reconfigureCamera(mLatestSessionParams);
+                }
+
+                statusTracker->markComponentActive(mStatusId);
+                setPaused(false);
+            }
+
+            if (mNextRequests[0].captureRequest->mInputStream != nullptr) {
+                mNextRequests[0].captureRequest->mInputStream->restoreConfiguredState();
+                if (res != OK) {
+                    ALOGE("%s: Failed to restore configured input stream: %d", __FUNCTION__, res);
+                    cleanUpFailedRequests(/*sendRequestError*/ false);
+                    return false;
+                }
+            }
+        }
+    }
+
     // Prepare a batch of HAL requests and output buffers.
     res = prepareHalRequests();
     if (res == TIMED_OUT) {
@@ -4980,7 +5135,7 @@
 
 Camera3Device::PreparerThread::PreparerThread() :
         Thread(/*canCallJava*/false), mListener(nullptr),
-        mActive(false), mCancelNow(false) {
+        mActive(false), mCancelNow(false), mCurrentMaxCount(0), mCurrentPrepareComplete(false) {
 }
 
 Camera3Device::PreparerThread::~PreparerThread() {
@@ -5031,18 +5186,101 @@
     }
 
     // queue up the work
-    mPendingStreams.push_back(stream);
+    mPendingStreams.emplace(maxCount, stream);
     ALOGV("%s: Stream %d queued for preparing", __FUNCTION__, stream->getId());
 
     return OK;
 }
 
+void Camera3Device::PreparerThread::pause() {
+    ATRACE_CALL();
+
+    Mutex::Autolock l(mLock);
+
+    std::unordered_map<int, sp<camera3::Camera3StreamInterface> > pendingStreams;
+    pendingStreams.insert(mPendingStreams.begin(), mPendingStreams.end());
+    sp<camera3::Camera3StreamInterface> currentStream = mCurrentStream;
+    int currentMaxCount = mCurrentMaxCount;
+    mPendingStreams.clear();
+    mCancelNow = true;
+    while (mActive) {
+        auto res = mThreadActiveSignal.waitRelative(mLock, kActiveTimeout);
+        if (res == TIMED_OUT) {
+            ALOGE("%s: Timed out waiting on prepare thread!", __FUNCTION__);
+            return;
+        } else if (res != OK) {
+            ALOGE("%s: Encountered an error: %d waiting on prepare thread!", __FUNCTION__, res);
+            return;
+        }
+    }
+
+    //Check whether the prepare thread was able to complete the current
+    //stream. In case work is still pending emplace it along with the rest
+    //of the streams in the pending list.
+    if (currentStream != nullptr) {
+        if (!mCurrentPrepareComplete) {
+            pendingStreams.emplace(currentMaxCount, currentStream);
+        }
+    }
+
+    mPendingStreams.insert(pendingStreams.begin(), pendingStreams.end());
+    for (const auto& it : mPendingStreams) {
+        it.second->cancelPrepare();
+    }
+}
+
+status_t Camera3Device::PreparerThread::resume() {
+    ATRACE_CALL();
+    status_t res;
+
+    Mutex::Autolock l(mLock);
+    sp<NotificationListener> listener = mListener.promote();
+
+    if (mActive) {
+        ALOGE("%s: Trying to resume an already active prepare thread!", __FUNCTION__);
+        return NO_INIT;
+    }
+
+    auto it = mPendingStreams.begin();
+    for (; it != mPendingStreams.end();) {
+        res = it->second->startPrepare(it->first);
+        if (res == OK) {
+            if (listener != NULL) {
+                listener->notifyPrepared(it->second->getId());
+            }
+            it = mPendingStreams.erase(it);
+        } else if (res != NOT_ENOUGH_DATA) {
+            ALOGE("%s: Unable to start preparer stream: %d (%s)", __FUNCTION__,
+                    res, strerror(-res));
+            it = mPendingStreams.erase(it);
+        } else {
+            it++;
+        }
+    }
+
+    if (mPendingStreams.empty()) {
+        return OK;
+    }
+
+    res = Thread::run("C3PrepThread", PRIORITY_BACKGROUND);
+    if (res != OK) {
+        ALOGE("%s: Unable to start preparer stream: %d (%s)",
+                __FUNCTION__, res, strerror(-res));
+        return res;
+    }
+    mCancelNow = false;
+    mActive = true;
+    ALOGV("%s: Preparer stream started", __FUNCTION__);
+
+    return OK;
+}
+
 status_t Camera3Device::PreparerThread::clear() {
     ATRACE_CALL();
     Mutex::Autolock l(mLock);
 
-    for (const auto& stream : mPendingStreams) {
-        stream->cancelPrepare();
+    for (const auto& it : mPendingStreams) {
+        it.second->cancelPrepare();
     }
     mPendingStreams.clear();
     mCancelNow = true;
@@ -5067,12 +5305,15 @@
                 // threadLoop _must not_ re-acquire mLock after it sets mActive to false; would
                 // cause deadlock with prepare()'s requestExitAndWait triggered by !mActive.
                 mActive = false;
+                mThreadActiveSignal.signal();
                 return false;
             }
 
             // Get next stream to prepare
             auto it = mPendingStreams.begin();
-            mCurrentStream = *it;
+            mCurrentStream = it->second;
+            mCurrentMaxCount = it->first;
+            mCurrentPrepareComplete = false;
             mPendingStreams.erase(it);
             ATRACE_ASYNC_BEGIN("stream prepare", mCurrentStream->getId());
             ALOGV("%s: Preparing stream %d", __FUNCTION__, mCurrentStream->getId());
@@ -5107,6 +5348,7 @@
 
     ATRACE_ASYNC_END("stream prepare", mCurrentStream->getId());
     mCurrentStream.clear();
+    mCurrentPrepareComplete = true;
 
     return true;
 }