CameraITS: add support for zoom in 3a/video

Details: Allowed zoom ratio to be passed in do_3a() and for basic video recording/preview recording.
Bug: 215603925
Test: tested locally on Pixel device.
Change-Id: Iedd884052dc182533b569fceb0967e150748e1a1
diff --git a/apps/CameraITS/utils/its_session_utils.py b/apps/CameraITS/utils/its_session_utils.py
index 76496d5..890d1e9 100644
--- a/apps/CameraITS/utils/its_session_utils.py
+++ b/apps/CameraITS/utils/its_session_utils.py
@@ -390,6 +390,17 @@
     if data['tag'] != 'cameraClosed':
       raise error_util.CameraItsError('Invalid command response')
 
+  def zoom_ratio_within_range(self, zoom_ratio):
+    """Determine if a given zoom ratio is within device zoom range.
+
+    Args:
+      zoom_ratio: float; zoom ratio requested
+    Returns:
+      Boolean: True, if zoom_ratio inside device range. False otherwise.
+    """
+    zoom_range = self.props['android.control.zoomRatioRange']
+    return zoom_ratio >= zoom_range[0] and zoom_ratio <= zoom_range[1]
+
   def get_sensors(self):
     """Get all sensors on the device.
 
@@ -475,7 +486,8 @@
     return data['strValue'] == 'true'
 
   def do_basic_recording(self, profile_id, quality, duration,
-                         video_stabilization_mode=0, hlg10_enabled=False):
+                         video_stabilization_mode=0, hlg10_enabled=False,
+                         zoom_ratio=None):
     """Issue a recording request and read back the video recording object.
 
     The recording will be done with the format specified in quality. These
@@ -492,6 +504,7 @@
       0: 'OFF', 1: 'ON', 2: 'PREVIEW'
       hlg10_enabled: boolean: True Enable 10-bit HLG video recording, False
       record using the regular SDR profile
+      zoom_ratio: float; zoom ratio. None if default zoom
     Returns:
       video_recorded_object: The recorded object returned from ItsService which
       contains path at which the recording is saved on the device, quality of
@@ -515,6 +528,11 @@
            'recordingDuration': duration,
            'videoStabilizationMode': video_stabilization_mode,
            'hlg10Enabled': hlg10_enabled}
+    if zoom_ratio:
+      if self.zoom_ratio_within_range(zoom_ratio):
+        cmd['zoomRatio'] = zoom_ratio
+      else:
+        raise AssertionError(f'Zoom ratio {zoom_ratio} out of range')
     self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
     timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT
     self.sock.settimeout(timeout)
@@ -525,7 +543,8 @@
     logging.debug('VideoRecordingObject: %s', data)
     return data['objValue']
 
-  def do_preview_recording(self, video_size, duration, stabilize):
+  def do_preview_recording(self, video_size, duration, stabilize,
+                           zoom_ratio=None):
     """Issue a preview request and read back the preview recording object.
 
     The resolution of the preview and its recording will be determined by
@@ -537,6 +556,7 @@
       video_size: str; Preview resolution at which to record. ex. "1920x1080"
       duration: int; The time in seconds for which the video will be recorded.
       stabilize: boolean; Whether the preview should be stabilized or not
+      zoom_ratio: float; zoom ratio. None if default zoom
     Returns:
       video_recorded_object: The recorded object returned from ItsService which
       contains path at which the recording is saved on the device, quality of
@@ -562,6 +582,11 @@
         'recordingDuration': duration,
         'stabilize': stabilize
     }
+    if zoom_ratio:
+      if self.zoom_ratio_within_range(zoom_ratio):
+        cmd['zoomRatio'] = zoom_ratio
+      else:
+        raise AssertionError(f'Zoom ratio {zoom_ratio} out of range')
     self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
     timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT
     self.sock.settimeout(timeout)
@@ -1222,7 +1247,8 @@
             get_results=False,
             ev_comp=0,
             auto_flash=False,
-            mono_camera=False):
+            mono_camera=False,
+            zoom_ratio=None):
     """Perform a 3A operation on the device.
 
     Triggers some or all of AE, AWB, and AF, and returns once they have
@@ -1244,6 +1270,7 @@
       ev_comp: An EV compensation value to use when running AE.
       auto_flash: AE control boolean to enable auto flash.
       mono_camera: Boolean for monochrome camera.
+      zoom_ratio: Zoom ratio. None if default zoom
 
       Region format in args:
          Arguments are lists of weighted regions; each weighted region is a
@@ -1281,6 +1308,11 @@
       cmd['autoFlash'] = True
     if self._hidden_physical_id:
       cmd['physicalId'] = self._hidden_physical_id
+    if zoom_ratio:
+      if self.zoom_ratio_within_range(zoom_ratio):
+        cmd['zoomRatio'] = zoom_ratio
+      else:
+        raise AssertionError(f'Zoom ratio {zoom_ratio} out of range')
     self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
 
     # Wait for each specified 3A to converge.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
index 4a6b877..950248a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
@@ -182,6 +182,7 @@
     public static final String VIB_PATTERN_KEY = "pattern";
     public static final String EVCOMP_KEY = "evComp";
     public static final String AUTO_FLASH_KEY = "autoFlash";
+    public static final String ZOOM_RATIO_KEY = "zoomRatio";
     public static final String AUDIO_RESTRICTION_MODE_KEY = "mode";
 
     private CameraManager mCameraManager = null;
@@ -268,19 +269,23 @@
         public Size videoSize;
         public int videoFrameRate; // -1 implies video framerate was not set by the test
         public int fileFormat;
+        public double zoomRatio;
 
         public VideoRecordingObject(String recordedOutputPath,
-                String quality, Size videoSize, int videoFrameRate, int fileFormat) {
+                String quality, Size videoSize, int videoFrameRate,
+                int fileFormat, double zoomRatio) {
             this.recordedOutputPath = recordedOutputPath;
             this.quality = quality;
             this.videoSize = videoSize;
             this.videoFrameRate = videoFrameRate;
             this.fileFormat = fileFormat;
+            this.zoomRatio = zoomRatio;
         }
 
         VideoRecordingObject(String recordedOutputPath, String quality, Size videoSize,
-                int fileFormat) {
-            this(recordedOutputPath, quality, videoSize, INVALID_FRAME_RATE, fileFormat);
+                int fileFormat, double zoomRatio) {
+            this(recordedOutputPath, quality, videoSize,
+                INVALID_FRAME_RATE, fileFormat, zoomRatio);
         }
 
         public boolean isFrameRateValid() {
@@ -812,14 +817,17 @@
                     int recordingDuration = cmdObj.getInt("recordingDuration");
                     int videoStabilizationMode = cmdObj.getInt("videoStabilizationMode");
                     boolean hlg10Enabled = cmdObj.getBoolean("hlg10Enabled");
+                    double zoomRatio = cmdObj.optDouble("zoomRatio");
                     doBasicRecording(cameraId, profileId, quality, recordingDuration,
-                            videoStabilizationMode, hlg10Enabled);
+                            videoStabilizationMode, hlg10Enabled, zoomRatio);
                 } else if ("doPreviewRecording".equals(cmdObj.getString("cmdName"))) {
                     String cameraId = cmdObj.getString("cameraId");
                     String videoSize = cmdObj.getString("videoSize");
                     int recordingDuration = cmdObj.getInt("recordingDuration");
                     boolean stabilize = cmdObj.getBoolean("stabilize");
-                    doBasicPreviewRecording(cameraId, videoSize, recordingDuration, stabilize);
+                    double zoomRatio = cmdObj.optDouble("zoomRatio");
+                    doBasicPreviewRecording(cameraId, videoSize, recordingDuration,
+                            stabilize, zoomRatio);
                 } else if ("isHLG10Supported".equals(cmdObj.getString("cmdName"))) {
                     String cameraId = cmdObj.getString("cameraId");
                     int profileId = cmdObj.getInt("profileId");
@@ -1454,6 +1462,11 @@
                 Logt.i(TAG, String.format("Running with auto flash mode."));
             }
 
+            double zoomRatio = params.optDouble(ZOOM_RATIO_KEY);
+            if (!Double.isNaN(zoomRatio)) {
+                Logt.i(TAG, String.format("Running 3A with zoom ratio: %f", zoomRatio));
+            }
+
             // By default, AE and AF both get triggered, but the user can optionally override this.
             // Also, AF won't get triggered if the lens is fixed-focus.
             if (params.has(TRIGGER_KEY)) {
@@ -1550,6 +1563,10 @@
                                     CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                         }
 
+                        if (!Double.isNaN(zoomRatio)) {
+                            req.set(CaptureRequest.CONTROL_ZOOM_RATIO, (float) zoomRatio);
+                        }
+
                         if (mConvergedAE && mNeedsLockedAE) {
                             req.set(CaptureRequest.CONTROL_AE_LOCK, true);
                         }
@@ -1921,13 +1938,14 @@
     }
 
     private void doBasicRecording(String cameraId, int profileId, String quality,
-            int recordingDuration, int videoStabilizationMode, boolean hlg10Enabled)
+            int recordingDuration, int videoStabilizationMode,
+            boolean hlg10Enabled, double zoomRatio)
             throws ItsException {
         final long SESSION_CLOSE_TIMEOUT_MS  = 3000;
 
         if (!hlg10Enabled) {
             doBasicRecording(cameraId, profileId, quality, recordingDuration,
-                    videoStabilizationMode);
+                    videoStabilizationMode, zoomRatio);
             return;
         }
 
@@ -1946,7 +1964,8 @@
         String outputFilePath = getOutputMediaFile(cameraDeviceId, videoSize, quality, fileFormat,
                 /* hlg10Enabled= */ true,
                 /* stabilized= */
-                videoStabilizationMode != CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF);
+                videoStabilizationMode != CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+                zoomRatio);
         assert (outputFilePath != null);
 
         MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
@@ -1994,7 +2013,7 @@
         // Configure and create capture session.
         try {
             configureAndCreateCaptureSession(CameraDevice.TEMPLATE_RECORD, mRecordSurface,
-                    videoStabilizationMode, DynamicRangeProfiles.HLG10, mockCallback);
+                    videoStabilizationMode, DynamicRangeProfiles.HLG10, mockCallback, zoomRatio);
         } catch (CameraAccessException e) {
             throw new ItsException("Access error: ", e);
         }
@@ -2032,12 +2051,13 @@
 
         // Send VideoRecordingObject for further processing.
         VideoRecordingObject obj = new VideoRecordingObject(outputFilePath,
-                quality, videoSize, camcorderProfile.videoFrameRate, fileFormat);
+                quality, videoSize, camcorderProfile.videoFrameRate, fileFormat, zoomRatio);
         mSocketRunnableObj.sendVideoRecordingObject(obj);
     }
 
     private void doBasicRecording(String cameraId, int profileId, String quality,
-            int recordingDuration, int videoStabilizationMode) throws ItsException {
+            int recordingDuration, int videoStabilizationMode,
+            double zoomRatio) throws ItsException {
         int cameraDeviceId = Integer.parseInt(cameraId);
         mMediaRecorder = new MediaRecorder();
         CamcorderProfile camcorderProfile = getCamcorderProfile(cameraDeviceId, profileId);
@@ -2053,7 +2073,8 @@
         int fileFormat = camcorderProfile.fileFormat;
         String outputFilePath = getOutputMediaFile(cameraDeviceId, videoSize, quality,
                 fileFormat, /* stabilized= */
-                videoStabilizationMode != CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF);
+                videoStabilizationMode != CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+                zoomRatio);
         assert(outputFilePath != null);
         Log.i(TAG, "Video recording outputFilePath:"+ outputFilePath);
         setupMediaRecorderWithProfile(cameraDeviceId, camcorderProfile, outputFilePath);
@@ -2068,7 +2089,7 @@
         // Configure and create capture session.
         try {
             configureAndCreateCaptureSession(CameraDevice.TEMPLATE_RECORD, mRecordSurface,
-                    videoStabilizationMode);
+                    videoStabilizationMode, zoomRatio);
         } catch (android.hardware.camera2.CameraAccessException e) {
             throw new ItsException("Access error: ", e);
         }
@@ -2098,7 +2119,7 @@
 
         // Send VideoRecordingObject for further processing.
         VideoRecordingObject obj = new VideoRecordingObject(outputFilePath,
-                quality, videoSize, camcorderProfile.videoFrameRate, fileFormat);
+                quality, videoSize, camcorderProfile.videoFrameRate, fileFormat, zoomRatio);
         mSocketRunnableObj.sendVideoRecordingObject(obj);
     }
 
@@ -2113,7 +2134,7 @@
      * ImageReader to the MediaRecorder surface which is encoded into a video.
      */
     private void doBasicPreviewRecording(String cameraId, String videoSizeString,
-            int recordingDuration, boolean stabilize)
+            int recordingDuration, boolean stabilize, double zoomRatio)
             throws ItsException {
 
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
@@ -2135,7 +2156,7 @@
         int fileFormat = MediaRecorder.OutputFormat.DEFAULT;
 
         String outputFilePath = getOutputMediaFile(cameraDeviceId, videoSize,
-                /* quality= */"preview", fileFormat, stabilize);
+                /* quality= */"preview", fileFormat, stabilize, zoomRatio);
         assert outputFilePath != null;
 
         try (PreviewRecorder pr = new PreviewRecorder(cameraDeviceId, videoSize,
@@ -2144,7 +2165,7 @@
                     ? CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION
                     : CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF;
             configureAndCreateCaptureSession(CameraDevice.TEMPLATE_PREVIEW,
-                    pr.getCameraSurface(), stabilizationMode);
+                    pr.getCameraSurface(), stabilizationMode, zoomRatio);
             pr.recordPreview(recordingDuration * 1000L);
             mSession.close();
         } catch (CameraAccessException e) {
@@ -2154,22 +2175,26 @@
         Log.i(TAG, "Preview recording complete: " + outputFilePath);
         // Send VideoRecordingObject for further processing.
         VideoRecordingObject obj = new VideoRecordingObject(outputFilePath, /* quality= */"preview",
-                videoSize, fileFormat);
+                videoSize, fileFormat, zoomRatio);
         mSocketRunnableObj.sendVideoRecordingObject(obj);
     }
 
     private void configureAndCreateCaptureSession(int requestTemplate, Surface recordSurface,
-            int videoStabilizationMode) throws CameraAccessException {
+            int videoStabilizationMode, double zoomRatio) throws CameraAccessException {
         configureAndCreateCaptureSession(requestTemplate, recordSurface, videoStabilizationMode,
-                DynamicRangeProfiles.STANDARD, /* stateCallback= */ null);
+                DynamicRangeProfiles.STANDARD, /* stateCallback= */ null, zoomRatio);
     }
 
     private void configureAndCreateCaptureSession(int requestTemplate, Surface recordSurface,
             int videoStabilizationMode, long dynamicRangeProfile,
-            CameraCaptureSession.StateCallback stateCallback) throws CameraAccessException {
+            CameraCaptureSession.StateCallback stateCallback,
+            double zoomRatio) throws CameraAccessException {
         assert (recordSurface != null);
         // Create capture request builder
         mCaptureRequestBuilder = mCamera.createCaptureRequest(requestTemplate);
+        if (!Double.isNaN(zoomRatio)) {
+            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, (float) zoomRatio);
+        }
 
         switch (videoStabilizationMode) {
             case CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON:
@@ -2250,13 +2275,13 @@
     }
 
     private String getOutputMediaFile(int cameraId, Size videoSize, String quality,
-            int fileFormat, boolean stabilized) {
+            int fileFormat, boolean stabilized, double zoomRatio) {
         return getOutputMediaFile(cameraId, videoSize, quality, fileFormat,
-                /* hlg10Enabled= */false, stabilized);
+                /* hlg10Enabled= */false, stabilized, zoomRatio);
     }
 
     private String getOutputMediaFile(int cameraId, Size videoSize, String quality,
-            int fileFormat, boolean hlg10Enabled, boolean stabilized) {
+            int fileFormat, boolean hlg10Enabled, boolean stabilized, double zoomRatio) {
         // If any quality has file format other than 3gp and webm then the
         // recording file will have mp4 as default extension.
         String fileExtension = "";
@@ -2281,7 +2306,11 @@
         }
         String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
         String fileName = mediaStorageDir.getPath() + File.separator +
-                "VID_" + timestamp + '_' + cameraId + '_' + quality + '_' + videoSize;
+                "VID_" + timestamp + '_' + cameraId + '_' + quality + '_' +
+                videoSize;
+        if (!Double.isNaN(zoomRatio)) {
+            fileName += "_" + zoomRatio;
+        }
         if (hlg10Enabled) {
             fileName += "_hlg10";
         }