Merge changes from topic "camera-rotate-and-crop"

* changes:
  CameraService: Implement SCALER_ROTATE_AND_CROP_AUTO, part 1
  Camera NDK: Add android.scaler.rotateAndCrop control
diff --git a/camera/ndk/impl/ACameraMetadata.cpp b/camera/ndk/impl/ACameraMetadata.cpp
index 0ff78ab..18c5d3c 100644
--- a/camera/ndk/impl/ACameraMetadata.cpp
+++ b/camera/ndk/impl/ACameraMetadata.cpp
@@ -528,6 +528,7 @@
         case ACAMERA_LENS_OPTICAL_STABILIZATION_MODE:
         case ACAMERA_NOISE_REDUCTION_MODE:
         case ACAMERA_SCALER_CROP_REGION:
+        case ACAMERA_SCALER_ROTATE_AND_CROP:
         case ACAMERA_SENSOR_EXPOSURE_TIME:
         case ACAMERA_SENSOR_FRAME_DURATION:
         case ACAMERA_SENSOR_SENSITIVITY:
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 8b371db..bd259eb 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -3690,6 +3690,108 @@
     ACAMERA_SCALER_AVAILABLE_RECOMMENDED_INPUT_OUTPUT_FORMATS_MAP = 
                                                                 // int32
             ACAMERA_SCALER_START + 15,
+    /**
+     * <p>List of rotate-and-crop modes for ACAMERA_SCALER_ROTATE_AND_CROP that are supported by this camera device.</p>
+     *
+     * @see ACAMERA_SCALER_ROTATE_AND_CROP
+     *
+     * <p>Type: byte[n]</p>
+     *
+     * <p>This tag may appear in:
+     * <ul>
+     *   <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+     * </ul></p>
+     *
+     * <p>This entry lists the valid modes for ACAMERA_SCALER_ROTATE_AND_CROP for this camera device.</p>
+     * <p>Starting with API level 30, all devices will list at least <code>ROTATE_AND_CROP_NONE</code>.
+     * Devices with support for rotate-and-crop will additionally list at least
+     * <code>ROTATE_AND_CROP_AUTO</code> and <code>ROTATE_AND_CROP_90</code>.</p>
+     *
+     * @see ACAMERA_SCALER_ROTATE_AND_CROP
+     */
+    ACAMERA_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES =            // byte[n]
+            ACAMERA_SCALER_START + 16,
+    /**
+     * <p>Whether a rotation-and-crop operation is applied to processed
+     * outputs from the camera.</p>
+     *
+     * <p>Type: byte (acamera_metadata_enum_android_scaler_rotate_and_crop_t)</p>
+     *
+     * <p>This tag may appear in:
+     * <ul>
+     *   <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
+     *   <li>ACaptureRequest</li>
+     * </ul></p>
+     *
+     * <p>This control is primarily intended to help camera applications with no support for
+     * multi-window modes to work correctly on devices where multi-window scenarios are
+     * unavoidable, such as foldables or other devices with variable display geometry or more
+     * free-form window placement (such as laptops, which often place portrait-orientation apps
+     * in landscape with pillarboxing).</p>
+     * <p>If supported, the default value is <code>ROTATE_AND_CROP_AUTO</code>, which allows the camera API
+     * to enable backwards-compatibility support for applications that do not support resizing
+     * / multi-window modes, when the device is in fact in a multi-window mode (such as inset
+     * portrait on laptops, or on a foldable device in some fold states).  In addition,
+     * <code>ROTATE_AND_CROP_NONE</code> and <code>ROTATE_AND_CROP_90</code> will always be available if this control
+     * is supported by the device.  If not supported, devices API level 30 or higher will always
+     * list only <code>ROTATE_AND_CROP_NONE</code>.</p>
+     * <p>When <code>CROP_AUTO</code> is in use, and the camera API activates backward-compatibility mode,
+     * several metadata fields will also be parsed differently to ensure that coordinates are
+     * correctly handled for features like drawing face detection boxes or passing in
+     * tap-to-focus coordinates.  The camera API will convert positions in the active array
+     * coordinate system to/from the cropped-and-rotated coordinate system to make the
+     * operation transparent for applications.  The following controls are affected:</p>
+     * <ul>
+     * <li>ACAMERA_CONTROL_AE_REGIONS</li>
+     * <li>ACAMERA_CONTROL_AF_REGIONS</li>
+     * <li>ACAMERA_CONTROL_AWB_REGIONS</li>
+     * <li>android.statistics.faces</li>
+     * </ul>
+     * <p>Capture results will contain the actual value selected by the API;
+     * <code>ROTATE_AND_CROP_AUTO</code> will never be seen in a capture result.</p>
+     * <p>Applications can also select their preferred cropping mode, either to opt out of the
+     * backwards-compatibility treatment, or to use the cropping feature themselves as needed.
+     * In this case, no coordinate translation will be done automatically, and all controls
+     * will continue to use the normal active array coordinates.</p>
+     * <p>Cropping and rotating is done after the application of digital zoom (via either
+     * ACAMERA_SCALER_CROP_REGION or ACAMERA_CONTROL_ZOOM_RATIO), but before each individual
+     * output is further cropped and scaled. It only affects processed outputs such as
+     * YUV, PRIVATE, and JPEG.  It has no effect on RAW outputs.</p>
+     * <p>When <code>CROP_90</code> or <code>CROP_270</code> are selected, there is a significant loss to the field of
+     * view. For example, with a 4:3 aspect ratio output of 1600x1200, <code>CROP_90</code> will still
+     * produce 1600x1200 output, but these buffers are cropped from a vertical 3:4 slice at the
+     * center of the 4:3 area, then rotated to be 4:3, and then upscaled to 1600x1200.  Only
+     * 56.25% of the original FOV is still visible.  In general, for an aspect ratio of <code>w:h</code>,
+     * the crop and rotate operation leaves <code>(h/w)^2</code> of the field of view visible. For 16:9,
+     * this is ~31.6%.</p>
+     * <p>As a visual example, the figure below shows the effect of <code>ROTATE_AND_CROP_90</code> on the
+     * outputs for the following parameters:</p>
+     * <ul>
+     * <li>Sensor active array: <code>2000x1500</code></li>
+     * <li>Crop region: top-left: <code>(500, 375)</code>, size: <code>(1000, 750)</code> (4:3 aspect ratio)</li>
+     * <li>Output streams: YUV <code>640x480</code> and YUV <code>1280x720</code></li>
+     * <li><code>ROTATE_AND_CROP_90</code></li>
+     * </ul>
+     * <p><img alt="Effect of ROTATE_AND_CROP_90" src="../images/camera2/metadata/android.scaler.rotateAndCrop/crop-region-rotate-90-43-ratio.png" /></p>
+     * <p>With these settings, the regions of the active array covered by the output streams are:</p>
+     * <ul>
+     * <li>640x480 stream crop: top-left: <code>(219, 375)</code>, size: <code>(562, 750)</code></li>
+     * <li>1280x720 stream crop: top-left: <code>(289, 375)</code>, size: <code>(422, 750)</code></li>
+     * </ul>
+     * <p>Since the buffers are rotated, the buffers as seen by the application are:</p>
+     * <ul>
+     * <li>640x480 stream: top-left: <code>(781, 375)</code> on active array, size: <code>(640, 480)</code>, downscaled 1.17x from sensor pixels</li>
+     * <li>1280x720 stream: top-left: <code>(711, 375)</code> on active array, size: <code>(1280, 720)</code>, upscaled 1.71x from sensor pixels</li>
+     * </ul>
+     *
+     * @see ACAMERA_CONTROL_AE_REGIONS
+     * @see ACAMERA_CONTROL_AF_REGIONS
+     * @see ACAMERA_CONTROL_AWB_REGIONS
+     * @see ACAMERA_CONTROL_ZOOM_RATIO
+     * @see ACAMERA_SCALER_CROP_REGION
+     */
+    ACAMERA_SCALER_ROTATE_AND_CROP =                            // byte (acamera_metadata_enum_android_scaler_rotate_and_crop_t)
+            ACAMERA_SCALER_START + 17,
     ACAMERA_SCALER_END,
 
     /**
@@ -8212,6 +8314,51 @@
 
 } acamera_metadata_enum_android_scaler_available_recommended_stream_configurations_t;
 
+// ACAMERA_SCALER_ROTATE_AND_CROP
+typedef enum acamera_metadata_enum_acamera_scaler_rotate_and_crop {
+    /**
+     * <p>No rotate and crop is applied. Processed outputs are in the sensor orientation.</p>
+     */
+    ACAMERA_SCALER_ROTATE_AND_CROP_NONE                              = 0,
+
+    /**
+     * <p>Processed images are rotated by 90 degrees clockwise, and then cropped
+     * to the original aspect ratio.</p>
+     */
+    ACAMERA_SCALER_ROTATE_AND_CROP_90                                = 1,
+
+    /**
+     * <p>Processed images are rotated by 180 degrees.  Since the aspect ratio does not
+     * change, no cropping is performed.</p>
+     */
+    ACAMERA_SCALER_ROTATE_AND_CROP_180                               = 2,
+
+    /**
+     * <p>Processed images are rotated by 270 degrees clockwise, and then cropped
+     * to the original aspect ratio.</p>
+     */
+    ACAMERA_SCALER_ROTATE_AND_CROP_270                               = 3,
+
+    /**
+     * <p>The camera API automatically selects the best concrete value for
+     * rotate-and-crop based on the application's support for resizability and the current
+     * multi-window mode.</p>
+     * <p>If the application does not support resizing but the display mode for its main
+     * Activity is not in a typical orientation, the camera API will set <code>ROTATE_AND_CROP_90</code>
+     * or some other supported rotation value, depending on device configuration,
+     * to ensure preview and captured images are correctly shown to the user. Otherwise,
+     * <code>ROTATE_AND_CROP_NONE</code> will be selected.</p>
+     * <p>When a value other than NONE is selected, several metadata fields will also be parsed
+     * differently to ensure that coordinates are correctly handled for features like drawing
+     * face detection boxes or passing in tap-to-focus coordinates.  The camera API will
+     * convert positions in the active array coordinate system to/from the cropped-and-rotated
+     * coordinate system to make the operation transparent for applications.</p>
+     * <p>No coordinate mapping will be done when the application selects a non-AUTO mode.</p>
+     */
+    ACAMERA_SCALER_ROTATE_AND_CROP_AUTO                              = 4,
+
+} acamera_metadata_enum_android_scaler_rotate_and_crop_t;
+
 
 // ACAMERA_SENSOR_REFERENCE_ILLUMINANT1
 typedef enum acamera_metadata_enum_acamera_sensor_reference_illuminant1 {
diff --git a/services/camera/libcameraservice/Android.bp b/services/camera/libcameraservice/Android.bp
index 87cb0b5..da44e56 100644
--- a/services/camera/libcameraservice/Android.bp
+++ b/services/camera/libcameraservice/Android.bp
@@ -62,6 +62,7 @@
         "device3/CoordinateMapper.cpp",
         "device3/DistortionMapper.cpp",
         "device3/ZoomRatioMapper.cpp",
+        "device3/RotateAndCropMapper.cpp",
         "device3/Camera3OutputStreamInterface.cpp",
         "device3/Camera3OutputUtils.cpp",
         "gui/RingBufferConsumer.cpp",
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 8666b7b..01d6c8e 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -1731,6 +1731,11 @@
             }
         }
 
+        // Set rotate-and-crop override behavior
+        if (mOverrideRotateAndCropMode != ANDROID_SCALER_ROTATE_AND_CROP_AUTO) {
+            client->setRotateAndCropOverride(mOverrideRotateAndCropMode);
+        }
+
         if (shimUpdateOnly) {
             // If only updating legacy shim parameters, immediately disconnect client
             mServiceLock.unlock();
@@ -3810,6 +3815,10 @@
         return handleResetUidState(args, err);
     } else if (args.size() >= 2 && args[0] == String16("get-uid-state")) {
         return handleGetUidState(args, out, err);
+    } else if (args.size() >= 2 && args[0] == String16("set-rotate-and-crop")) {
+        return handleSetRotateAndCrop(args);
+    } else if (args.size() >= 1 && args[0] == String16("get-rotate-and-crop")) {
+        return handleGetRotateAndCrop(out);
     } else if (args.size() == 1 && args[0] == String16("help")) {
         printHelp(out);
         return NO_ERROR;
@@ -3880,11 +3889,43 @@
     }
 }
 
+status_t CameraService::handleSetRotateAndCrop(const Vector<String16>& args) {
+    int rotateValue = atoi(String8(args[1]));
+    if (rotateValue < ANDROID_SCALER_ROTATE_AND_CROP_NONE ||
+            rotateValue > ANDROID_SCALER_ROTATE_AND_CROP_AUTO) return BAD_VALUE;
+    Mutex::Autolock lock(mServiceLock);
+
+    mOverrideRotateAndCropMode = rotateValue;
+
+    if (rotateValue == ANDROID_SCALER_ROTATE_AND_CROP_AUTO) return OK;
+
+    const auto clients = mActiveClientManager.getAll();
+    for (auto& current : clients) {
+        if (current != nullptr) {
+            const auto basicClient = current->getValue();
+            if (basicClient.get() != nullptr) {
+                basicClient->setRotateAndCropOverride(rotateValue);
+            }
+        }
+    }
+
+    return OK;
+}
+
+status_t CameraService::handleGetRotateAndCrop(int out) {
+    Mutex::Autolock lock(mServiceLock);
+
+    return dprintf(out, "rotateAndCrop override: %d\n", mOverrideRotateAndCropMode);
+}
+
 status_t CameraService::printHelp(int out) {
     return dprintf(out, "Camera service commands:\n"
         "  get-uid-state <PACKAGE> [--user USER_ID] gets the uid state\n"
         "  set-uid-state <PACKAGE> <active|idle> [--user USER_ID] overrides the uid state\n"
         "  reset-uid-state <PACKAGE> [--user USER_ID] clears the uid state override\n"
+        "  set-rotate-and-crop <ROTATION> overrides the rotate-and-crop value for AUTO backcompat\n"
+        "      Valid values 0=0 deg, 1=90 deg, 2=180 deg, 3=270 deg, 4=No override\n"
+        "  get-rotate-and-crop returns the current override rotate-and-crop value\n"
         "  help print this message\n");
 }
 
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index 34b970d..8d73183 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -280,6 +280,10 @@
         virtual int32_t getAudioRestriction() const;
 
         static bool isValidAudioRestriction(int32_t mode);
+
+        // Override rotate-and-crop AUTO behavior
+        virtual status_t setRotateAndCropOverride(uint8_t rotateAndCrop) = 0;
+
     protected:
         BasicClient(const sp<CameraService>& cameraService,
                 const sp<IBinder>& remoteCallback,
@@ -1014,6 +1018,12 @@
     // Gets the UID state
     status_t handleGetUidState(const Vector<String16>& args, int out, int err);
 
+    // Set the rotate-and-crop AUTO override behavior
+    status_t handleSetRotateAndCrop(const Vector<String16>& args);
+
+    // Get the rotate-and-crop AUTO override behavior
+    status_t handleGetRotateAndCrop(int out);
+
     // Prints the shell command help
     status_t printHelp(int out);
 
@@ -1059,6 +1069,9 @@
 
     // Aggreated audio restriction mode for all camera clients
     int32_t mAudioRestriction;
+
+    // Current override rotate-and-crop mode
+    uint8_t mOverrideRotateAndCropMode = ANDROID_SCALER_ROTATE_AND_CROP_AUTO;
 };
 
 } // namespace android
diff --git a/services/camera/libcameraservice/api1/Camera2Client.cpp b/services/camera/libcameraservice/api1/Camera2Client.cpp
index 5dbbc0b..1d62a74 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.cpp
+++ b/services/camera/libcameraservice/api1/Camera2Client.cpp
@@ -2271,6 +2271,13 @@
     return INVALID_OPERATION;
 }
 
+status_t Camera2Client::setRotateAndCropOverride(uint8_t rotateAndCrop) {
+    if (rotateAndCrop > ANDROID_SCALER_ROTATE_AND_CROP_AUTO) return BAD_VALUE;
+
+    return mDevice->setRotateAndCropAutoBehavior(
+        static_cast<camera_metadata_enum_android_scaler_rotate_and_crop_t>(rotateAndCrop));
+}
+
 status_t Camera2Client::waitUntilCurrentRequestIdLocked() {
     int32_t activeRequestId = mStreamingProcessor->getActiveRequestId();
     if (activeRequestId != 0) {
diff --git a/services/camera/libcameraservice/api1/Camera2Client.h b/services/camera/libcameraservice/api1/Camera2Client.h
index 8034ab4..3144e0e 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.h
+++ b/services/camera/libcameraservice/api1/Camera2Client.h
@@ -85,6 +85,7 @@
     virtual status_t        setVideoTarget(const sp<IGraphicBufferProducer>& bufferProducer);
     virtual status_t        setAudioRestriction(int mode);
     virtual int32_t         getGlobalAudioRestriction();
+    virtual status_t        setRotateAndCropOverride(uint8_t rotateAndCrop);
 
     /**
      * Interface used by CameraService
diff --git a/services/camera/libcameraservice/api1/CameraClient.cpp b/services/camera/libcameraservice/api1/CameraClient.cpp
index 83da880..892996c 100644
--- a/services/camera/libcameraservice/api1/CameraClient.cpp
+++ b/services/camera/libcameraservice/api1/CameraClient.cpp
@@ -1200,4 +1200,9 @@
     return BasicClient::getServiceAudioRestriction();
 }
 
+// API1->Device1 does not support this feature
+status_t CameraClient::setRotateAndCropOverride(uint8_t /*rotateAndCrop*/) {
+    return OK;
+}
+
 }; // namespace android
diff --git a/services/camera/libcameraservice/api1/CameraClient.h b/services/camera/libcameraservice/api1/CameraClient.h
index 501ad18..a7eb960 100644
--- a/services/camera/libcameraservice/api1/CameraClient.h
+++ b/services/camera/libcameraservice/api1/CameraClient.h
@@ -62,6 +62,8 @@
     virtual status_t        setAudioRestriction(int mode);
     virtual int32_t         getGlobalAudioRestriction();
 
+    virtual status_t        setRotateAndCropOverride(uint8_t override);
+
     // Interface used by CameraService
     CameraClient(const sp<CameraService>& cameraService,
             const sp<hardware::ICameraClient>& cameraClient,
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index 9deb662..8ed3ab6 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -1934,6 +1934,13 @@
     return binder::Status::ok();
 }
 
+status_t CameraDeviceClient::setRotateAndCropOverride(uint8_t rotateAndCrop) {
+    if (rotateAndCrop > ANDROID_SCALER_ROTATE_AND_CROP_AUTO) return BAD_VALUE;
+
+    return mDevice->setRotateAndCropAutoBehavior(
+        static_cast<camera_metadata_enum_android_scaler_rotate_and_crop_t>(rotateAndCrop));
+}
+
 binder::Status CameraDeviceClient::switchToOffline(
         const sp<hardware::camera2::ICameraDeviceCallbacks>& cameraCb,
         const std::vector<int>& offlineOutputIds,
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.h b/services/camera/libcameraservice/api2/CameraDeviceClient.h
index a3a3189..7d38f9f 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.h
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.h
@@ -184,6 +184,8 @@
     virtual status_t      initialize(sp<CameraProviderManager> manager,
             const String8& monitorTags) override;
 
+    virtual status_t      setRotateAndCropOverride(uint8_t rotateAndCrop) override;
+
     virtual status_t      dump(int fd, const Vector<String16>& args);
 
     virtual status_t      dumpClient(int fd, const Vector<String16>& args);
diff --git a/services/camera/libcameraservice/api2/CameraOfflineSessionClient.cpp b/services/camera/libcameraservice/api2/CameraOfflineSessionClient.cpp
index af7b9e1..fc3f137 100644
--- a/services/camera/libcameraservice/api2/CameraOfflineSessionClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraOfflineSessionClient.cpp
@@ -52,6 +52,12 @@
     return OK;
 }
 
+status_t CameraOfflineSessionClient::setRotateAndCropOverride(uint8_t /*rotateAndCrop*/) {
+    // Since we're not submitting more capture requests, changes to rotateAndCrop override
+    // make no difference.
+    return OK;
+}
+
 status_t CameraOfflineSessionClient::dump(int fd, const Vector<String16>& args) {
     return BasicClient::dump(fd, args);
 }
diff --git a/services/camera/libcameraservice/api2/CameraOfflineSessionClient.h b/services/camera/libcameraservice/api2/CameraOfflineSessionClient.h
index b0f000d..6792039 100644
--- a/services/camera/libcameraservice/api2/CameraOfflineSessionClient.h
+++ b/services/camera/libcameraservice/api2/CameraOfflineSessionClient.h
@@ -74,6 +74,8 @@
     status_t initialize(sp<CameraProviderManager> /*manager*/,
             const String8& /*monitorTags*/) override;
 
+    status_t setRotateAndCropOverride(uint8_t rotateAndCrop) override;
+
     // permissions management
     status_t startCameraOps() override;
     status_t finishCameraOps() override;
diff --git a/services/camera/libcameraservice/common/CameraDeviceBase.h b/services/camera/libcameraservice/common/CameraDeviceBase.h
index 2f01198..2aa1207 100644
--- a/services/camera/libcameraservice/common/CameraDeviceBase.h
+++ b/services/camera/libcameraservice/common/CameraDeviceBase.h
@@ -375,6 +375,16 @@
     virtual status_t switchToOffline(
             const std::vector<int32_t>& streamsToKeep,
             /*out*/ sp<CameraOfflineSessionBase>* session) = 0;
+
+    /**
+     * Set the current behavior for the ROTATE_AND_CROP control when in AUTO.
+     *
+     * The value must be one of the ROTATE_AND_CROP_* values besides AUTO,
+     * and defaults to NONE.
+     */
+    virtual status_t setRotateAndCropAutoBehavior(
+            camera_metadata_enum_android_scaler_rotate_and_crop_t rotateAndCropValue) = 0;
+
 };
 
 }; // namespace android
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.cpp b/services/camera/libcameraservice/common/CameraProviderManager.cpp
index 57f812f..1401ba7 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.cpp
+++ b/services/camera/libcameraservice/common/CameraProviderManager.cpp
@@ -926,6 +926,19 @@
     return res;
 }
 
+status_t CameraProviderManager::ProviderInfo::DeviceInfo3::addRotateCropTags() {
+    status_t res = OK;
+    auto& c = mCameraCharacteristics;
+
+    auto availableRotateCropEntry = c.find(ANDROID_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES);
+    if (availableRotateCropEntry.count == 0) {
+        uint8_t defaultAvailableRotateCropEntry = ANDROID_SCALER_ROTATE_AND_CROP_NONE;
+        res = c.update(ANDROID_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES,
+                &defaultAvailableRotateCropEntry, 1);
+    }
+    return res;
+}
+
 status_t CameraProviderManager::ProviderInfo::DeviceInfo3::removeAvailableKeys(
         CameraMetadata& c, const std::vector<uint32_t>& keys, uint32_t keyTag) {
     status_t res = OK;
@@ -2072,6 +2085,11 @@
         ALOGE("%s: Unable to derive HEIC tags based on camera and media capabilities: %s (%d)",
                 __FUNCTION__, strerror(-res), res);
     }
+    res = addRotateCropTags();
+    if (OK != res) {
+        ALOGE("%s: Unable to add default SCALER_ROTATE_AND_CROP tags: %s (%d)", __FUNCTION__,
+                strerror(-res), res);
+    }
 
     res = camera3::ZoomRatioMapper::overrideZoomRatioTags(
             &mCameraCharacteristics, &mSupportNativeZoomRatio);
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.h b/services/camera/libcameraservice/common/CameraProviderManager.h
index 3eba162..49a93cc 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.h
+++ b/services/camera/libcameraservice/common/CameraProviderManager.h
@@ -545,6 +545,9 @@
             SystemCameraKind getSystemCameraKind();
             status_t fixupMonochromeTags();
             status_t addDynamicDepthTags();
+            status_t deriveHeicTags();
+            status_t addRotateCropTags();
+
             static void getSupportedSizes(const CameraMetadata& ch, uint32_t tag,
                     android_pixel_format_t format,
                     std::vector<std::tuple<size_t, size_t>> *sizes /*out*/);
@@ -567,7 +570,6 @@
                     std::vector<int64_t>* stallDurations,
                     const camera_metadata_entry& halStreamConfigs,
                     const camera_metadata_entry& halStreamDurations);
-            status_t deriveHeicTags();
         };
 
     private:
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 23e26ce..d38bfee 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -351,6 +351,10 @@
     mZoomRatioMappers[mId.c_str()] = ZoomRatioMapper(&mDeviceInfo,
             mSupportNativeZoomRatio, usePrecorrectArray);
 
+    if (RotateAndCropMapper::isNeeded(&mDeviceInfo)) {
+        mRotateAndCropMappers.emplace(mId.c_str(), &mDeviceInfo);
+    }
+
     return OK;
 }
 
@@ -881,17 +885,12 @@
 
         // Setup burst Id and request Id
         newRequest->mResultExtras.burstId = burstId++;
-        if (metadataIt->begin()->metadata.exists(ANDROID_REQUEST_ID)) {
-            if (metadataIt->begin()->metadata.find(ANDROID_REQUEST_ID).count == 0) {
-                CLOGE("RequestID entry exists; but must not be empty in metadata");
-                return BAD_VALUE;
-            }
-            newRequest->mResultExtras.requestId = metadataIt->begin()->metadata.find(
-                    ANDROID_REQUEST_ID).data.i32[0];
-        } else {
+        auto requestIdEntry = metadataIt->begin()->metadata.find(ANDROID_REQUEST_ID);
+        if (requestIdEntry.count == 0) {
             CLOGE("RequestID does not exist in metadata");
             return BAD_VALUE;
         }
+        newRequest->mResultExtras.requestId = requestIdEntry.data.i32[0];
 
         requestList->push_back(newRequest);
 
@@ -1049,8 +1048,8 @@
         mNextReprocessResultFrameNumber, mNextZslStillResultFrameNumber,
         mUseHalBufManager, mUsePartialResult, mNeedFixupMonochromeTags,
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
-        mResultMetadataQueue, mDistortionMappers, mZoomRatioMappers, mTagMonitor,
-        mInputStream, mOutputStreams, listener, *this, *this, *mInterface
+        mResultMetadataQueue, mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
+        mTagMonitor, mInputStream, mOutputStreams, listener, *this, *this, *mInterface
     };
 
     for (const auto& result : results) {
@@ -1106,8 +1105,8 @@
         mNextReprocessResultFrameNumber, mNextZslStillResultFrameNumber,
         mUseHalBufManager, mUsePartialResult, mNeedFixupMonochromeTags,
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
-        mResultMetadataQueue, mDistortionMappers, mZoomRatioMappers, mTagMonitor,
-        mInputStream, mOutputStreams, listener, *this, *this, *mInterface
+        mResultMetadataQueue, mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
+        mTagMonitor, mInputStream, mOutputStreams, listener, *this, *this, *mInterface
     };
 
     for (const auto& result : results) {
@@ -1145,8 +1144,8 @@
         mNextReprocessResultFrameNumber, mNextZslStillResultFrameNumber,
         mUseHalBufManager, mUsePartialResult, mNeedFixupMonochromeTags,
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
-        mResultMetadataQueue, mDistortionMappers, mZoomRatioMappers, mTagMonitor,
-        mInputStream, mOutputStreams, listener, *this, *this, *mInterface
+        mResultMetadataQueue, mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
+        mTagMonitor, mInputStream, mOutputStreams, listener, *this, *this, *mInterface
     };
     for (const auto& msg : msgs) {
         camera3::notify(states, msg);
@@ -2222,7 +2221,7 @@
         const PhysicalCameraSettingsList &request, const SurfaceMap &surfaceMap) {
     ATRACE_CALL();
 
-    sp<CaptureRequest> newRequest = new CaptureRequest;
+    sp<CaptureRequest> newRequest = new CaptureRequest();
     newRequest->mSettingsList = request;
 
     camera_metadata_entry_t inputStreams =
@@ -2293,6 +2292,15 @@
     newRequest->mSettingsList.begin()->metadata.erase(ANDROID_REQUEST_OUTPUT_STREAMS);
     newRequest->mBatchSize = 1;
 
+    auto rotateAndCropEntry =
+            newRequest->mSettingsList.begin()->metadata.find(ANDROID_SCALER_ROTATE_AND_CROP);
+    if (rotateAndCropEntry.count > 0 &&
+            rotateAndCropEntry.data.u8[0] == ANDROID_SCALER_ROTATE_AND_CROP_AUTO) {
+        newRequest->mRotateAndCropAuto = true;
+    } else {
+        newRequest->mRotateAndCropAuto = false;
+    }
+
     return newRequest;
 }
 
@@ -2730,7 +2738,7 @@
         int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
         bool hasAppCallback, nsecs_t maxExpectedDuration,
         std::set<String8>& physicalCameraIds, bool isStillCapture,
-        bool isZslCapture, const std::set<std::string>& cameraIdsWithZoom,
+        bool isZslCapture, bool rotateAndCropAuto, const std::set<std::string>& cameraIdsWithZoom,
         const SurfaceMap& outputSurfaces) {
     ATRACE_CALL();
     std::lock_guard<std::mutex> l(mInFlightLock);
@@ -2738,7 +2746,7 @@
     ssize_t res;
     res = mInFlightMap.add(frameNumber, InFlightRequest(numBuffers, resultExtras, hasInput,
             hasAppCallback, maxExpectedDuration, physicalCameraIds, isStillCapture, isZslCapture,
-            cameraIdsWithZoom, outputSurfaces));
+            rotateAndCropAuto, cameraIdsWithZoom, outputSurfaces));
     if (res < 0) return res;
 
     if (mInFlightMap.size() == 1) {
@@ -3725,6 +3733,7 @@
         mLatestRequestId(NAME_NOT_FOUND),
         mCurrentAfTriggerId(0),
         mCurrentPreCaptureTriggerId(0),
+        mRotateAndCropOverride(ANDROID_SCALER_ROTATE_AND_CROP_NONE),
         mRepeatingLastFrameNumber(
             hardware::camera2::ICameraDeviceUser::NO_IN_FLIGHT_REPEATING_FRAMES),
         mPrepareVideoStream(false),
@@ -4358,8 +4367,11 @@
         bool triggersMixedIn = (triggerCount > 0 || mPrevTriggers > 0);
         mPrevTriggers = triggerCount;
 
+        bool rotateAndCropChanged = overrideAutoRotateAndCrop(captureRequest);
+
         // If the request is the same as last, or we had triggers last time
-        bool newRequest = (mPrevRequest != captureRequest || triggersMixedIn) &&
+        bool newRequest =
+                (mPrevRequest != captureRequest || triggersMixedIn || rotateAndCropChanged) &&
                 // Request settings are all the same within one batch, so only treat the first
                 // request in a batch as new
                 !(batchedRequest && i > 0);
@@ -4419,6 +4431,21 @@
                             return INVALID_OPERATION;
                         }
                     }
+                    if (captureRequest->mRotateAndCropAuto) {
+                        for (it = captureRequest->mSettingsList.begin();
+                                it != captureRequest->mSettingsList.end(); it++) {
+                            auto mapper = parent->mRotateAndCropMappers.find(it->cameraId);
+                            if (mapper != parent->mRotateAndCropMappers.end()) {
+                                res = mapper->second.updateCaptureRequest(&(it->metadata));
+                                if (res != OK) {
+                                    SET_ERR("RequestThread: Unable to correct capture requests "
+                                            "for rotate-and-crop for request %d: %s (%d)",
+                                            halRequest->frame_number, strerror(-res), res);
+                                    return INVALID_OPERATION;
+                                }
+                            }
+                        }
+                    }
                 }
             }
 
@@ -4617,7 +4644,8 @@
                 /*hasInput*/halRequest->input_buffer != NULL,
                 hasCallback,
                 calculateMaxExpectedDuration(halRequest->settings),
-                requestedPhysicalCameras, isStillCapture, isZslCapture, mPrevCameraIdsWithZoom,
+                requestedPhysicalCameras, isStillCapture, isZslCapture,
+                captureRequest->mRotateAndCropAuto, mPrevCameraIdsWithZoom,
                 (mUseHalBufManager) ? uniqueSurfaceIdMap :
                                       SurfaceMap{});
         ALOGVV("%s: registered in flight requestId = %" PRId32 ", frameNumber = %" PRId64
@@ -4763,6 +4791,17 @@
             streamsToKeep, offlineSessionInfo, offlineSession, bufferRecords);
 }
 
+status_t Camera3Device::RequestThread::setRotateAndCropAutoBehavior(
+        camera_metadata_enum_android_scaler_rotate_and_crop_t rotateAndCropValue) {
+    ATRACE_CALL();
+    Mutex::Autolock l(mTriggerMutex);
+    if (rotateAndCropValue == ANDROID_SCALER_ROTATE_AND_CROP_AUTO) {
+        return BAD_VALUE;
+    }
+    mRotateAndCropOverride = rotateAndCropValue;
+    return OK;
+}
+
 nsecs_t Camera3Device::getExpectedInFlightDuration() {
     ATRACE_CALL();
     std::lock_guard<std::mutex> l(mInFlightLock);
@@ -5278,6 +5317,32 @@
     return OK;
 }
 
+bool Camera3Device::RequestThread::overrideAutoRotateAndCrop(
+        const sp<CaptureRequest> &request) {
+    ATRACE_CALL();
+
+    if (request->mRotateAndCropAuto) {
+        Mutex::Autolock l(mTriggerMutex);
+        CameraMetadata &metadata = request->mSettingsList.begin()->metadata;
+
+        auto rotateAndCropEntry = metadata.find(ANDROID_SCALER_ROTATE_AND_CROP);
+        if (rotateAndCropEntry.count > 0) {
+            if (rotateAndCropEntry.data.u8[0] == mRotateAndCropOverride) {
+                return false;
+            } else {
+                rotateAndCropEntry.data.u8[0] = mRotateAndCropOverride;
+                return true;
+            }
+        } else {
+            uint8_t rotateAndCrop_u8 = mRotateAndCropOverride;
+            metadata.update(ANDROID_SCALER_ROTATE_AND_CROP,
+                    &rotateAndCrop_u8, 1);
+            return true;
+        }
+    }
+    return false;
+}
+
 /**
  * PreparerThread inner class methods
  */
@@ -5813,7 +5878,7 @@
             mNextReprocessResultFrameNumber, mNextZslStillResultFrameNumber,
             mNextShutterFrameNumber, mNextReprocessShutterFrameNumber,
             mNextZslStillShutterFrameNumber, mDeviceInfo, mPhysicalDeviceInfoMap,
-            mDistortionMappers, mZoomRatioMappers);
+            mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers);
 
     *session = new Camera3OfflineSession(mId, inputStream, offlineStreamSet,
             std::move(bufferRecords), offlineReqs, offlineStates, offlineSession);
@@ -5889,4 +5954,15 @@
     }
 }
 
+status_t Camera3Device::setRotateAndCropAutoBehavior(
+    camera_metadata_enum_android_scaler_rotate_and_crop_t rotateAndCropValue) {
+    ATRACE_CALL();
+    Mutex::Autolock il(mInterfaceLock);
+    Mutex::Autolock l(mLock);
+    if (mRequestThread == nullptr) {
+        return INVALID_OPERATION;
+    }
+    return mRequestThread->setRotateAndCropAutoBehavior(rotateAndCropValue);
+}
+
 }; // namespace android
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index 7279baf..0764320 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -48,6 +48,7 @@
 #include "device3/Camera3BufferManager.h"
 #include "device3/DistortionMapper.h"
 #include "device3/ZoomRatioMapper.h"
+#include "device3/RotateAndCropMapper.h"
 #include "device3/InFlightRequest.h"
 #include "device3/Camera3OutputInterface.h"
 #include "device3/Camera3OfflineSession.h"
@@ -222,6 +223,15 @@
     std::vector<sp<camera3::Camera3StreamInterface>> getAllStreams() override;
 
     /**
+     * Set the current behavior for the ROTATE_AND_CROP control when in AUTO.
+     *
+     * The value must be one of the ROTATE_AND_CROP_* values besides AUTO,
+     * and defaults to NONE.
+     */
+    status_t setRotateAndCropAutoBehavior(
+            camera_metadata_enum_android_scaler_rotate_and_crop_t rotateAndCropValue);
+
+    /**
      * Helper functions to map between framework and HIDL values
      */
     static hardware::graphics::common::V1_0::PixelFormat mapToPixelFormat(int frameworkFormat);
@@ -501,6 +511,10 @@
         int                                 mBatchSize;
         //  Whether this request is from a repeating or repeating burst.
         bool                                mRepeating;
+        // Whether this request has ROTATE_AND_CROP_AUTO set, so needs both
+        // overriding of ROTATE_AND_CROP value and adjustment of coordinates
+        // in several other controls in both the request and the result
+        bool                                mRotateAndCropAuto;
     };
     typedef List<sp<CaptureRequest> > RequestList;
 
@@ -824,6 +838,9 @@
                 /*out*/sp<hardware::camera::device::V3_6::ICameraOfflineSession>* offlineSession,
                 /*out*/camera3::BufferRecords* bufferRecords);
 
+        status_t setRotateAndCropAutoBehavior(
+                camera_metadata_enum_android_scaler_rotate_and_crop_t rotateAndCropValue);
+
       protected:
 
         virtual bool threadLoop();
@@ -840,7 +857,10 @@
 
         // HAL workaround: Make sure a trigger ID always exists if
         // a trigger does
-        status_t          addDummyTriggerIds(const sp<CaptureRequest> &request);
+        status_t           addDummyTriggerIds(const sp<CaptureRequest> &request);
+
+        // Override rotate_and_crop control if needed; returns true if the current value was changed
+        bool               overrideAutoRotateAndCrop(const sp<CaptureRequest> &request);
 
         static const nsecs_t kRequestTimeout = 50e6; // 50 ms
 
@@ -962,6 +982,7 @@
         TriggerMap         mTriggerReplacedMap;
         uint32_t           mCurrentAfTriggerId;
         uint32_t           mCurrentPreCaptureTriggerId;
+        camera_metadata_enum_android_scaler_rotate_and_crop_t mRotateAndCropOverride;
 
         int64_t            mRepeatingLastFrameNumber;
 
@@ -993,8 +1014,8 @@
     status_t registerInFlight(uint32_t frameNumber,
             int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
             bool callback, nsecs_t maxExpectedDuration, std::set<String8>& physicalCameraIds,
-            bool isStillCapture, bool isZslCapture, const std::set<std::string>& cameraIdsWithZoom,
-            const SurfaceMap& outputSurfaces);
+            bool isStillCapture, bool isZslCapture, bool rotateAndCropAuto,
+            const std::set<std::string>& cameraIdsWithZoom, const SurfaceMap& outputSurfaces);
 
     /**
      * Tracking for idle detection
@@ -1113,6 +1134,11 @@
      */
     std::unordered_map<std::string, camera3::ZoomRatioMapper> mZoomRatioMappers;
 
+    /**
+     * RotateAndCrop mapper support
+     */
+    std::unordered_map<std::string, camera3::RotateAndCropMapper> mRotateAndCropMappers;
+
     // Debug tracker for metadata tag value changes
     // - Enabled with the -m <taglist> option to dumpsys, such as
     //   dumpsys -m android.control.aeState,android.control.aeMode
diff --git a/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp b/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp
index 0f05632..8150de3 100644
--- a/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp
@@ -71,6 +71,7 @@
         mPhysicalDeviceInfoMap(offlineStates.mPhysicalDeviceInfoMap),
         mDistortionMappers(offlineStates.mDistortionMappers),
         mZoomRatioMappers(offlineStates.mZoomRatioMappers),
+        mRotateAndCropMappers(offlineStates.mRotateAndCropMappers),
         mStatus(STATUS_UNINITIALIZED) {
     ATRACE_CALL();
     ALOGV("%s: Created offline session for camera %s", __FUNCTION__, mId.string());
@@ -252,8 +253,8 @@
         mNextReprocessResultFrameNumber, mNextZslStillResultFrameNumber,
         mUseHalBufManager, mUsePartialResult, mNeedFixupMonochromeTags,
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
-        mResultMetadataQueue, mDistortionMappers, mZoomRatioMappers, mTagMonitor,
-        mInputStream, mOutputStreams, listener, *this, *this, mBufferRecords
+        mResultMetadataQueue, mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
+        mTagMonitor, mInputStream, mOutputStreams, listener, *this, *this, mBufferRecords
     };
 
     std::lock_guard<std::mutex> lock(mProcessCaptureResultLock);
@@ -290,8 +291,8 @@
         mNextReprocessResultFrameNumber, mNextZslStillResultFrameNumber,
         mUseHalBufManager, mUsePartialResult, mNeedFixupMonochromeTags,
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
-        mResultMetadataQueue, mDistortionMappers, mZoomRatioMappers, mTagMonitor,
-        mInputStream, mOutputStreams, listener, *this, *this, mBufferRecords
+        mResultMetadataQueue, mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
+        mTagMonitor, mInputStream, mOutputStreams, listener, *this, *this, mBufferRecords
     };
 
     std::lock_guard<std::mutex> lock(mProcessCaptureResultLock);
@@ -323,8 +324,8 @@
         mNextReprocessResultFrameNumber, mNextZslStillResultFrameNumber,
         mUseHalBufManager, mUsePartialResult, mNeedFixupMonochromeTags,
         mNumPartialResults, mVendorTagId, mDeviceInfo, mPhysicalDeviceInfoMap,
-        mResultMetadataQueue, mDistortionMappers, mZoomRatioMappers, mTagMonitor,
-        mInputStream, mOutputStreams, listener, *this, *this, mBufferRecords
+        mResultMetadataQueue, mDistortionMappers, mZoomRatioMappers, mRotateAndCropMappers,
+        mTagMonitor, mInputStream, mOutputStreams, listener, *this, *this, mBufferRecords
     };
     for (const auto& msg : msgs) {
         camera3::notify(states, msg);
diff --git a/services/camera/libcameraservice/device3/Camera3OfflineSession.h b/services/camera/libcameraservice/device3/Camera3OfflineSession.h
index b6b8a7d..969220f 100644
--- a/services/camera/libcameraservice/device3/Camera3OfflineSession.h
+++ b/services/camera/libcameraservice/device3/Camera3OfflineSession.h
@@ -33,6 +33,7 @@
 #include "device3/DistortionMapper.h"
 #include "device3/InFlightRequest.h"
 #include "device3/Camera3OutputUtils.h"
+#include "device3/RotateAndCropMapper.h"
 #include "device3/ZoomRatioMapper.h"
 #include "utils/TagMonitor.h"
 #include "utils/LatencyHistogram.h"
@@ -62,7 +63,9 @@
             const CameraMetadata& deviceInfo,
             const std::unordered_map<std::string, CameraMetadata>& physicalDeviceInfoMap,
             const std::unordered_map<std::string, camera3::DistortionMapper>& distortionMappers,
-            const std::unordered_map<std::string, camera3::ZoomRatioMapper>& zoomRatioMappers) :
+            const std::unordered_map<std::string, camera3::ZoomRatioMapper>& zoomRatioMappers,
+            const std::unordered_map<std::string, camera3::RotateAndCropMapper>&
+                rotateAndCropMappers) :
             mTagMonitor(tagMonitor), mVendorTagId(vendorTagId),
             mUseHalBufManager(useHalBufManager), mNeedFixupMonochromeTags(needFixupMonochromeTags),
             mUsePartialResult(usePartialResult), mNumPartialResults(numPartialResults),
@@ -75,7 +78,8 @@
             mDeviceInfo(deviceInfo),
             mPhysicalDeviceInfoMap(physicalDeviceInfoMap),
             mDistortionMappers(distortionMappers),
-            mZoomRatioMappers(zoomRatioMappers) {}
+            mZoomRatioMappers(zoomRatioMappers),
+            mRotateAndCropMappers(rotateAndCropMappers) {}
 
     const TagMonitor& mTagMonitor;
     const metadata_vendor_id_t mVendorTagId;
@@ -106,6 +110,8 @@
     const std::unordered_map<std::string, camera3::DistortionMapper>& mDistortionMappers;
 
     const std::unordered_map<std::string, camera3::ZoomRatioMapper>& mZoomRatioMappers;
+
+    const std::unordered_map<std::string, camera3::RotateAndCropMapper>& mRotateAndCropMappers;
 };
 
 /**
@@ -228,6 +234,8 @@
 
     std::unordered_map<std::string, camera3::ZoomRatioMapper> mZoomRatioMappers;
 
+    std::unordered_map<std::string, camera3::RotateAndCropMapper> mRotateAndCropMappers;
+
     mutable std::mutex mLock;
 
     enum Status {
diff --git a/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp b/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
index eb7b666..39b7db4 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OutputUtils.cpp
@@ -191,7 +191,7 @@
         CaptureResultExtras &resultExtras,
         CameraMetadata &collectedPartialResult,
         uint32_t frameNumber,
-        bool reprocess, bool zslStillCapture,
+        bool reprocess, bool zslStillCapture, bool rotateAndCropAuto,
         const std::set<std::string>& cameraIdsWithZoom,
         const std::vector<PhysicalCaptureResultInfo>& physicalMetadatas) {
     ATRACE_CALL();
@@ -277,10 +277,25 @@
         return;
     }
 
+    // Fix up result metadata to account for rotateAndCrop in AUTO mode
+    if (rotateAndCropAuto) {
+        auto mapper = states.rotateAndCropMappers.find(states.cameraId.c_str());
+        if (mapper != states.rotateAndCropMappers.end()) {
+            res = mapper->second.updateCaptureResult(
+                    &captureResult.mMetadata);
+            if (res != OK) {
+                SET_ERR("Unable to correct capture result rotate-and-crop for frame %d: %s (%d)",
+                        frameNumber, strerror(-res), res);
+                return;
+            }
+        }
+    }
+
     for (auto& physicalMetadata : captureResult.mPhysicalMetadatas) {
         String8 cameraId8(physicalMetadata.mPhysicalCameraId);
-        if (states.distortionMappers.find(cameraId8.c_str()) != states.distortionMappers.end()) {
-            res = states.distortionMappers[cameraId8.c_str()].correctCaptureResult(
+        auto mapper = states.distortionMappers.find(cameraId8.c_str());
+        if (mapper != states.distortionMappers.end()) {
+            res = mapper->second.correctCaptureResult(
                     &physicalMetadata.mPhysicalCameraMetadata);
             if (res != OK) {
                 SET_ERR("Unable to correct physical capture result metadata for frame %d: %s (%d)",
@@ -592,7 +607,8 @@
                 sendCaptureResult(states, metadata, request.resultExtras,
                     collectedPartialResult, frameNumber,
                     hasInputBufferInRequest, request.zslCapture && request.stillCapture,
-                    request.cameraIdsWithZoom, request.physicalMetadatas);
+                    request.rotateAndCropAuto, request.cameraIdsWithZoom,
+                    request.physicalMetadatas);
             }
         }
         removeInFlightRequestIfReadyLocked(states, idx);
@@ -886,7 +902,7 @@
                     r.pendingMetadata, r.resultExtras,
                     r.collectedPartialResult, msg.frame_number,
                     r.hasInputBuffer, r.zslCapture && r.stillCapture,
-                    r.cameraIdsWithZoom, r.physicalMetadatas);
+                    r.rotateAndCropAuto, r.cameraIdsWithZoom, r.physicalMetadatas);
             }
             bool timestampIncreasing = !(r.zslCapture || r.hasInputBuffer);
             returnOutputBuffers(
diff --git a/services/camera/libcameraservice/device3/Camera3OutputUtils.h b/services/camera/libcameraservice/device3/Camera3OutputUtils.h
index 47d8095..300df5b 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputUtils.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputUtils.h
@@ -29,6 +29,7 @@
 #include "device3/BufferUtils.h"
 #include "device3/DistortionMapper.h"
 #include "device3/ZoomRatioMapper.h"
+#include "device3/RotateAndCropMapper.h"
 #include "device3/InFlightRequest.h"
 #include "device3/Camera3Stream.h"
 #include "device3/Camera3OutputStreamInterface.h"
@@ -79,6 +80,7 @@
         std::unique_ptr<ResultMetadataQueue>& fmq;
         std::unordered_map<std::string, camera3::DistortionMapper>& distortionMappers;
         std::unordered_map<std::string, camera3::ZoomRatioMapper>& zoomRatioMappers;
+        std::unordered_map<std::string, camera3::RotateAndCropMapper>& rotateAndCropMappers;
         TagMonitor& tagMonitor;
         sp<Camera3Stream> inputStream;
         StreamSet& outputStreams;
diff --git a/services/camera/libcameraservice/device3/CoordinateMapper.cpp b/services/camera/libcameraservice/device3/CoordinateMapper.cpp
index d62f397..a0bbe54 100644
--- a/services/camera/libcameraservice/device3/CoordinateMapper.cpp
+++ b/services/camera/libcameraservice/device3/CoordinateMapper.cpp
@@ -24,6 +24,7 @@
 
 /**
  * Metadata keys to correct when adjusting coordinates for distortion correction
+ * or for crop and rotate
  */
 
 // Both capture request and result
@@ -33,7 +34,7 @@
     ANDROID_CONTROL_AWB_REGIONS
 };
 
-// Both capture request and result
+// Both capture request and result, not applicable to crop and rotate
 constexpr std::array<uint32_t, 1> CoordinateMapper::kRectsToCorrect = {
     ANDROID_SCALER_CROP_REGION,
 };
diff --git a/services/camera/libcameraservice/device3/InFlightRequest.h b/services/camera/libcameraservice/device3/InFlightRequest.h
index ceeaa71..424043b 100644
--- a/services/camera/libcameraservice/device3/InFlightRequest.h
+++ b/services/camera/libcameraservice/device3/InFlightRequest.h
@@ -91,6 +91,9 @@
     // Indicates a ZSL capture request
     bool zslCapture;
 
+    // Indicates that ROTATE_AND_CROP was set to AUTO
+    bool rotateAndCropAuto;
+
     // Requested camera ids (both logical and physical) with zoomRatio != 1.0f
     std::set<std::string> cameraIdsWithZoom;
 
@@ -112,13 +115,14 @@
             maxExpectedDuration(kDefaultExpectedDuration),
             skipResultMetadata(false),
             stillCapture(false),
-            zslCapture(false) {
+            zslCapture(false),
+            rotateAndCropAuto(false) {
     }
 
     InFlightRequest(int numBuffers, CaptureResultExtras extras, bool hasInput,
             bool hasAppCallback, nsecs_t maxDuration,
             const std::set<String8>& physicalCameraIdSet, bool isStillCapture,
-            bool isZslCapture, const std::set<std::string>& idsWithZoom,
+            bool isZslCapture, bool rotateAndCropAuto, const std::set<std::string>& idsWithZoom,
             const SurfaceMap& outSurfaces = SurfaceMap{}) :
             shutterTimestamp(0),
             sensorTimestamp(0),
@@ -133,6 +137,7 @@
             physicalCameraIds(physicalCameraIdSet),
             stillCapture(isStillCapture),
             zslCapture(isZslCapture),
+            rotateAndCropAuto(rotateAndCropAuto),
             cameraIdsWithZoom(idsWithZoom),
             outputSurfaces(outSurfaces) {
     }
diff --git a/services/camera/libcameraservice/device3/RotateAndCropMapper.cpp b/services/camera/libcameraservice/device3/RotateAndCropMapper.cpp
new file mode 100644
index 0000000..3718f54
--- /dev/null
+++ b/services/camera/libcameraservice/device3/RotateAndCropMapper.cpp
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2020 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 "Camera3-RotCropMapper"
+#define ATRACE_TAG ATRACE_TAG_CAMERA
+//#define LOG_NDEBUG 0
+
+#include <algorithm>
+#include <cmath>
+
+#include "device3/RotateAndCropMapper.h"
+
+namespace android {
+
+namespace camera3 {
+
+bool RotateAndCropMapper::isNeeded(const CameraMetadata* deviceInfo) {
+    auto entry = deviceInfo->find(ANDROID_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES);
+    for (size_t i = 0; i < entry.count; i++) {
+        if (entry.data.u8[i] == ANDROID_SCALER_ROTATE_AND_CROP_AUTO) return true;
+    }
+    return false;
+}
+
+RotateAndCropMapper::RotateAndCropMapper(const CameraMetadata* deviceInfo) {
+    auto entry = deviceInfo->find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+    if (entry.count != 4) return;
+
+    mArrayWidth = entry.data.i32[2];
+    mArrayHeight = entry.data.i32[3];
+    mArrayAspect = static_cast<float>(mArrayWidth) / mArrayHeight;
+    mRotateAspect = 1.f/mArrayAspect;
+}
+
+/**
+ * Adjust capture request when rotate and crop AUTO is enabled
+ */
+status_t RotateAndCropMapper::updateCaptureRequest(CameraMetadata *request) {
+    auto entry = request->find(ANDROID_SCALER_ROTATE_AND_CROP);
+    if (entry.count == 0) return OK;
+    uint8_t rotateMode = entry.data.u8[0];
+    if (rotateMode == ANDROID_SCALER_ROTATE_AND_CROP_NONE) return OK;
+
+    int32_t cx = 0;
+    int32_t cy = 0;
+    int32_t cw = mArrayWidth;
+    int32_t ch = mArrayHeight;
+    entry = request->find(ANDROID_SCALER_CROP_REGION);
+    if (entry.count == 4) {
+        cx = entry.data.i32[0];
+        cy = entry.data.i32[1];
+        cw = entry.data.i32[2];
+        ch = entry.data.i32[3];
+    }
+
+    // User inputs are relative to the rotated-and-cropped view, so convert back
+    // to active array coordinates. To be more specific, the application is
+    // calculating coordinates based on the crop rectangle and the active array,
+    // even though the view the user sees is the cropped-and-rotated one. So we
+    // need to adjust the coordinates so that a point that would be on the
+    // top-left corner of the crop region is mapped to the top-left corner of
+    // the rotated-and-cropped fov within the crop region, and the same for the
+    // bottom-right corner.
+    //
+    // Since the zoom ratio control scales everything uniformly (so an app does
+    // not need to adjust anything if it wants to put a metering region on the
+    // top-left quadrant of the preview FOV, when changing zoomRatio), it does
+    // not need to be factored into this calculation at all.
+    //
+    //   ->+x                       active array  aw
+    //  |+--------------------------------------------------------------------+
+    //  v|                                                                    |
+    // +y|         a         1       cw        2           b                  |
+    //   |          +=========*HHHHHHHHHHHHHHH*===========+                   |
+    //   |          I         H      rw       H           I                   |
+    //   |          I         H               H           I                   |
+    //   |          I         H               H           I                   |
+    //ah |       ch I         H rh            H           I crop region       |
+    //   |          I         H               H           I                   |
+    //   |          I         H               H           I                   |
+    //   |          I         H rotate region H           I                   |
+    //   |          +=========*HHHHHHHHHHHHHHH*===========+                   |
+    //   |         d         4                 3           c                  |
+    //   |                                                                    |
+    //   +--------------------------------------------------------------------+
+    //
+    // aw , ah = active array width,height
+    // cw , ch = crop region width,height
+    // rw , rh = rotated-and-cropped region width,height
+    // aw / ah = array aspect = rh / rw = 1 / rotated aspect
+    // Coordinate mappings:
+    //    ROTATE_AND_CROP_90: point a -> point 2
+    //                        point c -> point 4 = +x -> +y, +y -> -x
+    //    ROTATE_AND_CROP_180: point a -> point c
+    //                         point c -> point a = +x -> -x, +y -> -y
+    //    ROTATE_AND_CROP_270: point a -> point 4
+    //                         point c -> point 2 = +x -> -y, +y -> +x
+
+    float cropAspect = static_cast<float>(cw) / ch;
+    float transformMat[4] = {0, 0,
+                             0, 0};
+    float xShift = 0;
+    float yShift = 0;
+
+    if (rotateMode == ANDROID_SCALER_ROTATE_AND_CROP_180) {
+        transformMat[0] = -1;
+        transformMat[3] = -1;
+        xShift = cw;
+        yShift = ch;
+    } else {
+        float rw = cropAspect > mRotateAspect ?
+                   ch * mRotateAspect : // pillarbox, not full width
+                   cw;                  // letterbox or 1:1, full width
+        float rh = cropAspect >= mRotateAspect ?
+                   ch :                 // pillarbox or 1:1, full height
+                   cw / mRotateAspect;  // letterbox, not full height
+        switch (rotateMode) {
+            case ANDROID_SCALER_ROTATE_AND_CROP_90:
+                transformMat[1] = -rw / ch; // +y -> -x
+                transformMat[2] =  rh / cw; // +x -> +y
+                xShift = (cw + rw) / 2; // left edge of crop to right edge of rotated
+                yShift = (ch - rh) / 2; // top edge of crop to top edge of rotated
+                break;
+            case ANDROID_SCALER_ROTATE_AND_CROP_270:
+                transformMat[1] =  rw / ch; // +y -> +x
+                transformMat[2] = -rh / cw; // +x -> -y
+                xShift = (cw - rw) / 2; // left edge of crop to left edge of rotated
+                yShift = (ch + rh) / 2; // top edge of crop to bottom edge of rotated
+                break;
+            default:
+                ALOGE("%s: Unexpected rotate mode: %d", __FUNCTION__, rotateMode);
+                return BAD_VALUE;
+        }
+    }
+
+    for (auto regionTag : kMeteringRegionsToCorrect) {
+        entry = request->find(regionTag);
+        for (size_t i = 0; i < entry.count; i += 5) {
+            int32_t weight = entry.data.i32[i + 4];
+            if (weight == 0) {
+                continue;
+            }
+            transformPoints(entry.data.i32 + i, 2, transformMat, xShift, yShift, cx, cy);
+            swapRectToMinFirst(entry.data.i32 + i);
+        }
+    }
+
+    return OK;
+}
+
+/**
+ * Adjust capture result when rotate and crop AUTO is enabled
+ */
+status_t RotateAndCropMapper::updateCaptureResult(CameraMetadata *result) {
+    auto entry = result->find(ANDROID_SCALER_ROTATE_AND_CROP);
+    if (entry.count == 0) return OK;
+    uint8_t rotateMode = entry.data.u8[0];
+    if (rotateMode == ANDROID_SCALER_ROTATE_AND_CROP_NONE) return OK;
+
+    int32_t cx = 0;
+    int32_t cy = 0;
+    int32_t cw = mArrayWidth;
+    int32_t ch = mArrayHeight;
+    entry = result->find(ANDROID_SCALER_CROP_REGION);
+    if (entry.count == 4) {
+        cx = entry.data.i32[0];
+        cy = entry.data.i32[1];
+        cw = entry.data.i32[2];
+        ch = entry.data.i32[3];
+    }
+
+    // HAL inputs are relative to the full active array, so convert back to
+    // rotated-and-cropped coordinates for apps. To be more specific, the
+    // application is calculating coordinates based on the crop rectangle and
+    // the active array, even though the view the user sees is the
+    // cropped-and-rotated one. So we need to adjust the coordinates so that a
+    // point that would be on the top-left corner of the rotate-and-cropped
+    // region is mapped to the top-left corner of the crop region, and the same
+    // for the bottom-right corner.
+    //
+    // Since the zoom ratio control scales everything uniformly (so an app does
+    // not need to adjust anything if it wants to put a metering region on the
+    // top-left quadrant of the preview FOV, when changing zoomRatio), it does
+    // not need to be factored into this calculation at all.
+    //
+    // Also note that round-tripping between original request and final result
+    // fields can't be perfect, since the intermediate values have to be
+    // integers on a smaller range than the original crop region range. That
+    // means that multiple input values map to a single output value in
+    // adjusting a request, so when adjusting a result, the original answer may
+    // not be obtainable.  Given that aspect ratios are rarely > 16/9, the
+    // round-trip values should generally only be off by 1 at most.
+    //
+    //   ->+x                       active array  aw
+    //  |+--------------------------------------------------------------------+
+    //  v|                                                                    |
+    // +y|         a         1       cw        2           b                  |
+    //   |          +=========*HHHHHHHHHHHHHHH*===========+                   |
+    //   |          I         H      rw       H           I                   |
+    //   |          I         H               H           I                   |
+    //   |          I         H               H           I                   |
+    //ah |       ch I         H rh            H           I crop region       |
+    //   |          I         H               H           I                   |
+    //   |          I         H               H           I                   |
+    //   |          I         H rotate region H           I                   |
+    //   |          +=========*HHHHHHHHHHHHHHH*===========+                   |
+    //   |         d         4                 3           c                  |
+    //   |                                                                    |
+    //   +--------------------------------------------------------------------+
+    //
+    // aw , ah = active array width,height
+    // cw , ch = crop region width,height
+    // rw , rh = rotated-and-cropped region width,height
+    // aw / ah = array aspect = rh / rw = 1 / rotated aspect
+    // Coordinate mappings:
+    //    ROTATE_AND_CROP_90: point 2 -> point a
+    //                        point 4 -> point c = +x -> -y, +y -> +x
+    //    ROTATE_AND_CROP_180: point c -> point a
+    //                         point a -> point c = +x -> -x, +y -> -y
+    //    ROTATE_AND_CROP_270: point 4 -> point a
+    //                         point 2 -> point c = +x -> +y, +y -> -x
+
+    float cropAspect = static_cast<float>(cw) / ch;
+    float transformMat[4] = {0, 0,
+                             0, 0};
+    float xShift = 0;
+    float yShift = 0;
+    float rx = 0; // top-left corner of rotated region
+    float ry = 0;
+    if (rotateMode == ANDROID_SCALER_ROTATE_AND_CROP_180) {
+        transformMat[0] = -1;
+        transformMat[3] = -1;
+        xShift = cw;
+        yShift = ch;
+        rx = cx;
+        ry = cy;
+    } else {
+        float rw = cropAspect > mRotateAspect ?
+                   ch * mRotateAspect : // pillarbox, not full width
+                   cw;                  // letterbox or 1:1, full width
+        float rh = cropAspect >= mRotateAspect ?
+                   ch :                 // pillarbox or 1:1, full height
+                   cw / mRotateAspect;  // letterbox, not full height
+        rx = cx + (cw - rw) / 2;
+        ry = cy + (ch - rh) / 2;
+        switch (rotateMode) {
+            case ANDROID_SCALER_ROTATE_AND_CROP_90:
+                transformMat[1] =  ch / rw; // +y -> +x
+                transformMat[2] = -cw / rh; // +x -> -y
+                xShift = -(cw - rw) / 2; // left edge of rotated to left edge of cropped
+                yShift = ry - cy + ch;   // top edge of rotated to bottom edge of cropped
+                break;
+            case ANDROID_SCALER_ROTATE_AND_CROP_270:
+                transformMat[1] = -ch / rw; // +y -> -x
+                transformMat[2] =  cw / rh; // +x -> +y
+                xShift = (cw + rw) / 2; // left edge of rotated to left edge of cropped
+                yShift = (ch - rh) / 2; // top edge of rotated to bottom edge of cropped
+                break;
+            default:
+                ALOGE("%s: Unexpected rotate mode: %d", __FUNCTION__, rotateMode);
+                return BAD_VALUE;
+        }
+    }
+
+    for (auto regionTag : kMeteringRegionsToCorrect) {
+        entry = result->find(regionTag);
+        for (size_t i = 0; i < entry.count; i += 5) {
+            int32_t weight = entry.data.i32[i + 4];
+            if (weight == 0) {
+                continue;
+            }
+            transformPoints(entry.data.i32 + i, 2, transformMat, xShift, yShift, rx, ry);
+            swapRectToMinFirst(entry.data.i32 + i);
+        }
+    }
+
+    for (auto pointsTag: kResultPointsToCorrectNoClamp) {
+        entry = result->find(pointsTag);
+        transformPoints(entry.data.i32, entry.count / 2, transformMat, xShift, yShift, rx, ry);
+        if (pointsTag == ANDROID_STATISTICS_FACE_RECTANGLES) {
+            for (size_t i = 0; i < entry.count; i += 4) {
+                swapRectToMinFirst(entry.data.i32 + i);
+            }
+        }
+    }
+
+    return OK;
+}
+
+void RotateAndCropMapper::transformPoints(int32_t* pts, size_t count, float transformMat[4],
+        float xShift, float yShift, float ox, float oy) {
+    for (size_t i = 0; i < count * 2; i += 2) {
+        float x0 = pts[i] - ox;
+        float y0 = pts[i + 1] - oy;
+        int32_t nx = std::round(transformMat[0] * x0 + transformMat[1] * y0 + xShift + ox);
+        int32_t ny = std::round(transformMat[2] * x0 + transformMat[3] * y0 + yShift + oy);
+
+        pts[i] = std::min(std::max(nx, 0), mArrayWidth);
+        pts[i + 1] = std::min(std::max(ny, 0), mArrayHeight);
+    }
+}
+
+void RotateAndCropMapper::swapRectToMinFirst(int32_t* rect) {
+    if (rect[0] > rect[2]) {
+        auto tmp = rect[0];
+        rect[0] = rect[2];
+        rect[2] = tmp;
+    }
+    if (rect[1] > rect[3]) {
+        auto tmp = rect[1];
+        rect[1] = rect[3];
+        rect[3] = tmp;
+    }
+}
+
+} // namespace camera3
+
+} // namespace android
diff --git a/services/camera/libcameraservice/device3/RotateAndCropMapper.h b/services/camera/libcameraservice/device3/RotateAndCropMapper.h
new file mode 100644
index 0000000..459e27f
--- /dev/null
+++ b/services/camera/libcameraservice/device3/RotateAndCropMapper.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#ifndef ANDROID_SERVERS_ROTATEANDCROPMAPPER_H
+#define ANDROID_SERVERS_ROTATEANDCROPMAPPER_H
+
+#include <utils/Errors.h>
+#include <array>
+#include <mutex>
+
+#include "camera/CameraMetadata.h"
+#include "device3/CoordinateMapper.h"
+
+namespace android {
+
+namespace camera3 {
+
+/**
+ * Utilities to transform between unrotated and rotated-and-cropped coordinate systems
+ * for cameras that support SCALER_ROTATE_AND_CROP controls in AUTO mode.
+ */
+class RotateAndCropMapper : private CoordinateMapper {
+  public:
+    static bool isNeeded(const CameraMetadata* deviceInfo);
+
+    RotateAndCropMapper(const CameraMetadata* deviceInfo);
+
+    /**
+     * Adjust capture request assuming rotate and crop AUTO is enabled
+     */
+    status_t updateCaptureRequest(CameraMetadata *request);
+
+    /**
+     * Adjust capture result assuming rotate and crop AUTO is enabled
+     */
+    status_t updateCaptureResult(CameraMetadata *result);
+
+  private:
+    // Transform count's worth of x,y points passed in with 2x2 matrix + translate with transform
+    // origin (cx,cy)
+    void transformPoints(int32_t* pts, size_t count, float transformMat[4],
+            float xShift, float yShift, float cx, float cy);
+    // Take two corners of a rect as (x1,y1,x2,y2) and swap x and y components
+    // if needed so that x1 < x2, y1 < y2.
+    void swapRectToMinFirst(int32_t* rect);
+
+    int32_t mArrayWidth, mArrayHeight;
+    float mArrayAspect, mRotateAspect;
+}; // class RotateAndCroMapper
+
+} // namespace camera3
+
+} // namespace android
+
+#endif
diff --git a/services/camera/libcameraservice/tests/Android.mk b/services/camera/libcameraservice/tests/Android.mk
index ea4eb3b..8784c95 100644
--- a/services/camera/libcameraservice/tests/Android.mk
+++ b/services/camera/libcameraservice/tests/Android.mk
@@ -36,6 +36,9 @@
     android.hardware.camera.device@3.2 \
     android.hardware.camera.device@3.4
 
+LOCAL_STATIC_LIBRARIES := \
+    libgmock
+
 LOCAL_C_INCLUDES += \
     system/media/private/camera/include \
     external/dynamic_depth/includes \
diff --git a/services/camera/libcameraservice/tests/RotateAndCropMapperTest.cpp b/services/camera/libcameraservice/tests/RotateAndCropMapperTest.cpp
new file mode 100644
index 0000000..c638d40
--- /dev/null
+++ b/services/camera/libcameraservice/tests/RotateAndCropMapperTest.cpp
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2020 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_NDEBUG 0
+#define LOG_TAG "RotateAndCropMapperTest"
+
+#include <functional>
+#include <random>
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include "../device3/RotateAndCropMapper.h"
+
+namespace rotateAndCropMapperTest {
+
+using namespace android;
+using namespace android::camera3;
+
+using ::testing::ElementsAreArray;
+using ::testing::Each;
+using ::testing::AllOf;
+using ::testing::Ge;
+using ::testing::Le;
+
+#define EXPECT_EQUAL_WITHIN_N(vec, array, N, msg)   \
+{ \
+    std::vector<int32_t> vec_diff; \
+    std::transform(vec.begin(), vec.end(), array, \
+            std::back_inserter(vec_diff), std::minus()); \
+    EXPECT_THAT(vec_diff, Each(AllOf(Ge(-N), Le(N)))) << msg; \
+}
+
+int32_t testActiveArray[] = {100, 100, 4000, 3000};
+
+std::vector<uint8_t> basicModes = {
+    ANDROID_SCALER_ROTATE_AND_CROP_NONE,
+    ANDROID_SCALER_ROTATE_AND_CROP_90,
+    ANDROID_SCALER_ROTATE_AND_CROP_AUTO
+};
+
+CameraMetadata setupDeviceInfo(int32_t activeArray[4], std::vector<uint8_t> availableCropModes ) {
+    CameraMetadata deviceInfo;
+
+    deviceInfo.update(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE,
+            activeArray, 4);
+
+    deviceInfo.update(ANDROID_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES,
+            availableCropModes.data(), availableCropModes.size());
+
+    return deviceInfo;
+}
+
+TEST(RotationMapperTest, Initialization) {
+    CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
+            {ANDROID_SCALER_ROTATE_AND_CROP_NONE});
+
+    ASSERT_FALSE(RotateAndCropMapper::isNeeded(&deviceInfo));
+
+    deviceInfo.update(ANDROID_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES,
+            basicModes.data(), 3);
+
+    ASSERT_TRUE(RotateAndCropMapper::isNeeded(&deviceInfo));
+}
+
+TEST(RotationMapperTest, IdentityTransform) {
+    status_t res;
+
+    CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
+            basicModes);
+
+    RotateAndCropMapper mapper(&deviceInfo);
+
+    CameraMetadata request;
+    uint8_t mode = ANDROID_SCALER_ROTATE_AND_CROP_NONE;
+    auto full_crop = std::vector<int32_t>{0,0, testActiveArray[2], testActiveArray[3]};
+    auto full_region = std::vector<int32_t>{0,0, testActiveArray[2], testActiveArray[3], 1};
+    request.update(ANDROID_SCALER_ROTATE_AND_CROP,
+            &mode, 1);
+    request.update(ANDROID_SCALER_CROP_REGION,
+            full_crop.data(), full_crop.size());
+    request.update(ANDROID_CONTROL_AE_REGIONS,
+            full_region.data(), full_region.size());
+
+    // Map to HAL
+
+    res = mapper.updateCaptureRequest(&request);
+    ASSERT_TRUE(res == OK);
+
+    auto e = request.find(ANDROID_CONTROL_AE_REGIONS);
+    EXPECT_THAT(full_region, ElementsAreArray(e.data.i32, e.count));
+
+    e = request.find(ANDROID_SCALER_CROP_REGION);
+    EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count));
+
+    // Add fields in HAL
+
+    CameraMetadata result(request);
+
+    auto face = std::vector<int32_t> {300,300,500,500};
+    result.update(ANDROID_STATISTICS_FACE_RECTANGLES,
+            face.data(), face.size());
+
+    // Map to app
+
+    res = mapper.updateCaptureResult(&result);
+    ASSERT_TRUE(res == OK);
+
+    e = result.find(ANDROID_CONTROL_AE_REGIONS);
+    EXPECT_THAT(full_region, ElementsAreArray(e.data.i32, e.count));
+
+    e = result.find(ANDROID_SCALER_CROP_REGION);
+    EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count));
+
+    e = result.find(ANDROID_STATISTICS_FACE_RECTANGLES);
+    EXPECT_THAT(face, ElementsAreArray(e.data.i32, e.count));
+}
+
+TEST(RotationMapperTest, Transform90) {
+    status_t res;
+
+    CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
+            basicModes);
+
+    RotateAndCropMapper mapper(&deviceInfo);
+
+    CameraMetadata request;
+    uint8_t mode = ANDROID_SCALER_ROTATE_AND_CROP_90;
+    auto full_crop = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3]};
+    auto full_region = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3], 1};
+    request.update(ANDROID_SCALER_ROTATE_AND_CROP,
+            &mode, 1);
+    request.update(ANDROID_SCALER_CROP_REGION,
+            full_crop.data(), full_crop.size());
+    request.update(ANDROID_CONTROL_AE_REGIONS,
+            full_region.data(), full_region.size());
+
+    // Map to HAL
+
+    res = mapper.updateCaptureRequest(&request);
+    ASSERT_TRUE(res == OK);
+
+    auto e = request.find(ANDROID_CONTROL_AE_REGIONS);
+    float aspectRatio = static_cast<float>(full_crop[2]) / full_crop[3];
+    int32_t rw = full_crop[3] / aspectRatio;
+    int32_t rh = full_crop[3];
+    auto rotated_region = std::vector<int32_t> {
+        full_crop[0] + (full_crop[2] - rw) / 2, full_crop[1],
+        full_crop[0] + (full_crop[2] + rw) / 2, full_crop[1] + full_crop[3],
+        1
+    };
+    EXPECT_THAT(rotated_region, ElementsAreArray(e.data.i32, e.count))
+            << "Rotated AE region isn't right";
+
+    e = request.find(ANDROID_SCALER_CROP_REGION);
+    EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count))
+            << "Rotated crop region isn't right";
+
+    // Add fields in HAL
+
+    CameraMetadata result(request);
+
+    auto face = std::vector<int32_t> {
+        rotated_region[0] + rw / 4, rotated_region[1] + rh / 4,
+        rotated_region[2] - rw / 4, rotated_region[3] - rh / 4};
+    result.update(ANDROID_STATISTICS_FACE_RECTANGLES,
+            face.data(), face.size());
+
+    auto landmarks = std::vector<int32_t> {
+        rotated_region[0], rotated_region[1],
+        rotated_region[2], rotated_region[3],
+        rotated_region[0] + rw / 4, rotated_region[1] + rh / 4,
+        rotated_region[0] + rw / 2, rotated_region[1] + rh / 2,
+        rotated_region[2] - rw / 4, rotated_region[3] - rh / 4
+    };
+    result.update(ANDROID_STATISTICS_FACE_LANDMARKS,
+            landmarks.data(), landmarks.size());
+
+    // Map to app
+
+    res = mapper.updateCaptureResult(&result);
+    ASSERT_TRUE(res == OK);
+
+    // Round-trip results can't be exact since we've gone from a large int range -> small int range
+    // and back, leading to quantization. For 4/3 aspect ratio, no more than +-1 error expected
+    e = result.find(ANDROID_CONTROL_AE_REGIONS);
+    EXPECT_EQUAL_WITHIN_N(full_region, e.data.i32, 1, "Round-tripped AE region isn't right");
+
+    e = result.find(ANDROID_SCALER_CROP_REGION);
+    EXPECT_EQUAL_WITHIN_N(full_crop, e.data.i32, 1, "Round-tripped crop region isn't right");
+
+    auto full_face = std::vector<int32_t> {
+        full_crop[0] + full_crop[2]/4, full_crop[1] + full_crop[3]/4,
+        full_crop[0] + 3*full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4
+    };
+    e = result.find(ANDROID_STATISTICS_FACE_RECTANGLES);
+    EXPECT_EQUAL_WITHIN_N(full_face, e.data.i32, 1, "App-side face rectangle isn't right");
+
+    auto full_landmarks = std::vector<int32_t> {
+        full_crop[0], full_crop[1] + full_crop[3],
+        full_crop[0] + full_crop[2], full_crop[1],
+        full_crop[0] + full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4,
+        full_crop[0] + full_crop[2]/2, full_crop[1] + full_crop[3]/2,
+        full_crop[0] + 3*full_crop[2]/4, full_crop[1] + full_crop[3]/4
+    };
+    e = result.find(ANDROID_STATISTICS_FACE_LANDMARKS);
+    EXPECT_EQUAL_WITHIN_N(full_landmarks, e.data.i32, 1, "App-side face landmarks aren't right");
+}
+
+TEST(RotationMapperTest, Transform270) {
+    status_t res;
+
+    CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
+            basicModes);
+
+    RotateAndCropMapper mapper(&deviceInfo);
+
+    CameraMetadata request;
+    uint8_t mode = ANDROID_SCALER_ROTATE_AND_CROP_270;
+    auto full_crop = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3]};
+    auto full_region = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3], 1};
+    request.update(ANDROID_SCALER_ROTATE_AND_CROP,
+            &mode, 1);
+    request.update(ANDROID_SCALER_CROP_REGION,
+            full_crop.data(), full_crop.size());
+    request.update(ANDROID_CONTROL_AE_REGIONS,
+            full_region.data(), full_region.size());
+
+    // Map to HAL
+
+    res = mapper.updateCaptureRequest(&request);
+    ASSERT_TRUE(res == OK);
+
+    auto e = request.find(ANDROID_CONTROL_AE_REGIONS);
+    float aspectRatio = static_cast<float>(full_crop[2]) / full_crop[3];
+    int32_t rw = full_crop[3] / aspectRatio;
+    int32_t rh = full_crop[3];
+    auto rotated_region = std::vector<int32_t> {
+        full_crop[0] + (full_crop[2] - rw) / 2, full_crop[1],
+        full_crop[0] + (full_crop[2] + rw) / 2, full_crop[1] + full_crop[3],
+        1
+    };
+    EXPECT_THAT(rotated_region, ElementsAreArray(e.data.i32, e.count))
+            << "Rotated AE region isn't right";
+
+    e = request.find(ANDROID_SCALER_CROP_REGION);
+    EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count))
+            << "Rotated crop region isn't right";
+
+    // Add fields in HAL
+
+    CameraMetadata result(request);
+
+    auto face = std::vector<int32_t> {
+        rotated_region[0] + rw / 4, rotated_region[1] + rh / 4,
+        rotated_region[2] - rw / 4, rotated_region[3] - rh / 4};
+    result.update(ANDROID_STATISTICS_FACE_RECTANGLES,
+            face.data(), face.size());
+
+    auto landmarks = std::vector<int32_t> {
+        rotated_region[0], rotated_region[1],
+        rotated_region[2], rotated_region[3],
+        rotated_region[0] + rw / 4, rotated_region[1] + rh / 4,
+        rotated_region[0] + rw / 2, rotated_region[1] + rh / 2,
+        rotated_region[2] - rw / 4, rotated_region[3] - rh / 4
+    };
+    result.update(ANDROID_STATISTICS_FACE_LANDMARKS,
+            landmarks.data(), landmarks.size());
+
+    // Map to app
+
+    res = mapper.updateCaptureResult(&result);
+    ASSERT_TRUE(res == OK);
+
+    // Round-trip results can't be exact since we've gone from a large int range -> small int range
+    // and back, leading to quantization. For 4/3 aspect ratio, no more than +-1 error expected
+
+    e = result.find(ANDROID_CONTROL_AE_REGIONS);
+    EXPECT_EQUAL_WITHIN_N(full_region, e.data.i32, 1, "Round-tripped AE region isn't right");
+
+    e = result.find(ANDROID_SCALER_CROP_REGION);
+    EXPECT_EQUAL_WITHIN_N(full_crop, e.data.i32, 1, "Round-tripped crop region isn't right");
+
+    auto full_face = std::vector<int32_t> {
+        full_crop[0] + full_crop[2]/4, full_crop[1] + full_crop[3]/4,
+        full_crop[0] + 3*full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4
+    };
+    e = result.find(ANDROID_STATISTICS_FACE_RECTANGLES);
+    EXPECT_EQUAL_WITHIN_N(full_face, e.data.i32, 1, "App-side face rectangle isn't right");
+
+    auto full_landmarks = std::vector<int32_t> {
+        full_crop[0] + full_crop[2], full_crop[1],
+        full_crop[0], full_crop[1] + full_crop[3],
+        full_crop[0] + 3*full_crop[2]/4, full_crop[1] + full_crop[3]/4,
+        full_crop[0] + full_crop[2]/2, full_crop[1] + full_crop[3]/2,
+        full_crop[0] + full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4
+    };
+    e = result.find(ANDROID_STATISTICS_FACE_LANDMARKS);
+    EXPECT_EQUAL_WITHIN_N(full_landmarks, e.data.i32, 1, "App-side face landmarks aren't right");
+}
+
+TEST(RotationMapperTest, Transform180) {
+    status_t res;
+
+    CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
+            basicModes);
+
+    RotateAndCropMapper mapper(&deviceInfo);
+
+    CameraMetadata request;
+    uint8_t mode = ANDROID_SCALER_ROTATE_AND_CROP_180;
+    auto full_crop = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3]};
+    auto full_region = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3], 1};
+    request.update(ANDROID_SCALER_ROTATE_AND_CROP,
+            &mode, 1);
+    request.update(ANDROID_SCALER_CROP_REGION,
+            full_crop.data(), full_crop.size());
+    request.update(ANDROID_CONTROL_AE_REGIONS,
+            full_region.data(), full_region.size());
+
+    // Map to HAL
+
+    res = mapper.updateCaptureRequest(&request);
+    ASSERT_TRUE(res == OK);
+
+    auto e = request.find(ANDROID_CONTROL_AE_REGIONS);
+    auto rotated_region = full_region;
+    EXPECT_THAT(rotated_region, ElementsAreArray(e.data.i32, e.count))
+            << "Rotated AE region isn't right";
+
+    e = request.find(ANDROID_SCALER_CROP_REGION);
+    EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count))
+            << "Rotated crop region isn't right";
+
+    // Add fields in HAL
+
+    CameraMetadata result(request);
+
+    float rw = full_region[2] - full_region[0];
+    float rh = full_region[3] - full_region[1];
+    auto face = std::vector<int32_t> {
+        rotated_region[0] + (int)(rw / 4), rotated_region[1] + (int)(rh / 4),
+        rotated_region[2] - (int)(rw / 4), rotated_region[3] - (int)(rh / 4)
+    };
+    result.update(ANDROID_STATISTICS_FACE_RECTANGLES,
+            face.data(), face.size());
+
+    auto landmarks = std::vector<int32_t> {
+        rotated_region[0], rotated_region[1],
+        rotated_region[2], rotated_region[3],
+        rotated_region[0] + (int)(rw / 4), rotated_region[1] + (int)(rh / 4),
+        rotated_region[0] + (int)(rw / 2), rotated_region[1] + (int)(rh / 2),
+        rotated_region[2] - (int)(rw / 4), rotated_region[3] - (int)(rh / 4)
+    };
+    result.update(ANDROID_STATISTICS_FACE_LANDMARKS,
+            landmarks.data(), landmarks.size());
+
+    // Map to app
+
+    res = mapper.updateCaptureResult(&result);
+    ASSERT_TRUE(res == OK);
+
+    e = result.find(ANDROID_CONTROL_AE_REGIONS);
+    EXPECT_THAT(full_region, ElementsAreArray(e.data.i32, e.count))
+            << "Round-tripped AE region isn't right";
+
+    e = result.find(ANDROID_SCALER_CROP_REGION);
+    EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count))
+            << "Round-tripped crop region isn't right";
+
+    auto full_face = std::vector<int32_t> {
+        full_crop[0] + full_crop[2]/4, full_crop[1] + full_crop[3]/4,
+        full_crop[0] + 3*full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4
+    };
+    e = result.find(ANDROID_STATISTICS_FACE_RECTANGLES);
+    EXPECT_EQUAL_WITHIN_N(full_face, e.data.i32, 1, "App-side face rectangle isn't right");
+
+    auto full_landmarks = std::vector<int32_t> {
+        full_crop[0] + full_crop[2], full_crop[1] + full_crop[3],
+        full_crop[0], full_crop[1],
+        full_crop[0] + 3*full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4,
+        full_crop[0] + full_crop[2]/2, full_crop[1] + full_crop[3]/2,
+        full_crop[0] + full_crop[2]/4, full_crop[1] + full_crop[3]/4
+    };
+    e = result.find(ANDROID_STATISTICS_FACE_LANDMARKS);
+    EXPECT_EQUAL_WITHIN_N(full_landmarks, e.data.i32, 1, "App-side face landmarks aren't right");
+}
+
+
+} // namespace rotateAndCropMapperTest