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