1 /*
2  * Copyright 2020 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.camera2.internal.compat;
18 
19 import android.hardware.camera2.CameraCharacteristics;
20 import android.hardware.camera2.CameraMetadata;
21 import android.hardware.camera2.params.StreamConfigurationMap;
22 import android.os.Build;
23 
24 import androidx.annotation.GuardedBy;
25 import androidx.annotation.IntRange;
26 import androidx.annotation.VisibleForTesting;
27 import androidx.camera.camera2.internal.compat.workaround.OutputSizesCorrector;
28 
29 import org.jspecify.annotations.NonNull;
30 import org.jspecify.annotations.Nullable;
31 
32 import java.util.HashMap;
33 import java.util.Map;
34 import java.util.Set;
35 
36 /**
37  * A wrapper for {@link CameraCharacteristics} which caches the retrieved values to optimize
38  * the latency and might contain backward compatible fixes for certain parameters.
39  */
40 public class CameraCharacteristicsCompat {
41     @GuardedBy("this")
42     private final @NonNull Map<CameraCharacteristics.Key<?>, Object> mValuesCache = new HashMap<>();
43     private final @NonNull CameraCharacteristicsCompatImpl mCameraCharacteristicsImpl;
44     private final @NonNull String mCameraId;
45 
46     private @Nullable StreamConfigurationMapCompat mStreamConfigurationMapCompat = null;
47 
CameraCharacteristicsCompat(@onNull CameraCharacteristics cameraCharacteristics, @NonNull String cameraId)48     private CameraCharacteristicsCompat(@NonNull CameraCharacteristics cameraCharacteristics,
49             @NonNull String cameraId) {
50         if (Build.VERSION.SDK_INT >= 28) {
51             mCameraCharacteristicsImpl = new CameraCharacteristicsApi28Impl(cameraCharacteristics);
52         } else {
53             mCameraCharacteristicsImpl = new CameraCharacteristicsBaseImpl(cameraCharacteristics);
54         }
55         mCameraId = cameraId;
56     }
57 
58     /**
59      * Tests might need to create CameraCharacteristicsCompat directly for convenience. Elsewhere
60      * we should get the CameraCharacteristicsCompat instance from {@link CameraManagerCompat}.
61      */
62     @VisibleForTesting
toCameraCharacteristicsCompat( @onNull CameraCharacteristics characteristics, @NonNull String cameraId)63     public static @NonNull CameraCharacteristicsCompat toCameraCharacteristicsCompat(
64             @NonNull CameraCharacteristics characteristics, @NonNull String cameraId) {
65         return new CameraCharacteristicsCompat(characteristics, cameraId);
66     }
67 
68     /**
69      * Return true if the key should be retrieved from {@link CameraCharacteristics} without
70      * caching it.
71      */
isKeyNonCacheable(CameraCharacteristics.@onNull Key<?> key)72     private boolean isKeyNonCacheable(CameraCharacteristics.@NonNull Key<?> key) {
73         // SENSOR_ORIENTATION value should change in some circumstances.
74         return key.equals(CameraCharacteristics.SENSOR_ORIENTATION);
75     }
76 
77     /**
78      * Gets a camera characteristics field value and caches the value for later use.
79      *
80      * <p>It will cache the value once get() is called. If get() is called more than once using
81      * the same key, it will return instantly.
82      *
83      * @param key The characteristics field to read.
84      * @return The value of that key, or null if the field is not set.
85      */
get(CameraCharacteristics.@onNull Key<T> key)86     public <T> @Nullable T get(CameraCharacteristics.@NonNull Key<T> key) {
87         // For some keys that will have varying value and cannot be cached, we need to always
88         // retrieve the key from the CameraCharacteristics.
89         if (isKeyNonCacheable(key)) {
90             return mCameraCharacteristicsImpl.get(key);
91         }
92 
93         synchronized (this) {
94             @SuppressWarnings("unchecked") // The value type always matches the key type.
95             T value = (T) mValuesCache.get(key);
96             if (value != null) {
97                 return value;
98             }
99 
100             value = mCameraCharacteristicsImpl.get(key);
101             if (value != null) {
102                 mValuesCache.put(key, value);
103             }
104             return value;
105         }
106     }
107 
108     /**
109      * Returns the physical camera Ids if it is a logical camera. Otherwise it would
110      * return an empty set.
111      */
getPhysicalCameraIds()112     public @NonNull Set<String> getPhysicalCameraIds() {
113         return mCameraCharacteristicsImpl.getPhysicalCameraIds();
114     }
115 
116     /**
117      * Returns {@code true} if overriding zoom setting is available, otherwise {@code false}.
118      */
isZoomOverrideAvailable()119     public boolean isZoomOverrideAvailable() {
120         if (Build.VERSION.SDK_INT >= 34) {
121             int[] availableSettingsOverrides = mCameraCharacteristicsImpl.get(
122                     CameraCharacteristics.CONTROL_AVAILABLE_SETTINGS_OVERRIDES);
123             if (availableSettingsOverrides != null) {
124                 for (int i : availableSettingsOverrides) {
125                     if (i == CameraMetadata.CONTROL_SETTINGS_OVERRIDE_ZOOM) {
126                         return true;
127                     }
128                 }
129             }
130         }
131         return false;
132     }
133 
134     /**
135      * Returns the default torch strength level.
136      */
getDefaultTorchStrengthLevel()137     public int getDefaultTorchStrengthLevel() {
138         Integer defaultLevel = null;
139         if (hasFlashUnit() && Build.VERSION.SDK_INT >= 35) {
140             defaultLevel = get(CameraCharacteristics.FLASH_TORCH_STRENGTH_DEFAULT_LEVEL);
141         }
142         // The framework returns 1 when the device doesn't support configuring torch strength. So
143         // also return 1 if the device doesn't have flash unit or is unable to provide the
144         // information.
145         return defaultLevel == null ? 1 : defaultLevel;
146     }
147 
148     /**
149      * Returns the maximum torch strength level.
150      */
151     @IntRange(from = 1)
getMaxTorchStrengthLevel()152     public int getMaxTorchStrengthLevel() {
153         Integer maxLevel = null;
154         if (hasFlashUnit() && Build.VERSION.SDK_INT >= 35) {
155             maxLevel = get(CameraCharacteristics.FLASH_TORCH_STRENGTH_MAX_LEVEL);
156         }
157         // The framework returns 1 when the device doesn't support configuring torch strength. So
158         // also return 1 if the device doesn't have flash unit or is unable to provide the
159         // information.
160         return maxLevel == null ? 1 : maxLevel;
161     }
162 
isTorchStrengthLevelSupported()163     public boolean isTorchStrengthLevelSupported() {
164         return hasFlashUnit() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM
165                 && getMaxTorchStrengthLevel() > 1;
166     }
167 
hasFlashUnit()168     private boolean hasFlashUnit() {
169         Boolean flashInfoAvailable = get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
170         return flashInfoAvailable != null && flashInfoAvailable;
171     }
172 
173     /**
174      * Obtains the {@link StreamConfigurationMapCompat} which contains the output sizes related
175      * workarounds in it.
176      */
getStreamConfigurationMapCompat()177     public @NonNull StreamConfigurationMapCompat getStreamConfigurationMapCompat() {
178         if (mStreamConfigurationMapCompat == null) {
179             StreamConfigurationMap map;
180             try {
181                 map = get(
182                         CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
183             } catch (NullPointerException | AssertionError e) {
184                 // Some devices may throw AssertionError when querying stream configuration map
185                 // from CameraCharacteristics during bindToLifecycle. Catch the AssertionError and
186                 // throw IllegalArgumentException so app level can decide how to handle.
187                 throw new IllegalArgumentException(e.getMessage());
188             }
189             if (map == null) {
190                 throw new IllegalArgumentException("StreamConfigurationMap is null!");
191             }
192             OutputSizesCorrector outputSizesCorrector = new OutputSizesCorrector(mCameraId);
193             mStreamConfigurationMapCompat =
194                     StreamConfigurationMapCompat.toStreamConfigurationMapCompat(map,
195                             outputSizesCorrector);
196         }
197 
198         return mStreamConfigurationMapCompat;
199     }
200 
201     /**
202      * Returns the {@link CameraCharacteristics} represented by this object.
203      */
toCameraCharacteristics()204     public @NonNull CameraCharacteristics toCameraCharacteristics() {
205         return mCameraCharacteristicsImpl.unwrap();
206     }
207 
208     /**
209      * Returns the camera id associated with the camera characteristics.
210      */
getCameraId()211     public @NonNull String getCameraId() {
212         return mCameraId;
213     }
214 
215     /**
216      * CameraCharacteristic Implementation Interface
217      */
218     public interface CameraCharacteristicsCompatImpl {
219         /**
220          * Gets the key/values from the CameraCharacteristics.
221          */
get(CameraCharacteristics.@onNull Key<T> key)222         <T> @Nullable T get(CameraCharacteristics.@NonNull Key<T> key);
223 
224         /**
225          * Gets physical camera ids.
226          */
getPhysicalCameraIds()227         @NonNull Set<String> getPhysicalCameraIds();
228 
229         /**
230          * Returns the underlying {@link CameraCharacteristics} instance.
231          */
unwrap()232         @NonNull CameraCharacteristics unwrap();
233     }
234 }