1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.verifier.camera.its;
18 
19 import android.annotation.NonNull;
20 import android.graphics.Rect;
21 import android.hardware.camera2.CameraAccessException;
22 import android.hardware.camera2.CameraCaptureSession;
23 import android.hardware.camera2.CameraCharacteristics;
24 import android.hardware.camera2.CaptureRequest;
25 import android.hardware.camera2.params.MeteringRectangle;
26 import android.os.Handler;
27 
28 import org.json.JSONArray;
29 
30 import java.util.Arrays;
31 import java.util.Locale;
32 
33 /**
34  * An action to be executed during preview recordings, controlling
35  * a {@link CameraCaptureSession} if needed.
36  */
37 abstract class IntraPreviewAction {
38     /**
39      * Time to sleep after a preview recording action that sends new {@link CaptureRequest}s.
40      */
41     static final long PREVIEW_RECORDING_FINAL_SLEEP_MS = 200;
42 
43     /**
44      * Time to sleep for AutoFocus to converge.
45      */
46     static final long PREVIEW_AUTOFOCUS_SLEEP_MS = 400;
47 
48     /**
49      * Initialized after {@link ItsService} configures and creates the session.
50      */
51     volatile CameraCaptureSession mSession;
52 
53     /**
54      * Initialized after {@link ItsService} configures and creates the session.
55      */
56     volatile CaptureRequest.Builder mCaptureRequestBuilder;
57 
58     /**
59      * {@link CameraCharacteristics} that are initialized when the camera is opened.
60      */
61     CameraCharacteristics mCameraCharacteristics;
62 
63     /**
64      * The {@link android.os.Handler} on which the listener should be invoked.
65      */
66     Handler mCameraHandler;
67 
68     /**
69      * The {@link com.android.cts.verifier.camera.its.ItsService.RecordingResultListener} that
70      * tracks certain {@link android.hardware.camera2.TotalCaptureResult} values received
71      * during recording.
72      */
73     ItsService.RecordingResultListener mRecordingResultListener;
74 
IntraPreviewAction( CameraCharacteristics cameraCharacteristics, Handler handler, ItsService.RecordingResultListener recordingResultListener)75     protected IntraPreviewAction(
76             CameraCharacteristics cameraCharacteristics,
77             Handler handler,
78             ItsService.RecordingResultListener recordingResultListener) {
79         mCameraCharacteristics = cameraCharacteristics;
80         mCameraHandler = handler;
81         mRecordingResultListener = recordingResultListener;
82     }
83 
84     /**
85      * Sets the value for the current {@link CameraCaptureSession}.
86      */
setSession(@onNull CameraCaptureSession session)87     void setSession(@NonNull CameraCaptureSession session) {
88         mSession = session;
89     }
90 
91     /**
92      * Sets the value for the current {@link CaptureRequest.Builder}, so that
93      * {@link IntraPreviewAction} can update the same {@link CaptureRequest} used during
94      * configuration.
95      */
setCaptureRequestBuilder(@onNull CaptureRequest.Builder builder)96     void setCaptureRequestBuilder(@NonNull CaptureRequest.Builder builder) {
97         mCaptureRequestBuilder = builder;
98     }
99 
100     /**
101      * Gets the value for the current
102      * {@link com.android.cts.verifier.camera.its.ItsService.RecordingResultListener}.
103      */
getRecordingResultListener()104     ItsService.RecordingResultListener getRecordingResultListener() {
105         return mRecordingResultListener;
106     }
107 
108     /**
109      * Perform actions between {@link PreviewRecorder#startRecording()} and
110      * {@link CameraCaptureSession#stopRepeating()}. The execute() method can be used to define the
111      * duration of the recording, using {@link Thread#sleep(long)}. The method can also call
112      * {@link CameraCaptureSession#setRepeatingRequest(CaptureRequest, CameraCaptureSession.CaptureCallback, Handler)}
113      * to change the requests during the recording.
114      *
115      * @throws InterruptedException if {@link Thread#sleep(long)} was interrupted.
116      * @throws CameraAccessException if a camera device could not be opened to set requests.
117      * @throws ItsException if parsing a JSONObject or JSONArray was unsuccessful.
118      */
execute()119     public abstract void execute() throws
120             InterruptedException, CameraAccessException, ItsException;
121 }
122 
123 /**
124  * A simple action that sleeps for a given duration during a preview recording.
125  */
126 class PreviewSleepAction extends IntraPreviewAction {
127     long mRecordingDuration;
128 
PreviewSleepAction( CameraCharacteristics cameraCharacteristics, Handler handler, ItsService.RecordingResultListener recordingResultListener, long recordingDuration)129     PreviewSleepAction(
130             CameraCharacteristics cameraCharacteristics,
131             Handler handler,
132             ItsService.RecordingResultListener recordingResultListener,
133             long recordingDuration) {
134         super(cameraCharacteristics, handler, recordingResultListener);
135         mRecordingDuration = recordingDuration;
136     }
137 
138     @Override
execute()139     public void execute() throws InterruptedException {
140         Thread.sleep(mRecordingDuration);
141     }
142 }
143 
144 /**
145  * An action that sets new repeating {@link CaptureRequest}s to change zoom ratios during recording.
146  */
147 class PreviewDynamicZoomAction extends IntraPreviewAction {
148     double mZoomStart;
149     double mZoomEnd;
150     double mStepSize;
151     long mStepDuration;
PreviewDynamicZoomAction( CameraCharacteristics cameraCharacteristics, Handler handler, ItsService.RecordingResultListener recordingResultListener, double zoomStart, double zoomEnd, double stepSize, long stepDuration)152     PreviewDynamicZoomAction(
153             CameraCharacteristics cameraCharacteristics,
154             Handler handler,
155             ItsService.RecordingResultListener recordingResultListener,
156             double zoomStart, double zoomEnd, double stepSize, long stepDuration) {
157         super(cameraCharacteristics, handler,  recordingResultListener);
158         mZoomStart = zoomStart;
159         mZoomEnd = zoomEnd;
160         mStepSize = stepSize;
161         mStepDuration = stepDuration;
162     }
163 
164     @Override
execute()165     public void execute() throws InterruptedException, CameraAccessException {
166         // Allow autofocus to converge
167         // TODO: b/333791992 - replace with waiting for desired AF state in CaptureResult
168         Thread.sleep(PREVIEW_AUTOFOCUS_SLEEP_MS);
169         for (double z = mZoomStart; z <= mZoomEnd; z += mStepSize) {
170             Logt.i(ItsService.TAG, String.format(
171                     Locale.getDefault(),
172                     "zoomRatio set to %.4f during preview recording.", z));
173             mCaptureRequestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, (float) z);
174             mSession.setRepeatingRequest(mCaptureRequestBuilder.build(),
175                     mRecordingResultListener, mCameraHandler);
176             Logt.i(ItsService.TAG, String.format(
177                     Locale.getDefault(),
178                     "Sleeping %d ms during video recording", mStepDuration));
179             Thread.sleep(mStepDuration);
180         }
181         Thread.sleep(PREVIEW_RECORDING_FINAL_SLEEP_MS);
182     }
183 }
184 
185 /**
186  * An action that sets new repeating {@link CaptureRequest}s to change metering regions during
187  * recording.
188  */
189 class PreviewDynamicMeteringAction extends IntraPreviewAction {
190     JSONArray mAeAwbRegionOne;
191     JSONArray mAeAwbRegionTwo;
192     JSONArray mAeAwbRegionThree;
193     JSONArray mAeAwbRegionFour;
194     long mAeAwbRegionDuration;
195 
PreviewDynamicMeteringAction( CameraCharacteristics cameraCharacteristics, Handler handler, ItsService.RecordingResultListener recordingResultListener, JSONArray aeAwbRegionOne, JSONArray aeAwbRegionTwo, JSONArray aeAwbRegionThree, JSONArray aeAwbRegionFour, long aeAwbRegionDuration)196     PreviewDynamicMeteringAction(
197             CameraCharacteristics cameraCharacteristics,
198             Handler handler,
199             ItsService.RecordingResultListener recordingResultListener,
200             JSONArray aeAwbRegionOne,
201             JSONArray aeAwbRegionTwo,
202             JSONArray aeAwbRegionThree,
203             JSONArray aeAwbRegionFour,
204             long aeAwbRegionDuration) {
205         super(cameraCharacteristics, handler, recordingResultListener);
206         mAeAwbRegionOne = aeAwbRegionOne;
207         mAeAwbRegionTwo = aeAwbRegionTwo;
208         mAeAwbRegionThree = aeAwbRegionThree;
209         mAeAwbRegionFour = aeAwbRegionFour;
210         mAeAwbRegionDuration = aeAwbRegionDuration;
211     }
212 
213     @Override
execute()214     public void execute() throws InterruptedException, CameraAccessException, ItsException {
215         // Allow autofocus to converge
216         // TODO: b/333791992 - replace with waiting for desired AF state in CaptureResult
217         Thread.sleep(PREVIEW_AUTOFOCUS_SLEEP_MS);
218         Rect activeArray = mCameraCharacteristics.get(
219                 CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
220         assert activeArray != null;
221         int aaWidth = activeArray.right - activeArray.left;
222         int aaHeight = activeArray.bottom - activeArray.top;
223         JSONArray[] aeAwbRegionRoutine = {
224                 mAeAwbRegionOne, mAeAwbRegionTwo, mAeAwbRegionThree, mAeAwbRegionFour};
225         for (JSONArray aeAwbRegion : aeAwbRegionRoutine) {
226             MeteringRectangle[] region = ItsUtils.getJsonWeightedRectsFromArray(
227                     aeAwbRegion, /*normalized=*/true, aaWidth, aaHeight);
228             Logt.i(ItsService.TAG, String.format(
229                     Locale.getDefault(),
230                     "AE/AWB region set to %s during preview recording.",
231                     Arrays.toString(region)));
232             mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, region);
233             mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AWB_REGIONS, region);
234             mSession.setRepeatingRequest(mCaptureRequestBuilder.build(),
235                     mRecordingResultListener, mCameraHandler);
236             Logt.i(ItsService.TAG, String.format(
237                     Locale.getDefault(),
238                     "Sleeping %d ms during recording", mAeAwbRegionDuration));
239             Thread.sleep(mAeAwbRegionDuration);
240         }
241         Thread.sleep(PREVIEW_RECORDING_FINAL_SLEEP_MS);
242     }
243 }
244