1 /* 2 * Copyright 2019 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.extensions.util; 18 19 import static androidx.camera.extensions.ExtensionMode.AUTO; 20 import static androidx.camera.extensions.ExtensionMode.BOKEH; 21 import static androidx.camera.extensions.ExtensionMode.FACE_RETOUCH; 22 import static androidx.camera.extensions.ExtensionMode.HDR; 23 import static androidx.camera.extensions.ExtensionMode.NIGHT; 24 import static androidx.camera.extensions.impl.ExtensionsTestlibControl.ImplementationType.OEM_IMPL; 25 import static androidx.camera.extensions.impl.ExtensionsTestlibControl.ImplementationType.TESTLIB_ADVANCED; 26 import static androidx.camera.extensions.impl.ExtensionsTestlibControl.ImplementationType.TESTLIB_BASIC; 27 28 import android.content.Context; 29 import android.hardware.camera2.CameraCaptureSession; 30 import android.hardware.camera2.CameraCharacteristics; 31 import android.hardware.camera2.CameraManager; 32 import android.os.Build; 33 34 import androidx.camera.camera2.Camera2Config; 35 import androidx.camera.camera2.pipe.integration.CameraPipeConfig; 36 import androidx.camera.core.CameraSelector; 37 import androidx.camera.core.CameraXConfig; 38 import androidx.camera.core.ExtendableBuilder; 39 import androidx.camera.core.impl.Config; 40 import androidx.camera.extensions.ExtensionMode; 41 import androidx.camera.extensions.ExtensionsManager; 42 import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl; 43 import androidx.camera.extensions.impl.AutoPreviewExtenderImpl; 44 import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl; 45 import androidx.camera.extensions.impl.BeautyPreviewExtenderImpl; 46 import androidx.camera.extensions.impl.BokehImageCaptureExtenderImpl; 47 import androidx.camera.extensions.impl.BokehPreviewExtenderImpl; 48 import androidx.camera.extensions.impl.ExtensionVersionImpl; 49 import androidx.camera.extensions.impl.ExtensionsTestlibControl; 50 import androidx.camera.extensions.impl.HdrImageCaptureExtenderImpl; 51 import androidx.camera.extensions.impl.HdrPreviewExtenderImpl; 52 import androidx.camera.extensions.impl.NightImageCaptureExtenderImpl; 53 import androidx.camera.extensions.impl.NightPreviewExtenderImpl; 54 import androidx.camera.extensions.impl.advanced.AutoAdvancedExtenderImpl; 55 import androidx.camera.extensions.impl.advanced.BeautyAdvancedExtenderImpl; 56 import androidx.camera.extensions.impl.advanced.BokehAdvancedExtenderImpl; 57 import androidx.camera.extensions.impl.advanced.HdrAdvancedExtenderImpl; 58 import androidx.camera.extensions.impl.advanced.NightAdvancedExtenderImpl; 59 import androidx.camera.extensions.internal.AdvancedVendorExtender; 60 import androidx.camera.extensions.internal.BasicVendorExtender; 61 import androidx.camera.extensions.internal.Camera2ExtensionsVendorExtender; 62 import androidx.camera.extensions.internal.ExtensionVersion; 63 import androidx.camera.extensions.internal.VendorExtender; 64 import androidx.camera.extensions.internal.Version; 65 import androidx.camera.extensions.internal.compat.workaround.ExtensionDisabledValidator; 66 import androidx.camera.lifecycle.ProcessCameraProvider; 67 import androidx.camera.testing.impl.CameraUtil; 68 69 import org.jspecify.annotations.NonNull; 70 71 import java.util.ArrayList; 72 import java.util.Arrays; 73 import java.util.Collection; 74 import java.util.List; 75 import java.util.concurrent.TimeUnit; 76 77 /** 78 * Extension test util functions. 79 */ 80 public class ExtensionsTestUtil { 81 public static final Config.Option<CameraCaptureSession.CaptureCallback> 82 SESSION_CAPTURE_CALLBACK_OPTION = 83 Config.Option.create("camera2.cameraCaptureSession.captureCallback", 84 CameraCaptureSession.CaptureCallback.class); 85 public static final String CAMERA2_IMPLEMENTATION_OPTION = "camera2"; 86 public static final String CAMERA_PIPE_IMPLEMENTATION_OPTION = "camera_pipe"; 87 isAdvancedExtender()88 private static boolean isAdvancedExtender() { 89 ExtensionVersionImpl extensionVersion = new ExtensionVersionImpl(); 90 try { 91 if (ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_2) 92 && extensionVersion.isAdvancedExtenderImplemented()) { 93 return true; 94 } 95 } catch (NoSuchMethodError e) { 96 // in case some devices remove the isAdvancedExtenderImplemented method in 97 // ExtensionVersionImpl. 98 return false; 99 } 100 return false; 101 } 102 hasNoSuchMethod(Runnable runnable)103 private static boolean hasNoSuchMethod(Runnable runnable) { 104 try { 105 runnable.run(); 106 } catch (NoSuchMethodError e) { 107 return true; 108 } 109 return false; 110 } 111 112 // Check if the OEM implementation class for the given mode exists or not. doesOEMImplementationExistForMode(int extensionMode)113 private static boolean doesOEMImplementationExistForMode(int extensionMode) { 114 if (isAdvancedExtender()) { 115 switch (extensionMode) { 116 case HDR: 117 return hasNoSuchMethod( 118 () -> HdrAdvancedExtenderImpl.checkTestlibRunning()); 119 case BOKEH: 120 return hasNoSuchMethod( 121 () -> BokehAdvancedExtenderImpl.checkTestlibRunning()); 122 case AUTO: 123 return hasNoSuchMethod( 124 () -> AutoAdvancedExtenderImpl.checkTestlibRunning()); 125 case FACE_RETOUCH: 126 return hasNoSuchMethod( 127 () -> BeautyAdvancedExtenderImpl.checkTestlibRunning()); 128 case NIGHT: 129 return hasNoSuchMethod( 130 () -> NightAdvancedExtenderImpl.checkTestlibRunning()); 131 } 132 } else { 133 switch (extensionMode) { 134 case HDR: 135 return hasNoSuchMethod( 136 () -> HdrImageCaptureExtenderImpl.checkTestlibRunning()) 137 && hasNoSuchMethod( 138 () -> HdrPreviewExtenderImpl.checkTestlibRunning()); 139 case BOKEH: 140 return hasNoSuchMethod( 141 () -> BokehImageCaptureExtenderImpl.checkTestlibRunning()) 142 && hasNoSuchMethod( 143 () -> BokehPreviewExtenderImpl.checkTestlibRunning()); 144 case AUTO: 145 return hasNoSuchMethod( 146 () -> AutoImageCaptureExtenderImpl.checkTestlibRunning()) 147 && hasNoSuchMethod( 148 () -> AutoPreviewExtenderImpl.checkTestlibRunning()); 149 case FACE_RETOUCH: 150 return hasNoSuchMethod( 151 () -> BeautyImageCaptureExtenderImpl.checkTestlibRunning()) 152 && hasNoSuchMethod( 153 () -> BeautyPreviewExtenderImpl.checkTestlibRunning()); 154 case NIGHT: 155 return hasNoSuchMethod( 156 () -> NightImageCaptureExtenderImpl.checkTestlibRunning()) 157 && hasNoSuchMethod( 158 () -> NightPreviewExtenderImpl.checkTestlibRunning()); 159 } 160 } 161 return false; 162 } 163 164 /** 165 * Returns if extension is supported with the given mode and lens facing. Please note that 166 * if some classes are removed by OEMs, the classes in the test lib could still be used so we 167 * need to return false in this case. 168 */ isExtensionAvailable( ExtensionsManager extensionsManager, int lensFacing, int extensionMode)169 public static boolean isExtensionAvailable( 170 ExtensionsManager extensionsManager, int lensFacing, int extensionMode) { 171 CameraSelector cameraSelector = new CameraSelector.Builder() 172 .requireLensFacing(lensFacing) 173 .build(); 174 return isExtensionAvailable(extensionsManager, cameraSelector, extensionMode); 175 } 176 177 /** 178 * Returns if extension is supported with the given mode and camera selector. Please note that 179 * if some classes are removed by OEMs, the classes in the test lib could still be used so we 180 * need to return false in this case. 181 */ isExtensionAvailable(@onNull ExtensionsManager extensionsManager, @NonNull CameraSelector cameraSelector, int extensionMode)182 public static boolean isExtensionAvailable(@NonNull ExtensionsManager extensionsManager, 183 @NonNull CameraSelector cameraSelector, int extensionMode) { 184 // Return false if classes are removed by OEMs 185 if (ExtensionsTestlibControl.getInstance().getImplementationType() == OEM_IMPL 186 && !doesOEMImplementationExistForMode(extensionMode)) { 187 return false; 188 } 189 190 return extensionsManager.isExtensionAvailable(cameraSelector, extensionMode); 191 } 192 193 /** 194 * Returns the parameters which contains the combination of CameraXConfig 195 * name, CameraXConfig, implementationType, extensions mode and lens facing. 196 */ getAllImplExtensionsLensFacingCombinations( @onNull Context context, boolean excludeUnavailableModes )197 public static @NonNull Collection<Object[]> getAllImplExtensionsLensFacingCombinations( 198 @NonNull Context context, 199 boolean excludeUnavailableModes 200 ) { 201 ExtensionsTestlibControl.ImplementationType implType = 202 ExtensionsTestlibControl.getInstance().getImplementationType(); 203 204 if (implType == TESTLIB_ADVANCED) { 205 ExtensionsTestlibControl.getInstance().setImplementationType(TESTLIB_BASIC); 206 implType = TESTLIB_BASIC; 207 } 208 209 List<Object[]> basicOrOemImplList = Arrays.asList(new Object[][]{ 210 {implType, BOKEH, CameraSelector.LENS_FACING_FRONT}, 211 {implType, BOKEH, CameraSelector.LENS_FACING_BACK}, 212 {implType, HDR, CameraSelector.LENS_FACING_FRONT}, 213 {implType, HDR, CameraSelector.LENS_FACING_BACK}, 214 {implType, FACE_RETOUCH, CameraSelector.LENS_FACING_FRONT}, 215 {implType, FACE_RETOUCH, CameraSelector.LENS_FACING_BACK}, 216 {implType, NIGHT, CameraSelector.LENS_FACING_FRONT}, 217 {implType, NIGHT, CameraSelector.LENS_FACING_BACK}, 218 {implType, AUTO, CameraSelector.LENS_FACING_FRONT}, 219 {implType, AUTO, CameraSelector.LENS_FACING_BACK} 220 }); 221 222 if (implType == OEM_IMPL) { 223 List<Object[]> allList = excludeUnavailableModes ? filterOutUnavailableMode(context, 224 basicOrOemImplList) : basicOrOemImplList; 225 return getConfigPrependedCombinations(allList); 226 } 227 228 List<Object[]> advancedList = Arrays.asList(new Object[][]{ 229 {TESTLIB_ADVANCED, BOKEH, CameraSelector.LENS_FACING_FRONT}, 230 {TESTLIB_ADVANCED, BOKEH, CameraSelector.LENS_FACING_BACK}, 231 {TESTLIB_ADVANCED, HDR, CameraSelector.LENS_FACING_FRONT}, 232 {TESTLIB_ADVANCED, HDR, CameraSelector.LENS_FACING_BACK}, 233 {TESTLIB_ADVANCED, FACE_RETOUCH, CameraSelector.LENS_FACING_FRONT}, 234 {TESTLIB_ADVANCED, FACE_RETOUCH, CameraSelector.LENS_FACING_BACK}, 235 {TESTLIB_ADVANCED, NIGHT, CameraSelector.LENS_FACING_FRONT}, 236 {TESTLIB_ADVANCED, NIGHT, CameraSelector.LENS_FACING_BACK}, 237 {TESTLIB_ADVANCED, AUTO, CameraSelector.LENS_FACING_FRONT}, 238 {TESTLIB_ADVANCED, AUTO, CameraSelector.LENS_FACING_BACK} 239 }); 240 241 List<Object[]> allList = new ArrayList<>(); 242 allList.addAll(excludeUnavailableModes 243 ? filterOutUnavailableMode(context, basicOrOemImplList) : basicOrOemImplList); 244 ExtensionsTestlibControl.getInstance().setImplementationType(TESTLIB_ADVANCED); 245 246 allList.addAll(excludeUnavailableModes 247 ? filterOutUnavailableMode(context, advancedList) : advancedList); 248 249 // Reset to basic in case advanced is used accidentally. 250 ExtensionsTestlibControl.getInstance().setImplementationType(TESTLIB_BASIC); 251 252 return getConfigPrependedCombinations(allList); 253 } 254 filterOutUnavailableMode(Context context, List<Object[]> list)255 private static List<Object[]> filterOutUnavailableMode(Context context, 256 List<Object[]> list) { 257 ExtensionsManager extensionsManager = null; 258 ProcessCameraProvider cameraProvider = null; 259 try { 260 cameraProvider = ProcessCameraProvider.getInstance(context).get(2, TimeUnit.SECONDS); 261 extensionsManager = ExtensionsManager.getInstanceAsync(context, cameraProvider) 262 .get(2, TimeUnit.SECONDS); 263 264 List<Object[]> result = new ArrayList<>(); 265 for (Object[] item : list) { 266 int mode = (int) item[1]; 267 int lensFacing = (int) item[2]; 268 if (isExtensionAvailable(extensionsManager, lensFacing, mode)) { 269 result.add(item); 270 } 271 } 272 return result; 273 } catch (Exception e) { 274 return list; 275 } finally { 276 try { 277 if (cameraProvider != null) { 278 cameraProvider.shutdownAsync().get(); 279 } 280 if (extensionsManager != null) { 281 extensionsManager.shutdown().get(); 282 } 283 } catch (Exception e) { 284 } 285 } 286 } 287 getConfigPrependedCombinations(List<Object[]> combinations)288 private static List<Object[]> getConfigPrependedCombinations(List<Object[]> combinations) { 289 CameraXConfig camera2Config = Camera2Config.defaultConfig(); 290 CameraXConfig cameraPipeConfig = CameraPipeConfig.defaultConfig(); 291 List<Object[]> combinationsWithConfig = new ArrayList<Object[]>(); 292 for (Object[] combination: combinations) { 293 List<Object> combinationCamera2 = new ArrayList<Object>( 294 Arrays.asList(CAMERA2_IMPLEMENTATION_OPTION, camera2Config)); 295 combinationCamera2.addAll(Arrays.asList(combination)); 296 combinationsWithConfig.add(combinationCamera2.toArray()); 297 298 List<Object> combinationCameraPipe = new ArrayList<Object>( 299 Arrays.asList(CAMERA_PIPE_IMPLEMENTATION_OPTION, cameraPipeConfig)); 300 combinationCameraPipe.addAll(Arrays.asList(combination)); 301 combinationsWithConfig.add(combinationCameraPipe.toArray()); 302 } 303 return combinationsWithConfig; 304 } 305 306 /** 307 * Returns whether the target camera device can support the test for a specific extension mode. 308 */ isTargetDeviceAvailableForExtensions( @ameraSelector.LensFacing int lensFacing, @ExtensionMode.Mode int mode)309 public static boolean isTargetDeviceAvailableForExtensions( 310 @CameraSelector.LensFacing int lensFacing, @ExtensionMode.Mode int mode) { 311 return CameraUtil.hasCameraWithLensFacing(lensFacing) && isLimitedAboveDevice(lensFacing) 312 && !isSpecificSkippedDevice() && !isSpecificSkippedDeviceWithExtensionMode(mode); 313 } 314 isAdvancedExtenderSupported()315 private static boolean isAdvancedExtenderSupported() { 316 if (ExtensionVersion.getRuntimeVersion().compareTo(Version.VERSION_1_2) < 0) { 317 return false; 318 } 319 return ExtensionVersion.isAdvancedExtenderSupported(); 320 } 321 322 /** 323 * Creates the {@link VendorExtender} instance for testing. 324 * 325 * @param applicationContext the application context which will be used to retrieve the 326 * camera characteristics info. 327 * @param mode the target extension mode. 328 * @param configImplType the config impl type to run the test 329 * @return the corresponding {@link VendorExtender} instance. 330 */ createVendorExtender(@onNull Context applicationContext, @ExtensionMode.Mode int mode, @CameraXConfig.ImplType int configImplType)331 public static @NonNull VendorExtender createVendorExtender(@NonNull Context applicationContext, 332 @ExtensionMode.Mode int mode, 333 @CameraXConfig.ImplType int configImplType) { 334 if (configImplType == CameraXConfig.CAMERAX_CONFIG_IMPL_TYPE_PIPE) { 335 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 336 CameraManager cameraManager = applicationContext.getSystemService( 337 CameraManager.class); 338 return new Camera2ExtensionsVendorExtender(mode, cameraManager); 339 } else { 340 return new VendorExtender() { 341 }; 342 } 343 } 344 if (isAdvancedExtenderSupported()) { 345 return new AdvancedVendorExtender(mode); 346 } 347 return new BasicVendorExtender(mode); 348 } 349 350 /** 351 * Returns whether the device is LIMITED hardware level above. 352 * 353 * <p>The test cases bind both ImageCapture and Preview. In the test lib implementation for 354 * HDR mode, both use cases will occupy YUV_420_888 format of stream. Therefore, the testing 355 * target devices need to be LIMITED hardware level at least to support two YUV_420_888 356 * streams at the same time. 357 * 358 * @return true if the testing target camera device is LIMITED hardware level at least. 359 * @throws IllegalArgumentException if unable to retrieve {@link CameraCharacteristics} for 360 * given lens facing. 361 */ isLimitedAboveDevice(@ameraSelector.LensFacing int lensFacing)362 private static boolean isLimitedAboveDevice(@CameraSelector.LensFacing int lensFacing) { 363 CameraCharacteristics cameraCharacteristics = CameraUtil.getCameraCharacteristics( 364 lensFacing); 365 366 if (cameraCharacteristics != null) { 367 Integer keyValue = cameraCharacteristics.get( 368 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); 369 370 if (keyValue != null) { 371 return keyValue != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; 372 } 373 } else { 374 throw new IllegalArgumentException( 375 "Unable to retrieve info for " + lensFacing + " camera."); 376 } 377 378 return false; 379 } 380 381 /** 382 * Returns that whether the device should be skipped for the test. 383 */ isSpecificSkippedDevice()384 private static boolean isSpecificSkippedDevice() { 385 return (Build.BRAND.equalsIgnoreCase("SONY") && (Build.MODEL.equalsIgnoreCase("G8142") 386 || Build.MODEL.equalsIgnoreCase("G8342"))) 387 || Build.MODEL.contains("Cuttlefish") 388 || Build.MODEL.equalsIgnoreCase("Pixel XL") 389 || Build.MODEL.equalsIgnoreCase("Pixel") 390 // Skip all devices that have ExtraCropping Quirk 391 || Build.MODEL.equalsIgnoreCase("SM-T580") 392 || Build.MODEL.equalsIgnoreCase("SM-J710MN") 393 || Build.MODEL.equalsIgnoreCase("SM-A320FL") 394 || Build.MODEL.equalsIgnoreCase("SM-G570M") 395 || Build.MODEL.equalsIgnoreCase("SM-G610F") 396 || Build.MODEL.equalsIgnoreCase("SM-G610M"); 397 } 398 399 /** 400 * Returns that whether the device with specific extension mode should be skipped for the test. 401 */ isSpecificSkippedDeviceWithExtensionMode(@xtensionMode.Mode int mode)402 private static boolean isSpecificSkippedDeviceWithExtensionMode(@ExtensionMode.Mode int mode) { 403 return "tecno".equalsIgnoreCase(Build.BRAND) && "tecno-ke5".equalsIgnoreCase(Build.DEVICE) 404 && (mode == ExtensionMode.HDR || mode == ExtensionMode.NIGHT); 405 } 406 407 /** 408 * Returns whether extensions is disabled by quirk. 409 */ extensionsDisabledByQuirk(@onNull String cameraId)410 public static boolean extensionsDisabledByQuirk(@NonNull String cameraId) { 411 return new ExtensionDisabledValidator().shouldDisableExtension(cameraId); 412 } 413 414 /** 415 * Sets the camera2 repeating request capture callback to the use case builder. 416 */ setCamera2SessionCaptureCallback( ExtendableBuilder<T> usecaseBuilder, CameraCaptureSession.@NonNull CaptureCallback captureCallback)417 public static <T> void setCamera2SessionCaptureCallback( 418 ExtendableBuilder<T> usecaseBuilder, 419 CameraCaptureSession.@NonNull CaptureCallback captureCallback) { 420 usecaseBuilder.getMutableConfig().insertOption( 421 SESSION_CAPTURE_CALLBACK_OPTION, 422 captureCallback 423 ); 424 } 425 } 426