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