Camera muting via sensor test pattern

Currently enabled/disabled via
  adb shell cmd media.camera set-camera-mute <1/0>

TODO:
- Connect to the wider sensor muting feature

This is suitable for testing apps to see how they react, and how Pixel phones
implement test patterns.

Test: GoogleCameraApp and AOSP Camera2 on sunfish appear to act as if they were in a dark room,
      no crashes, after calling 'adb shell cmd media.camera set-camera-mute 1'
Bug: 170156750
Change-Id: I85e1d2d6ee7d9bf1aee99c4bc8d5f98d36754228
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 385bfd6..7606d7d 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -357,6 +357,15 @@
         mRotateAndCropMappers.emplace(mId.c_str(), &mDeviceInfo);
     }
 
+    camera_metadata_entry_t availableTestPatternModes = mDeviceInfo.find(
+            ANDROID_SENSOR_AVAILABLE_TEST_PATTERN_MODES);
+    for (size_t i = 0; i < availableTestPatternModes.count; i++) {
+        if (availableTestPatternModes.data.i32[i] == ANDROID_SENSOR_TEST_PATTERN_MODE_SOLID_COLOR) {
+            mSupportCameraMute = true;
+            break;
+        }
+    }
+
     return OK;
 }
 
@@ -2388,6 +2397,26 @@
         newRequest->mZoomRatioIs1x = false;
     }
 
+    if (mSupportCameraMute) {
+        auto testPatternModeEntry =
+                newRequest->mSettingsList.begin()->metadata.find(ANDROID_SENSOR_TEST_PATTERN_MODE);
+        newRequest->mOriginalTestPatternMode = testPatternModeEntry.count > 0 ?
+                testPatternModeEntry.data.i32[0] :
+                ANDROID_SENSOR_TEST_PATTERN_MODE_OFF;
+
+        auto testPatternDataEntry =
+                newRequest->mSettingsList.begin()->metadata.find(ANDROID_SENSOR_TEST_PATTERN_DATA);
+        if (testPatternDataEntry.count > 0) {
+            memcpy(newRequest->mOriginalTestPatternData, testPatternModeEntry.data.i32,
+                    sizeof(newRequest->mOriginalTestPatternData));
+        } else {
+            newRequest->mOriginalTestPatternData[0] = 0;
+            newRequest->mOriginalTestPatternData[1] = 0;
+            newRequest->mOriginalTestPatternData[2] = 0;
+            newRequest->mOriginalTestPatternData[3] = 0;
+        }
+    }
+
     return newRequest;
 }
 
@@ -3860,6 +3889,8 @@
         mCurrentAfTriggerId(0),
         mCurrentPreCaptureTriggerId(0),
         mRotateAndCropOverride(ANDROID_SCALER_ROTATE_AND_CROP_NONE),
+        mCameraMute(false),
+        mCameraMuteChanged(false),
         mRepeatingLastFrameNumber(
             hardware::camera2::ICameraDeviceUser::NO_IN_FLIGHT_REPEATING_FRAMES),
         mPrepareVideoStream(false),
@@ -4484,10 +4515,13 @@
         mPrevTriggers = triggerCount;
 
         bool rotateAndCropChanged = overrideAutoRotateAndCrop(captureRequest);
+        bool testPatternChanged = overrideTestPattern(captureRequest);
 
-        // If the request is the same as last, or we had triggers last time
+        // If the request is the same as last, or we had triggers now or last time or
+        // changing overrides this time
         bool newRequest =
-                (mPrevRequest != captureRequest || triggersMixedIn || rotateAndCropChanged) &&
+                (mPrevRequest != captureRequest || triggersMixedIn ||
+                        rotateAndCropChanged || testPatternChanged) &&
                 // Request settings are all the same within one batch, so only treat the first
                 // request in a batch as new
                 !(batchedRequest && i > 0);
@@ -4952,6 +4986,16 @@
     return OK;
 }
 
+status_t Camera3Device::RequestThread::setCameraMute(bool enabled) {
+    ATRACE_CALL();
+    Mutex::Autolock l(mTriggerMutex);
+    if (enabled != mCameraMute) {
+        mCameraMute = enabled;
+        mCameraMuteChanged = true;
+    }
+    return OK;
+}
+
 nsecs_t Camera3Device::getExpectedInFlightDuration() {
     ATRACE_CALL();
     std::lock_guard<std::mutex> l(mInFlightLock);
@@ -5504,6 +5548,61 @@
     return false;
 }
 
+bool Camera3Device::RequestThread::overrideTestPattern(
+        const sp<CaptureRequest> &request) {
+    ATRACE_CALL();
+
+    Mutex::Autolock l(mTriggerMutex);
+
+    bool changed = false;
+
+    int32_t testPatternMode = request->mOriginalTestPatternMode;
+    int32_t testPatternData[4] = {
+        request->mOriginalTestPatternData[0],
+        request->mOriginalTestPatternData[1],
+        request->mOriginalTestPatternData[2],
+        request->mOriginalTestPatternData[3]
+    };
+
+    if (mCameraMute) {
+        testPatternMode = ANDROID_SENSOR_TEST_PATTERN_MODE_SOLID_COLOR;
+        testPatternData[0] = 0;
+        testPatternData[1] = 0;
+        testPatternData[2] = 0;
+        testPatternData[3] = 0;
+    }
+
+    CameraMetadata &metadata = request->mSettingsList.begin()->metadata;
+
+    auto testPatternEntry = metadata.find(ANDROID_SENSOR_TEST_PATTERN_MODE);
+    if (testPatternEntry.count > 0) {
+        if (testPatternEntry.data.i32[0] != testPatternMode) {
+            testPatternEntry.data.i32[0] = testPatternMode;
+            changed = true;
+        }
+    } else {
+        metadata.update(ANDROID_SENSOR_TEST_PATTERN_MODE,
+                &testPatternMode, 1);
+        changed = true;
+    }
+
+    auto testPatternColor = metadata.find(ANDROID_SENSOR_TEST_PATTERN_DATA);
+    if (testPatternColor.count > 0) {
+        for (size_t i = 0; i < 4; i++) {
+            if (testPatternColor.data.i32[i] != (int32_t)testPatternData[i]) {
+                testPatternColor.data.i32[i] = testPatternData[i];
+                changed = true;
+            }
+        }
+    } else {
+        metadata.update(ANDROID_SENSOR_TEST_PATTERN_DATA,
+                (int32_t*)testPatternData, 4);
+        changed = true;
+    }
+
+    return changed;
+}
+
 /**
  * PreparerThread inner class methods
  */
@@ -6129,4 +6228,22 @@
     return mRequestThread->setRotateAndCropAutoBehavior(rotateAndCropValue);
 }
 
+bool Camera3Device::supportsCameraMute() {
+    Mutex::Autolock il(mInterfaceLock);
+    Mutex::Autolock l(mLock);
+
+    return mSupportCameraMute;
+}
+
+status_t Camera3Device::setCameraMute(bool enabled) {
+    ATRACE_CALL();
+    Mutex::Autolock il(mInterfaceLock);
+    Mutex::Autolock l(mLock);
+
+    if (mRequestThread == nullptr || !mSupportCameraMute) {
+        return INVALID_OPERATION;
+    }
+    return mRequestThread->setCameraMute(enabled);
+}
+
 }; // namespace android
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index b06ce45..567b3ad 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -238,6 +238,21 @@
     status_t setRotateAndCropAutoBehavior(
             camera_metadata_enum_android_scaler_rotate_and_crop_t rotateAndCropValue);
 
+    /**
+     * Whether camera muting (producing black-only output) is supported.
+     *
+     * Calling setCameraMute(true) when this returns false will return an
+     * INVALID_OPERATION error.
+     */
+    bool supportsCameraMute();
+
+    /**
+     * Mute the camera.
+     *
+     * When muted, black image data is output on all output streams.
+     */
+    status_t setCameraMute(bool enabled);
+
     // Get the status trackeer for the camera device
     wp<camera3::StatusTracker> getStatusTracker() { return mStatusTracker; }
 
@@ -525,6 +540,11 @@
         // overriding of ROTATE_AND_CROP value and adjustment of coordinates
         // in several other controls in both the request and the result
         bool                                mRotateAndCropAuto;
+        // Original value of TEST_PATTERN_MODE and DATA so that they can be
+        // restored when sensor muting is turned off
+        int32_t                             mOriginalTestPatternMode;
+        int32_t                             mOriginalTestPatternData[4];
+
         // Whether this capture request has its zoom ratio set to 1.0x before
         // the framework overrides it for camera HAL consumption.
         bool                                mZoomRatioIs1x;
@@ -868,6 +888,7 @@
         status_t setRotateAndCropAutoBehavior(
                 camera_metadata_enum_android_scaler_rotate_and_crop_t rotateAndCropValue);
 
+        status_t setCameraMute(bool enabled);
       protected:
 
         virtual bool threadLoop();
@@ -889,6 +910,10 @@
         // Override rotate_and_crop control if needed; returns true if the current value was changed
         bool               overrideAutoRotateAndCrop(const sp<CaptureRequest> &request);
 
+        // Override test_pattern control if needed for camera mute; returns true
+        // if the current value was changed
+        bool               overrideTestPattern(const sp<CaptureRequest> &request);
+
         static const nsecs_t kRequestTimeout = 50e6; // 50 ms
 
         // TODO: does this need to be adjusted for long exposure requests?
@@ -1011,6 +1036,8 @@
         uint32_t           mCurrentAfTriggerId;
         uint32_t           mCurrentPreCaptureTriggerId;
         camera_metadata_enum_android_scaler_rotate_and_crop_t mRotateAndCropOverride;
+        bool               mCameraMute;
+        bool               mCameraMuteChanged;
 
         int64_t            mRepeatingLastFrameNumber;
 
@@ -1276,6 +1303,10 @@
 
     // Whether HAL supports offline processing capability.
     bool mSupportOfflineProcessing = false;
+
+    // Whether the HAL supports camera muting via test pattern
+    bool mSupportCameraMute = false;
+
 }; // class Camera3Device
 
 }; // namespace android