1 /*
2  * Copyright (C) 2024 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.camera2.cts;
18 
19 import static android.hardware.camera2.cts.CameraTestUtils.MaxStreamSizes;
20 import static android.hardware.camera2.cts.CameraTestUtils.MaxStreamSizes.JPEG;
21 import static android.hardware.camera2.cts.CameraTestUtils.MaxStreamSizes.JPEG_R;
22 import static android.hardware.camera2.cts.CameraTestUtils.MaxStreamSizes.PRIV;
23 import static android.hardware.camera2.cts.CameraTestUtils.MaxStreamSizes.YUV;
24 import static android.hardware.camera2.cts.CameraTestUtils.assertNull;
25 
26 import static junit.framework.Assert.assertNotNull;
27 import static junit.framework.Assert.assertTrue;
28 import static junit.framework.Assert.fail;
29 
30 import android.graphics.ImageFormat;
31 import android.graphics.SurfaceTexture;
32 import android.hardware.HardwareBuffer;
33 import android.hardware.camera2.CameraCaptureSession;
34 import android.hardware.camera2.CameraCharacteristics;
35 import android.hardware.camera2.CameraDevice;
36 import android.hardware.camera2.CaptureRequest;
37 import android.hardware.camera2.cts.CameraTestUtils.MockStateCallback;
38 import android.hardware.camera2.cts.helpers.StaticMetadata;
39 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
40 import android.hardware.camera2.params.DynamicRangeProfiles;
41 import android.hardware.camera2.params.OutputConfiguration;
42 import android.hardware.camera2.params.SessionConfiguration;
43 import android.media.ImageReader;
44 import android.os.Build;
45 import android.platform.test.annotations.RequiresFlagsEnabled;
46 import android.platform.test.flag.junit.CheckFlagsRule;
47 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
48 import android.util.Log;
49 import android.util.Range;
50 import android.util.Size;
51 
52 import com.android.ex.camera2.blocking.BlockingSessionCallback;
53 import com.android.ex.camera2.blocking.BlockingStateCallback;
54 import com.android.internal.camera.flags.Flags;
55 
56 import org.junit.Assert;
57 import org.junit.Rule;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 import org.junit.runners.Parameterized;
61 
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.HashMap;
65 import java.util.HashSet;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Random;
69 import java.util.Set;
70 import java.util.concurrent.Executor;
71 import java.util.concurrent.ExecutorService;
72 import java.util.concurrent.Executors;
73 
74 /**
75  * Tests the functionality of {@link CameraDevice.CameraDeviceSetup} APIs.
76  * <p>
77  * NOTE: Functionality of {@link CameraDevice.CameraDeviceSetup#createCaptureRequest} and
78  * {@link CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported} is tested by
79  * {@link FeatureCombinationTest}
80  */
81 @RunWith(Parameterized.class)
82 public class CameraDeviceSetupTest extends Camera2AndroidTestCase {
83     private static final String TAG = CameraDeviceSetupTest.class.getSimpleName();
84     private static final int CAMERA_STATE_TIMEOUT_MS = 3000;
85 
86     @Rule
87     public final CheckFlagsRule mFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
88 
89     @Test
90     @RequiresFlagsEnabled({Flags.FLAG_CAMERA_DEVICE_SETUP, Flags.FLAG_FEATURE_COMBINATION_QUERY})
testCameraDeviceSetupSupport()91     public void testCameraDeviceSetupSupport() throws Exception {
92         for (String cameraId : getCameraIdsUnderTest()) {
93             CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);
94             Integer queryVersion = chars.get(
95                     CameraCharacteristics.INFO_SESSION_CONFIGURATION_QUERY_VERSION);
96             mCollector.expectNotNull(CameraCharacteristics.INFO_SESSION_CONFIGURATION_QUERY_VERSION
97                     + " must not be null", queryVersion);
98             if (queryVersion == null) {
99                 continue;
100             }
101             boolean cameraDeviceSetupSupported = mCameraManager.isCameraDeviceSetupSupported(
102                     cameraId);
103 
104             // CameraDeviceSetup must be supported for all CameraDevices that report
105             // INFO_SESSION_CONFIGURATION_QUERY_VERSION > U, and false for those that don't.
106             mCollector.expectEquals(CameraCharacteristics.INFO_SESSION_CONFIGURATION_QUERY_VERSION
107                             + " and CameraManager.isCameraDeviceSetupSupported give differing "
108                             + "answers for camera id" + cameraId,
109                     queryVersion > Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
110                     cameraDeviceSetupSupported);
111         }
112     }
113 
114     @Test
115     @RequiresFlagsEnabled(Flags.FLAG_CAMERA_DEVICE_SETUP)
testCameraDeviceSetupCreationSuccessful()116     public void testCameraDeviceSetupCreationSuccessful() throws Exception {
117         for (String cameraId : getCameraIdsUnderTest()) {
118             if (!mCameraManager.isCameraDeviceSetupSupported(cameraId)) {
119                 Log.i(TAG, "CameraDeviceSetup not supported for camera id " + cameraId);
120                 continue;
121             }
122 
123             CameraDevice.CameraDeviceSetup cameraDeviceSetup =
124                     mCameraManager.getCameraDeviceSetup(cameraId);
125             mCollector.expectEquals("Camera ID of created object is not the same as that "
126                             + "passed to CameraManager.",
127                     cameraId, cameraDeviceSetup.getId());
128         }
129     }
130 
131     @Test
132     @RequiresFlagsEnabled(Flags.FLAG_CAMERA_DEVICE_SETUP)
testCameraDeviceSetupCreationFailure()133     public void testCameraDeviceSetupCreationFailure() throws Exception {
134         try {
135             mCameraManager.getCameraDeviceSetup(/*cameraId=*/ null);
136             Assert.fail("Calling getCameraDeviceSetup with null should have raised an "
137                     + "IllegalArgumentException.");
138         } catch (IllegalArgumentException e) {
139             // Expected. Don't do anything.
140         }
141 
142         // Try to get an invalid camera ID by randomly generating 100 integers.
143         // NOTE: We don't actually expect to generate more than an int or two.
144         // Not being able to find an invalid camera within 100 random ints
145         // is astronomical.
146         HashSet<String> cameraIds = new HashSet<>(List.of(getCameraIdsUnderTest()));
147         Random rng = new Random();
148         int invalidId = rng.ints(/*streamSize=*/ 100, /*randomNumberOrigin=*/ 0,
149                         /*randomNumberBound=*/ Integer.MAX_VALUE)
150                 .filter(i -> !cameraIds.contains(String.valueOf(i)))
151                 .findAny()
152                 .orElseThrow(() -> new AssertionError(
153                         "Could not find an invalid cameraID within 100 randomly generated "
154                                 + "numbers."));
155         String invalidCameraId = String.valueOf(invalidId);
156         Log.i(TAG, "Using invalid Camera ID: " + invalidCameraId);
157         try {
158             mCameraManager.getCameraDeviceSetup(invalidCameraId);
159             Assert.fail("Calling getCameraDeviceSetup with a cameraId not in getCameraIdList()"
160                     + "should have raised an IllegalArgumentException.");
161         } catch (IllegalArgumentException e) {
162             // Expected. Don't do anything.
163         }
164     }
165 
166     @Test
167     @RequiresFlagsEnabled(Flags.FLAG_CAMERA_DEVICE_SETUP)
testOpenSuccessful()168     public void testOpenSuccessful() throws Exception {
169         ExecutorService callbackExecutor = Executors.newCachedThreadPool();
170         for (String cameraId : getCameraIdsUnderTest()) {
171             if (!mCameraManager.isCameraDeviceSetupSupported(cameraId)) {
172                 Log.i(TAG, "CameraDeviceSetup not supported for camera id " + cameraId);
173                 continue;
174             }
175 
176             // mock listener to capture the CameraDevice from callbacks
177             MockStateCallback mockListener = MockStateCallback.mock();
178             BlockingStateCallback callback = new BlockingStateCallback(mockListener);
179 
180             CameraDevice.CameraDeviceSetup cameraDeviceSetup =
181                     mCameraManager.getCameraDeviceSetup(cameraId);
182             cameraDeviceSetup.openCamera(callbackExecutor, callback);
183 
184             callback.waitForState(BlockingStateCallback.STATE_OPENED, CAMERA_STATE_TIMEOUT_MS);
185             CameraDevice cameraDevice = CameraTestUtils.verifyCameraStateOpened(
186                     cameraId, mockListener);
187 
188             mCollector.expectEquals("CameraDeviceSetup and created CameraDevice must have "
189                             + "the same ID",
190                     cameraDeviceSetup.getId(), cameraDevice.getId());
191 
192             cameraDevice.close();
193             callback.waitForState(BlockingStateCallback.STATE_CLOSED, CAMERA_STATE_TIMEOUT_MS);
194         }
195     }
196 
197     /**
198      * Verify if valid session characteristics can be fetched for a particular camera.
199      */
200     @Test
201     @RequiresFlagsEnabled({Flags.FLAG_FEATURE_COMBINATION_QUERY, Flags.FLAG_CAMERA_DEVICE_SETUP})
testSessionCharacteristics()202     public void testSessionCharacteristics() throws Exception {
203         String[] cameraIdsUnderTest = getCameraIdsUnderTest();
204         for (String cameraId : cameraIdsUnderTest) {
205             // Without the following check, mOrderedPreviewSizes will be null.
206             StaticMetadata staticChars = new StaticMetadata(
207                     mCameraManager.getCameraCharacteristics(cameraId));
208 
209             if (!staticChars.isColorOutputSupported()) {
210                 Log.i(TAG, "Camera " + cameraId + " does not support color outputs, skipping.");
211                 continue;
212             }
213 
214             if (!mCameraManager.isCameraDeviceSetupSupported(cameraId)) {
215                 Log.i(TAG, "CameraDeviceSetup not supported for camera id " + cameraId);
216                 continue;
217             }
218 
219             CameraDevice.CameraDeviceSetup cameraDeviceSetup = mCameraManager.getCameraDeviceSetup(
220                     cameraId);
221 
222             int outputFormat = ImageFormat.YUV_420_888;
223             List<Size> orderedPreviewSizes = CameraTestUtils.getSupportedPreviewSizes(cameraId,
224                     mCameraManager, CameraTestUtils.PREVIEW_SIZE_BOUND);
225             Size outputSize = orderedPreviewSizes.get(0);
226             try (ImageReader imageReader = ImageReader.newInstance(outputSize.getWidth(),
227                     outputSize.getHeight(), outputFormat, /*maxImages*/3)) {
228                 CameraCaptureSession.StateCallback sessionListener = new BlockingSessionCallback();
229 
230                 List<OutputConfiguration> outputs = new ArrayList<>();
231                 outputs.add(new OutputConfiguration(imageReader.getSurface()));
232 
233                 SessionConfiguration sessionConfig = new SessionConfiguration(
234                         SessionConfiguration.SESSION_REGULAR, outputs,
235                         new CameraTestUtils.HandlerExecutor(mHandler), sessionListener);
236 
237                 CaptureRequest.Builder builder = cameraDeviceSetup.createCaptureRequest(
238                         CameraDevice.TEMPLATE_STILL_CAPTURE);
239                 builder.addTarget(imageReader.getSurface());
240 
241                 CaptureRequest request = builder.build();
242                 sessionConfig.setSessionParameters(request);
243 
244                 CameraCharacteristics sessionCharacteristics =
245                         cameraDeviceSetup.getSessionCharacteristics(sessionConfig);
246                 StaticMetadata sessionMetadata = new StaticMetadata(sessionCharacteristics);
247 
248                 List<CameraCharacteristics.Key<?>> availableSessionCharKeys =
249                         staticChars.getCharacteristics().getAvailableSessionCharacteristicsKeys();
250 
251                 mCollector.expectNotNull("Session Characteristics keys must not be null",
252                         availableSessionCharKeys);
253 
254                 // Ensure every key in availableSessionCharKeys is present in
255                 // sessionCharacteristics.
256                 for (CameraCharacteristics.Key<?> key : availableSessionCharKeys) {
257                     mCollector.expectNotNull(key.toString()
258                                     + " is null in Session Characteristics",
259                             sessionCharacteristics.get(key));
260                 }
261 
262                 List<CameraCharacteristics.Key<?>> staticKeys =
263                         staticChars.getCharacteristics().getKeys();
264                 List<CameraCharacteristics.Key<?>> sessionCharKeys =
265                         sessionCharacteristics.getKeys();
266 
267                 // Ensure that there are no duplicate keys in session chars
268                 HashSet<CameraCharacteristics.Key<?>> uniqueSessionCharKeys =
269                         new HashSet<>(sessionCharKeys);
270                 mCollector.expectEquals(
271                         "Session Characteristics should not contain duplicate keys.",
272                         uniqueSessionCharKeys.size(), sessionCharKeys.size());
273 
274                 // Ensure all keys in static chars are present in session chars.
275                 mCollector.expectEquals(
276                         "Session Characteristics and Static Characteristics must have the same "
277                                 + "number of keys.",
278                         staticKeys.size(), uniqueSessionCharKeys.size());
279                 mCollector.expectContainsAll(
280                         "Session Characteristics must have all the keys in Static "
281                                 + "Characteristics",
282                         uniqueSessionCharKeys, staticKeys);
283 
284 
285                 // TODO: Do more thorough testing to make sure the max_digital_zoom and
286                 //       zoom_ratio_range have valid values.
287                 sessionMetadata.getAvailableMaxDigitalZoomChecked();
288                 sessionMetadata.getZoomRatioRangeChecked();
289 
290                 checkSessionCharacteristicsForNoCallbackConfig(outputs, builder,
291                         cameraDeviceSetup, sessionCharacteristics);
292             }
293         }
294     }
295 
296     /**
297      * Check the session characteristics consistency when queried via a SessionConfiguration
298      * without callbacks.
299      */
checkSessionCharacteristicsForNoCallbackConfig(List<OutputConfiguration> outputs, CaptureRequest.Builder requestBuilder, CameraDevice.CameraDeviceSetup deviceSetup, CameraCharacteristics sessionCharacteristics)300     private void checkSessionCharacteristicsForNoCallbackConfig(List<OutputConfiguration> outputs,
301             CaptureRequest.Builder requestBuilder,
302             CameraDevice.CameraDeviceSetup deviceSetup,
303             CameraCharacteristics sessionCharacteristics) throws Exception {
304         // Session configuration with no callbacks
305         SessionConfiguration sessionConfigNoCallback = new SessionConfiguration(
306                 SessionConfiguration.SESSION_REGULAR, outputs);
307         sessionConfigNoCallback.setSessionParameters(requestBuilder.build());
308 
309         CameraCharacteristics sessionCharsWithoutCallbacks = deviceSetup.getSessionCharacteristics(
310                 sessionConfigNoCallback);
311         List<CameraCharacteristics.Key<?>> sessionCharKeysWithoutCallback =
312                 sessionCharsWithoutCallbacks.getKeys();
313 
314         // Ensure that there are no duplicate keys in session chars without callbacks.
315         HashSet<CameraCharacteristics.Key<?>> uniqueSessionCharNoCallbackKeys =
316                 new HashSet<>(sessionCharKeysWithoutCallback);
317         mCollector.expectEquals(
318                 "Session Characteristics without callback should not contain duplicate keys.",
319                 uniqueSessionCharNoCallbackKeys.size(), sessionCharKeysWithoutCallback.size());
320 
321         // Ensure all keys in session chars with callback are present in session chars
322         // without callback.
323         List<CameraCharacteristics.Key<?>> sessionCharKeys = sessionCharacteristics.getKeys();
324         mCollector.expectEquals(
325                 "Session Characteristics with and without callback must have the same "
326                         + "number of keys.",
327                 sessionCharKeys.size(), uniqueSessionCharNoCallbackKeys.size());
328         mCollector.expectContainsAll(
329                 "Session Characteristics without callback must have the all the keys in Session "
330                         + "Characteristics with callback.",
331                 uniqueSessionCharNoCallbackKeys, sessionCharKeys);
332 
333         // setStateCallback works as expected
334         CameraCaptureSession.StateCallback sessionListener = new BlockingSessionCallback();
335         Executor executor = new CameraTestUtils.HandlerExecutor(mHandler);
336         sessionConfigNoCallback.setStateCallback(executor, sessionListener);
337         mCollector.expectEquals(
338                 "CameraCaptureSession.StateCallback set by setStateCallback not reflected in "
339                         + "getStateCallback.",
340                 sessionListener, sessionConfigNoCallback.getStateCallback());
341         mCollector.expectEquals("Executor set by setStateCallback not reflect in getExecutor.",
342                 executor, sessionConfigNoCallback.getExecutor());
343     }
344 
345     @Test
346     @RequiresFlagsEnabled(Flags.FLAG_CAMERA_DEVICE_SETUP)
testOutputConfigurationForCameraSetup()347     public void testOutputConfigurationForCameraSetup() throws Exception {
348         Size size = new Size(640, 480);
349         int fmt = ImageFormat.YUV_420_888;
350         int jpegFmt = ImageFormat.JPEG;
351         int maxImages = 2;
352         int surfaceGroupId = 1;
353         ImageReader reader = ImageReader.newInstance(
354                 size.getWidth(), size.getHeight(), fmt, maxImages);
355 
356         // Test new constructors for OutputConfiguration and add Surface later.
357         OutputConfiguration config = new OutputConfiguration(fmt, size);
358         config.addSurface(reader.getSurface());
359 
360         // Adding a mismatched format surface throws an IllegalArgumentException.
361         OutputConfiguration configWithGroupId = new OutputConfiguration(
362                 surfaceGroupId, jpegFmt, size);
363         try {
364             configWithGroupId.addSurface(reader.getSurface());
365             fail("Adding surface with mismatching format must throw an exception!");
366         } catch (IllegalArgumentException e) {
367         }
368 
369         // Adding a format with different dataspace throws an IllegalArgumentException.
370         final long usageFlag = HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
371                 | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE;
372         OutputConfiguration configWithUsageFlag = new OutputConfiguration(
373                 ImageFormat.JPEG_R, size, usageFlag);
374         try {
375             configWithUsageFlag.addSurface(reader.getSurface());
376             fail("Adding surface with mismatching dataspace must throw an exception!");
377         } catch (IllegalArgumentException e) {
378         }
379 
380         // Create OutputConfiguration with groupdId, format, size, and usage flag
381         OutputConfiguration configWithGroupIdAndUsage = new OutputConfiguration(
382                 surfaceGroupId, fmt, size, usageFlag);
383         assertNull(configWithGroupIdAndUsage.getSurface());
384     }
385 
386     @Test
387     @RequiresFlagsEnabled({Flags.FLAG_CAMERA_DEVICE_SETUP, Flags.FLAG_FEATURE_COMBINATION_QUERY})
testCameraDeviceSetupTemplates()388     public void testCameraDeviceSetupTemplates() throws Exception {
389         for (String cameraId : getCameraIdsUnderTest()) {
390             if (!mCameraManager.isCameraDeviceSetupSupported(cameraId)) {
391                 Log.i(TAG, "CameraDeviceSetup not supported for camera id " + cameraId);
392                 continue;
393             }
394 
395             testCameraDeviceSetupTemplatesByCamera(cameraId);
396         }
397     }
398 
testCameraDeviceSetupTemplatesByCamera(String cameraId)399     private void testCameraDeviceSetupTemplatesByCamera(String cameraId) throws Exception {
400         int[] templates = {
401                 CameraDevice.TEMPLATE_PREVIEW,
402                 CameraDevice.TEMPLATE_STILL_CAPTURE,
403                 CameraDevice.TEMPLATE_RECORD,
404                 CameraDevice.TEMPLATE_VIDEO_SNAPSHOT,
405                 CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
406                 CameraDevice.TEMPLATE_MANUAL,
407         };
408 
409         try {
410             CameraDevice.CameraDeviceSetup cameraDeviceSetup =
411                     mCameraManager.getCameraDeviceSetup(cameraId);
412             assertNotNull("Failed to create camera device setup for id " + cameraId,
413                     cameraDeviceSetup);
414 
415             openDevice(cameraId);
416             mCollector.setCameraId(cameraId);
417 
418             for (int template : templates) {
419                 try {
420                     CaptureRequest.Builder requestFromSetup =
421                             cameraDeviceSetup.createCaptureRequest(template);
422                     assertNotNull("CameraDeviceSetup failed to create capture request for camera "
423                             + cameraId + " template " + template, requestFromSetup);
424 
425                     CaptureRequest.Builder request = mCamera.createCaptureRequest(template);
426                     assertNotNull("CameraDevice failed to create capture request for template "
427                             + template, request);
428 
429                     mCollector.expectEquals("The CaptureRequest created by CameraDeviceSetup "
430                             + "and CameraDevice must have the same set of keys",
431                             request.build().getKeys(), requestFromSetup.build().getKeys());
432                 } catch (IllegalArgumentException e) {
433                     if (template == CameraDevice.TEMPLATE_MANUAL
434                             && !mStaticInfo.isCapabilitySupported(CameraCharacteristics
435                                     .REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
436                         // OK
437                     } else if (template == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG
438                             && !mStaticInfo.isCapabilitySupported(CameraCharacteristics
439                                     .REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING)) {
440                         // OK.
441                     } else if (sLegacySkipTemplates.contains(template)
442                             && mStaticInfo.isHardwareLevelLegacy()) {
443                         // OK
444                     } else if (template != CameraDevice.TEMPLATE_PREVIEW
445                             && mStaticInfo.isDepthOutputSupported()
446                             && !mStaticInfo.isColorOutputSupported()) {
447                         // OK, depth-only devices need only support PREVIEW template
448                     } else {
449                         throw e; // rethrow
450                     }
451                 }
452             }
453         } finally {
454             closeDevice(cameraId);
455         }
456     }
457 
458     /**
459      * Verify if valid session characteristics can be fetched for a particular camera.
460      */
461     @Test
462     @RequiresFlagsEnabled({Flags.FLAG_FEATURE_COMBINATION_QUERY, Flags.FLAG_CAMERA_DEVICE_SETUP})
testFeatureCombinationQueryConsistency()463     public void testFeatureCombinationQueryConsistency() throws Exception {
464         for (String cameraId : getCameraIdsUnderTest()) {
465             if (!mCameraManager.isCameraDeviceSetupSupported(cameraId)) {
466                 Log.i(TAG, "CameraDeviceSetup not supported for camera id " + cameraId);
467                 continue;
468             }
469 
470             testFeatureCombinationQueryConsistencyByCamera(cameraId);
471         }
472     }
473 
474     /**
475      * Check the feature combination query consistency between different feature combinations.
476      */
testFeatureCombinationQueryConsistencyByCamera(String cameraId)477     private void testFeatureCombinationQueryConsistencyByCamera(String cameraId) throws Exception {
478         CameraDevice.CameraDeviceSetup cameraDeviceSetup =
479                 mCameraManager.getCameraDeviceSetup(cameraId);
480         assertNotNull("Failed to create camera device setup for id " + cameraId,
481                 cameraDeviceSetup);
482 
483         StaticMetadata staticInfo = mAllStaticInfo.get(cameraId);
484         if (!staticInfo.isColorOutputSupported()) {
485             Log.i(TAG, "Camera " + cameraId + " does not support color outputs, skipping");
486             return;
487         }
488 
489         MaxStreamSizes maxStreamSizes = new MaxStreamSizes(staticInfo,
490                 cameraDeviceSetup.getId(), mContext, /*matchSize*/true);
491         Set<Long> dynamicRangeProfiles = staticInfo.getAvailableDynamicRangeProfilesChecked();
492         int[] videoStabilizationModes = staticInfo.getAvailableVideoStabilizationModesChecked();
493         Range<Integer>[] fpsRanges = staticInfo.getAeAvailableTargetFpsRangesChecked();
494         final int kPreviewStabilization =
495                 CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION;
496         boolean supportPreviewStab = CameraTestUtils.contains(videoStabilizationModes,
497                 kPreviewStabilization);
498         Map<CameraFeatureWrapper, Boolean> featureCombinationSupport = new HashMap<>();
499         for (int[] c : maxStreamSizes.getQueryableCombinations()) {
500             for (Long dynamicProfile : dynamicRangeProfiles) {
501                 if (dynamicProfile != DynamicRangeProfiles.STANDARD
502                         && dynamicProfile != DynamicRangeProfiles.HLG10) {
503                     // Only verify HLG10 and STANDARD. Skip for others.
504                     continue;
505                 }
506                 // Setup outputs
507                 List<OutputConfiguration> outputConfigs = new ArrayList<>();
508                 long minFrameDuration = setupConfigurations(staticInfo, c, maxStreamSizes,
509                         outputConfigs, dynamicProfile);
510                 if (minFrameDuration  == -1) {
511                     // Stream combination is not valid. For example, if the stream sizes are not
512                     // supported, or if the device doesn't support JPEG_R, minFrameDuration will
513                     // be -1.
514                     continue;
515                 }
516 
517                 for (Range<Integer> fpsRange : fpsRanges) {
518                     if ((fpsRange.getUpper() != 60) && (fpsRange.getUpper() != 30)) {
519                         // Skip fps ranges that are not 30fps or 60fps.
520                         continue;
521                     }
522                     if (minFrameDuration > 1e9 * 1.01 / fpsRange.getUpper()) {
523                         // Skip the fps range because the minFrameDuration cannot meet the
524                         // required range
525                         continue;
526                     }
527 
528                     String combinationStr = MaxStreamSizes.combinationToString(c)
529                             + ", dynamicRangeProfile " + dynamicProfile
530                             + ", fpsRange " + fpsRange.toString();
531                     try {
532                         CaptureRequest.Builder builder = cameraDeviceSetup.createCaptureRequest(
533                                 CameraDevice.TEMPLATE_PREVIEW);
534                         builder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
535                                 CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF);
536                         CaptureRequest request = builder.build();
537                         SessionConfiguration sessionConfig = new SessionConfiguration(
538                                 SessionConfiguration.SESSION_REGULAR, outputConfigs);
539                         sessionConfig.setSessionParameters(request);
540                         boolean isSupported = cameraDeviceSetup.isSessionConfigurationSupported(
541                                 sessionConfig);
542 
543                         // Make sure isSessionConfigurationSupported and getSessionCharacteristics
544                         // behaviors are consistent.
545                         try {
546                             cameraDeviceSetup.getSessionCharacteristics(sessionConfig);
547                             mCollector.expectTrue("getSessionCharacteristics succeeds, but "
548                                     + "isSessionConfigurationSupported return false!",
549                                     isSupported);
550                         } catch (IllegalArgumentException e) {
551                             mCollector.expectTrue(
552                                     "getSessionCharacteristics throws IllegalArgumentException,"
553                                     + "but isSessionConfigurationSupported returns true!",
554                                     !isSupported);
555                         }
556 
557                         // If preview stabilization is supported, the return value of
558                         // isSessionConfigurationSupported when stabilization is off should match
559                         // the result value when preview stabilization is on.
560                         if (supportPreviewStab) {
561                             builder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
562                                     kPreviewStabilization);
563                             CaptureRequest requestWithStab = builder.build();
564                             SessionConfiguration sessionConfigWithStab = new SessionConfiguration(
565                                     SessionConfiguration.SESSION_REGULAR, outputConfigs);
566                             sessionConfigWithStab.setSessionParameters(requestWithStab);
567 
568                             boolean isSupportedWithStab =
569                                     cameraDeviceSetup.isSessionConfigurationSupported(
570                                             sessionConfigWithStab);
571 
572                             mCollector.expectEquals(
573                                     "isSessionCharacteristicsSupported returns " + isSupported
574                                     + " with stabilization off, but returns " + isSupportedWithStab
575                                     + " with preview stabilization on.", isSupported,
576                                     isSupportedWithStab);
577 
578                             try {
579                                 cameraDeviceSetup.getSessionCharacteristics(sessionConfigWithStab);
580                                 mCollector.expectTrue(
581                                         "With stabilization on, getSessionCharacteristics "
582                                         + "succeeds, but isSessionConfigurationSupported return "
583                                         + "false!", isSupportedWithStab);
584                             } catch (IllegalArgumentException e) {
585                                 mCollector.expectTrue("With stabilization on, "
586                                         + "getSessionCharacteristics throws "
587                                         + "IllegalArgumentException, but "
588                                         + "isSessionConfigurationSupported returns true!",
589                                         !isSupportedWithStab);
590                             }
591                         }
592 
593                         featureCombinationSupport.put(
594                                 new CameraFeatureWrapper(c, fpsRange, dynamicProfile),
595                                 isSupported);
596                     } catch (Throwable e) {
597                         mCollector.addMessage(String.format(
598                                 "Output combination %s failed due to: %s",
599                                 combinationStr, e.getMessage()));
600                     }
601                 }
602             }
603         }
604 
605         // Verify that:
606         // - If a combination with JPEG_R is supported, replacing JPEG_R with JPEG is
607         //   still supported.
608         // - If a combination with 10-bit HDR is supported, replacing HDR with SDR is
609         //   still supported.
610         for (Map.Entry<CameraFeatureWrapper, Boolean> entry
611                 : featureCombinationSupport.entrySet()) {
612             CameraFeatureWrapper features = entry.getKey();
613             boolean isSupported = entry.getValue();
614 
615             Log.v(TAG, "Features: "
616                     + Arrays.toString(features.mStreamCombination) + ", isJpegR "
617                     + features.mIsJpegR + ", fpsRange: "
618                     + features.mFpsRange + ", dynamicProfile: "
619                     + features.mDynamicProfile);
620             if (features.mIsJpegR) {
621                 CameraFeatureWrapper featuresNoJpegR = new CameraFeatureWrapper(features,
622                         /*isJpegR*/false);
623                 assertTrue(featureCombinationSupport.containsKey(featuresNoJpegR));
624                 boolean isSupportedNoJpegR = featureCombinationSupport.get(featuresNoJpegR);
625                 mCollector.expectEquals(
626                         features.toString() + " (support: " + isSupported
627                         + ") doesn't match " + featuresNoJpegR.toString() + " (support: "
628                         + isSupportedNoJpegR, isSupported, isSupportedNoJpegR);
629             }
630 
631             if (features.mDynamicProfile != DynamicRangeProfiles.STANDARD) {
632                 CameraFeatureWrapper featuresNoHdr = new CameraFeatureWrapper(
633                         features, DynamicRangeProfiles.STANDARD);
634                 assertTrue(featureCombinationSupport.containsKey(featuresNoHdr));
635                 boolean isSupportedNoHdr = featureCombinationSupport.get(featuresNoHdr);
636                 mCollector.expectEquals(
637                         features.toString() + " (support: " + isSupported
638                         + ") doesn't match " + featuresNoHdr.toString() + " (support: "
639                         + isSupportedNoHdr, isSupported, isSupportedNoHdr);
640             }
641         }
642     }
643 
644     /**
645      * A helper class to wrap camera features.
646      */
647     static class CameraFeatureWrapper {
CameraFeatureWrapper(int[] streamCombination, Range<Integer> fpsRange, long dynamicProfile)648         CameraFeatureWrapper(int[] streamCombination,
649                 Range<Integer> fpsRange, long dynamicProfile) {
650             mFpsRange = fpsRange;
651             mDynamicProfile = dynamicProfile;
652             mStreamCombination = Arrays.copyOf(streamCombination, streamCombination.length);
653 
654             // Replace JPEG_R format with JPEG, and use a boolean to indicate whether
655             // it's JPEG_R or JPEG. This is for easier feature look-up.
656             boolean hasJpegR = false;
657             for (int i = 0; i < mStreamCombination.length; i += 2) {
658                 if (mStreamCombination[i] == JPEG_R) {
659                     mStreamCombination[i] = JPEG;
660                     hasJpegR = true;
661                     break;
662                 }
663             }
664             mIsJpegR = hasJpegR;
665         }
666 
CameraFeatureWrapper(CameraFeatureWrapper other, boolean isJpegR)667         CameraFeatureWrapper(CameraFeatureWrapper other, boolean isJpegR) {
668             this.mStreamCombination = other.mStreamCombination;
669             this.mIsJpegR = isJpegR;
670             this.mFpsRange = other.mFpsRange;
671             this.mDynamicProfile = other.mDynamicProfile;
672         }
673 
CameraFeatureWrapper(CameraFeatureWrapper other, long dynamicRangeProfile)674         CameraFeatureWrapper(CameraFeatureWrapper other, long dynamicRangeProfile) {
675             this.mStreamCombination = other.mStreamCombination;
676             this.mIsJpegR = other.mIsJpegR;
677             this.mFpsRange = other.mFpsRange;
678             this.mDynamicProfile = dynamicRangeProfile;
679         }
680 
681         @Override
hashCode()682         public int hashCode() {
683             int result = 17;
684             for (int i : mStreamCombination) {
685                 result = 31 * result + i;
686             }
687             result = 31 * result + (mIsJpegR ? 1 : 0);
688             result = 31 * result + mFpsRange.hashCode();
689             result = 31 * result + (int) mDynamicProfile;
690             return result;
691         }
692 
693         @Override
equals(Object obj)694         public boolean equals(Object obj) {
695             if (obj == null) {
696                 return false;
697             } else if (obj == this) {
698                 return true;
699             } else if (obj instanceof CameraFeatureWrapper) {
700                 final CameraFeatureWrapper other = (CameraFeatureWrapper) obj;
701                 return (Arrays.equals(mStreamCombination, other.mStreamCombination)
702                         && mIsJpegR == other.mIsJpegR
703                         && mFpsRange.equals(other.mFpsRange)
704                         && mDynamicProfile == other.mDynamicProfile);
705             }
706 
707             return false;
708         }
709 
710         @Override
toString()711         public String toString() {
712             return "Stream combination: " + Arrays.toString(mStreamCombination)
713                     + ", isJpegR: " + mIsJpegR
714                     + ", fpsRange: " + mFpsRange
715                     + ", dynamicRangeProfile: " + mDynamicProfile;
716         }
717 
718         public final int[] mStreamCombination;
719         public final boolean mIsJpegR;
720         public final Range<Integer> mFpsRange;
721         public final long mDynamicProfile;
722     }
723 
setupConfigurations(StaticMetadata staticInfo, int[] configs, MaxStreamSizes maxSizes, List<OutputConfiguration> outputConfigs, Long dynamicProfile)724     private long setupConfigurations(StaticMetadata staticInfo, int[] configs,
725             MaxStreamSizes maxSizes, List<OutputConfiguration> outputConfigs, Long dynamicProfile) {
726         long frameDuration = -1;
727         for (int i = 0; i < configs.length; i += 2) {
728             int format = configs[i];
729             int sizeLimit = configs[i + 1];
730 
731             Size targetSize = null;
732             switch (format) {
733                 case PRIV: {
734                     targetSize = maxSizes.getOutputSizeForFormat(PRIV, sizeLimit);
735                     OutputConfiguration config = new OutputConfiguration(
736                             targetSize, SurfaceTexture.class);
737                     config.setDynamicRangeProfile(dynamicProfile);
738                     outputConfigs.add(config);
739                     break;
740                 }
741                 case JPEG:
742                 case JPEG_R: {
743                     targetSize = maxSizes.getOutputSizeForFormat(format, sizeLimit);
744                     OutputConfiguration config = new OutputConfiguration(format, targetSize);
745                     outputConfigs.add(config);
746                     break;
747                 }
748                 case YUV: {
749                     if (dynamicProfile == DynamicRangeProfiles.HLG10) {
750                         format = ImageFormat.YCBCR_P010;
751                     }
752                     targetSize = maxSizes.getOutputSizeForFormat(YUV, sizeLimit);
753                     OutputConfiguration config = new OutputConfiguration(format, targetSize);
754                     config.setDynamicRangeProfile(dynamicProfile);
755                     outputConfigs.add(config);
756                     break;
757                 }
758                 default:
759                     fail("Unknown output format " + format);
760             }
761 
762             Map<Size, Long> minFrameDurations =
763                     staticInfo.getAvailableMinFrameDurationsForFormatChecked(format);
764             if (minFrameDurations.containsKey(targetSize)
765                     && minFrameDurations.get(targetSize) > frameDuration) {
766                 frameDuration = minFrameDurations.get(targetSize);
767             }
768         }
769 
770         return frameDuration;
771     }
772 }
773