1 /*
2  * Copyright 2023 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 androidx.camera.core.impl;
18 
19 import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON;
20 import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION;
21 
22 import android.util.Range;
23 import android.util.Rational;
24 
25 import androidx.annotation.IntDef;
26 import androidx.camera.core.ExposureState;
27 import androidx.camera.core.FocusMeteringAction;
28 import androidx.camera.core.TorchState;
29 import androidx.camera.core.ZoomState;
30 import androidx.camera.core.impl.utils.LiveDataUtil;
31 import androidx.camera.core.impl.utils.SessionProcessorUtil;
32 import androidx.camera.core.internal.ImmutableZoomState;
33 import androidx.core.math.MathUtils;
34 import androidx.lifecycle.LiveData;
35 import androidx.lifecycle.MutableLiveData;
36 
37 import org.jspecify.annotations.NonNull;
38 import org.jspecify.annotations.Nullable;
39 
40 import java.lang.annotation.Retention;
41 import java.lang.annotation.RetentionPolicy;
42 
43 /**
44  * A {@link CameraInfoInternal} that returns disabled state if the corresponding operation in the
45  * given {@link AdapterCameraControl} is disabled.
46  */
47 public class AdapterCameraInfo extends ForwardingCameraInfo {
48     /**
49      * Defines the list of supported camera operations.
50      */
51     public static final int CAMERA_OPERATION_ZOOM = 0;
52     public static final int CAMERA_OPERATION_AUTO_FOCUS = 1;
53     public static final int CAMERA_OPERATION_AF_REGION = 2;
54     public static final int CAMERA_OPERATION_AE_REGION = 3;
55     public static final int CAMERA_OPERATION_AWB_REGION = 4;
56     public static final int CAMERA_OPERATION_FLASH = 5;
57     public static final int CAMERA_OPERATION_TORCH = 6;
58     public static final int CAMERA_OPERATION_EXPOSURE_COMPENSATION = 7;
59     public static final int CAMERA_OPERATION_EXTENSION_STRENGTH = 8;
60 
61     @IntDef({CAMERA_OPERATION_ZOOM, CAMERA_OPERATION_AUTO_FOCUS, CAMERA_OPERATION_AF_REGION,
62             CAMERA_OPERATION_AE_REGION, CAMERA_OPERATION_AWB_REGION, CAMERA_OPERATION_FLASH,
63             CAMERA_OPERATION_TORCH, CAMERA_OPERATION_EXPOSURE_COMPENSATION,
64             CAMERA_OPERATION_EXTENSION_STRENGTH})
65     @Retention(RetentionPolicy.SOURCE)
66     public @interface CameraOperation {
67     }
68 
69     private final CameraInfoInternal mCameraInfo;
70     private final @Nullable SessionProcessor mSessionProcessor;
71     private boolean mIsPostviewSupported = false;
72     private boolean mIsCaptureProcessProgressSupported = false;
73     private final @NonNull CameraConfig mCameraConfig;
74     private @Nullable LiveData<ZoomState> mExtensionZoomStateLiveData = null;
75 
AdapterCameraInfo(@onNull CameraInfoInternal cameraInfo, @NonNull CameraConfig cameraConfig)76     public AdapterCameraInfo(@NonNull CameraInfoInternal cameraInfo,
77             @NonNull CameraConfig cameraConfig) {
78         super(cameraInfo);
79         mCameraInfo = cameraInfo;
80         mCameraConfig = cameraConfig;
81         mSessionProcessor = cameraConfig.getSessionProcessor(null);
82 
83         setPostviewSupported(cameraConfig.isPostviewSupported());
84         setCaptureProcessProgressSupported(cameraConfig.isCaptureProcessProgressSupported());
85     }
86 
getCameraConfig()87     public @NonNull CameraConfig getCameraConfig() {
88         return mCameraConfig;
89     }
90 
91     @Override
getImplementation()92     public @NonNull CameraInfoInternal getImplementation() {
93         return mCameraInfo;
94     }
95 
96     /**
97      * Returns the session processor associated with the AdapterCameraInfo.
98      */
getSessionProcessor()99     public @Nullable SessionProcessor getSessionProcessor() {
100         return mSessionProcessor;
101     }
102 
103     @Override
hasFlashUnit()104     public boolean hasFlashUnit() {
105         if (!SessionProcessorUtil.isOperationSupported(mSessionProcessor, CAMERA_OPERATION_FLASH)) {
106             return false;
107         }
108 
109         return mCameraInfo.hasFlashUnit();
110     }
111 
112     @Override
getTorchState()113     public @NonNull LiveData<Integer> getTorchState() {
114         if (!SessionProcessorUtil.isOperationSupported(mSessionProcessor, CAMERA_OPERATION_TORCH)) {
115             return new MutableLiveData<>(TorchState.OFF);
116         }
117 
118         return mCameraInfo.getTorchState();
119     }
120 
121     /**
122      * Return the zoom ratio calculated by the linear zoom (percentage)
123      */
getZoomRatioByPercentage(float percentage, float minZoomRatio, float maxZoomRatio)124     public static float getZoomRatioByPercentage(float percentage,
125             float minZoomRatio, float maxZoomRatio) {
126         // Make sure 1.0f and 0.0 return exactly the same max/min ratio.
127         if (percentage == 1.0f) {
128             return maxZoomRatio;
129         } else if (percentage == 0f) {
130             return minZoomRatio;
131         }
132         // This crop width is proportional to the real crop width.
133         // The real crop with = sensorWidth/ zoomRatio,  but we need the ratio only so we can
134         // assume sensorWidth as 1.0f.
135         double cropWidthInMaxZoom = 1.0f / maxZoomRatio;
136         double cropWidthInMinZoom = 1.0f / minZoomRatio;
137 
138         double cropWidth = cropWidthInMinZoom + (cropWidthInMaxZoom - cropWidthInMinZoom)
139                 * percentage;
140 
141         double ratio = 1.0 / cropWidth;
142 
143         return (float) MathUtils.clamp(ratio, minZoomRatio, maxZoomRatio);
144     }
145 
146     /**
147      * Return the linear zoom (percentage) calculated by the zoom ratio.
148      */
getPercentageByRatio(float ratio, float minZoomRatio, float maxZoomRatio)149     public static float getPercentageByRatio(float ratio, float minZoomRatio, float maxZoomRatio) {
150         // if zoom is not supported, return 0
151         if (maxZoomRatio == minZoomRatio) {
152             return 0f;
153         }
154 
155         // To make the min/max same value when doing conversion between ratio / percentage.
156         // We return the max/min value directly.
157         if (ratio == maxZoomRatio) {
158             return 1f;
159         } else if (ratio == minZoomRatio) {
160             return 0f;
161         }
162 
163         float cropWidth = 1.0f / ratio;
164         float cropWidthInMaxZoom = 1.0f / maxZoomRatio;
165         float cropWidthInMinZoom = 1.0f / minZoomRatio;
166 
167         return (cropWidth - cropWidthInMinZoom) / (cropWidthInMaxZoom - cropWidthInMinZoom);
168     }
169 
170     @Override
getZoomState()171     public @NonNull LiveData<ZoomState> getZoomState() {
172         if (!SessionProcessorUtil.isOperationSupported(mSessionProcessor, CAMERA_OPERATION_ZOOM)) {
173             return new MutableLiveData<>(ImmutableZoomState.create(
174                     /* zoomRatio */1f, /* maxZoomRatio */ 1f,
175                     /* minZoomRatio */ 1f, /* linearZoom*/ 0f));
176         }
177 
178         if (mSessionProcessor != null) {
179             ZoomState zoomState = mCameraInfo.getZoomState().getValue();
180             Range<Float> extensionsZoomRange = mSessionProcessor.getExtensionZoomRange();
181             if (extensionsZoomRange != null
182                     && (extensionsZoomRange.getLower() != zoomState.getMinZoomRatio()
183                     || extensionsZoomRange.getUpper() != zoomState.getMaxZoomRatio())) {
184                 if (mExtensionZoomStateLiveData == null) {
185                     // Transform the zoomState to have adjusted maxzoom, minzoom and linear zoom
186                     mExtensionZoomStateLiveData = LiveDataUtil.map(mCameraInfo.getZoomState(),
187                             (state) -> {
188                                 return ImmutableZoomState.create(
189                                         state.getZoomRatio(),
190                                         extensionsZoomRange.getUpper(),
191                                         extensionsZoomRange.getLower(),
192                                         getPercentageByRatio(state.getZoomRatio(),
193                                                 extensionsZoomRange.getLower(),
194                                                 extensionsZoomRange.getUpper())
195                                 );
196                             });
197                 }
198                 return mExtensionZoomStateLiveData;
199             }
200         }
201         return mCameraInfo.getZoomState();
202     }
203 
204     @Override
getExposureState()205     public @NonNull ExposureState getExposureState() {
206         if (!SessionProcessorUtil.isOperationSupported(mSessionProcessor,
207                 CAMERA_OPERATION_EXPOSURE_COMPENSATION)) {
208             return new ExposureState() {
209                 @Override
210                 public int getExposureCompensationIndex() {
211                     return 0;
212                 }
213 
214                 @Override
215                 public @NonNull Range<Integer> getExposureCompensationRange() {
216                     return new Range<>(0, 0);
217                 }
218 
219                 @Override
220                 public @NonNull Rational getExposureCompensationStep() {
221                     return Rational.ZERO;
222                 }
223 
224                 @Override
225                 public boolean isExposureCompensationSupported() {
226                     return false;
227                 }
228             };
229         }
230         return mCameraInfo.getExposureState();
231     }
232 
233     @Override
234     public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
235         FocusMeteringAction modifiedAction =
236                 SessionProcessorUtil.getModifiedFocusMeteringAction(mSessionProcessor, action);
237         if (modifiedAction == null) {
238             return false;
239         }
240         return mCameraInfo.isFocusMeteringSupported(modifiedAction);
241     }
242 
243     /**
244      * Sets if postview is supported or not.
245      */
246     public void setPostviewSupported(boolean isPostviewSupported) {
247         mIsPostviewSupported = isPostviewSupported;
248     }
249 
250     /**
251      * Sets if capture process progress is supported or not.
252      */
253     public void setCaptureProcessProgressSupported(boolean isCaptureProcessProgressSupported) {
254         mIsCaptureProcessProgressSupported = isCaptureProcessProgressSupported;
255     }
256 
257     /**
258      * Returns if postview is supported.
259      */
260     @Override
261     public boolean isPostviewSupported() {
262         return mIsPostviewSupported;
263     }
264 
265     @Override
266     public boolean isCaptureProcessProgressSupported() {
267         return mIsCaptureProcessProgressSupported;
268     }
269 
270     @Override
271     public boolean isVideoStabilizationSupported() {
272         if (mSessionProcessor != null) {
273             int[] stabilizationModes = mSessionProcessor.getExtensionAvailableStabilizationModes();
274             if (stabilizationModes != null) {
275                 for (int mode : stabilizationModes) {
276                     if (mode == CONTROL_VIDEO_STABILIZATION_MODE_ON) {
277                         return true;
278                     }
279                 }
280                 return false;
281             }
282         }
283         return super.isVideoStabilizationSupported();
284     }
285 
286     @Override
287     public boolean isPreviewStabilizationSupported() {
288         if (mSessionProcessor != null) {
289             int[] stabilizationModes = mSessionProcessor.getExtensionAvailableStabilizationModes();
290             if (stabilizationModes != null) {
291                 for (int mode : stabilizationModes) {
292                     if (mode == CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION) {
293                         return true;
294                     }
295                 }
296                 return false;
297             }
298         }
299         return super.isPreviewStabilizationSupported();
300     }
301 }
302