1 /*
2  * Copyright 2014 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 android.hardware.cts.helpers;
18 
19 import static org.junit.Assert.assertNotNull;
20 
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.graphics.SurfaceTexture;
24 import android.hardware.Camera;
25 import android.hardware.Camera.Parameters;
26 import android.hardware.Camera.Size;
27 import android.hardware.camera2.CameraCharacteristics;
28 import android.hardware.camera2.CameraManager;
29 import android.hardware.camera2.CameraMetadata;
30 import android.hardware.camera2.cts.helpers.StaticMetadata;
31 import android.hardware.devicestate.DeviceState;
32 import android.hardware.devicestate.DeviceStateManager;
33 import android.os.Bundle;
34 import android.os.SystemClock;
35 import android.util.Log;
36 import android.view.TextureView;
37 
38 import androidx.test.InstrumentationRegistry;
39 
40 import java.util.Arrays;
41 import java.util.Comparator;
42 import java.util.List;
43 import java.util.Set;
44 import java.util.stream.Collectors;
45 import java.util.stream.IntStream;
46 
47 /**
48  * Utility class containing helper functions for the Camera CTS tests.
49  */
50 public class CameraUtils {
51     private static final float FOCAL_LENGTH_TOLERANCE = .01f;
52 
53 
54     private static final String TAG = "CameraUtils";
55     private static final long SHORT_SLEEP_WAIT_TIME_MS = 100;
56 
57     /**
58      * Returns {@code true} if this device only supports {@code LEGACY} mode operation in the
59      * Camera2 API for the given camera ID.
60      *
61      * @param context {@link Context} to access the {@link CameraManager} in.
62      * @param cameraId the ID of the camera device to check.
63      * @return {@code true} if this device only supports {@code LEGACY} mode.
64      */
isLegacyHAL(Context context, int cameraId)65     public static boolean isLegacyHAL(Context context, int cameraId) throws Exception {
66         CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
67         String cameraIdStr = manager.getCameraIdListNoLazy()[cameraId];
68         return isLegacyHAL(manager, cameraIdStr);
69     }
70 
71     /**
72      * Returns {@code true} if this device only supports {@code LEGACY} mode operation in the
73      * Camera2 API for the given camera ID.
74      *
75      * @param manager The {@link CameraManager} used to retrieve camera characteristics.
76      * @param cameraId the ID of the camera device to check.
77      * @return {@code true} if this device only supports {@code LEGACY} mode.
78      */
isLegacyHAL(CameraManager manager, String cameraIdStr)79     public static boolean isLegacyHAL(CameraManager manager, String cameraIdStr) throws Exception {
80         CameraCharacteristics characteristics =
81                 manager.getCameraCharacteristics(cameraIdStr);
82 
83         return characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
84                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
85     }
86 
87     /**
88      * Returns {@code true} if the Camera.Parameter and Camera.Info arguments describe a similar
89      * camera as the CameraCharacteristics.
90      *
91      * @param params Camera.Parameters to use for matching.
92      * @param info Camera.CameraInfo to use for matching.
93      * @param ch CameraCharacteristics to use for matching.
94      * @return {@code true} if the arguments describe similar camera devices.
95      */
matchParametersToCharacteristics(Camera.Parameters params, Camera.CameraInfo info, CameraCharacteristics ch)96     public static boolean matchParametersToCharacteristics(Camera.Parameters params,
97             Camera.CameraInfo info, CameraCharacteristics ch) {
98         Integer facing = ch.get(CameraCharacteristics.LENS_FACING);
99         switch (facing.intValue()) {
100             case CameraMetadata.LENS_FACING_EXTERNAL:
101                 if (info.facing != Camera.CameraInfo.CAMERA_FACING_FRONT &&
102                     info.facing != Camera.CameraInfo.CAMERA_FACING_BACK) {
103                     return false;
104                 }
105                 break;
106             case CameraMetadata.LENS_FACING_FRONT:
107                 if (info.facing != Camera.CameraInfo.CAMERA_FACING_FRONT) {
108                     return false;
109                 }
110                 break;
111             case CameraMetadata.LENS_FACING_BACK:
112                 if (info.facing != Camera.CameraInfo.CAMERA_FACING_BACK) {
113                     return false;
114                 }
115                 break;
116             default:
117                 return false;
118         }
119 
120         Integer orientation = ch.get(CameraCharacteristics.SENSOR_ORIENTATION);
121         if (orientation.intValue() != info.orientation) {
122             return false;
123         }
124 
125         StaticMetadata staticMeta = new StaticMetadata(ch);
126         boolean legacyHasFlash = params.getSupportedFlashModes() != null;
127         if (staticMeta.hasFlash() != legacyHasFlash) {
128             return false;
129         }
130 
131         boolean isExternal = (ch.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
132                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL);
133         boolean hasValidMinFocusDistance = staticMeta.areKeysAvailable(
134                 CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
135         boolean fixedFocusExternal = isExternal && !hasValidMinFocusDistance;
136         boolean hasFocuser = staticMeta.hasFocuser() && !fixedFocusExternal;
137         List<String> legacyFocusModes = params.getSupportedFocusModes();
138         boolean legacyHasFocuser = !((legacyFocusModes.size() == 1) &&
139                 (legacyFocusModes.contains(Camera.Parameters.FOCUS_MODE_FIXED)));
140         if (hasFocuser != legacyHasFocuser) {
141             return false;
142         }
143 
144         if (staticMeta.isVideoStabilizationSupported() !=
145                 params.isVideoStabilizationSupported()) {
146             return false;
147         }
148 
149         float legacyFocalLength = params.getFocalLength();
150         if (ch.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) != null) {
151             float [] focalLengths = staticMeta.getAvailableFocalLengthsChecked();
152             boolean found = false;
153             for (float focalLength : focalLengths) {
154                 if (Math.abs(focalLength - legacyFocalLength) <= FOCAL_LENGTH_TOLERANCE) {
155                     found = true;
156                     break;
157                 }
158             }
159             return found;
160         } else if (legacyFocalLength != -1.0f) {
161             return false;
162         }
163 
164         return true;
165     }
166 
167     /**
168      * Returns {@code true} if this device only supports {@code EXTERNAL} mode operation in the
169      * Camera2 API for the given camera ID.
170      *
171      * @param context {@link Context} to access the {@link CameraManager} in.
172      * @param cameraId the ID of the camera device to check.
173      * @return {@code true} if this device only supports {@code LEGACY} mode.
174      */
isExternal(Context context, int cameraId)175     public static boolean isExternal(Context context, int cameraId) throws Exception {
176         CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
177 
178         Camera camera = null;
179         Camera.Parameters params = null;
180         Camera.CameraInfo info = new Camera.CameraInfo();
181         try {
182             Camera.getCameraInfo(cameraId, info);
183             camera = Camera.open(cameraId);
184             params = camera.getParameters();
185         } finally {
186             if (camera != null) {
187                 camera.release();
188             }
189         }
190 
191         String [] cameraIdList = manager.getCameraIdList();
192         CameraCharacteristics characteristics =
193                 manager.getCameraCharacteristics(cameraIdList[cameraId]);
194 
195         if (!matchParametersToCharacteristics(params, info, characteristics)) {
196             boolean found = false;
197             for (String id : cameraIdList) {
198                 characteristics = manager.getCameraCharacteristics(id);
199                 if (matchParametersToCharacteristics(params, info, characteristics)) {
200                     found = true;
201                     break;
202                 }
203             }
204             if (!found) {
205                 return false;
206             }
207         }
208 
209         return characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
210                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL;
211     }
212 
213     /**
214      * Shared size comparison method used by size comparators.
215      *
216      * <p>Compares the number of pixels it covers.If two the areas of two sizes are same, compare
217      * the widths.</p>
218      */
compareSizes(int widthA, int heightA, int widthB, int heightB)219      public static int compareSizes(int widthA, int heightA, int widthB, int heightB) {
220         long left = widthA * (long) heightA;
221         long right = widthB * (long) heightB;
222         if (left == right) {
223             left = widthA;
224             right = widthB;
225         }
226         return (left < right) ? -1 : (left > right ? 1 : 0);
227     }
228 
229     /**
230      * Size comparator that compares the number of pixels it covers.
231      *
232      * <p>If two the areas of two sizes are same, compare the widths.</p>
233      */
234     public static class LegacySizeComparator implements Comparator<Camera.Size> {
235         @Override
compare(Camera.Size lhs, Camera.Size rhs)236         public int compare(Camera.Size lhs, Camera.Size rhs) {
237             return compareSizes(lhs.width, lhs.height, rhs.width, rhs.height);
238         }
239     }
240 
getOverrideCameraId()241     public static String getOverrideCameraId() {
242         Bundle bundle = InstrumentationRegistry.getArguments();
243         return bundle.getString("camera-id");
244     }
245 
deriveCameraIdsUnderTest()246     public static int[] deriveCameraIdsUnderTest() throws Exception {
247         String overrideId = getOverrideCameraId();
248         int numberOfCameras = Camera.getNumberOfCameras();
249         int[] cameraIds;
250         if (overrideId == null) {
251             cameraIds = IntStream.range(0, numberOfCameras).toArray();
252         } else {
253             int overrideCameraId = Integer.parseInt(overrideId);
254             if (overrideCameraId >= 0 && overrideCameraId < numberOfCameras) {
255                 cameraIds = new int[]{overrideCameraId};
256             } else {
257                 cameraIds = new int[]{};
258             }
259         }
260         return cameraIds;
261     }
262 
263     /**
264      * Wait until the SurfaceTexture available from the TextureView, then return it.
265      * Return null if the wait times out.
266      *
267      * @param timeOutMs The timeout value for the wait
268      * @return The available SurfaceTexture, return null if the wait times out.
269     */
getAvailableSurfaceTexture(long timeOutMs, TextureView view)270     public static SurfaceTexture getAvailableSurfaceTexture(long timeOutMs, TextureView view) {
271         long waitTime = timeOutMs;
272 
273         while (!view.isAvailable() && waitTime > 0) {
274             long startTimeMs = SystemClock.elapsedRealtime();
275             SystemClock.sleep(SHORT_SLEEP_WAIT_TIME_MS);
276             waitTime -= (SystemClock.elapsedRealtime() - startTimeMs);
277         }
278 
279         if (view.isAvailable()) {
280             return view.getSurfaceTexture();
281         } else {
282             Log.w(TAG, "Wait for SurfaceTexture available timed out after " + timeOutMs + "ms");
283             return null;
284         }
285     }
286 
287     /**
288      * Uses {@link DeviceStateManager} to determine if the device is foldable or not. It relies on
289      * the OEM exposing supported states, and setting
290      * com.android.internal.R.array.config_foldedDeviceStates correctly with the folded states.
291      *
292      * @return true is the device is a foldable; false otherwise
293      */
isDeviceFoldable(Context mContext)294     public static boolean isDeviceFoldable(Context mContext) {
295         DeviceStateManager deviceStateManager =
296                 mContext.getSystemService(DeviceStateManager.class);
297         if (deviceStateManager == null) {
298             Log.w(TAG, "Couldn't locate DeviceStateManager to detect if the device is foldable"
299                     + " or not. Defaulting to not-foldable.");
300             return false;
301         }
302         Set<Integer> supportedStates = deviceStateManager.getSupportedDeviceStates().stream().map(
303                 DeviceState::getIdentifier).collect(Collectors.toSet());
304 
305         Resources systemRes = Resources.getSystem();
306         int foldedStatesArrayIdentifier = systemRes.getIdentifier("config_foldedDeviceStates",
307                 "array", "android");
308         int[] foldedDeviceStates = systemRes.getIntArray(foldedStatesArrayIdentifier);
309 
310         // Device is a foldable if supportedStates contains any state in foldedDeviceStates
311         return Arrays.stream(foldedDeviceStates).anyMatch(supportedStates::contains);
312     }
313 
314     /**
315      * Returns Gets the supported video frame sizes that can be used by MediaRecorder.
316      *
317      * @param camera Camera for which to get the supported video sizes.
318      * @return a list of Size object if camera has separate preview and video output;
319      * in case there isn't a separate video/preview size option,
320      * it returns the preview sizes.
321      */
getSupportedVideoSizes(Camera camera)322     public static List<Size> getSupportedVideoSizes(Camera camera) {
323         Parameters parameters = camera.getParameters();
324         assertNotNull("Camera did not provide parameters", parameters);
325         List<Size> videoSizes = parameters.getSupportedVideoSizes();
326 
327         if (videoSizes == null) {
328             videoSizes = parameters.getSupportedPreviewSizes();
329             assertNotNull(videoSizes);
330         }
331 
332         return videoSizes;
333     }
334 }
335