1 /*
2  * Copyright 2018 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.ImageDropperListener;
20 import static android.hardware.camera2.cts.CameraTestUtils.PREVIEW_SIZE_BOUND;
21 import static android.hardware.camera2.cts.CameraTestUtils.SESSION_CONFIGURE_TIMEOUT_MS;
22 import static android.hardware.camera2.cts.CameraTestUtils.SessionConfigSupport;
23 import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
24 import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
25 import static android.hardware.camera2.cts.CameraTestUtils.assertFalse;
26 import static android.hardware.camera2.cts.CameraTestUtils.assertNotNull;
27 import static android.hardware.camera2.cts.CameraTestUtils.assertTrue;
28 import static android.hardware.camera2.cts.CameraTestUtils.configureCameraSessionWithConfig;
29 import static android.hardware.camera2.cts.CameraTestUtils.fail;
30 import static android.hardware.camera2.cts.CameraTestUtils.getCropRegionForZoom;
31 import static android.hardware.camera2.cts.CameraTestUtils.getMaxPreviewSize;
32 import static android.hardware.camera2.cts.CameraTestUtils.getPreviewSizeBound;
33 import static android.hardware.camera2.cts.CameraTestUtils.getSupportedPreviewSizes;
34 import static android.hardware.camera2.cts.CameraTestUtils.getUnavailablePhysicalCameras;
35 import static android.hardware.camera2.cts.CameraTestUtils.isSessionConfigSupported;
36 
37 import static org.mockito.Mockito.any;
38 import static org.mockito.Mockito.mock;
39 import static org.mockito.Mockito.never;
40 import static org.mockito.Mockito.timeout;
41 import static org.mockito.Mockito.verify;
42 
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.IntentFilter;
46 import android.graphics.ImageFormat;
47 import android.graphics.PointF;
48 import android.graphics.Rect;
49 import android.hardware.camera2.CameraCaptureSession;
50 import android.hardware.camera2.CameraCharacteristics;
51 import android.hardware.camera2.CameraDevice;
52 import android.hardware.camera2.CaptureFailure;
53 import android.hardware.camera2.CaptureRequest;
54 import android.hardware.camera2.CaptureResult;
55 import android.hardware.camera2.TotalCaptureResult;
56 import android.hardware.camera2.cts.helpers.StaticMetadata;
57 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
58 import android.hardware.camera2.params.OutputConfiguration;
59 import android.hardware.camera2.params.SessionConfiguration;
60 import android.hardware.camera2.params.StreamConfigurationMap;
61 import android.media.Image;
62 import android.media.ImageReader;
63 import android.os.BatteryManager;
64 import android.util.ArraySet;
65 import android.util.DisplayMetrics;
66 import android.util.Log;
67 import android.util.Pair;
68 import android.util.Range;
69 import android.util.Size;
70 import android.util.SizeF;
71 import android.view.WindowManager;
72 
73 import com.android.compatibility.common.util.CddTest;
74 import com.android.ex.camera2.blocking.BlockingSessionCallback;
75 import com.android.ex.camera2.utils.StateWaiter;
76 
77 import org.junit.Test;
78 import org.junit.runner.RunWith;
79 import org.junit.runners.Parameterized;
80 
81 import java.util.ArrayList;
82 import java.util.Arrays;
83 import java.util.HashMap;
84 import java.util.List;
85 import java.util.Map;
86 import java.util.Set;
87 
88 /**
89  * Tests exercising logical camera setup, configuration, and usage.
90  */
91 @RunWith(Parameterized.class)
92 public final class LogicalCameraDeviceTest extends Camera2SurfaceViewTestCase {
93     private static final String TAG = "LogicalCameraDeviceTest";
94     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
95 
96     private static final int CONFIGURE_TIMEOUT = 5000; //ms
97 
98     private static final double NS_PER_MS = 1000000.0;
99     private static final int MAX_IMAGE_COUNT = 3;
100     private static final int NUM_FRAMES_CHECKED = 300;
101 
102     private static final double FRAME_DURATION_THRESHOLD = 0.03;
103     private static final double FOV_THRESHOLD = 0.03;
104     private static final double ZOOM_RATIO_THRESHOLD = 0.01;
105     private static final long MAX_TIMESTAMP_DIFFERENCE_THRESHOLD = 10000000; // 10ms
106 
107     private StateWaiter mSessionWaiter;
108 
109     private final int[] sTemplates = new int[] {
110         CameraDevice.TEMPLATE_PREVIEW,
111         CameraDevice.TEMPLATE_RECORD,
112         CameraDevice.TEMPLATE_STILL_CAPTURE,
113         CameraDevice.TEMPLATE_VIDEO_SNAPSHOT,
114         CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
115         CameraDevice.TEMPLATE_MANUAL,
116     };
117 
118     @Override
setUp()119     public void setUp() throws Exception {
120         super.setUp();
121     }
122 
123     /**
124      * Test that passing in invalid physical camera ids in OutputConfiguragtion behaves as expected
125      * for logical multi-camera and non-logical multi-camera.
126      */
127     @Test
testInvalidPhysicalCameraIdInOutputConfiguration()128     public void testInvalidPhysicalCameraIdInOutputConfiguration() throws Exception {
129         for (String id : getCameraIdsUnderTest()) {
130             try {
131                 Log.i(TAG, "Testing Camera " + id);
132                 if (mAllStaticInfo.get(id).isHardwareLevelLegacy()) {
133                     Log.i(TAG, "Camera " + id + " is legacy, skipping");
134                     continue;
135                 }
136                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
137                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
138                     continue;
139                 }
140 
141                 openDevice(id);
142                 Size yuvSize = mOrderedPreviewSizes.get(0);
143                 // Create a YUV image reader.
144                 ImageReader imageReader = ImageReader.newInstance(yuvSize.getWidth(),
145                         yuvSize.getHeight(), ImageFormat.YUV_420_888, /*maxImages*/1);
146 
147                 CameraCaptureSession.StateCallback sessionListener =
148                         mock(CameraCaptureSession.StateCallback.class);
149                 List<OutputConfiguration> outputs = new ArrayList<>();
150                 OutputConfiguration outputConfig = new OutputConfiguration(
151                         imageReader.getSurface());
152                 OutputConfiguration outputConfigCopy =
153                         new OutputConfiguration(imageReader.getSurface());
154                 assertTrue("OutputConfiguration must be equal to its copy",
155                         outputConfig.equals(outputConfigCopy));
156                 outputConfig.setPhysicalCameraId(id);
157                 assertFalse("OutputConfigurations with different physical Ids must be different",
158                         outputConfig.equals(outputConfigCopy));
159                 String idCopy = new String(id);
160                 outputConfigCopy.setPhysicalCameraId(idCopy);
161                 assertTrue("OutputConfigurations with same physical Ids must be equal",
162                         outputConfig.equals(outputConfigCopy));
163 
164                 // Regardless of logical camera or non-logical camera, create a session of an
165                 // output configuration with invalid physical camera id, verify that the
166                 // createCaptureSession fails.
167                 outputs.add(outputConfig);
168                 CameraCaptureSession session =
169                         CameraTestUtils.configureCameraSessionWithConfig(mCamera, outputs,
170                                 sessionListener, mHandler);
171                 verify(sessionListener, timeout(CONFIGURE_TIMEOUT).atLeastOnce()).
172                         onConfigureFailed(any(CameraCaptureSession.class));
173                 verify(sessionListener, never()).onConfigured(any(CameraCaptureSession.class));
174                 verify(sessionListener, never()).onReady(any(CameraCaptureSession.class));
175                 verify(sessionListener, never()).onActive(any(CameraCaptureSession.class));
176                 verify(sessionListener, never()).onClosed(any(CameraCaptureSession.class));
177             } finally {
178                 closeDevice();
179             }
180         }
181     }
182 
183     /**
184      * Test for making sure that streaming from physical streams work as expected.
185      */
186     @Test
testBasicPhysicalStreaming()187     public void testBasicPhysicalStreaming() throws Exception {
188         Set<Pair<String, String>> unavailablePhysicalCameras = getUnavailablePhysicalCameras(
189                 mCameraManager, mHandler);
190         for (String id : getCameraIdsUnderTest()) {
191             try {
192                 Log.i(TAG, "Testing Camera " + id);
193 
194                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
195                 if (!staticInfo.isColorOutputSupported()) {
196                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
197                     continue;
198                 }
199 
200                 if (!staticInfo.isLogicalMultiCamera()) {
201                     Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
202                     continue;
203                 }
204 
205                 openDevice(id);
206                 assertTrue("Logical multi-camera must be LIMITED or higher",
207                         mStaticInfo.isHardwareLevelAtLeastLimited());
208 
209                 // Figure out preview size and physical cameras to use.
210                 ArrayList<String> dualPhysicalCameraIds = new ArrayList<String>();
211                 Size previewSize = findCommonPreviewSize(id, dualPhysicalCameraIds,
212                         unavailablePhysicalCameras);
213                 if (previewSize == null) {
214                     Log.i(TAG, "Camera " + id + ": No matching physical preview streams, skipping");
215                     continue;
216                 }
217 
218                 testBasicPhysicalStreamingForCamera(
219                         id, dualPhysicalCameraIds, previewSize);
220             } finally {
221                 closeDevice();
222             }
223         }
224     }
225 
226     /**
227      * Test for making sure that logical/physical stream requests work when both logical stream
228      * and physical stream are configured.
229      */
230     @Test
testBasicLogicalPhysicalStreamCombination()231     public void testBasicLogicalPhysicalStreamCombination() throws Exception {
232         Set<Pair<String, String>> unavailablePhysicalCameras = getUnavailablePhysicalCameras(
233                 mCameraManager, mHandler);
234         for (String id : getCameraIdsUnderTest()) {
235             try {
236                 Log.i(TAG, "Testing Camera " + id);
237 
238                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
239                 if (!staticInfo.isColorOutputSupported()) {
240                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
241                     continue;
242                 }
243 
244                 if (!staticInfo.isLogicalMultiCamera()) {
245                     Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
246                     continue;
247                 }
248 
249                 openDevice(id);
250                 assertTrue("Logical multi-camera must be LIMITED or higher",
251                         mStaticInfo.isHardwareLevelAtLeastLimited());
252 
253                 // Figure out yuv size and physical cameras to use.
254                 List<String> dualPhysicalCameraIds = new ArrayList<String>();
255                 Size yuvSize = findCommonPreviewSize(id, dualPhysicalCameraIds,
256                         unavailablePhysicalCameras);
257                 if (yuvSize == null) {
258                     Log.i(TAG, "Camera " + id + ": No matching physical YUV streams, skipping");
259                     continue;
260                 }
261 
262                 if (VERBOSE) {
263                     Log.v(TAG, "Camera " + id + ": Testing YUV size of " + yuvSize.getWidth() +
264                         " x " + yuvSize.getHeight());
265                 }
266 
267                 List<OutputConfiguration> outputConfigs = new ArrayList<>();
268                 List<SimpleImageReaderListener> imageReaderListeners = new ArrayList<>();
269                 SimpleImageReaderListener readerListenerPhysical = new SimpleImageReaderListener();
270                 ImageReader yuvTargetPhysical = CameraTestUtils.makeImageReader(yuvSize,
271                         ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
272                         readerListenerPhysical, mHandler);
273                 OutputConfiguration config = new OutputConfiguration(yuvTargetPhysical.getSurface());
274                 config.setPhysicalCameraId(dualPhysicalCameraIds.get(0));
275                 outputConfigs.add(config);
276 
277                 SimpleImageReaderListener readerListenerLogical = new SimpleImageReaderListener();
278                 ImageReader yuvTargetLogical = CameraTestUtils.makeImageReader(yuvSize,
279                         ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
280                         readerListenerLogical, mHandler);
281                 outputConfigs.add(new OutputConfiguration(yuvTargetLogical.getSurface()));
282                 imageReaderListeners.add(readerListenerLogical);
283 
284                 SessionConfigSupport sessionConfigSupport = isSessionConfigSupported(
285                         mCamera, mHandler, outputConfigs, /*inputConfig*/ null,
286                         SessionConfiguration.SESSION_REGULAR, mCameraManager,
287                         false/*defaultSupport*/);
288                 assertTrue("Session configuration query for logical camera failed with error",
289                         !sessionConfigSupport.error);
290                 if (!sessionConfigSupport.callSupported) {
291                     continue;
292                 }
293 
294                 mSessionListener = new BlockingSessionCallback();
295                 mSessionWaiter = mSessionListener.getStateWaiter();
296                 mSession = configureCameraSessionWithConfig(mCamera, outputConfigs,
297                         mSessionListener, mHandler);
298                 if (!sessionConfigSupport.configSupported) {
299                     mSessionWaiter.waitForState(BlockingSessionCallback.SESSION_CONFIGURE_FAILED,
300                             SESSION_CONFIGURE_TIMEOUT_MS);
301                     continue;
302                 }
303 
304                 // Test request logical stream with an idle physical stream.
305                 CaptureRequest.Builder requestBuilder =
306                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
307                 requestBuilder.addTarget(yuvTargetLogical.getSurface());
308 
309                 for (int i = 0; i < MAX_IMAGE_COUNT; i++) {
310                     mSession.capture(requestBuilder.build(), new SimpleCaptureCallback(), mHandler);
311                     for (SimpleImageReaderListener readerListener : imageReaderListeners) {
312                         Image image = readerListener.getImage(WAIT_FOR_RESULT_TIMEOUT_MS);
313                         image.close();
314                     }
315                 }
316 
317                 // Test request physical stream with an idle logical stream.
318                 imageReaderListeners.clear();
319                 imageReaderListeners.add(readerListenerPhysical);
320 
321                 requestBuilder.removeTarget(yuvTargetLogical.getSurface());
322                 requestBuilder.addTarget(yuvTargetPhysical.getSurface());
323 
324                 for (int i = 0; i < MAX_IMAGE_COUNT; i++) {
325                     mSession.capture(requestBuilder.build(), new SimpleCaptureCallback(), mHandler);
326                     for (SimpleImageReaderListener readerListener : imageReaderListeners) {
327                         Image image = readerListener.getImage(WAIT_FOR_RESULT_TIMEOUT_MS);
328                         image.close();
329                     }
330                 }
331 
332                 // Test request logical and physical streams at the same time
333                 imageReaderListeners.clear();
334                 readerListenerLogical.drain();
335                 imageReaderListeners.add(readerListenerLogical);
336                 readerListenerPhysical.drain();
337                 imageReaderListeners.add(readerListenerPhysical);
338 
339                 requestBuilder.addTarget(yuvTargetLogical.getSurface());
340                 for (int i = 0; i < MAX_IMAGE_COUNT; i++) {
341                     mSession.capture(requestBuilder.build(), new SimpleCaptureCallback(), mHandler);
342                     for (SimpleImageReaderListener readerListener : imageReaderListeners) {
343                         Image image = readerListener.getImage(WAIT_FOR_RESULT_TIMEOUT_MS);
344                         image.close();
345                     }
346                 }
347 
348                 if (mSession != null) {
349                     mSession.close();
350                 }
351 
352                 // Image readers need to be referenced so that they aren't garbage collected
353                 // before the function exit.
354                 CameraTestUtils.closeImageReader(yuvTargetPhysical);
355                 CameraTestUtils.closeImageReader(yuvTargetLogical);
356             } finally {
357                 closeDevice();
358             }
359         }
360     }
361 
362     /**
363      * Test for making sure that multiple requests for physical cameras work as expected.
364      */
365     @Test
testBasicPhysicalRequests()366     public void testBasicPhysicalRequests() throws Exception {
367         Set<Pair<String, String>> unavailablePhysicalCameras = getUnavailablePhysicalCameras(
368                 mCameraManager, mHandler);
369         for (String id : getCameraIdsUnderTest()) {
370             try {
371                 Log.i(TAG, "Testing Camera " + id);
372 
373                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
374                 if (!staticInfo.isColorOutputSupported()) {
375                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
376                     continue;
377                 }
378 
379                 if (!staticInfo.isLogicalMultiCamera()) {
380                     Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
381                     continue;
382                 }
383 
384                 openDevice(id);
385                 assertTrue("Logical multi-camera must be LIMITED or higher",
386                         mStaticInfo.isHardwareLevelAtLeastLimited());
387 
388                 // Figure out yuv size and physical cameras to use.
389                 List<String> dualPhysicalCameraIds = new ArrayList<String>();
390                 Size yuvSize = findCommonPreviewSize(id, dualPhysicalCameraIds,
391                         unavailablePhysicalCameras);
392                 if (yuvSize == null) {
393                     Log.i(TAG, "Camera " + id + ": No matching physical YUV streams, skipping");
394                     continue;
395                 }
396                 ArraySet<String> physicalIdSet = new ArraySet<String>(dualPhysicalCameraIds.size());
397                 physicalIdSet.addAll(dualPhysicalCameraIds);
398 
399                 if (VERBOSE) {
400                     Log.v(TAG, "Camera " + id + ": Testing YUV size of " + yuvSize.getWidth() +
401                         " x " + yuvSize.getHeight());
402                 }
403                 List<CaptureRequest.Key<?>> physicalRequestKeys =
404                     mStaticInfo.getCharacteristics().getAvailablePhysicalCameraRequestKeys();
405                 if (physicalRequestKeys == null) {
406                     Log.i(TAG, "Camera " + id + ": no available physical request keys, skipping");
407                     continue;
408                 }
409 
410                 List<OutputConfiguration> outputConfigs = new ArrayList<>();
411                 List<ImageReader> imageReaders = new ArrayList<>();
412                 List<SimpleImageReaderListener> imageReaderListeners = new ArrayList<>();
413                 for (String physicalCameraId : dualPhysicalCameraIds) {
414                     SimpleImageReaderListener readerListener = new SimpleImageReaderListener();
415                     ImageReader yuvTarget = CameraTestUtils.makeImageReader(yuvSize,
416                             ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
417                             readerListener, mHandler);
418                     OutputConfiguration config = new OutputConfiguration(yuvTarget.getSurface());
419                     config.setPhysicalCameraId(physicalCameraId);
420                     outputConfigs.add(config);
421                     imageReaders.add(yuvTarget);
422                     imageReaderListeners.add(readerListener);
423                 }
424 
425                 SessionConfigSupport sessionConfigSupport = isSessionConfigSupported(
426                         mCamera, mHandler, outputConfigs, /*inputConfig*/ null,
427                         SessionConfiguration.SESSION_REGULAR, mCameraManager,
428                         false/*defaultSupport*/);
429                 assertTrue("Session configuration query for logical camera failed with error",
430                         !sessionConfigSupport.error);
431                 if (!sessionConfigSupport.callSupported) {
432                     continue;
433                 }
434 
435                 CaptureRequest.Builder requestBuilder =
436                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, physicalIdSet);
437                 for (OutputConfiguration c : outputConfigs) {
438                     requestBuilder.addTarget(c.getSurface());
439                 }
440 
441                 mSessionListener = new BlockingSessionCallback();
442                 mSessionWaiter = mSessionListener.getStateWaiter();
443                 mSession = configureCameraSessionWithConfig(mCamera, outputConfigs,
444                         mSessionListener, mHandler);
445                 if (!sessionConfigSupport.configSupported) {
446                     mSessionWaiter.waitForState(BlockingSessionCallback.SESSION_CONFIGURE_FAILED,
447                             SESSION_CONFIGURE_TIMEOUT_MS);
448                     continue;
449                 }
450 
451                 for (int i = 0; i < MAX_IMAGE_COUNT; i++) {
452                     mSession.capture(requestBuilder.build(), new SimpleCaptureCallback(), mHandler);
453                     for (SimpleImageReaderListener readerListener : imageReaderListeners) {
454                         readerListener.getImage(WAIT_FOR_RESULT_TIMEOUT_MS);
455                     }
456                 }
457 
458                 // Test streaming requests
459                 imageReaders.clear();
460                 outputConfigs.clear();
461 
462                 // Add physical YUV streams
463                 List<ImageReader> physicalTargets = new ArrayList<>();
464                 for (String physicalCameraId : dualPhysicalCameraIds) {
465                     ImageReader physicalTarget = CameraTestUtils.makeImageReader(yuvSize,
466                             ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
467                             new ImageDropperListener(), mHandler);
468                     OutputConfiguration config = new OutputConfiguration(
469                             physicalTarget.getSurface());
470                     config.setPhysicalCameraId(physicalCameraId);
471                     outputConfigs.add(config);
472                     physicalTargets.add(physicalTarget);
473                 }
474 
475                 mSessionListener = new BlockingSessionCallback();
476                 mSession = configureCameraSessionWithConfig(mCamera, outputConfigs,
477                         mSessionListener, mHandler);
478 
479                 requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW,
480                             physicalIdSet);
481                 for (OutputConfiguration c : outputConfigs) {
482                     requestBuilder.addTarget(c.getSurface());
483                 }
484 
485                 SimpleCaptureCallback simpleResultListener = new SimpleCaptureCallback();
486                 mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListener,
487                         mHandler);
488 
489                 // Converge AE
490                 waitForAeStable(simpleResultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
491 
492                 if (mSession != null) {
493                     mSession.close();
494                 }
495                 physicalTargets.clear();
496 
497             } finally {
498                 closeDevice();
499             }
500         }
501     }
502 
503     /**
504      * Tests invalid/incorrect multiple physical capture request cases.
505      */
506     @Test
testInvalidPhysicalCameraRequests()507     public void testInvalidPhysicalCameraRequests() throws Exception {
508         Set<Pair<String, String>> unavailablePhysicalCameras = getUnavailablePhysicalCameras(
509                 mCameraManager, mHandler);
510         for (String id : getCameraIdsUnderTest()) {
511             try {
512                 Log.i(TAG, "Testing Camera " + id);
513 
514                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
515                 if (staticInfo.isHardwareLevelLegacy()) {
516                     Log.i(TAG, "Camera " + id + " is legacy, skipping");
517                     continue;
518                 }
519                 if (!staticInfo.isColorOutputSupported()) {
520                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
521                     continue;
522                 }
523 
524                 openDevice(id);
525                 Size yuvSize = mOrderedPreviewSizes.get(0);
526                 List<OutputConfiguration> outputConfigs = new ArrayList<>();
527                 List<ImageReader> imageReaders = new ArrayList<>();
528                 SimpleImageReaderListener readerListener = new SimpleImageReaderListener();
529                 ImageReader yuvTarget = CameraTestUtils.makeImageReader(yuvSize,
530                         ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
531                         readerListener, mHandler);
532                 imageReaders.add(yuvTarget);
533                 OutputConfiguration config = new OutputConfiguration(yuvTarget.getSurface());
534                 outputConfigs.add(new OutputConfiguration(yuvTarget.getSurface()));
535 
536                 ArraySet<String> physicalIdSet = new ArraySet<String>();
537                 // Invalid physical id
538                 physicalIdSet.add("-2");
539 
540                 CaptureRequest.Builder requestBuilder =
541                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, physicalIdSet);
542                 requestBuilder.addTarget(config.getSurface());
543 
544                 // Check for invalid setting get/set
545                 try {
546                     requestBuilder.getPhysicalCameraKey(CaptureRequest.CONTROL_CAPTURE_INTENT, "-1");
547                     fail("No exception for invalid physical camera id");
548                 } catch (IllegalArgumentException e) {
549                     //expected
550                 }
551 
552                 try {
553                     requestBuilder.setPhysicalCameraKey(CaptureRequest.CONTROL_CAPTURE_INTENT,
554                             new Integer(0), "-1");
555                     fail("No exception for invalid physical camera id");
556                 } catch (IllegalArgumentException e) {
557                     //expected
558                 }
559 
560                 mSessionListener = new BlockingSessionCallback();
561                 mSession = configureCameraSessionWithConfig(mCamera, outputConfigs,
562                         mSessionListener, mHandler);
563 
564                 try {
565                     mSession.capture(requestBuilder.build(), new SimpleCaptureCallback(),
566                             mHandler);
567                     fail("No exception for invalid physical camera id");
568                 } catch (IllegalArgumentException e) {
569                     //expected
570                 }
571 
572                 if (mStaticInfo.isLogicalMultiCamera()) {
573                     // Figure out yuv size to use.
574                     List<String> dualPhysicalCameraIds = new ArrayList<String>();
575                     Size sharedSize = findCommonPreviewSize(id, dualPhysicalCameraIds,
576                             unavailablePhysicalCameras);
577                     if (sharedSize == null) {
578                         Log.i(TAG, "Camera " + id + ": No matching physical YUV streams, skipping");
579                         continue;
580                     }
581 
582                     physicalIdSet.clear();
583                     physicalIdSet.addAll(dualPhysicalCameraIds);
584                     requestBuilder =
585                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, physicalIdSet);
586                     requestBuilder.addTarget(config.getSurface());
587                     CaptureRequest request = requestBuilder.build();
588 
589                     try {
590                         mSession.capture(request, new SimpleCaptureCallback(), mHandler);
591                         fail("Capture requests that don't configure outputs with corresponding " +
592                                 "physical camera id should fail");
593                     } catch (IllegalArgumentException e) {
594                         //expected
595                     }
596                 }
597 
598                 if (mSession != null) {
599                     mSession.close();
600                 }
601                 imageReaders.clear();
602             } finally {
603                 closeDevice();
604             }
605         }
606     }
607 
608     /**
609      * Test for physical camera switch based on focal length (optical zoom) and crop region
610      * (digital zoom).
611      *
612      * - Focal length and crop region change must be synchronized to not have sudden jump in field
613      *   of view.
614      * - Main physical id must be valid.
615      */
616     @Test
testLogicalCameraZoomSwitch()617     public void testLogicalCameraZoomSwitch() throws Exception {
618 
619         for (String id : getCameraIdsUnderTest()) {
620             try {
621                 Log.i(TAG, "Testing Camera " + id);
622 
623                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
624                 if (!staticInfo.isColorOutputSupported()) {
625                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
626                     continue;
627                 }
628 
629                 if (!staticInfo.isLogicalMultiCamera()) {
630                     Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
631                     continue;
632                 }
633 
634                 openDevice(id);
635                 Size yuvSize = mOrderedPreviewSizes.get(0);
636                 // Create a YUV image reader.
637                 ImageReader imageReader = CameraTestUtils.makeImageReader(yuvSize,
638                         ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
639                         new ImageDropperListener(), mHandler);
640 
641                 List<OutputConfiguration> outputConfigs = new ArrayList<>();
642                 OutputConfiguration config = new OutputConfiguration(imageReader.getSurface());
643                 outputConfigs.add(config);
644 
645                 mSessionListener = new BlockingSessionCallback();
646                 mSession = configureCameraSessionWithConfig(mCamera, outputConfigs,
647                         mSessionListener, mHandler);
648 
649                 final float FOV_MARGIN = 0.01f;
650                 final float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked();
651                 final int zoomSteps = focalLengths.length;
652                 final float maxZoom = staticInfo.getAvailableMaxDigitalZoomChecked();
653                 final Rect activeArraySize = staticInfo.getActiveArraySizeChecked();
654                 final Set<String> physicalIds =
655                         staticInfo.getCharacteristics().getPhysicalCameraIds();
656 
657                 CaptureRequest.Builder requestBuilder =
658                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
659                 requestBuilder.addTarget(imageReader.getSurface());
660 
661                 // For each adjacent focal lengths, set different crop region such that the
662                 // resulting angle of view is the same. This is to make sure that no sudden FOV
663                 // (field of view) jump when switching between different focal lengths.
664                 for (int i = 0; i < zoomSteps-1; i++) {
665                     // Start with larger focal length + full active array crop region.
666                     requestBuilder.set(CaptureRequest.LENS_FOCAL_LENGTH, focalLengths[i+1]);
667                     requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, activeArraySize);
668                     SimpleCaptureCallback simpleResultListener = new SimpleCaptureCallback();
669                     mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListener,
670                             mHandler);
671                     waitForAeStable(simpleResultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
672 
673                     // This is an approximate, assuming that subject distance >> focal length.
674                     float zoomFactor = focalLengths[i+1]/focalLengths[i];
675                     PointF zoomCenter = new PointF(0.5f, 0.5f);
676                     Rect requestCropRegion = getCropRegionForZoom(zoomFactor,
677                             zoomCenter, maxZoom, activeArraySize);
678                     if (VERBOSE) {
679                         Log.v(TAG, "Switching from crop region " + activeArraySize + ", focal " +
680                             "length " + focalLengths[i+1] + " to crop region " + requestCropRegion +
681                             ", focal length " + focalLengths[i]);
682                     }
683 
684                     // Create a burst capture to switch between different focal_length/crop_region
685                     // combination with same field of view.
686                     List<CaptureRequest> requests = new ArrayList<CaptureRequest>();
687                     SimpleCaptureCallback listener = new SimpleCaptureCallback();
688 
689                     requestBuilder.set(CaptureRequest.LENS_FOCAL_LENGTH, focalLengths[i]);
690                     requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, requestCropRegion);
691                     requests.add(requestBuilder.build());
692                     requests.add(requestBuilder.build());
693 
694                     requestBuilder.set(CaptureRequest.LENS_FOCAL_LENGTH, focalLengths[i+1]);
695                     requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, activeArraySize);
696                     requests.add(requestBuilder.build());
697                     requests.add(requestBuilder.build());
698 
699                     requestBuilder.set(CaptureRequest.LENS_FOCAL_LENGTH, focalLengths[i]);
700                     requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, requestCropRegion);
701                     requests.add(requestBuilder.build());
702                     requests.add(requestBuilder.build());
703 
704                     mSession.captureBurst(requests, listener, mHandler);
705                     TotalCaptureResult[] results = listener.getTotalCaptureResultsForRequests(
706                             requests, /*numResultsWait*/0,
707                             /*timeoutForResult*/WAIT_FOR_RESULT_TIMEOUT_MS);
708 
709                     // Verify result metadata to produce similar field of view.
710                     float fov = activeArraySize.width()/(2*focalLengths[i+1]);
711                     for (int j = 0; j < results.length; j++) {
712                         TotalCaptureResult result = results[j];
713                         Float resultFocalLength = result.get(CaptureResult.LENS_FOCAL_LENGTH);
714                         Rect resultCropRegion = result.get(CaptureResult.SCALER_CROP_REGION);
715 
716                         if (VERBOSE) {
717                             Log.v(TAG, "Result crop region " + resultCropRegion + ", focal length "
718                                     + resultFocalLength + " for result " + j);
719                         }
720                         float newFov = resultCropRegion.width()/(2*resultFocalLength);
721 
722                         mCollector.expectTrue("Field of view must be consistent with focal " +
723                                 "length and crop region change cancelling out each other.",
724                                 Math.abs(newFov - fov)/fov < FOV_MARGIN);
725 
726                         if (j + 1 < results.length) {
727                             TotalCaptureResult nextResult = results[j+1];
728                             float[] lensIntrinsics = result.get(
729                                     CaptureResult.LENS_INTRINSIC_CALIBRATION);
730                             float[] nextLensIntrinsics = nextResult.get(
731                                     CaptureResult.LENS_INTRINSIC_CALIBRATION);
732                             if ((lensIntrinsics != null) && (nextLensIntrinsics != null)) {
733                                 mCollector.expectTrue("The lens intrinsics between two " +
734                                                 "different focal lengths are not expected to " +
735                                                 " match",
736                                         !Arrays.equals(lensIntrinsics, nextLensIntrinsics));
737                             } else {
738                                 mCollector.expectNull("If not supported, lens intrinsics " +
739                                                 "must always remain invalid regardless of the " +
740                                                 "focal length", lensIntrinsics);
741                                 mCollector.expectNull("If not supported, lens intrinsics " +
742                                         "must always remain invalid regardless of the " +
743                                         "focal length", nextLensIntrinsics);
744                             }
745                         }
746 
747                         if (staticInfo.isActivePhysicalCameraIdSupported()) {
748                             String activePhysicalId = result.get(
749                                     CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID);
750                             assertTrue(physicalIds.contains(activePhysicalId));
751 
752                             StaticMetadata physicalCameraStaticInfo =
753                                     mAllStaticInfo.get(activePhysicalId);
754                             float[] physicalCameraFocalLengths =
755                                     physicalCameraStaticInfo.getAvailableFocalLengthsChecked();
756                             mCollector.expectTrue("Current focal length " + resultFocalLength
757                                     + " must be supported by active physical camera "
758                                     + activePhysicalId, Arrays.asList(CameraTestUtils.toObject(
759                                     physicalCameraFocalLengths)).contains(resultFocalLength));
760                         }
761                     }
762                 }
763 
764                 if (mSession != null) {
765                     mSession.close();
766                 }
767                 CameraTestUtils.closeImageReader(imageReader);
768 
769             } finally {
770                 closeDevice();
771             }
772         }
773     }
774 
775    /**
776      * Test that for logical multi-camera, the activePhysicalId is valid, and is the same
777      * for all capture templates.
778      */
779     @Test
testActivePhysicalId()780     public void testActivePhysicalId() throws Exception {
781         Set<Pair<String, String>> unavailablePhysicalCameras = getUnavailablePhysicalCameras(
782                 mCameraManager, mHandler);
783 
784         for (String id : getCameraIdsUnderTest()) {
785             try {
786                 Log.i(TAG, "Testing Camera " + id);
787 
788                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
789                 if (!staticInfo.isColorOutputSupported()) {
790                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
791                     continue;
792                 }
793 
794                 if (!staticInfo.isLogicalMultiCamera()) {
795                     Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
796                     continue;
797                 }
798 
799                 if (!staticInfo.isActivePhysicalCameraIdSupported()) {
800                     continue;
801                 }
802 
803                 final Set<String> physicalIds =
804                         staticInfo.getCharacteristics().getPhysicalCameraIds();
805                 openDevice(id);
806                 Size previewSz =
807                         getMaxPreviewSize(mCamera.getId(), mCameraManager,
808                         getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
809 
810                 String storedActiveId = null;
811                 for (int template : sTemplates) {
812                     try {
813                         CaptureRequest.Builder requestBuilder =
814                                 mCamera.createCaptureRequest(template);
815                         SimpleCaptureCallback listener = new SimpleCaptureCallback();
816                         startPreview(requestBuilder, previewSz, listener);
817                         waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
818 
819                         CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
820                         String activePhysicalId = result.get(
821                                 CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID);
822 
823                         assertNotNull("activePhysicalId must not be null", activePhysicalId);
824                         if (storedActiveId == null) {
825                             storedActiveId = activePhysicalId;
826                             assertTrue(
827                                   "Camera device reported invalid activePhysicalId: " +
828                                   activePhysicalId, physicalIds.contains(activePhysicalId));
829                         } else {
830                             assertTrue(
831                                   "Camera device reported different activePhysicalId " +
832                                   activePhysicalId + " vs " + storedActiveId +
833                                   " for different capture templates",
834                                   storedActiveId.equals(activePhysicalId));
835                         }
836                     } catch (IllegalArgumentException e) {
837                         if (template == CameraDevice.TEMPLATE_MANUAL &&
838                                 !staticInfo.isCapabilitySupported(CameraCharacteristics.
839                                 REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
840                             // OK
841                         } else if (template == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG &&
842                                 !staticInfo.isCapabilitySupported(CameraCharacteristics.
843                                 REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING)) {
844                             // OK.
845                         } else {
846                             throw e; // rethrow
847                         }
848                     }
849                 }
850 
851                 // Query unavailable physical cameras, and make sure the active physical id
852                 // isn't an unavailable physical camera.
853                 for (Pair<String, String> unavailPhysicalCamera: unavailablePhysicalCameras) {
854                     assertFalse(unavailPhysicalCamera.first.equals(id) &&
855                            unavailPhysicalCamera.second.equals(storedActiveId));
856                 }
857             } finally {
858                 closeDevice();
859             }
860         }
861 
862     }
863 
864     /**
865      * Test that for logical multi-camera of a Handheld device, the default FOV is
866      * between 50 and 95 degrees for all capture templates.
867      */
868     @Test
869     @CddTest(requirement="7.5.4/H-1-1")
testDefaultFov()870     public void testDefaultFov() throws Exception {
871         final double MIN_FOV = 50;
872         final double MAX_FOV = 95;
873         if (!isHandheldDevice()) {
874             return;
875         }
876         for (String id : getCameraIdsUnderTest()) {
877             try {
878                 Log.i(TAG, "Testing Camera " + id);
879 
880                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
881                 if (!staticInfo.isColorOutputSupported()) {
882                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
883                     continue;
884                 }
885 
886                 if (!staticInfo.isLogicalMultiCamera()) {
887                     Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
888                     continue;
889                 }
890 
891                 SizeF physicalSize = staticInfo.getCharacteristics().get(
892                         CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE);
893                 double physicalDiag = Math.sqrt(Math.pow(physicalSize.getWidth(), 2)
894                         + Math.pow(physicalSize.getHeight(), 2));
895                 Rect activeArraySize = staticInfo.getCharacteristics().get(
896                         CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
897 
898                 openDevice(id);
899                 for (int template : sTemplates) {
900                     try {
901                         CaptureRequest.Builder requestBuilder =
902                                 mCamera.createCaptureRequest(template);
903                         Float requestFocalLength = requestBuilder.get(
904                                 CaptureRequest.LENS_FOCAL_LENGTH);
905                         assertNotNull("LENS_FOCAL_LENGTH must not be null", requestFocalLength);
906 
907                         Float requestZoomRatio = requestBuilder.get(
908                                 CaptureRequest.CONTROL_ZOOM_RATIO);
909                         assertNotNull("CONTROL_ZOOM_RATIO must not be null", requestZoomRatio);
910                         Rect requestCropRegion = requestBuilder.get(
911                                 CaptureRequest.SCALER_CROP_REGION);
912                         assertNotNull("SCALER_CROP_REGION must not be null", requestCropRegion);
913                         float totalZoomRatio = Math.min(
914                                 1.0f * activeArraySize.width() / requestCropRegion.width(),
915                                 1.0f * activeArraySize.height() / requestCropRegion.height()) *
916                                 requestZoomRatio;
917 
918                         double fov = 2 *
919                                 Math.toDegrees(Math.atan2(physicalDiag/(2 * totalZoomRatio),
920                                 requestFocalLength));
921 
922                         Log.v(TAG, "Camera " +  id + " template " + template +
923                                 "'s default FOV is " + fov);
924                         mCollector.expectInRange("Camera " +  id + " template " + template +
925                                 "'s default FOV must fall between [50, 95] degrees",
926                                 fov, MIN_FOV, MAX_FOV);
927                     } catch (IllegalArgumentException e) {
928                         if (template == CameraDevice.TEMPLATE_MANUAL &&
929                                 !staticInfo.isCapabilitySupported(CameraCharacteristics.
930                                 REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
931                             // OK
932                         } else if (template == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG &&
933                                 !staticInfo.isCapabilitySupported(CameraCharacteristics.
934                                 REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING)) {
935                             // OK.
936                         } else {
937                             throw e; // rethrow
938                         }
939                     }
940                 }
941             } finally {
942                 closeDevice();
943             }
944         }
945     }
946 
947     /**
948      * Find a common preview size that's supported by both the logical camera and
949      * two of the underlying physical cameras.
950      */
findCommonPreviewSize(String cameraId, List<String> dualPhysicalCameraIds, Set<Pair<String, String>> unavailablePhysicalCameras)951     private Size findCommonPreviewSize(String cameraId,
952             List<String> dualPhysicalCameraIds,
953             Set<Pair<String, String>> unavailablePhysicalCameras) throws Exception {
954         Set<String> physicalCameraIds =
955                 mStaticInfo.getCharacteristics().getPhysicalCameraIds();
956         assertTrue("Logical camera must contain at least 2 physical camera ids",
957                 physicalCameraIds.size() >= 2);
958 
959         List<Size> previewSizes = getSupportedPreviewSizes(
960                 cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
961         HashMap<String, List<Size>> physicalPreviewSizesMap = new HashMap<String, List<Size>>();
962         HashMap<String, StreamConfigurationMap> physicalConfigs = new HashMap<>();
963         for (String physicalCameraId : physicalCameraIds) {
964             if (unavailablePhysicalCameras.contains(new Pair<>(cameraId, physicalCameraId))) {
965                 continue;
966             }
967             CameraCharacteristics properties =
968                     mCameraManager.getCameraCharacteristics(physicalCameraId);
969             assertNotNull("Can't get camera characteristics!", properties);
970             if (!mAllStaticInfo.get(physicalCameraId).isColorOutputSupported()) {
971                 // No color output support, skip.
972                 continue;
973             }
974             StreamConfigurationMap configMap =
975                     properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
976             physicalConfigs.put(physicalCameraId, configMap);
977             physicalPreviewSizesMap.put(physicalCameraId,
978                     getSupportedPreviewSizes(physicalCameraId, mCameraManager, PREVIEW_SIZE_BOUND));
979         }
980 
981         // Find display size from window service.
982         Context context = mActivityRule.getActivity();
983         WindowManager windowManager =
984                 (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
985         Rect windowBounds = windowManager.getCurrentWindowMetrics().getBounds();
986 
987         int windowWidth = windowBounds.width();
988         int windowHeight = windowBounds.height();
989 
990         if (windowHeight > windowWidth) {
991             windowHeight = windowWidth;
992             windowWidth = windowBounds.height();
993         }
994 
995         StreamConfigurationMap config = mStaticInfo.getCharacteristics().get(
996                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
997         for (Size previewSize : previewSizes) {
998             dualPhysicalCameraIds.clear();
999             // Skip preview sizes larger than screen size
1000             if (previewSize.getWidth() > windowWidth
1001                     || previewSize.getHeight() > windowHeight) {
1002                 continue;
1003             }
1004 
1005             final long minFrameDuration = config.getOutputMinFrameDuration(
1006                     ImageFormat.YUV_420_888, previewSize);
1007 
1008             ArrayList<String> supportedPhysicalCameras = new ArrayList<String>();
1009             for (String physicalCameraId : physicalCameraIds) {
1010                 List<Size> physicalPreviewSizes = physicalPreviewSizesMap.get(physicalCameraId);
1011                 if (physicalPreviewSizes != null && physicalPreviewSizes.contains(previewSize)) {
1012                     long minDurationPhysical =
1013                             physicalConfigs.get(physicalCameraId).getOutputMinFrameDuration(
1014                                     ImageFormat.YUV_420_888, previewSize);
1015                     if (minDurationPhysical <= minFrameDuration) {
1016                         dualPhysicalCameraIds.add(physicalCameraId);
1017                         if (dualPhysicalCameraIds.size() == 2) {
1018                             return previewSize;
1019                         }
1020                     }
1021                 }
1022             }
1023         }
1024         return null;
1025     }
1026 
1027     /**
1028      * Validate that physical cameras are not cropping too much.
1029      *
1030      * This is to make sure physical processed streams have at least the same field of view as
1031      * the logical stream, or the maximum field of view of the physical camera, whichever is
1032      * smaller.
1033      *
1034      * Note that the FOV is calculated in the directio of sensor width.
1035      */
validatePhysicalCamerasFov(TotalCaptureResult totalCaptureResult, List<String> physicalCameraIds)1036     private void validatePhysicalCamerasFov(TotalCaptureResult totalCaptureResult,
1037             List<String> physicalCameraIds) {
1038         Rect cropRegion = totalCaptureResult.get(CaptureResult.SCALER_CROP_REGION);
1039         Float focalLength = totalCaptureResult.get(CaptureResult.LENS_FOCAL_LENGTH);
1040         Float zoomRatio = totalCaptureResult.get(CaptureResult.CONTROL_ZOOM_RATIO);
1041         Rect activeArraySize = mStaticInfo.getActiveArraySizeChecked();
1042         SizeF sensorSize = mStaticInfo.getValueFromKeyNonNull(
1043                 CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE);
1044         Size logicalPixelArraySize = mStaticInfo.getValueFromKeyNonNull(
1045                 CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);
1046         Rect logicalPreCorrActiveArraySize = mStaticInfo.getValueFromKeyNonNull(
1047                 CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
1048         Float fovTransferRatio = logicalPreCorrActiveArraySize.width() * 1f /
1049                 logicalPixelArraySize.getWidth();
1050         Float logicalSensorWidth = sensorSize.getWidth() * fovTransferRatio;
1051 
1052         // Assume subject distance >> focal length, and subject distance >> camera baseline.
1053         double fov = 2 * Math.toDegrees(Math.atan2(logicalSensorWidth * cropRegion.width() /
1054                 (2 * zoomRatio * activeArraySize.width()),  focalLength));
1055 
1056         Map<String, CaptureResult> physicalResultsDual =
1057                     totalCaptureResult.getPhysicalCameraResults();
1058         for (String physicalId : physicalCameraIds) {
1059             StaticMetadata physicalStaticInfo = mAllStaticInfo.get(physicalId);
1060             CaptureResult physicalResult = physicalResultsDual.get(physicalId);
1061             Rect physicalCropRegion = physicalResult.get(CaptureResult.SCALER_CROP_REGION);
1062             Float physicalFocalLength = physicalResult.get(CaptureResult.LENS_FOCAL_LENGTH);
1063             Float physicalZoomRatio = physicalResult.get(CaptureResult.CONTROL_ZOOM_RATIO);
1064             Rect physicalActiveArraySize = physicalStaticInfo.getActiveArraySizeChecked();
1065             SizeF physicalSensorSize = physicalStaticInfo.getValueFromKeyNonNull(
1066                     CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE);
1067 
1068             Size physicalPixelArraySize = physicalStaticInfo.getValueFromKeyNonNull(
1069                     CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);
1070             Rect physicalPreCorrActiveArraySize = physicalStaticInfo.getValueFromKeyNonNull(
1071                     CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
1072             Float transferRatio = physicalPreCorrActiveArraySize.width() * 1f /
1073                     physicalPixelArraySize.getWidth();
1074             Float physicalSensorWidth = physicalSensorSize.getWidth() * transferRatio;
1075 
1076             // Physical result metadata's ZOOM_RATIO is 1.0f.
1077             assertTrue("Physical result metadata ZOOM_RATIO should be 1.0f, but is " +
1078                     physicalZoomRatio, Math.abs(physicalZoomRatio - 1.0f) < ZOOM_RATIO_THRESHOLD);
1079 
1080             double physicalFov = 2 * Math.toDegrees(Math.atan2(
1081                     physicalSensorWidth * physicalCropRegion.width() /
1082                     (2 * physicalZoomRatio * physicalActiveArraySize.width()), physicalFocalLength));
1083 
1084             double maxPhysicalFov = 2 * Math.toDegrees(Math.atan2(physicalSensorWidth  / 2,
1085                     physicalFocalLength));
1086             double expectedPhysicalFov = Math.min(maxPhysicalFov, fov);
1087 
1088             if (VERBOSE) {
1089                 Log.v(TAG, "Logical camera Fov: " + fov + ", maxPhyiscalFov: " + maxPhysicalFov +
1090                         ", physicalFov: " + physicalFov);
1091             }
1092             assertTrue("Physical stream FOV (Field of view) should be greater or equal to"
1093                     + " min(logical stream FOV, max physical stream FOV). Physical FOV: "
1094                     + physicalFov + " vs min(" + fov + ", " + maxPhysicalFov,
1095                     physicalFov - expectedPhysicalFov > -FOV_THRESHOLD);
1096         }
1097     }
1098 
1099     /**
1100      * Test physical camera YUV streaming within a particular logical camera.
1101      *
1102      * Use 2 YUV streams with PREVIEW or smaller size.
1103      */
testBasicPhysicalStreamingForCamera(String logicalCameraId, List<String> physicalCameraIds, Size previewSize)1104     private void testBasicPhysicalStreamingForCamera(String logicalCameraId,
1105             List<String> physicalCameraIds, Size previewSize) throws Exception {
1106         List<OutputConfiguration> outputConfigs = new ArrayList<>();
1107         List<ImageReader> imageReaders = new ArrayList<>();
1108 
1109         // Add 1 logical YUV stream
1110         ImageReader logicalTarget = CameraTestUtils.makeImageReader(previewSize,
1111                 ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
1112                 new ImageDropperListener(), mHandler);
1113         imageReaders.add(logicalTarget);
1114         outputConfigs.add(new OutputConfiguration(logicalTarget.getSurface()));
1115 
1116         // Add physical YUV streams
1117         if (physicalCameraIds.size() != 2) {
1118             throw new IllegalArgumentException("phyiscalCameraIds must contain 2 camera ids");
1119         }
1120         List<ImageReader> physicalTargets = new ArrayList<>();
1121         for (String physicalCameraId : physicalCameraIds) {
1122             ImageReader physicalTarget = CameraTestUtils.makeImageReader(previewSize,
1123                     ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
1124                     new ImageDropperListener(), mHandler);
1125             OutputConfiguration config = new OutputConfiguration(physicalTarget.getSurface());
1126             config.setPhysicalCameraId(physicalCameraId);
1127             outputConfigs.add(config);
1128             physicalTargets.add(physicalTarget);
1129         }
1130 
1131         SessionConfigSupport sessionConfigSupport = isSessionConfigSupported(
1132                 mCamera, mHandler, outputConfigs, /*inputConfig*/ null,
1133                 SessionConfiguration.SESSION_REGULAR, mCameraManager,
1134                 false/*defaultSupport*/);
1135         assertTrue("Session configuration query for logical camera failed with error",
1136                 !sessionConfigSupport.error);
1137         if (!sessionConfigSupport.callSupported) {
1138             return;
1139         }
1140 
1141         mSessionListener = new BlockingSessionCallback();
1142         mSessionWaiter = mSessionListener.getStateWaiter();
1143         mSession = configureCameraSessionWithConfig(mCamera, outputConfigs,
1144                 mSessionListener, mHandler);
1145         if (!sessionConfigSupport.configSupported) {
1146             mSessionWaiter.waitForState(BlockingSessionCallback.SESSION_CONFIGURE_FAILED,
1147                     SESSION_CONFIGURE_TIMEOUT_MS);
1148             return;
1149         }
1150 
1151         // Stream logical YUV stream and note down the FPS
1152         CaptureRequest.Builder requestBuilder =
1153                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
1154         requestBuilder.addTarget(logicalTarget.getSurface());
1155 
1156         SimpleCaptureCallback simpleResultListener =
1157                 new SimpleCaptureCallback();
1158         StreamConfigurationMap config = mStaticInfo.getCharacteristics().get(
1159                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
1160         final long minFrameDuration = config.getOutputMinFrameDuration(
1161                 ImageFormat.YUV_420_888, previewSize);
1162         if (minFrameDuration > 0) {
1163             Range<Integer> targetRange = getSuitableFpsRangeForDuration(logicalCameraId,
1164                     minFrameDuration);
1165             requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
1166         }
1167         mSession.setRepeatingRequest(requestBuilder.build(),
1168                 simpleResultListener, mHandler);
1169 
1170         // Converge AE
1171         waitForAeStable(simpleResultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
1172 
1173         if (mStaticInfo.isAeLockSupported()) {
1174             // Lock AE if supported.
1175             requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
1176             mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListener,
1177                     mHandler);
1178             waitForResultValue(simpleResultListener, CaptureResult.CONTROL_AE_STATE,
1179                     CaptureResult.CONTROL_AE_STATE_LOCKED, NUM_RESULTS_WAIT_TIMEOUT);
1180         }
1181 
1182         // Verify results
1183         CaptureResultTest.validateCaptureResult(mCollector, simpleResultListener,
1184                 mStaticInfo, mAllStaticInfo, null/*requestedPhysicalIds*/,
1185                 requestBuilder, NUM_FRAMES_CHECKED);
1186 
1187         // Collect timestamps for one logical stream only.
1188         long prevTimestamp = -1;
1189         long[] logicalTimestamps = new long[NUM_FRAMES_CHECKED];
1190         for (int i = 0; i < NUM_FRAMES_CHECKED; i++) {
1191             TotalCaptureResult totalCaptureResult =
1192                     simpleResultListener.getTotalCaptureResult(
1193                     CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS);
1194             logicalTimestamps[i] = totalCaptureResult.get(CaptureResult.SENSOR_TIMESTAMP);
1195         }
1196 
1197         double logicalAvgDurationMs = (logicalTimestamps[NUM_FRAMES_CHECKED-1] -
1198                 logicalTimestamps[0])/(NS_PER_MS*(NUM_FRAMES_CHECKED-1));
1199 
1200         // Request one logical stream and one physical stream
1201         simpleResultListener = new SimpleCaptureCallback();
1202         requestBuilder.addTarget(physicalTargets.get(1).getSurface());
1203         mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListener,
1204                 mHandler);
1205 
1206         // Verify results for physical streams request.
1207         CaptureResultTest.validateCaptureResult(mCollector, simpleResultListener,
1208                 mStaticInfo, mAllStaticInfo, physicalCameraIds.subList(1, 2), requestBuilder,
1209                 NUM_FRAMES_CHECKED);
1210 
1211 
1212         // Start requesting on both logical and physical streams
1213         SimpleCaptureCallback simpleResultListenerDual =
1214                 new SimpleCaptureCallback();
1215         for (ImageReader physicalTarget : physicalTargets) {
1216             requestBuilder.addTarget(physicalTarget.getSurface());
1217         }
1218         mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListenerDual,
1219                 mHandler);
1220 
1221         // Verify results for physical streams request.
1222         CaptureResultTest.validateCaptureResult(mCollector, simpleResultListenerDual,
1223                 mStaticInfo, mAllStaticInfo, physicalCameraIds, requestBuilder,
1224                 NUM_FRAMES_CHECKED);
1225 
1226         // Acquire the timestamps of the physical camera.
1227         long[] logicalTimestamps2 = new long[NUM_FRAMES_CHECKED];
1228         long [][] physicalTimestamps = new long[physicalTargets.size()][];
1229         for (int i = 0; i < physicalTargets.size(); i++) {
1230             physicalTimestamps[i] = new long[NUM_FRAMES_CHECKED];
1231         }
1232         for (int i = 0; i < NUM_FRAMES_CHECKED; i++) {
1233             TotalCaptureResult totalCaptureResultDual =
1234                     simpleResultListenerDual.getTotalCaptureResult(
1235                     CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS);
1236             logicalTimestamps2[i] = totalCaptureResultDual.get(CaptureResult.SENSOR_TIMESTAMP);
1237 
1238             int index = 0;
1239             Map<String, CaptureResult> physicalResultsDual =
1240                     totalCaptureResultDual.getPhysicalCameraResults();
1241             for (String physicalId : physicalCameraIds) {
1242                 assertTrue("Physical capture result camera ID must match the right camera",
1243                         physicalResultsDual.get(physicalId).getCameraId().equals(physicalId));
1244                 if (physicalResultsDual.containsKey(physicalId)) {
1245                     physicalTimestamps[index][i] = physicalResultsDual.get(physicalId).get(
1246                         CaptureResult.SENSOR_TIMESTAMP);
1247                 } else {
1248                     physicalTimestamps[index][i] = -1;
1249                 }
1250                 index++;
1251             }
1252         }
1253 
1254         // Check both logical and physical streams' crop region, and make sure their FOVs
1255         // are similar.
1256         TotalCaptureResult totalCaptureResult =
1257                 simpleResultListenerDual.getTotalCaptureResult(
1258                 CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS);
1259         validatePhysicalCamerasFov(totalCaptureResult, physicalCameraIds);
1260 
1261         // Check timestamp monolithity for individual camera and across cameras
1262         for (int i = 0; i < NUM_FRAMES_CHECKED-1; i++) {
1263             assertTrue("Logical camera timestamp must monolithically increase",
1264                     logicalTimestamps2[i] < logicalTimestamps2[i+1]);
1265         }
1266         for (int i = 0; i < physicalCameraIds.size(); i++) {
1267             for (int j = 0 ; j < NUM_FRAMES_CHECKED-1; j++) {
1268                 if (physicalTimestamps[i][j] != -1 && physicalTimestamps[i][j+1] != -1) {
1269                     assertTrue("Physical camera timestamp must monolithically increase",
1270                             physicalTimestamps[i][j] < physicalTimestamps[i][j+1]);
1271                 }
1272                 if (j > 0 && physicalTimestamps[i][j] != -1) {
1273                     assertTrue("Physical camera's timestamp N must be greater than logical " +
1274                             "camera's timestamp N-1",
1275                             physicalTimestamps[i][j] > logicalTimestamps[j-1]);
1276                 }
1277                 if (physicalTimestamps[i][j] != -1) {
1278                     assertTrue("Physical camera's timestamp N must be less than logical camera's " +
1279                             "timestamp N+1", physicalTimestamps[i][j] > logicalTimestamps[j+1]);
1280                 }
1281             }
1282         }
1283 
1284         double logicalAvgDurationMs2 = (logicalTimestamps2[NUM_FRAMES_CHECKED-1] -
1285                 logicalTimestamps2[0])/(NS_PER_MS*(NUM_FRAMES_CHECKED-1));
1286         // Check CALIBRATED synchronization between physical cameras
1287         Integer syncType = mStaticInfo.getCharacteristics().get(
1288                 CameraCharacteristics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE);
1289         double fpsRatio = (logicalAvgDurationMs2 - logicalAvgDurationMs)/logicalAvgDurationMs;
1290         if (syncType == CameraCharacteristics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED) {
1291             // Check framerate doesn't slow down with physical streams
1292             mCollector.expectTrue(
1293                     "The average frame duration with concurrent physical streams is" +
1294                     logicalAvgDurationMs2 + " ms vs " + logicalAvgDurationMs +
1295                     " ms for logical streams only", fpsRatio <= FRAME_DURATION_THRESHOLD);
1296 
1297             long maxTimestampDelta = 0;
1298             for (int i = 0; i < NUM_FRAMES_CHECKED; i++) {
1299                 long delta = Math.abs(physicalTimestamps[0][i] - physicalTimestamps[1][i]);
1300                 if (delta > maxTimestampDelta) {
1301                     maxTimestampDelta = delta;
1302                 }
1303             }
1304 
1305             Log.i(TAG, "Maximum difference between physical camera timestamps: "
1306                     + maxTimestampDelta);
1307 
1308             // The maximum timestamp difference should not be larger than the threshold.
1309             mCollector.expectTrue(
1310                     "The maximum timestamp deltas between the physical cameras "
1311                     + maxTimestampDelta + " is larger than " + MAX_TIMESTAMP_DIFFERENCE_THRESHOLD,
1312                     maxTimestampDelta <= MAX_TIMESTAMP_DIFFERENCE_THRESHOLD);
1313         } else {
1314             // Do not enforce fps check for APPROXIMATE synced device.
1315             if (fpsRatio > FRAME_DURATION_THRESHOLD) {
1316                 Log.w(TAG, "The average frame duration with concurrent physical streams is" +
1317                         logicalAvgDurationMs2 + " ms vs " + logicalAvgDurationMs +
1318                         " ms for logical streams only");
1319             }
1320         }
1321 
1322         if (VERBOSE) {
1323             while (simpleResultListenerDual.hasMoreFailures()) {
1324                 ArrayList<CaptureFailure> failures =
1325                     simpleResultListenerDual.getCaptureFailures(/*maxNumFailures*/ 1);
1326                 for (CaptureFailure failure : failures) {
1327                     String physicalCameraId = failure.getPhysicalCameraId();
1328                     if (physicalCameraId != null) {
1329                         Log.v(TAG, "Capture result failure for physical camera id: " +
1330                                 physicalCameraId);
1331                     }
1332                 }
1333             }
1334         }
1335 
1336         // Stop preview
1337         if (mSession != null) {
1338             mSession.close();
1339         }
1340         // Image readers need to be referenced so that they aren't garbage collected
1341         // before the function exit.
1342         CameraTestUtils.closeImageReader(logicalTarget);
1343         physicalTargets.clear();
1344     }
1345 
1346     /**
1347      * The CDD defines a handheld device as one that has a battery and a screen size between
1348      * 2.5 and 8 inches.
1349      */
isHandheldDevice()1350     private boolean isHandheldDevice() throws Exception {
1351         double screenInches = getScreenSizeInInches();
1352         return deviceHasBattery() && screenInches >= 2.5 && screenInches <= 8.0;
1353     }
1354 
deviceHasBattery()1355     private boolean deviceHasBattery() {
1356         final Intent batteryInfo = mContext.registerReceiver(null,
1357                 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
1358         return batteryInfo != null && batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
1359     }
1360 
getScreenSizeInInches()1361     private double getScreenSizeInInches() {
1362         DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
1363         double widthInInchesSquared = Math.pow(dm.widthPixels / dm.xdpi, 2);
1364         double heightInInchesSquared = Math.pow(dm.heightPixels / dm.ydpi, 2);
1365         return Math.sqrt(widthInInchesSquared + heightInInchesSquared);
1366     }
1367 }
1368