| /* | |
| * Copyright 2013 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| package android.hardware.camera2.cts; | |
| import static android.hardware.camera2.cts.CameraTestUtils.*; | |
| import static com.android.ex.camera2.blocking.BlockingStateCallback.*; | |
| import static com.android.ex.camera2.blocking.BlockingSessionCallback.*; | |
| import static org.mockito.Mockito.*; | |
| import static android.hardware.camera2.CaptureRequest.*; | |
| import android.content.Context; | |
| import android.graphics.SurfaceTexture; | |
| import android.graphics.ImageFormat; | |
| import android.hardware.camera2.CameraAccessException; | |
| import android.hardware.camera2.CameraCaptureSession; | |
| import android.hardware.camera2.CameraCharacteristics; | |
| import android.hardware.camera2.CameraDevice; | |
| import android.hardware.camera2.CameraMetadata; | |
| import android.hardware.camera2.CaptureFailure; | |
| import android.hardware.camera2.CaptureRequest; | |
| import android.hardware.camera2.CaptureResult; | |
| import android.hardware.camera2.TotalCaptureResult; | |
| import android.hardware.camera2.cts.helpers.StaticMetadata; | |
| import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel; | |
| import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase; | |
| import android.hardware.camera2.params.MeteringRectangle; | |
| import android.hardware.camera2.params.InputConfiguration; | |
| import android.hardware.camera2.params.OutputConfiguration; | |
| import android.hardware.camera2.params.SessionConfiguration; | |
| import android.hardware.camera2.params.StreamConfigurationMap; | |
| import android.media.ImageReader; | |
| import android.os.Handler; | |
| import android.os.SystemClock; | |
| import android.util.Log; | |
| import android.util.Range; | |
| import android.view.Surface; | |
| import com.android.ex.camera2.blocking.BlockingSessionCallback; | |
| import com.android.ex.camera2.blocking.BlockingStateCallback; | |
| import com.android.ex.camera2.utils.StateWaiter; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.concurrent.locks.Condition; | |
| import java.util.concurrent.locks.Lock; | |
| import java.util.concurrent.locks.ReentrantLock; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.HashMap; | |
| import java.util.Set; | |
| import android.util.Size; | |
| import org.mockito.ArgumentMatcher; | |
| import java.util.concurrent.LinkedBlockingQueue; | |
| import java.util.concurrent.TimeUnit; | |
| /** | |
| * <p>Basic test for CameraDevice APIs.</p> | |
| */ | |
| public class CameraDeviceTest extends Camera2AndroidTestCase { | |
| private static final String TAG = "CameraDeviceTest"; | |
| private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); | |
| private static final int ERROR_LISTENER_WAIT_TIMEOUT_MS = 1000; | |
| private static final int REPEATING_CAPTURE_EXPECTED_RESULT_COUNT = 5; | |
| private static final int MAX_NUM_IMAGES = 5; | |
| private static final int MIN_FPS_REQUIRED_FOR_STREAMING = 20; | |
| private static final int DEFAULT_POST_RAW_SENSITIVITY_BOOST = 100; | |
| private CameraCaptureSession mSession; | |
| private BlockingStateCallback mCameraMockListener; | |
| private int mLatestDeviceState = STATE_UNINITIALIZED; | |
| private BlockingSessionCallback mSessionMockListener; | |
| private StateWaiter mSessionWaiter; | |
| private int mLatestSessionState = -1; // uninitialized | |
| private static int[] sTemplates = new int[] { | |
| CameraDevice.TEMPLATE_PREVIEW, | |
| CameraDevice.TEMPLATE_RECORD, | |
| CameraDevice.TEMPLATE_STILL_CAPTURE, | |
| CameraDevice.TEMPLATE_VIDEO_SNAPSHOT | |
| }; | |
| private static int[] sInvalidTemplates = new int[] { | |
| CameraDevice.TEMPLATE_PREVIEW - 1, | |
| CameraDevice.TEMPLATE_MANUAL + 1, | |
| }; | |
| // Request templates that are unsupported by LEGACY mode. | |
| private static Set<Integer> sLegacySkipTemplates = new HashSet<>(); | |
| static { | |
| sLegacySkipTemplates.add(CameraDevice.TEMPLATE_VIDEO_SNAPSHOT); | |
| sLegacySkipTemplates.add(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG); | |
| sLegacySkipTemplates.add(CameraDevice.TEMPLATE_MANUAL); | |
| } | |
| @Override | |
| public void setContext(Context context) { | |
| super.setContext(context); | |
| /** | |
| * Workaround for mockito and JB-MR2 incompatibility | |
| * | |
| * Avoid java.lang.IllegalArgumentException: dexcache == null | |
| * https://code.google.com/p/dexmaker/issues/detail?id=2 | |
| */ | |
| System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString()); | |
| /** | |
| * Create error listener in context scope, to catch asynchronous device error. | |
| * Use spy object here since we want to use the SimpleDeviceListener callback | |
| * implementation (spy doesn't stub the functions unless we ask it to do so). | |
| */ | |
| mCameraMockListener = spy(new BlockingStateCallback()); | |
| } | |
| @Override | |
| protected void setUp() throws Exception { | |
| super.setUp(); | |
| /** | |
| * Due to the asynchronous nature of camera device error callback, we | |
| * have to make sure device doesn't run into error state before. If so, | |
| * fail the rest of the tests. This is especially needed when error | |
| * callback is fired too late. | |
| */ | |
| verify(mCameraMockListener, never()) | |
| .onError( | |
| any(CameraDevice.class), | |
| anyInt()); | |
| verify(mCameraMockListener, never()) | |
| .onDisconnected( | |
| any(CameraDevice.class)); | |
| mCameraListener = mCameraMockListener; | |
| createDefaultImageReader(DEFAULT_CAPTURE_SIZE, ImageFormat.YUV_420_888, MAX_NUM_IMAGES, | |
| new ImageDropperListener()); | |
| } | |
| @Override | |
| protected void tearDown() throws Exception { | |
| super.tearDown(); | |
| } | |
| /** | |
| * <p> | |
| * Test camera capture request preview capture template. | |
| * </p> | |
| * | |
| * <p> | |
| * The request template returned by the camera device must include a | |
| * necessary set of metadata keys, and their values must be set correctly. | |
| * It mainly requires below settings: | |
| * </p> | |
| * <ul> | |
| * <li>All 3A settings are auto.</li> | |
| * <li>All sensor settings are not null.</li> | |
| * <li>All ISP processing settings should be non-manual, and the camera | |
| * device should make sure the stable frame rate is guaranteed for the given | |
| * settings.</li> | |
| * </ul> | |
| */ | |
| public void testCameraDevicePreviewTemplate() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_PREVIEW); | |
| } | |
| // TODO: test the frame rate sustainability in preview use case test. | |
| } | |
| /** | |
| * <p> | |
| * Test camera capture request still capture template. | |
| * </p> | |
| * | |
| * <p> | |
| * The request template returned by the camera device must include a | |
| * necessary set of metadata keys, and their values must be set correctly. | |
| * It mainly requires below settings: | |
| * </p> | |
| * <ul> | |
| * <li>All 3A settings are auto.</li> | |
| * <li>All sensor settings are not null.</li> | |
| * <li>All ISP processing settings should be non-manual, and the camera | |
| * device should make sure the high quality takes priority to the stable | |
| * frame rate for the given settings.</li> | |
| * </ul> | |
| */ | |
| public void testCameraDeviceStillTemplate() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_STILL_CAPTURE); | |
| } | |
| } | |
| /** | |
| * <p> | |
| * Test camera capture video recording template. | |
| * </p> | |
| * | |
| * <p> | |
| * The request template returned by the camera device must include a | |
| * necessary set of metadata keys, and their values must be set correctly. | |
| * It has the similar requirement as preview, with one difference: | |
| * </p> | |
| * <ul> | |
| * <li>Frame rate should be stable, for example, wide fps range like [7, 30] | |
| * is a bad setting.</li> | |
| */ | |
| public void testCameraDeviceRecordingTemplate() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_RECORD); | |
| } | |
| // TODO: test the frame rate sustainability in recording use case test. | |
| } | |
| /** | |
| *<p>Test camera capture video snapshot template.</p> | |
| * | |
| * <p>The request template returned by the camera device must include a necessary set of | |
| * metadata keys, and their values must be set correctly. It has the similar requirement | |
| * as recording, with an additional requirement: the settings should maximize image quality | |
| * without compromising stable frame rate.</p> | |
| */ | |
| public void testCameraDeviceVideoSnapShotTemplate() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_VIDEO_SNAPSHOT); | |
| } | |
| // TODO: test the frame rate sustainability in video snapshot use case test. | |
| } | |
| /** | |
| *<p>Test camera capture request zero shutter lag template.</p> | |
| * | |
| * <p>The request template returned by the camera device must include a necessary set of | |
| * metadata keys, and their values must be set correctly. It has the similar requirement | |
| * as preview, with an additional requirement: </p> | |
| */ | |
| public void testCameraDeviceZSLTemplate() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG); | |
| } | |
| } | |
| /** | |
| * <p> | |
| * Test camera capture request manual template. | |
| * </p> | |
| * | |
| * <p> | |
| * The request template returned by the camera device must include a | |
| * necessary set of metadata keys, and their values must be set correctly. It | |
| * mainly requires below settings: | |
| * </p> | |
| * <ul> | |
| * <li>All 3A settings are manual.</li> | |
| * <li>ISP processing parameters are set to preview quality.</li> | |
| * <li>The manual capture parameters (exposure, sensitivity, and so on) are | |
| * set to reasonable defaults.</li> | |
| * </ul> | |
| */ | |
| public void testCameraDeviceManualTemplate() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_MANUAL); | |
| } | |
| } | |
| public void testCameraDeviceCreateCaptureBuilder() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| try { | |
| openDevice(mCameraIds[i], mCameraMockListener); | |
| /** | |
| * Test: that each template type is supported, and that its required fields are | |
| * present. | |
| */ | |
| for (int j = 0; j < sTemplates.length; j++) { | |
| // Skip video snapshots for LEGACY mode | |
| if (mStaticInfo.isHardwareLevelLegacy() && | |
| sTemplates[j] == CameraDevice.TEMPLATE_VIDEO_SNAPSHOT) { | |
| continue; | |
| } | |
| // Skip non-PREVIEW templates for non-color output | |
| if (!mStaticInfo.isColorOutputSupported() && | |
| sTemplates[j] != CameraDevice.TEMPLATE_PREVIEW) { | |
| continue; | |
| } | |
| CaptureRequest.Builder capReq = mCamera.createCaptureRequest(sTemplates[j]); | |
| assertNotNull("Failed to create capture request", capReq); | |
| if (mStaticInfo.areKeysAvailable(CaptureRequest.SENSOR_EXPOSURE_TIME)) { | |
| assertNotNull("Missing field: SENSOR_EXPOSURE_TIME", | |
| capReq.get(CaptureRequest.SENSOR_EXPOSURE_TIME)); | |
| } | |
| if (mStaticInfo.areKeysAvailable(CaptureRequest.SENSOR_SENSITIVITY)) { | |
| assertNotNull("Missing field: SENSOR_SENSITIVITY", | |
| capReq.get(CaptureRequest.SENSOR_SENSITIVITY)); | |
| } | |
| } | |
| /** | |
| * Test: creating capture requests with an invalid template ID should throw | |
| * IllegalArgumentException. | |
| */ | |
| for (int j = 0; j < sInvalidTemplates.length; j++) { | |
| try { | |
| CaptureRequest.Builder capReq = | |
| mCamera.createCaptureRequest(sInvalidTemplates[j]); | |
| fail("Should get IllegalArgumentException due to an invalid template ID."); | |
| } catch (IllegalArgumentException e) { | |
| // Expected exception. | |
| } | |
| } | |
| } | |
| finally { | |
| try { | |
| closeSession(); | |
| } finally { | |
| closeDevice(mCameraIds[i], mCameraMockListener); | |
| } | |
| } | |
| } | |
| } | |
| public void testCameraDeviceSetErrorListener() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| try { | |
| openDevice(mCameraIds[i], mCameraMockListener); | |
| /** | |
| * Test: that the error listener can be set without problems. | |
| * Also, wait some time to check if device doesn't run into error. | |
| */ | |
| SystemClock.sleep(ERROR_LISTENER_WAIT_TIMEOUT_MS); | |
| verify(mCameraMockListener, never()) | |
| .onError( | |
| any(CameraDevice.class), | |
| anyInt()); | |
| } | |
| finally { | |
| try { | |
| closeSession(); | |
| } finally { | |
| closeDevice(mCameraIds[i], mCameraMockListener); | |
| } | |
| } | |
| } | |
| } | |
| public void testCameraDeviceCapture() throws Exception { | |
| runCaptureTest(/*burst*/false, /*repeating*/false, /*abort*/false); | |
| } | |
| public void testCameraDeviceCaptureBurst() throws Exception { | |
| runCaptureTest(/*burst*/true, /*repeating*/false, /*abort*/false); | |
| } | |
| public void testCameraDeviceRepeatingRequest() throws Exception { | |
| runCaptureTest(/*burst*/false, /*repeating*/true, /*abort*/false); | |
| } | |
| public void testCameraDeviceRepeatingBurst() throws Exception { | |
| runCaptureTest(/*burst*/true, /*repeating*/true, /*abort*/false); | |
| } | |
| /** | |
| * Test {@link android.hardware.camera2.CameraCaptureSession#abortCaptures} API. | |
| * | |
| * <p>Abort is the fastest way to idle the camera device for reconfiguration with | |
| * {@link android.hardware.camera2.CameraCaptureSession#abortCaptures}, at the cost of | |
| * discarding in-progress work. Once the abort is complete, the idle callback will be called. | |
| * </p> | |
| */ | |
| public void testCameraDeviceAbort() throws Exception { | |
| runCaptureTest(/*burst*/false, /*repeating*/true, /*abort*/true); | |
| runCaptureTest(/*burst*/true, /*repeating*/true, /*abort*/true); | |
| /** | |
| * TODO: this is only basic test of abort. we probably should also test below cases: | |
| * | |
| * 1. Performance. Make sure abort is faster than stopRepeating, we can test each one a | |
| * couple of times, then compare the average. Also, for abortCaptures() alone, we should | |
| * make sure it doesn't take too long time (e.g. <100ms for full devices, <500ms for limited | |
| * devices), after the abort, we should be able to get all results back very quickly. This | |
| * can be done in performance test. | |
| * | |
| * 2. Make sure all in-flight request comes back after abort, e.g. submit a couple of | |
| * long exposure single captures, then abort, then check if we can get the pending | |
| * request back quickly. | |
| * | |
| * 3. Also need check onCaptureSequenceCompleted for repeating burst after abortCaptures(). | |
| */ | |
| } | |
| /** | |
| * Test invalid capture (e.g. null or empty capture request). | |
| */ | |
| public void testInvalidCapture() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| try { | |
| openDevice(mCameraIds[i], mCameraMockListener); | |
| waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS); | |
| prepareCapture(); | |
| invalidRequestCaptureTestByCamera(); | |
| closeSession(); | |
| } | |
| finally { | |
| closeDevice(mCameraIds[i], mCameraMockListener); | |
| } | |
| } | |
| } | |
| /** | |
| * Test to ensure that we can call camera2 API methods inside callbacks. | |
| * | |
| * Tests: | |
| * onOpened -> createCaptureSession, createCaptureRequest | |
| * onConfigured -> getDevice, abortCaptures, | |
| * createCaptureRequest, capture, setRepeatingRequest, stopRepeating | |
| * onCaptureCompleted -> createCaptureRequest, getDevice, abortCaptures, | |
| * capture, setRepeatingRequest, stopRepeating, session+device.close | |
| */ | |
| public void testChainedOperation() throws Throwable { | |
| final ArrayList<Surface> outputs = new ArrayList<>(); | |
| outputs.add(mReaderSurface); | |
| // A queue for the chained listeners to push results to | |
| // A success Throwable indicates no errors; other Throwables detail a test failure; | |
| // nulls indicate timeouts. | |
| final Throwable success = new Throwable("Success"); | |
| final LinkedBlockingQueue<Throwable> results = new LinkedBlockingQueue<>(); | |
| // Define listeners | |
| // A cascade of Device->Session->Capture listeners, each of which invokes at least one | |
| // method on the camera device or session. | |
| class ChainedCaptureCallback extends CameraCaptureSession.CaptureCallback { | |
| @Override | |
| public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, | |
| TotalCaptureResult result) { | |
| try { | |
| CaptureRequest.Builder request2 = | |
| session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); | |
| request2.addTarget(mReaderSurface); | |
| // Some calls to the camera for coverage | |
| session.abortCaptures(); | |
| session.capture(request2.build(), | |
| /*listener*/ null, /*handler*/ null); | |
| session.setRepeatingRequest(request2.build(), | |
| /*listener*/ null, /*handler*/ null); | |
| session.stopRepeating(); | |
| CameraDevice camera = session.getDevice(); | |
| session.close(); | |
| camera.close(); | |
| results.offer(success); | |
| } catch (Throwable t) { | |
| results.offer(t); | |
| } | |
| } | |
| @Override | |
| public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, | |
| CaptureFailure failure) { | |
| try { | |
| CameraDevice camera = session.getDevice(); | |
| session.close(); | |
| camera.close(); | |
| fail("onCaptureFailed invoked with failure reason: " + failure.getReason()); | |
| } catch (Throwable t) { | |
| results.offer(t); | |
| } | |
| } | |
| } | |
| class ChainedSessionListener extends CameraCaptureSession.StateCallback { | |
| private final ChainedCaptureCallback mCaptureCallback = new ChainedCaptureCallback(); | |
| @Override | |
| public void onConfigured(CameraCaptureSession session) { | |
| try { | |
| CaptureRequest.Builder request = | |
| session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); | |
| request.addTarget(mReaderSurface); | |
| // Some calls to the camera for coverage | |
| session.getDevice(); | |
| session.abortCaptures(); | |
| // The important call for the next level of chaining | |
| session.capture(request.build(), mCaptureCallback, mHandler); | |
| // Some more calls | |
| session.setRepeatingRequest(request.build(), | |
| /*listener*/ null, /*handler*/ null); | |
| session.stopRepeating(); | |
| results.offer(success); | |
| } catch (Throwable t) { | |
| results.offer(t); | |
| } | |
| } | |
| @Override | |
| public void onConfigureFailed(CameraCaptureSession session) { | |
| try { | |
| CameraDevice camera = session.getDevice(); | |
| session.close(); | |
| camera.close(); | |
| fail("onConfigureFailed was invoked"); | |
| } catch (Throwable t) { | |
| results.offer(t); | |
| } | |
| } | |
| } | |
| class ChainedCameraListener extends CameraDevice.StateCallback { | |
| private final ChainedSessionListener mSessionListener = new ChainedSessionListener(); | |
| public CameraDevice cameraDevice; | |
| @Override | |
| public void onOpened(CameraDevice camera) { | |
| cameraDevice = camera; | |
| try { | |
| // Some calls for coverage | |
| camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); | |
| // The important call for next level of chaining | |
| camera.createCaptureSession(outputs, mSessionListener, mHandler); | |
| results.offer(success); | |
| } catch (Throwable t) { | |
| try { | |
| camera.close(); | |
| results.offer(t); | |
| } catch (Throwable t2) { | |
| Log.e(TAG, | |
| "Second failure reached; discarding first exception with trace " + | |
| Log.getStackTraceString(t)); | |
| results.offer(t2); | |
| } | |
| } | |
| } | |
| @Override | |
| public void onDisconnected(CameraDevice camera) { | |
| try { | |
| camera.close(); | |
| fail("onDisconnected invoked"); | |
| } catch (Throwable t) { | |
| results.offer(t); | |
| } | |
| } | |
| @Override | |
| public void onError(CameraDevice camera, int error) { | |
| try { | |
| camera.close(); | |
| fail("onError invoked with error code: " + error); | |
| } catch (Throwable t) { | |
| results.offer(t); | |
| } | |
| } | |
| } | |
| // Actual test code | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| Throwable result; | |
| if (!(new StaticMetadata(mCameraManager.getCameraCharacteristics(mCameraIds[i]))). | |
| isColorOutputSupported()) { | |
| Log.i(TAG, "Camera " + mCameraIds[i] + " does not support color outputs, skipping"); | |
| continue; | |
| } | |
| // Start chained cascade | |
| ChainedCameraListener cameraListener = new ChainedCameraListener(); | |
| mCameraManager.openCamera(mCameraIds[i], cameraListener, mHandler); | |
| // Check if open succeeded | |
| result = results.poll(CAMERA_OPEN_TIMEOUT_MS, TimeUnit.MILLISECONDS); | |
| if (result != success) { | |
| if (cameraListener.cameraDevice != null) cameraListener.cameraDevice.close(); | |
| if (result == null) { | |
| fail("Timeout waiting for camera open"); | |
| } else { | |
| throw result; | |
| } | |
| } | |
| // Check if configure succeeded | |
| result = results.poll(SESSION_CONFIGURE_TIMEOUT_MS, TimeUnit.MILLISECONDS); | |
| if (result != success) { | |
| if (cameraListener.cameraDevice != null) cameraListener.cameraDevice.close(); | |
| if (result == null) { | |
| fail("Timeout waiting for session configure"); | |
| } else { | |
| throw result; | |
| } | |
| } | |
| // Check if capture succeeded | |
| result = results.poll(CAPTURE_RESULT_TIMEOUT_MS, TimeUnit.MILLISECONDS); | |
| if (result != success) { | |
| if (cameraListener.cameraDevice != null) cameraListener.cameraDevice.close(); | |
| if (result == null) { | |
| fail("Timeout waiting for capture completion"); | |
| } else { | |
| throw result; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Verify basic semantics and error conditions of the prepare call. | |
| * | |
| */ | |
| public void testPrepare() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| try { | |
| openDevice(mCameraIds[i], mCameraMockListener); | |
| waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS); | |
| if (!mStaticInfo.isColorOutputSupported()) { | |
| Log.i(TAG, "Camera " + mCameraIds[i] + | |
| " does not support color outputs, skipping"); | |
| continue; | |
| } | |
| prepareTestByCamera(); | |
| } | |
| finally { | |
| closeDevice(mCameraIds[i], mCameraMockListener); | |
| } | |
| } | |
| } | |
| /** | |
| * Verify prepare call behaves properly when sharing surfaces. | |
| * | |
| */ | |
| public void testPrepareForSharedSurfaces() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| try { | |
| openDevice(mCameraIds[i], mCameraMockListener); | |
| waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS); | |
| if (mStaticInfo.isHardwareLevelLegacy()) { | |
| Log.i(TAG, "Camera " + mCameraIds[i] + " is legacy, skipping"); | |
| continue; | |
| } | |
| if (!mStaticInfo.isColorOutputSupported()) { | |
| Log.i(TAG, "Camera " + mCameraIds[i] + | |
| " does not support color outputs, skipping"); | |
| continue; | |
| } | |
| prepareTestForSharedSurfacesByCamera(); | |
| } | |
| finally { | |
| closeDevice(mCameraIds[i], mCameraMockListener); | |
| } | |
| } | |
| } | |
| /** | |
| * Verify creating sessions back to back. | |
| */ | |
| public void testCreateSessions() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| try { | |
| openDevice(mCameraIds[i], mCameraMockListener); | |
| waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS); | |
| if (!mStaticInfo.isColorOutputSupported()) { | |
| Log.i(TAG, "Camera " + mCameraIds[i] + | |
| " does not support color outputs, skipping"); | |
| continue; | |
| } | |
| testCreateSessionsByCamera(mCameraIds[i]); | |
| } | |
| finally { | |
| closeDevice(mCameraIds[i], mCameraMockListener); | |
| } | |
| } | |
| } | |
| /** | |
| * Verify creating a custom session | |
| */ | |
| public void testCreateCustomSession() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| try { | |
| openDevice(mCameraIds[i], mCameraMockListener); | |
| waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS); | |
| if (!mStaticInfo.isColorOutputSupported()) { | |
| Log.i(TAG, "Camera " + mCameraIds[i] + | |
| " does not support color outputs, skipping"); | |
| continue; | |
| } | |
| testCreateCustomSessionByCamera(mCameraIds[i]); | |
| } | |
| finally { | |
| closeDevice(mCameraIds[i], mCameraMockListener); | |
| } | |
| } | |
| } | |
| /** | |
| * Verify creating a custom mode session works | |
| */ | |
| private void testCreateCustomSessionByCamera(String cameraId) throws Exception { | |
| final int SESSION_TIMEOUT_MS = 1000; | |
| final int CAPTURE_TIMEOUT_MS = 3000; | |
| if (VERBOSE) { | |
| Log.v(TAG, "Testing creating custom session for camera " + cameraId); | |
| } | |
| Size yuvSize = mOrderedPreviewSizes.get(0); | |
| // Create a list of image readers. JPEG for last one and YUV for the rest. | |
| ImageReader imageReader = ImageReader.newInstance(yuvSize.getWidth(), yuvSize.getHeight(), | |
| ImageFormat.YUV_420_888, /*maxImages*/1); | |
| try { | |
| // Create a normal-mode session via createCustomCaptureSession | |
| mSessionMockListener = spy(new BlockingSessionCallback()); | |
| mSessionWaiter = mSessionMockListener.getStateWaiter(); | |
| List<OutputConfiguration> outputs = new ArrayList<>(); | |
| outputs.add(new OutputConfiguration(imageReader.getSurface())); | |
| mCamera.createCustomCaptureSession(/*inputConfig*/null, outputs, | |
| CameraDevice.SESSION_OPERATION_MODE_NORMAL, mSessionMockListener, mHandler); | |
| mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); | |
| // Verify we can capture a frame with the session. | |
| SimpleCaptureCallback captureListener = new SimpleCaptureCallback(); | |
| SimpleImageReaderListener imageListener = new SimpleImageReaderListener(); | |
| imageReader.setOnImageAvailableListener(imageListener, mHandler); | |
| CaptureRequest.Builder builder = | |
| mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); | |
| builder.addTarget(imageReader.getSurface()); | |
| CaptureRequest request = builder.build(); | |
| mSession.capture(request, captureListener, mHandler); | |
| captureListener.getCaptureResultForRequest(request, CAPTURE_TIMEOUT_MS); | |
| imageListener.getImage(CAPTURE_TIMEOUT_MS).close(); | |
| // Create a few invalid custom sessions by using undefined non-vendor mode indices, and | |
| // check that they fail to configure | |
| mSessionMockListener = spy(new BlockingSessionCallback()); | |
| mSessionWaiter = mSessionMockListener.getStateWaiter(); | |
| mCamera.createCustomCaptureSession(/*inputConfig*/null, outputs, | |
| CameraDevice.SESSION_OPERATION_MODE_VENDOR_START - 1, mSessionMockListener, mHandler); | |
| mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); | |
| waitForSessionState(BlockingSessionCallback.SESSION_CONFIGURE_FAILED, | |
| SESSION_CONFIGURE_TIMEOUT_MS); | |
| mSessionMockListener = spy(new BlockingSessionCallback()); | |
| mCamera.createCustomCaptureSession(/*inputConfig*/null, outputs, | |
| CameraDevice.SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED + 1, mSessionMockListener, | |
| mHandler); | |
| mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); | |
| mSessionWaiter = mSessionMockListener.getStateWaiter(); | |
| waitForSessionState(BlockingSessionCallback.SESSION_CONFIGURE_FAILED, | |
| SESSION_CONFIGURE_TIMEOUT_MS); | |
| } finally { | |
| imageReader.close(); | |
| mSession.close(); | |
| } | |
| } | |
| /** | |
| * Test session configuration. | |
| */ | |
| public void testSessionConfiguration() throws Exception { | |
| ArrayList<OutputConfiguration> outConfigs = new ArrayList<OutputConfiguration> (); | |
| outConfigs.add(new OutputConfiguration(new Size(1, 1), SurfaceTexture.class)); | |
| outConfigs.add(new OutputConfiguration(new Size(2, 2), SurfaceTexture.class)); | |
| mSessionMockListener = spy(new BlockingSessionCallback()); | |
| InputConfiguration inputConfig = new InputConfiguration(1, 1, ImageFormat.PRIVATE); | |
| SessionConfiguration regularSessionConfig = new SessionConfiguration( | |
| SessionConfiguration.SESSION_REGULAR, outConfigs, mSessionMockListener, null); | |
| SessionConfiguration highspeedSessionConfig = new SessionConfiguration( | |
| SessionConfiguration.SESSION_HIGH_SPEED, outConfigs, mSessionMockListener, null); | |
| assertEquals("Session configuration output doesn't match", | |
| regularSessionConfig.getOutputConfigurations(), outConfigs); | |
| assertEquals("Session configuration output doesn't match", | |
| regularSessionConfig.getOutputConfigurations(), | |
| highspeedSessionConfig.getOutputConfigurations()); | |
| assertEquals("Session configuration callback doesn't match", | |
| regularSessionConfig.getStateCallback(), mSessionMockListener); | |
| assertEquals("Session configuration callback doesn't match", | |
| regularSessionConfig.getStateCallback(), | |
| highspeedSessionConfig.getStateCallback()); | |
| assertEquals("Session configuration handler doesn't match", | |
| regularSessionConfig.getHandler(), null); | |
| assertEquals("Session configuration handler doesn't match", | |
| regularSessionConfig.getHandler(), highspeedSessionConfig.getHandler()); | |
| regularSessionConfig.setInputConfiguration(inputConfig); | |
| assertEquals("Session configuration input doesn't match", | |
| regularSessionConfig.getInputConfiguration(), inputConfig); | |
| try { | |
| highspeedSessionConfig.setInputConfiguration(inputConfig); | |
| fail("No exception for valid input configuration in hight speed session configuration"); | |
| } catch (UnsupportedOperationException e) { | |
| //expected | |
| } | |
| assertEquals("Session configuration input doesn't match", | |
| highspeedSessionConfig.getInputConfiguration(), null); | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| try { | |
| openDevice(mCameraIds[i], mCameraMockListener); | |
| waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS); | |
| CaptureRequest.Builder builder = | |
| mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); | |
| CaptureRequest request = builder.build(); | |
| regularSessionConfig.setSessionParameters(request); | |
| highspeedSessionConfig.setSessionParameters(request); | |
| assertEquals("Session configuration parameters doesn't match", | |
| regularSessionConfig.getSessionParameters(), request); | |
| assertEquals("Session configuration parameters doesn't match", | |
| regularSessionConfig.getSessionParameters(), | |
| highspeedSessionConfig.getSessionParameters()); | |
| } | |
| finally { | |
| closeDevice(mCameraIds[i], mCameraMockListener); | |
| } | |
| } | |
| } | |
| /** | |
| * Verify creating a session with additional parameters. | |
| */ | |
| public void testCreateSessionWithParameters() throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| try { | |
| openDevice(mCameraIds[i], mCameraMockListener); | |
| waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS); | |
| if (!mStaticInfo.isColorOutputSupported()) { | |
| Log.i(TAG, "Camera " + mCameraIds[i] + | |
| " does not support color outputs, skipping"); | |
| continue; | |
| } | |
| testCreateSessionWithParametersByCamera(mCameraIds[i], /*reprocessable*/false); | |
| testCreateSessionWithParametersByCamera(mCameraIds[i], /*reprocessable*/true); | |
| } | |
| finally { | |
| closeDevice(mCameraIds[i], mCameraMockListener); | |
| } | |
| } | |
| } | |
| /** | |
| * Verify creating a session with additional parameters works | |
| */ | |
| private void testCreateSessionWithParametersByCamera(String cameraId, boolean reprocessable) | |
| throws Exception { | |
| final int SESSION_TIMEOUT_MS = 1000; | |
| final int CAPTURE_TIMEOUT_MS = 3000; | |
| int inputFormat = ImageFormat.YUV_420_888; | |
| int outputFormat = inputFormat; | |
| Size outputSize = mOrderedPreviewSizes.get(0); | |
| Size inputSize = outputSize; | |
| InputConfiguration inputConfig = null; | |
| if (VERBOSE) { | |
| Log.v(TAG, "Testing creating session with parameters for camera " + cameraId); | |
| } | |
| CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId); | |
| StreamConfigurationMap config = characteristics.get( | |
| CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); | |
| if (reprocessable) { | |
| //Pick a supported i/o format and size combination. | |
| //Ideally the input format should match the output. | |
| boolean found = false; | |
| int inputFormats [] = config.getInputFormats(); | |
| if (inputFormats.length == 0) { | |
| return; | |
| } | |
| for (int inFormat : inputFormats) { | |
| int outputFormats [] = config.getValidOutputFormatsForInput(inputFormat); | |
| for (int outFormat : outputFormats) { | |
| if (inFormat == outFormat) { | |
| inputFormat = inFormat; | |
| outputFormat = outFormat; | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (found) { | |
| break; | |
| } | |
| } | |
| //In case the above combination doesn't exist, pick the first first supported | |
| //pair. | |
| if (!found) { | |
| inputFormat = inputFormats[0]; | |
| int outputFormats [] = config.getValidOutputFormatsForInput(inputFormat); | |
| assertTrue("No output formats supported for input format: " + inputFormat, | |
| (outputFormats.length > 0)); | |
| outputFormat = outputFormats[0]; | |
| } | |
| Size inputSizes[] = config.getInputSizes(inputFormat); | |
| Size outputSizes[] = config.getOutputSizes(outputFormat); | |
| assertTrue("No valid sizes supported for input format: " + inputFormat, | |
| (inputSizes.length > 0)); | |
| assertTrue("No valid sizes supported for output format: " + outputFormat, | |
| (outputSizes.length > 0)); | |
| inputSize = inputSizes[0]; | |
| outputSize = outputSizes[0]; | |
| inputConfig = new InputConfiguration(inputSize.getWidth(), | |
| inputSize.getHeight(), inputFormat); | |
| } else { | |
| if (config.isOutputSupportedFor(outputFormat)) { | |
| outputSize = config.getOutputSizes(outputFormat)[0]; | |
| } else { | |
| return; | |
| } | |
| } | |
| ImageReader imageReader = ImageReader.newInstance(outputSize.getWidth(), | |
| outputSize.getHeight(), outputFormat, /*maxImages*/1); | |
| try { | |
| mSessionMockListener = spy(new BlockingSessionCallback()); | |
| mSessionWaiter = mSessionMockListener.getStateWaiter(); | |
| List<OutputConfiguration> outputs = new ArrayList<>(); | |
| outputs.add(new OutputConfiguration(imageReader.getSurface())); | |
| SessionConfiguration sessionConfig = new SessionConfiguration( | |
| SessionConfiguration.SESSION_REGULAR, outputs, mSessionMockListener, mHandler); | |
| CaptureRequest.Builder builder = | |
| mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); | |
| builder.addTarget(imageReader.getSurface()); | |
| CaptureRequest request = builder.build(); | |
| sessionConfig.setInputConfiguration(inputConfig); | |
| sessionConfig.setSessionParameters(request); | |
| mCamera.createCaptureSession(sessionConfig); | |
| mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); | |
| // Verify we can capture a frame with the session. | |
| SimpleCaptureCallback captureListener = new SimpleCaptureCallback(); | |
| SimpleImageReaderListener imageListener = new SimpleImageReaderListener(); | |
| imageReader.setOnImageAvailableListener(imageListener, mHandler); | |
| mSession.capture(request, captureListener, mHandler); | |
| captureListener.getCaptureResultForRequest(request, CAPTURE_TIMEOUT_MS); | |
| imageListener.getImage(CAPTURE_TIMEOUT_MS).close(); | |
| } finally { | |
| imageReader.close(); | |
| mSession.close(); | |
| } | |
| } | |
| /** | |
| * Verify creating sessions back to back and only the last one is valid for | |
| * submitting requests. | |
| */ | |
| private void testCreateSessionsByCamera(String cameraId) throws Exception { | |
| final int NUM_SESSIONS = 3; | |
| final int SESSION_TIMEOUT_MS = 1000; | |
| final int CAPTURE_TIMEOUT_MS = 3000; | |
| if (VERBOSE) { | |
| Log.v(TAG, "Testing creating sessions for camera " + cameraId); | |
| } | |
| Size yuvSize = getSortedSizesForFormat(cameraId, mCameraManager, ImageFormat.YUV_420_888, | |
| /*bound*/null).get(0); | |
| Size jpegSize = getSortedSizesForFormat(cameraId, mCameraManager, ImageFormat.JPEG, | |
| /*bound*/null).get(0); | |
| // Create a list of image readers. JPEG for last one and YUV for the rest. | |
| List<ImageReader> imageReaders = new ArrayList<>(); | |
| List<CameraCaptureSession> allSessions = new ArrayList<>(); | |
| try { | |
| for (int i = 0; i < NUM_SESSIONS - 1; i++) { | |
| imageReaders.add(ImageReader.newInstance(yuvSize.getWidth(), yuvSize.getHeight(), | |
| ImageFormat.YUV_420_888, /*maxImages*/1)); | |
| } | |
| imageReaders.add(ImageReader.newInstance(jpegSize.getWidth(), jpegSize.getHeight(), | |
| ImageFormat.JPEG, /*maxImages*/1)); | |
| // Create multiple sessions back to back. | |
| MultipleSessionCallback sessionListener = | |
| new MultipleSessionCallback(/*failOnConfigureFailed*/true); | |
| for (int i = 0; i < NUM_SESSIONS; i++) { | |
| List<Surface> outputs = new ArrayList<>(); | |
| outputs.add(imageReaders.get(i).getSurface()); | |
| mCamera.createCaptureSession(outputs, sessionListener, mHandler); | |
| } | |
| // Verify we get onConfigured() for all sessions. | |
| allSessions = sessionListener.getAllSessions(NUM_SESSIONS, | |
| SESSION_TIMEOUT_MS * NUM_SESSIONS); | |
| assertEquals(String.format("Got %d sessions but configured %d sessions", | |
| allSessions.size(), NUM_SESSIONS), allSessions.size(), NUM_SESSIONS); | |
| // Verify all sessions except the last one are closed. | |
| for (int i = 0; i < NUM_SESSIONS - 1; i++) { | |
| sessionListener.waitForSessionClose(allSessions.get(i), SESSION_TIMEOUT_MS); | |
| } | |
| // Verify we can capture a frame with the last session. | |
| CameraCaptureSession session = allSessions.get(allSessions.size() - 1); | |
| SimpleCaptureCallback captureListener = new SimpleCaptureCallback(); | |
| ImageReader reader = imageReaders.get(imageReaders.size() - 1); | |
| SimpleImageReaderListener imageListener = new SimpleImageReaderListener(); | |
| reader.setOnImageAvailableListener(imageListener, mHandler); | |
| CaptureRequest.Builder builder = | |
| mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); | |
| builder.addTarget(reader.getSurface()); | |
| CaptureRequest request = builder.build(); | |
| session.capture(request, captureListener, mHandler); | |
| captureListener.getCaptureResultForRequest(request, CAPTURE_TIMEOUT_MS); | |
| imageListener.getImage(CAPTURE_TIMEOUT_MS).close(); | |
| } finally { | |
| for (ImageReader reader : imageReaders) { | |
| reader.close(); | |
| } | |
| for (CameraCaptureSession session : allSessions) { | |
| session.close(); | |
| } | |
| } | |
| } | |
| private void prepareTestByCamera() throws Exception { | |
| final int PREPARE_TIMEOUT_MS = 10000; | |
| mSessionMockListener = spy(new BlockingSessionCallback()); | |
| SurfaceTexture output1 = new SurfaceTexture(1); | |
| Surface output1Surface = new Surface(output1); | |
| SurfaceTexture output2 = new SurfaceTexture(2); | |
| Surface output2Surface = new Surface(output2); | |
| List<Surface> outputSurfaces = new ArrayList<>( | |
| Arrays.asList(output1Surface, output2Surface)); | |
| mCamera.createCaptureSession(outputSurfaces, mSessionMockListener, mHandler); | |
| mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); | |
| // Try basic prepare | |
| mSession.prepare(output1Surface); | |
| verify(mSessionMockListener, timeout(PREPARE_TIMEOUT_MS).times(1)) | |
| .onSurfacePrepared(eq(mSession), eq(output1Surface)); | |
| // Should not complain if preparing already prepared stream | |
| mSession.prepare(output1Surface); | |
| verify(mSessionMockListener, timeout(PREPARE_TIMEOUT_MS).times(2)) | |
| .onSurfacePrepared(eq(mSession), eq(output1Surface)); | |
| // Check surface not included in session | |
| SurfaceTexture output3 = new SurfaceTexture(3); | |
| Surface output3Surface = new Surface(output3); | |
| try { | |
| mSession.prepare(output3Surface); | |
| // Legacy camera prepare always succeed | |
| if (mStaticInfo.isHardwareLevelAtLeastLimited()) { | |
| fail("Preparing surface not part of session must throw IllegalArgumentException"); | |
| } | |
| } catch (IllegalArgumentException e) { | |
| // expected | |
| } | |
| // Ensure second prepare also works | |
| mSession.prepare(output2Surface); | |
| verify(mSessionMockListener, timeout(PREPARE_TIMEOUT_MS).times(1)) | |
| .onSurfacePrepared(eq(mSession), eq(output2Surface)); | |
| // Use output1 | |
| CaptureRequest.Builder r = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); | |
| r.addTarget(output1Surface); | |
| mSession.capture(r.build(), null, null); | |
| try { | |
| mSession.prepare(output1Surface); | |
| // Legacy camera prepare always succeed | |
| if (mStaticInfo.isHardwareLevelAtLeastLimited()) { | |
| fail("Preparing already-used surface must throw IllegalArgumentException"); | |
| } | |
| } catch (IllegalArgumentException e) { | |
| // expected | |
| } | |
| // Create new session with outputs 1 and 3, ensure output1Surface still can't be prepared | |
| // again | |
| mSessionMockListener = spy(new BlockingSessionCallback()); | |
| outputSurfaces = new ArrayList<>( | |
| Arrays.asList(output1Surface, output3Surface)); | |
| mCamera.createCaptureSession(outputSurfaces, mSessionMockListener, mHandler); | |
| mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); | |
| try { | |
| mSession.prepare(output1Surface); | |
| // Legacy camera prepare always succeed | |
| if (mStaticInfo.isHardwareLevelAtLeastLimited()) { | |
| fail("Preparing surface used in previous session must throw " + | |
| "IllegalArgumentException"); | |
| } | |
| } catch (IllegalArgumentException e) { | |
| // expected | |
| } | |
| // Use output3, wait for result, then make sure prepare still doesn't work | |
| r = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); | |
| r.addTarget(output3Surface); | |
| SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); | |
| mSession.capture(r.build(), resultListener, mHandler); | |
| resultListener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS); | |
| try { | |
| mSession.prepare(output3Surface); | |
| // Legacy camera prepare always succeed | |
| if (mStaticInfo.isHardwareLevelAtLeastLimited()) { | |
| fail("Preparing already-used surface must throw IllegalArgumentException"); | |
| } | |
| } catch (IllegalArgumentException e) { | |
| // expected | |
| } | |
| // Create new session with outputs 1 and 2, ensure output2Surface can be prepared again | |
| mSessionMockListener = spy(new BlockingSessionCallback()); | |
| outputSurfaces = new ArrayList<>( | |
| Arrays.asList(output1Surface, output2Surface)); | |
| mCamera.createCaptureSession(outputSurfaces, mSessionMockListener, mHandler); | |
| mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); | |
| mSession.prepare(output2Surface); | |
| verify(mSessionMockListener, timeout(PREPARE_TIMEOUT_MS).times(1)) | |
| .onSurfacePrepared(eq(mSession), eq(output2Surface)); | |
| try { | |
| mSession.prepare(output1Surface); | |
| // Legacy camera prepare always succeed | |
| if (mStaticInfo.isHardwareLevelAtLeastLimited()) { | |
| fail("Preparing surface used in previous session must throw " + | |
| "IllegalArgumentException"); | |
| } | |
| } catch (IllegalArgumentException e) { | |
| // expected | |
| } | |
| } | |
| private void prepareTestForSharedSurfacesByCamera() throws Exception { | |
| final int PREPARE_TIMEOUT_MS = 10000; | |
| mSessionMockListener = spy(new BlockingSessionCallback()); | |
| SurfaceTexture output1 = new SurfaceTexture(1); | |
| Surface output1Surface = new Surface(output1); | |
| SurfaceTexture output2 = new SurfaceTexture(2); | |
| Surface output2Surface = new Surface(output2); | |
| List<Surface> outputSurfaces = new ArrayList<>( | |
| Arrays.asList(output1Surface, output2Surface)); | |
| OutputConfiguration surfaceSharedConfig = new OutputConfiguration( | |
| OutputConfiguration.SURFACE_GROUP_ID_NONE, output1Surface); | |
| surfaceSharedConfig.enableSurfaceSharing(); | |
| surfaceSharedConfig.addSurface(output2Surface); | |
| List<OutputConfiguration> outputConfigurations = new ArrayList<>(); | |
| outputConfigurations.add(surfaceSharedConfig); | |
| mCamera.createCaptureSessionByOutputConfigurations( | |
| outputConfigurations, mSessionMockListener, mHandler); | |
| mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); | |
| // Try prepare on output1Surface | |
| mSession.prepare(output1Surface); | |
| verify(mSessionMockListener, timeout(PREPARE_TIMEOUT_MS).times(1)) | |
| .onSurfacePrepared(eq(mSession), eq(output1Surface)); | |
| verify(mSessionMockListener, timeout(PREPARE_TIMEOUT_MS).times(1)) | |
| .onSurfacePrepared(eq(mSession), eq(output2Surface)); | |
| // Try prepare on output2Surface | |
| mSession.prepare(output2Surface); | |
| verify(mSessionMockListener, timeout(PREPARE_TIMEOUT_MS).times(2)) | |
| .onSurfacePrepared(eq(mSession), eq(output1Surface)); | |
| verify(mSessionMockListener, timeout(PREPARE_TIMEOUT_MS).times(2)) | |
| .onSurfacePrepared(eq(mSession), eq(output2Surface)); | |
| // Try prepare on output1Surface again | |
| mSession.prepare(output1Surface); | |
| verify(mSessionMockListener, timeout(PREPARE_TIMEOUT_MS).times(3)) | |
| .onSurfacePrepared(eq(mSession), eq(output1Surface)); | |
| verify(mSessionMockListener, timeout(PREPARE_TIMEOUT_MS).times(3)) | |
| .onSurfacePrepared(eq(mSession), eq(output2Surface)); | |
| } | |
| private void invalidRequestCaptureTestByCamera() throws Exception { | |
| if (VERBOSE) Log.v(TAG, "invalidRequestCaptureTestByCamera"); | |
| List<CaptureRequest> emptyRequests = new ArrayList<CaptureRequest>(); | |
| CaptureRequest.Builder requestBuilder = | |
| mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); | |
| CaptureRequest unConfiguredRequest = requestBuilder.build(); | |
| List<CaptureRequest> unConfiguredRequests = new ArrayList<CaptureRequest>(); | |
| unConfiguredRequests.add(unConfiguredRequest); | |
| try { | |
| // Test: CameraCaptureSession capture should throw IAE for null request. | |
| mSession.capture(/*request*/null, /*listener*/null, mHandler); | |
| mCollector.addMessage( | |
| "Session capture should throw IllegalArgumentException for null request"); | |
| } catch (IllegalArgumentException e) { | |
| // Pass. | |
| } | |
| try { | |
| // Test: CameraCaptureSession capture should throw IAE for request | |
| // without surface configured. | |
| mSession.capture(unConfiguredRequest, /*listener*/null, mHandler); | |
| mCollector.addMessage("Session capture should throw " + | |
| "IllegalArgumentException for request without surface configured"); | |
| } catch (IllegalArgumentException e) { | |
| // Pass. | |
| } | |
| try { | |
| // Test: CameraCaptureSession setRepeatingRequest should throw IAE for null request. | |
| mSession.setRepeatingRequest(/*request*/null, /*listener*/null, mHandler); | |
| mCollector.addMessage("Session setRepeatingRequest should throw " + | |
| "IllegalArgumentException for null request"); | |
| } catch (IllegalArgumentException e) { | |
| // Pass. | |
| } | |
| try { | |
| // Test: CameraCaptureSession setRepeatingRequest should throw IAE for for request | |
| // without surface configured. | |
| mSession.setRepeatingRequest(unConfiguredRequest, /*listener*/null, mHandler); | |
| mCollector.addMessage("Capture zero burst should throw IllegalArgumentException " + | |
| "for request without surface configured"); | |
| } catch (IllegalArgumentException e) { | |
| // Pass. | |
| } | |
| try { | |
| // Test: CameraCaptureSession captureBurst should throw IAE for null request list. | |
| mSession.captureBurst(/*requests*/null, /*listener*/null, mHandler); | |
| mCollector.addMessage("Session captureBurst should throw " + | |
| "IllegalArgumentException for null request list"); | |
| } catch (IllegalArgumentException e) { | |
| // Pass. | |
| } | |
| try { | |
| // Test: CameraCaptureSession captureBurst should throw IAE for empty request list. | |
| mSession.captureBurst(emptyRequests, /*listener*/null, mHandler); | |
| mCollector.addMessage("Session captureBurst should throw " + | |
| " IllegalArgumentException for empty request list"); | |
| } catch (IllegalArgumentException e) { | |
| // Pass. | |
| } | |
| try { | |
| // Test: CameraCaptureSession captureBurst should throw IAE for request | |
| // without surface configured. | |
| mSession.captureBurst(unConfiguredRequests, /*listener*/null, mHandler); | |
| fail("Session captureBurst should throw IllegalArgumentException " + | |
| "for null request list"); | |
| } catch (IllegalArgumentException e) { | |
| // Pass. | |
| } | |
| try { | |
| // Test: CameraCaptureSession setRepeatingBurst should throw IAE for null request list. | |
| mSession.setRepeatingBurst(/*requests*/null, /*listener*/null, mHandler); | |
| mCollector.addMessage("Session setRepeatingBurst should throw " + | |
| "IllegalArgumentException for null request list"); | |
| } catch (IllegalArgumentException e) { | |
| // Pass. | |
| } | |
| try { | |
| // Test: CameraCaptureSession setRepeatingBurst should throw IAE for empty request list. | |
| mSession.setRepeatingBurst(emptyRequests, /*listener*/null, mHandler); | |
| mCollector.addMessage("Session setRepeatingBurst should throw " + | |
| "IllegalArgumentException for empty request list"); | |
| } catch (IllegalArgumentException e) { | |
| // Pass. | |
| } | |
| try { | |
| // Test: CameraCaptureSession setRepeatingBurst should throw IAE for request | |
| // without surface configured. | |
| mSession.setRepeatingBurst(unConfiguredRequests, /*listener*/null, mHandler); | |
| mCollector.addMessage("Session setRepeatingBurst should throw " + | |
| "IllegalArgumentException for request without surface configured"); | |
| } catch (IllegalArgumentException e) { | |
| // Pass. | |
| } | |
| } | |
| private class IsCaptureResultNotEmpty | |
| implements ArgumentMatcher<TotalCaptureResult> { | |
| @Override | |
| public boolean matches(TotalCaptureResult result) { | |
| /** | |
| * Do the simple verification here. Only verify the timestamp for now. | |
| * TODO: verify more required capture result metadata fields. | |
| */ | |
| Long timeStamp = result.get(CaptureResult.SENSOR_TIMESTAMP); | |
| if (timeStamp != null && timeStamp.longValue() > 0L) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| } | |
| /** | |
| * Run capture test with different test configurations. | |
| * | |
| * @param burst If the test uses {@link CameraCaptureSession#captureBurst} or | |
| * {@link CameraCaptureSession#setRepeatingBurst} to capture the burst. | |
| * @param repeating If the test uses {@link CameraCaptureSession#setRepeatingBurst} or | |
| * {@link CameraCaptureSession#setRepeatingRequest} for repeating capture. | |
| * @param abort If the test uses {@link CameraCaptureSession#abortCaptures} to stop the | |
| * repeating capture. It has no effect if repeating is false. | |
| */ | |
| private void runCaptureTest(boolean burst, boolean repeating, boolean abort) throws Exception { | |
| for (int i = 0; i < mCameraIds.length; i++) { | |
| try { | |
| openDevice(mCameraIds[i], mCameraMockListener); | |
| waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS); | |
| prepareCapture(); | |
| if (!burst) { | |
| // Test: that a single capture of each template type succeeds. | |
| for (int j = 0; j < sTemplates.length; j++) { | |
| // Skip video snapshots for LEGACY mode | |
| if (mStaticInfo.isHardwareLevelLegacy() && | |
| sTemplates[j] == CameraDevice.TEMPLATE_VIDEO_SNAPSHOT) { | |
| continue; | |
| } | |
| // Skip non-PREVIEW templates for non-color output | |
| if (!mStaticInfo.isColorOutputSupported() && | |
| sTemplates[j] != CameraDevice.TEMPLATE_PREVIEW) { | |
| continue; | |
| } | |
| captureSingleShot(mCameraIds[i], sTemplates[j], repeating, abort); | |
| } | |
| } | |
| else { | |
| // Test: burst of one shot | |
| captureBurstShot(mCameraIds[i], sTemplates, 1, repeating, abort); | |
| int template = mStaticInfo.isColorOutputSupported() ? | |
| CameraDevice.TEMPLATE_STILL_CAPTURE : | |
| CameraDevice.TEMPLATE_PREVIEW; | |
| int[] templates = new int[] { | |
| template, | |
| template, | |
| template, | |
| template, | |
| template | |
| }; | |
| // Test: burst of 5 shots of the same template type | |
| captureBurstShot(mCameraIds[i], templates, templates.length, repeating, abort); | |
| // Test: burst of 5 shots of different template types | |
| captureBurstShot( | |
| mCameraIds[i], sTemplates, sTemplates.length, repeating, abort); | |
| } | |
| verify(mCameraMockListener, never()) | |
| .onError( | |
| any(CameraDevice.class), | |
| anyInt()); | |
| } catch (Exception e) { | |
| mCollector.addError(e); | |
| } finally { | |
| try { | |
| closeSession(); | |
| } catch (Exception e) { | |
| mCollector.addError(e); | |
| }finally { | |
| closeDevice(mCameraIds[i], mCameraMockListener); | |
| } | |
| } | |
| } | |
| } | |
| private void captureSingleShot( | |
| String id, | |
| int template, | |
| boolean repeating, boolean abort) throws Exception { | |
| assertEquals("Bad initial state for preparing to capture", | |
| mLatestSessionState, SESSION_READY); | |
| CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(template); | |
| assertNotNull("Failed to create capture request", requestBuilder); | |
| requestBuilder.addTarget(mReaderSurface); | |
| CameraCaptureSession.CaptureCallback mockCaptureCallback = | |
| mock(CameraCaptureSession.CaptureCallback.class); | |
| if (VERBOSE) { | |
| Log.v(TAG, String.format("Capturing shot for device %s, template %d", | |
| id, template)); | |
| } | |
| startCapture(requestBuilder.build(), repeating, mockCaptureCallback, mHandler); | |
| waitForSessionState(SESSION_ACTIVE, SESSION_ACTIVE_TIMEOUT_MS); | |
| int expectedCaptureResultCount = repeating ? REPEATING_CAPTURE_EXPECTED_RESULT_COUNT : 1; | |
| verifyCaptureResults(mockCaptureCallback, expectedCaptureResultCount); | |
| if (repeating) { | |
| if (abort) { | |
| mSession.abortCaptures(); | |
| // Have to make sure abort and new requests aren't interleave together. | |
| waitForSessionState(SESSION_READY, SESSION_READY_TIMEOUT_MS); | |
| // Capture a single capture, and verify the result. | |
| SimpleCaptureCallback resultCallback = new SimpleCaptureCallback(); | |
| CaptureRequest singleRequest = requestBuilder.build(); | |
| mSession.capture(singleRequest, resultCallback, mHandler); | |
| resultCallback.getCaptureResultForRequest(singleRequest, CAPTURE_RESULT_TIMEOUT_MS); | |
| // Resume the repeating, and verify that results are returned. | |
| mSession.setRepeatingRequest(singleRequest, resultCallback, mHandler); | |
| for (int i = 0; i < REPEATING_CAPTURE_EXPECTED_RESULT_COUNT; i++) { | |
| resultCallback.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS); | |
| } | |
| } | |
| mSession.stopRepeating(); | |
| } | |
| waitForSessionState(SESSION_READY, SESSION_READY_TIMEOUT_MS); | |
| } | |
| private void captureBurstShot( | |
| String id, | |
| int[] templates, | |
| int len, | |
| boolean repeating, | |
| boolean abort) throws Exception { | |
| assertEquals("Bad initial state for preparing to capture", | |
| mLatestSessionState, SESSION_READY); | |
| assertTrue("Invalid args to capture function", len <= templates.length); | |
| List<CaptureRequest> requests = new ArrayList<CaptureRequest>(); | |
| List<CaptureRequest> postAbortRequests = new ArrayList<CaptureRequest>(); | |
| for (int i = 0; i < len; i++) { | |
| // Skip video snapshots for LEGACY mode | |
| if (mStaticInfo.isHardwareLevelLegacy() && | |
| templates[i] == CameraDevice.TEMPLATE_VIDEO_SNAPSHOT) { | |
| continue; | |
| } | |
| // Skip non-PREVIEW templates for non-color outpu | |
| if (!mStaticInfo.isColorOutputSupported() && | |
| templates[i] != CameraDevice.TEMPLATE_PREVIEW) { | |
| continue; | |
| } | |
| CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(templates[i]); | |
| assertNotNull("Failed to create capture request", requestBuilder); | |
| requestBuilder.addTarget(mReaderSurface); | |
| requests.add(requestBuilder.build()); | |
| if (abort) { | |
| postAbortRequests.add(requestBuilder.build()); | |
| } | |
| } | |
| CameraCaptureSession.CaptureCallback mockCaptureCallback = | |
| mock(CameraCaptureSession.CaptureCallback.class); | |
| if (VERBOSE) { | |
| Log.v(TAG, String.format("Capturing burst shot for device %s", id)); | |
| } | |
| if (!repeating) { | |
| mSession.captureBurst(requests, mockCaptureCallback, mHandler); | |
| } | |
| else { | |
| mSession.setRepeatingBurst(requests, mockCaptureCallback, mHandler); | |
| } | |
| waitForSessionState(SESSION_ACTIVE, SESSION_READY_TIMEOUT_MS); | |
| int expectedResultCount = requests.size(); | |
| if (repeating) { | |
| expectedResultCount *= REPEATING_CAPTURE_EXPECTED_RESULT_COUNT; | |
| } | |
| verifyCaptureResults(mockCaptureCallback, expectedResultCount); | |
| if (repeating) { | |
| if (abort) { | |
| mSession.abortCaptures(); | |
| // Have to make sure abort and new requests aren't interleave together. | |
| waitForSessionState(SESSION_READY, SESSION_READY_TIMEOUT_MS); | |
| // Capture a burst of captures, and verify the results. | |
| SimpleCaptureCallback resultCallback = new SimpleCaptureCallback(); | |
| mSession.captureBurst(postAbortRequests, resultCallback, mHandler); | |
| // Verify that the results are returned. | |
| for (int i = 0; i < postAbortRequests.size(); i++) { | |
| resultCallback.getCaptureResultForRequest( | |
| postAbortRequests.get(i), CAPTURE_RESULT_TIMEOUT_MS); | |
| } | |
| // Resume the repeating, and verify that results are returned. | |
| mSession.setRepeatingBurst(requests, resultCallback, mHandler); | |
| for (int i = 0; i < REPEATING_CAPTURE_EXPECTED_RESULT_COUNT; i++) { | |
| resultCallback.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS); | |
| } | |
| } | |
| mSession.stopRepeating(); | |
| } | |
| waitForSessionState(SESSION_READY, SESSION_READY_TIMEOUT_MS); | |
| } | |
| /** | |
| * Precondition: Device must be in known OPENED state (has been waited for). | |
| * | |
| * <p>Creates a new capture session and waits until it is in the {@code SESSION_READY} state. | |
| * </p> | |
| * | |
| * <p>Any existing capture session will be closed as a result of calling this.</p> | |
| * */ | |
| private void prepareCapture() throws Exception { | |
| if (VERBOSE) Log.v(TAG, "prepareCapture"); | |
| assertTrue("Bad initial state for preparing to capture", | |
| mLatestDeviceState == STATE_OPENED); | |
| if (mSession != null) { | |
| if (VERBOSE) Log.v(TAG, "prepareCapture - closing existing session"); | |
| closeSession(); | |
| } | |
| // Create a new session listener each time, it's not reusable across cameras | |
| mSessionMockListener = spy(new BlockingSessionCallback()); | |
| mSessionWaiter = mSessionMockListener.getStateWaiter(); | |
| if (!mStaticInfo.isColorOutputSupported()) { | |
| createDefaultImageReader(getMaxDepthSize(mCamera.getId(), mCameraManager), | |
| ImageFormat.DEPTH16, MAX_NUM_IMAGES, new ImageDropperListener()); | |
| } | |
| List<Surface> outputSurfaces = new ArrayList<>(Arrays.asList(mReaderSurface)); | |
| mCamera.createCaptureSession(outputSurfaces, mSessionMockListener, mHandler); | |
| mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS); | |
| waitForSessionState(SESSION_CONFIGURED, SESSION_CONFIGURE_TIMEOUT_MS); | |
| waitForSessionState(SESSION_READY, SESSION_READY_TIMEOUT_MS); | |
| } | |
| private void waitForDeviceState(int state, long timeoutMs) { | |
| mCameraMockListener.waitForState(state, timeoutMs); | |
| mLatestDeviceState = state; | |
| } | |
| private void waitForSessionState(int state, long timeoutMs) { | |
| mSessionWaiter.waitForState(state, timeoutMs); | |
| mLatestSessionState = state; | |
| } | |
| private void verifyCaptureResults( | |
| CameraCaptureSession.CaptureCallback mockListener, | |
| int expectResultCount) { | |
| final int TIMEOUT_PER_RESULT_MS = 2000; | |
| // Should receive expected number of capture results. | |
| verify(mockListener, | |
| timeout(TIMEOUT_PER_RESULT_MS * expectResultCount).atLeast(expectResultCount)) | |
| .onCaptureCompleted( | |
| eq(mSession), | |
| isA(CaptureRequest.class), | |
| argThat(new IsCaptureResultNotEmpty())); | |
| // Should not receive any capture failed callbacks. | |
| verify(mockListener, never()) | |
| .onCaptureFailed( | |
| eq(mSession), | |
| isA(CaptureRequest.class), | |
| isA(CaptureFailure.class)); | |
| // Should receive expected number of capture shutter calls | |
| verify(mockListener, | |
| atLeast(expectResultCount)) | |
| .onCaptureStarted( | |
| eq(mSession), | |
| isA(CaptureRequest.class), | |
| anyLong(), | |
| anyLong()); | |
| } | |
| private void checkFpsRange(CaptureRequest.Builder request, int template, | |
| CameraCharacteristics props) { | |
| CaptureRequest.Key<Range<Integer>> fpsRangeKey = CONTROL_AE_TARGET_FPS_RANGE; | |
| Range<Integer> fpsRange; | |
| if ((fpsRange = mCollector.expectKeyValueNotNull(request, fpsRangeKey)) == null) { | |
| return; | |
| } | |
| int minFps = fpsRange.getLower(); | |
| int maxFps = fpsRange.getUpper(); | |
| Range<Integer>[] availableFpsRange = props | |
| .get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); | |
| boolean foundRange = false; | |
| for (int i = 0; i < availableFpsRange.length; i += 1) { | |
| if (minFps == availableFpsRange[i].getLower() | |
| && maxFps == availableFpsRange[i].getUpper()) { | |
| foundRange = true; | |
| break; | |
| } | |
| } | |
| if (!foundRange) { | |
| mCollector.addMessage(String.format("Unable to find the fps range (%d, %d)", | |
| minFps, maxFps)); | |
| return; | |
| } | |
| if (template != CameraDevice.TEMPLATE_MANUAL && | |
| template != CameraDevice.TEMPLATE_STILL_CAPTURE) { | |
| if (maxFps < MIN_FPS_REQUIRED_FOR_STREAMING) { | |
| mCollector.addMessage("Max fps should be at least " | |
| + MIN_FPS_REQUIRED_FOR_STREAMING); | |
| return; | |
| } | |
| // Relax framerate constraints on legacy mode | |
| if (mStaticInfo.isHardwareLevelAtLeastLimited()) { | |
| // Need give fixed frame rate for video recording template. | |
| if (template == CameraDevice.TEMPLATE_RECORD) { | |
| if (maxFps != minFps) { | |
| mCollector.addMessage("Video recording frame rate should be fixed"); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| private void checkAfMode(CaptureRequest.Builder request, int template, | |
| CameraCharacteristics props) { | |
| boolean hasFocuser = props.getKeys().contains(CameraCharacteristics. | |
| LENS_INFO_MINIMUM_FOCUS_DISTANCE) && | |
| (props.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE) > 0f); | |
| if (!hasFocuser) { | |
| return; | |
| } | |
| int targetAfMode = CaptureRequest.CONTROL_AF_MODE_AUTO; | |
| int[] availableAfMode = props.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); | |
| if (template == CameraDevice.TEMPLATE_PREVIEW || | |
| template == CameraDevice.TEMPLATE_STILL_CAPTURE || | |
| template == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG) { | |
| // Default to CONTINUOUS_PICTURE if it is available, otherwise AUTO. | |
| for (int i = 0; i < availableAfMode.length; i++) { | |
| if (availableAfMode[i] == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) { | |
| targetAfMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE; | |
| break; | |
| } | |
| } | |
| } else if (template == CameraDevice.TEMPLATE_RECORD || | |
| template == CameraDevice.TEMPLATE_VIDEO_SNAPSHOT) { | |
| // Default to CONTINUOUS_VIDEO if it is available, otherwise AUTO. | |
| for (int i = 0; i < availableAfMode.length; i++) { | |
| if (availableAfMode[i] == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO) { | |
| targetAfMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO; | |
| break; | |
| } | |
| } | |
| } else if (template == CameraDevice.TEMPLATE_MANUAL) { | |
| targetAfMode = CaptureRequest.CONTROL_AF_MODE_OFF; | |
| } | |
| mCollector.expectKeyValueEquals(request, CONTROL_AF_MODE, targetAfMode); | |
| if (mStaticInfo.areKeysAvailable(CaptureRequest.LENS_FOCUS_DISTANCE)) { | |
| mCollector.expectKeyValueNotNull(request, LENS_FOCUS_DISTANCE); | |
| } | |
| } | |
| private void checkAntiBandingMode(CaptureRequest.Builder request, int template) { | |
| if (template == CameraDevice.TEMPLATE_MANUAL) { | |
| return; | |
| } | |
| if (!mStaticInfo.isColorOutputSupported()) return; | |
| List<Integer> availableAntiBandingModes = | |
| Arrays.asList(toObject(mStaticInfo.getAeAvailableAntiBandingModesChecked())); | |
| if (availableAntiBandingModes.contains(CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_AUTO)) { | |
| mCollector.expectKeyValueEquals(request, CONTROL_AE_ANTIBANDING_MODE, | |
| CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_AUTO); | |
| } else { | |
| mCollector.expectKeyValueIsIn(request, CONTROL_AE_ANTIBANDING_MODE, | |
| CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_50HZ, | |
| CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_60HZ); | |
| } | |
| } | |
| /** | |
| * <p>Check if 3A metering settings are "up to HAL" in request template</p> | |
| * | |
| * <p>This function doesn't fail the test immediately, it updates the | |
| * test pass/fail status and appends the failure message to the error collector each key.</p> | |
| * | |
| * @param regions The metering rectangles to be checked | |
| */ | |
| private void checkMeteringRect(MeteringRectangle[] regions) { | |
| if (regions == null) { | |
| return; | |
| } | |
| mCollector.expectNotEquals("Number of metering region should not be 0", 0, regions.length); | |
| for (int i = 0; i < regions.length; i++) { | |
| mCollector.expectEquals("Default metering regions should have all zero weight", | |
| 0, regions[i].getMeteringWeight()); | |
| } | |
| } | |
| /** | |
| * <p>Check if the request settings are suitable for a given request template.</p> | |
| * | |
| * <p>This function doesn't fail the test immediately, it updates the | |
| * test pass/fail status and appends the failure message to the error collector each key.</p> | |
| * | |
| * @param request The request to be checked. | |
| * @param template The capture template targeted by this request. | |
| * @param props The CameraCharacteristics this request is checked against with. | |
| */ | |
| private void checkRequestForTemplate(CaptureRequest.Builder request, int template, | |
| CameraCharacteristics props) { | |
| // 3A settings--control.mode. | |
| if (template != CameraDevice.TEMPLATE_MANUAL) { | |
| mCollector.expectKeyValueEquals(request, CONTROL_MODE, | |
| CaptureRequest.CONTROL_MODE_AUTO); | |
| } | |
| // 3A settings--AE/AWB/AF. | |
| Integer maxRegionsAeVal = props.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); | |
| int maxRegionsAe = maxRegionsAeVal != null ? maxRegionsAeVal : 0; | |
| Integer maxRegionsAwbVal = props.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB); | |
| int maxRegionsAwb = maxRegionsAwbVal != null ? maxRegionsAwbVal : 0; | |
| Integer maxRegionsAfVal = props.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF); | |
| int maxRegionsAf = maxRegionsAfVal != null ? maxRegionsAfVal : 0; | |
| checkFpsRange(request, template, props); | |
| checkAfMode(request, template, props); | |
| checkAntiBandingMode(request, template); | |
| if (template == CameraDevice.TEMPLATE_MANUAL) { | |
| mCollector.expectKeyValueEquals(request, CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF); | |
| mCollector.expectKeyValueEquals(request, CONTROL_AE_MODE, | |
| CaptureRequest.CONTROL_AE_MODE_OFF); | |
| mCollector.expectKeyValueEquals(request, CONTROL_AWB_MODE, | |
| CaptureRequest.CONTROL_AWB_MODE_OFF); | |
| } else { | |
| if (mStaticInfo.isColorOutputSupported()) { | |
| mCollector.expectKeyValueEquals(request, CONTROL_AE_MODE, | |
| CaptureRequest.CONTROL_AE_MODE_ON); | |
| mCollector.expectKeyValueEquals(request, CONTROL_AE_EXPOSURE_COMPENSATION, 0); | |
| mCollector.expectKeyValueEquals(request, CONTROL_AE_PRECAPTURE_TRIGGER, | |
| CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE); | |
| // if AE lock is not supported, expect the control key to be non-exist or false | |
| if (mStaticInfo.isAeLockSupported() || request.get(CONTROL_AE_LOCK) != null) { | |
| mCollector.expectKeyValueEquals(request, CONTROL_AE_LOCK, false); | |
| } | |
| mCollector.expectKeyValueEquals(request, CONTROL_AF_TRIGGER, | |
| CaptureRequest.CONTROL_AF_TRIGGER_IDLE); | |
| mCollector.expectKeyValueEquals(request, CONTROL_AWB_MODE, | |
| CaptureRequest.CONTROL_AWB_MODE_AUTO); | |
| // if AWB lock is not supported, expect the control key to be non-exist or false | |
| if (mStaticInfo.isAwbLockSupported() || request.get(CONTROL_AWB_LOCK) != null) { | |
| mCollector.expectKeyValueEquals(request, CONTROL_AWB_LOCK, false); | |
| } | |
| // Check 3A regions. | |
| if (VERBOSE) { | |
| Log.v(TAG, String.format("maxRegions is: {AE: %s, AWB: %s, AF: %s}", | |
| maxRegionsAe, maxRegionsAwb, maxRegionsAf)); | |
| } | |
| if (maxRegionsAe > 0) { | |
| mCollector.expectKeyValueNotNull(request, CONTROL_AE_REGIONS); | |
| MeteringRectangle[] aeRegions = request.get(CONTROL_AE_REGIONS); | |
| checkMeteringRect(aeRegions); | |
| } | |
| if (maxRegionsAwb > 0) { | |
| mCollector.expectKeyValueNotNull(request, CONTROL_AWB_REGIONS); | |
| MeteringRectangle[] awbRegions = request.get(CONTROL_AWB_REGIONS); | |
| checkMeteringRect(awbRegions); | |
| } | |
| if (maxRegionsAf > 0) { | |
| mCollector.expectKeyValueNotNull(request, CONTROL_AF_REGIONS); | |
| MeteringRectangle[] afRegions = request.get(CONTROL_AF_REGIONS); | |
| checkMeteringRect(afRegions); | |
| } | |
| } | |
| } | |
| // Sensor settings. | |
| mCollector.expectEquals("Lens aperture must be present in request if available apertures " + | |
| "are present in metadata, and vice-versa.", | |
| mStaticInfo.areKeysAvailable(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES), | |
| mStaticInfo.areKeysAvailable(CaptureRequest.LENS_APERTURE)); | |
| if (mStaticInfo.areKeysAvailable(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)) { | |
| float[] availableApertures = | |
| props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES); | |
| if (availableApertures.length > 1) { | |
| mCollector.expectKeyValueNotNull(request, LENS_APERTURE); | |
| } | |
| } | |
| mCollector.expectEquals("Lens filter density must be present in request if available " + | |
| "filter densities are present in metadata, and vice-versa.", | |
| mStaticInfo.areKeysAvailable(CameraCharacteristics. | |
| LENS_INFO_AVAILABLE_FILTER_DENSITIES), | |
| mStaticInfo.areKeysAvailable(CaptureRequest.LENS_FILTER_DENSITY)); | |
| if (mStaticInfo.areKeysAvailable(CameraCharacteristics. | |
| LENS_INFO_AVAILABLE_FILTER_DENSITIES)) { | |
| float[] availableFilters = | |
| props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FILTER_DENSITIES); | |
| if (availableFilters.length > 1) { | |
| mCollector.expectKeyValueNotNull(request, LENS_FILTER_DENSITY); | |
| } | |
| } | |
| float[] availableFocalLen = | |
| props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS); | |
| if (availableFocalLen.length > 1) { | |
| mCollector.expectKeyValueNotNull(request, LENS_FOCAL_LENGTH); | |
| } | |
| mCollector.expectEquals("Lens optical stabilization must be present in request if " + | |
| "available optical stabilizations are present in metadata, and vice-versa.", | |
| mStaticInfo.areKeysAvailable(CameraCharacteristics. | |
| LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION), | |
| mStaticInfo.areKeysAvailable(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE)); | |
| if (mStaticInfo.areKeysAvailable(CameraCharacteristics. | |
| LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION)) { | |
| int[] availableOIS = | |
| props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION); | |
| if (availableOIS.length > 1) { | |
| mCollector.expectKeyValueNotNull(request, LENS_OPTICAL_STABILIZATION_MODE); | |
| } | |
| } | |
| if (mStaticInfo.areKeysAvailable(BLACK_LEVEL_LOCK)) { | |
| mCollector.expectKeyValueEquals(request, BLACK_LEVEL_LOCK, false); | |
| } | |
| if (mStaticInfo.areKeysAvailable(SENSOR_FRAME_DURATION)) { | |
| mCollector.expectKeyValueNotNull(request, SENSOR_FRAME_DURATION); | |
| } | |
| if (mStaticInfo.areKeysAvailable(SENSOR_EXPOSURE_TIME)) { | |
| mCollector.expectKeyValueNotNull(request, SENSOR_EXPOSURE_TIME); | |
| } | |
| if (mStaticInfo.areKeysAvailable(SENSOR_SENSITIVITY)) { | |
| mCollector.expectKeyValueNotNull(request, SENSOR_SENSITIVITY); | |
| } | |
| // ISP-processing settings. | |
| if (mStaticInfo.isColorOutputSupported()) { | |
| mCollector.expectKeyValueEquals( | |
| request, STATISTICS_FACE_DETECT_MODE, | |
| CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF); | |
| mCollector.expectKeyValueEquals(request, FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); | |
| } | |
| List<Integer> availableCaps = mStaticInfo.getAvailableCapabilitiesChecked(); | |
| if (mStaticInfo.areKeysAvailable(STATISTICS_LENS_SHADING_MAP_MODE)) { | |
| // If the device doesn't support RAW, all template should have OFF as default. | |
| if (!availableCaps.contains(REQUEST_AVAILABLE_CAPABILITIES_RAW)) { | |
| mCollector.expectKeyValueEquals( | |
| request, STATISTICS_LENS_SHADING_MAP_MODE, | |
| CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE_OFF); | |
| } | |
| } | |
| boolean supportReprocessing = | |
| availableCaps.contains(REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING) || | |
| availableCaps.contains(REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING); | |
| if (template == CameraDevice.TEMPLATE_STILL_CAPTURE) { | |
| // Not enforce high quality here, as some devices may not effectively have high quality | |
| // mode. | |
| if (mStaticInfo.areKeysAvailable(COLOR_CORRECTION_MODE)) { | |
| mCollector.expectKeyValueNotEquals( | |
| request, COLOR_CORRECTION_MODE, | |
| CaptureRequest.COLOR_CORRECTION_MODE_TRANSFORM_MATRIX); | |
| } | |
| // Edge enhancement, noise reduction and aberration correction modes. | |
| mCollector.expectEquals("Edge mode must be present in request if " + | |
| "available edge modes are present in metadata, and vice-versa.", | |
| mStaticInfo.areKeysAvailable(CameraCharacteristics. | |
| EDGE_AVAILABLE_EDGE_MODES), | |
| mStaticInfo.areKeysAvailable(CaptureRequest.EDGE_MODE)); | |
| if (mStaticInfo.areKeysAvailable(EDGE_MODE)) { | |
| List<Integer> availableEdgeModes = | |
| Arrays.asList(toObject(mStaticInfo.getAvailableEdgeModesChecked())); | |
| // Don't need check fast as fast or high quality must be both present or both not. | |
| if (availableEdgeModes.contains(CaptureRequest.EDGE_MODE_HIGH_QUALITY)) { | |
| mCollector.expectKeyValueEquals(request, EDGE_MODE, | |
| CaptureRequest.EDGE_MODE_HIGH_QUALITY); | |
| } else { | |
| mCollector.expectKeyValueEquals(request, EDGE_MODE, | |
| CaptureRequest.EDGE_MODE_OFF); | |
| } | |
| } | |
| mCollector.expectEquals("Noise reduction mode must be present in request if " + | |
| "available noise reductions are present in metadata, and vice-versa.", | |
| mStaticInfo.areKeysAvailable(CameraCharacteristics. | |
| NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES), | |
| mStaticInfo.areKeysAvailable(CaptureRequest.NOISE_REDUCTION_MODE)); | |
| if (mStaticInfo.areKeysAvailable( | |
| CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES)) { | |
| List<Integer> availableNoiseReductionModes = | |
| Arrays.asList(toObject(mStaticInfo.getAvailableNoiseReductionModesChecked())); | |
| // Don't need check fast as fast or high quality must be both present or both not. | |
| if (availableNoiseReductionModes | |
| .contains(CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY)) { | |
| mCollector.expectKeyValueEquals( | |
| request, NOISE_REDUCTION_MODE, | |
| CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY); | |
| } else { | |
| mCollector.expectKeyValueEquals( | |
| request, NOISE_REDUCTION_MODE, CaptureRequest.NOISE_REDUCTION_MODE_OFF); | |
| } | |
| } | |
| boolean supportAvailableAberrationModes = mStaticInfo.areKeysAvailable( | |
| CameraCharacteristics.COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES); | |
| boolean supportAberrationRequestKey = mStaticInfo.areKeysAvailable( | |
| CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE); | |
| mCollector.expectEquals("Aberration correction mode must be present in request if " + | |
| "available aberration correction reductions are present in metadata, and " | |
| + "vice-versa.", supportAvailableAberrationModes, supportAberrationRequestKey); | |
| if (supportAberrationRequestKey) { | |
| List<Integer> availableAberrationModes = Arrays.asList( | |
| toObject(mStaticInfo.getAvailableColorAberrationModesChecked())); | |
| // Don't need check fast as fast or high quality must be both present or both not. | |
| if (availableAberrationModes | |
| .contains(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY)) { | |
| mCollector.expectKeyValueEquals( | |
| request, COLOR_CORRECTION_ABERRATION_MODE, | |
| CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY); | |
| } else { | |
| mCollector.expectKeyValueEquals( | |
| request, COLOR_CORRECTION_ABERRATION_MODE, | |
| CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE_OFF); | |
| } | |
| } | |
| } else if (template == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG && supportReprocessing) { | |
| mCollector.expectKeyValueEquals(request, EDGE_MODE, | |
| CaptureRequest.EDGE_MODE_ZERO_SHUTTER_LAG); | |
| mCollector.expectKeyValueEquals(request, NOISE_REDUCTION_MODE, | |
| CaptureRequest.NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG); | |
| } else if (template == CameraDevice.TEMPLATE_PREVIEW || | |
| template == CameraDevice.TEMPLATE_RECORD){ | |
| if (mStaticInfo.areKeysAvailable(EDGE_MODE)) { | |
| List<Integer> availableEdgeModes = | |
| Arrays.asList(toObject(mStaticInfo.getAvailableEdgeModesChecked())); | |
| if (availableEdgeModes.contains(CaptureRequest.EDGE_MODE_FAST)) { | |
| mCollector.expectKeyValueEquals(request, EDGE_MODE, | |
| CaptureRequest.EDGE_MODE_FAST); | |
| } else { | |
| mCollector.expectKeyValueEquals(request, EDGE_MODE, | |
| CaptureRequest.EDGE_MODE_OFF); | |
| } | |
| } | |
| if (mStaticInfo.areKeysAvailable(NOISE_REDUCTION_MODE)) { | |
| List<Integer> availableNoiseReductionModes = | |
| Arrays.asList(toObject( | |
| mStaticInfo.getAvailableNoiseReductionModesChecked())); | |
| if (availableNoiseReductionModes | |
| .contains(CaptureRequest.NOISE_REDUCTION_MODE_FAST)) { | |
| mCollector.expectKeyValueEquals( | |
| request, NOISE_REDUCTION_MODE, | |
| CaptureRequest.NOISE_REDUCTION_MODE_FAST); | |
| } else { | |
| mCollector.expectKeyValueEquals( | |
| request, NOISE_REDUCTION_MODE, CaptureRequest.NOISE_REDUCTION_MODE_OFF); | |
| } | |
| } | |
| if (mStaticInfo.areKeysAvailable(COLOR_CORRECTION_ABERRATION_MODE)) { | |
| List<Integer> availableAberrationModes = Arrays.asList( | |
| toObject(mStaticInfo.getAvailableColorAberrationModesChecked())); | |
| if (availableAberrationModes | |
| .contains(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE_FAST)) { | |
| mCollector.expectKeyValueEquals( | |
| request, COLOR_CORRECTION_ABERRATION_MODE, | |
| CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE_FAST); | |
| } else { | |
| mCollector.expectKeyValueEquals( | |
| request, COLOR_CORRECTION_ABERRATION_MODE, | |
| CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE_OFF); | |
| } | |
| } | |
| } else { | |
| if (mStaticInfo.areKeysAvailable(EDGE_MODE)) { | |
| mCollector.expectKeyValueNotNull(request, EDGE_MODE); | |
| } | |
| if (mStaticInfo.areKeysAvailable(NOISE_REDUCTION_MODE)) { | |
| mCollector.expectKeyValueNotNull(request, NOISE_REDUCTION_MODE); | |
| } | |
| if (mStaticInfo.areKeysAvailable(COLOR_CORRECTION_ABERRATION_MODE)) { | |
| mCollector.expectKeyValueNotNull(request, COLOR_CORRECTION_ABERRATION_MODE); | |
| } | |
| } | |
| // Tone map and lens shading modes. | |
| if (template == CameraDevice.TEMPLATE_STILL_CAPTURE) { | |
| mCollector.expectEquals("Tonemap mode must be present in request if " + | |
| "available tonemap modes are present in metadata, and vice-versa.", | |
| mStaticInfo.areKeysAvailable(CameraCharacteristics. | |
| TONEMAP_AVAILABLE_TONE_MAP_MODES), | |
| mStaticInfo.areKeysAvailable(CaptureRequest.TONEMAP_MODE)); | |
| if (mStaticInfo.areKeysAvailable( | |
| CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES)) { | |
| List<Integer> availableToneMapModes = | |
| Arrays.asList(toObject(mStaticInfo.getAvailableToneMapModesChecked())); | |
| if (availableToneMapModes.contains(CaptureRequest.TONEMAP_MODE_HIGH_QUALITY)) { | |
| mCollector.expectKeyValueEquals(request, TONEMAP_MODE, | |
| CaptureRequest.TONEMAP_MODE_HIGH_QUALITY); | |
| } else { | |
| mCollector.expectKeyValueEquals(request, TONEMAP_MODE, | |
| CaptureRequest.TONEMAP_MODE_FAST); | |
| } | |
| } | |
| // Still capture template should have android.statistics.lensShadingMapMode ON when | |
| // RAW capability is supported. | |
| if (mStaticInfo.areKeysAvailable(STATISTICS_LENS_SHADING_MAP_MODE) && | |
| availableCaps.contains(REQUEST_AVAILABLE_CAPABILITIES_RAW)) { | |
| mCollector.expectKeyValueEquals(request, STATISTICS_LENS_SHADING_MAP_MODE, | |
| STATISTICS_LENS_SHADING_MAP_MODE_ON); | |
| } | |
| } else { | |
| if (mStaticInfo.areKeysAvailable(TONEMAP_MODE)) { | |
| mCollector.expectKeyValueNotEquals(request, TONEMAP_MODE, | |
| CaptureRequest.TONEMAP_MODE_CONTRAST_CURVE); | |
| mCollector.expectKeyValueNotEquals(request, TONEMAP_MODE, | |
| CaptureRequest.TONEMAP_MODE_GAMMA_VALUE); | |
| mCollector.expectKeyValueNotEquals(request, TONEMAP_MODE, | |
| CaptureRequest.TONEMAP_MODE_PRESET_CURVE); | |
| } | |
| if (mStaticInfo.areKeysAvailable(STATISTICS_LENS_SHADING_MAP_MODE)) { | |
| mCollector.expectKeyValueNotNull(request, STATISTICS_LENS_SHADING_MAP_MODE); | |
| } | |
| } | |
| // Enable ZSL | |
| if (template != CameraDevice.TEMPLATE_STILL_CAPTURE) { | |
| if (mStaticInfo.areKeysAvailable(CONTROL_ENABLE_ZSL)) { | |
| mCollector.expectKeyValueEquals(request, CONTROL_ENABLE_ZSL, false); | |
| } | |
| } | |
| int[] outputFormats = mStaticInfo.getAvailableFormats( | |
| StaticMetadata.StreamDirection.Output); | |
| boolean supportRaw = false; | |
| for (int format : outputFormats) { | |
| if (format == ImageFormat.RAW_SENSOR || format == ImageFormat.RAW10 || | |
| format == ImageFormat.RAW12 || format == ImageFormat.RAW_PRIVATE) { | |
| supportRaw = true; | |
| break; | |
| } | |
| } | |
| if (supportRaw) { | |
| mCollector.expectKeyValueEquals(request, | |
| CONTROL_POST_RAW_SENSITIVITY_BOOST, | |
| DEFAULT_POST_RAW_SENSITIVITY_BOOST); | |
| } | |
| mCollector.expectKeyValueEquals(request, CONTROL_CAPTURE_INTENT, template); | |
| // TODO: use the list of keys from CameraCharacteristics to avoid expecting | |
| // keys which are not available by this CameraDevice. | |
| } | |
| private void captureTemplateTestByCamera(String cameraId, int template) throws Exception { | |
| try { | |
| openDevice(cameraId, mCameraMockListener); | |
| assertTrue("Camera template " + template + " is out of range!", | |
| template >= CameraDevice.TEMPLATE_PREVIEW | |
| && template <= CameraDevice.TEMPLATE_MANUAL); | |
| mCollector.setCameraId(cameraId); | |
| try { | |
| CaptureRequest.Builder request = mCamera.createCaptureRequest(template); | |
| assertNotNull("Failed to create capture request for template " + template, request); | |
| CameraCharacteristics props = mStaticInfo.getCharacteristics(); | |
| checkRequestForTemplate(request, template, props); | |
| } catch (IllegalArgumentException e) { | |
| if (template == CameraDevice.TEMPLATE_MANUAL && | |
| !mStaticInfo.isCapabilitySupported(CameraCharacteristics. | |
| REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) { | |
| // OK | |
| } else if (template == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG && | |
| !mStaticInfo.isCapabilitySupported(CameraCharacteristics. | |
| REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING)) { | |
| // OK. | |
| } else if (sLegacySkipTemplates.contains(template) && | |
| mStaticInfo.isHardwareLevelLegacy()) { | |
| // OK | |
| } else if (template != CameraDevice.TEMPLATE_PREVIEW && | |
| mStaticInfo.isDepthOutputSupported() && | |
| !mStaticInfo.isColorOutputSupported()) { | |
| // OK, depth-only devices need only support PREVIEW template | |
| } else { | |
| throw e; // rethrow | |
| } | |
| } | |
| } | |
| finally { | |
| try { | |
| closeSession(); | |
| } finally { | |
| closeDevice(cameraId, mCameraMockListener); | |
| } | |
| } | |
| } | |
| /** | |
| * Start capture with given {@link #CaptureRequest}. | |
| * | |
| * @param request The {@link #CaptureRequest} to be captured. | |
| * @param repeating If the capture is single capture or repeating. | |
| * @param listener The {@link #CaptureCallback} camera device used to notify callbacks. | |
| * @param handler The handler camera device used to post callbacks. | |
| */ | |
| @Override | |
| protected void startCapture(CaptureRequest request, boolean repeating, | |
| CameraCaptureSession.CaptureCallback listener, Handler handler) | |
| throws CameraAccessException { | |
| if (VERBOSE) Log.v(TAG, "Starting capture from session"); | |
| if (repeating) { | |
| mSession.setRepeatingRequest(request, listener, handler); | |
| } else { | |
| mSession.capture(request, listener, handler); | |
| } | |
| } | |
| /** | |
| * Close a {@link #CameraCaptureSession capture session}; blocking until | |
| * the close finishes with a transition to {@link CameraCaptureSession.StateCallback#onClosed}. | |
| */ | |
| protected void closeSession() { | |
| if (mSession == null) { | |
| return; | |
| } | |
| mSession.close(); | |
| waitForSessionState(SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS); | |
| mSession = null; | |
| mSessionMockListener = null; | |
| mSessionWaiter = null; | |
| } | |
| /** | |
| * A camera capture session listener that keeps all the configured and closed sessions. | |
| */ | |
| private class MultipleSessionCallback extends CameraCaptureSession.StateCallback { | |
| public static final int SESSION_CONFIGURED = 0; | |
| public static final int SESSION_CLOSED = 1; | |
| final List<CameraCaptureSession> mSessions = new ArrayList<>(); | |
| final Map<CameraCaptureSession, Integer> mSessionStates = new HashMap<>(); | |
| CameraCaptureSession mCurrentConfiguredSession = null; | |
| final ReentrantLock mLock = new ReentrantLock(); | |
| final Condition mNewStateCond = mLock.newCondition(); | |
| final boolean mFailOnConfigureFailed; | |
| /** | |
| * If failOnConfigureFailed is true, it calls fail() when onConfigureFailed() is invoked | |
| * for any session. | |
| */ | |
| public MultipleSessionCallback(boolean failOnConfigureFailed) { | |
| mFailOnConfigureFailed = failOnConfigureFailed; | |
| } | |
| @Override | |
| public void onClosed(CameraCaptureSession session) { | |
| mLock.lock(); | |
| mSessionStates.put(session, SESSION_CLOSED); | |
| mNewStateCond.signal(); | |
| mLock.unlock(); | |
| } | |
| @Override | |
| public void onConfigured(CameraCaptureSession session) { | |
| mLock.lock(); | |
| mSessions.add(session); | |
| mSessionStates.put(session, SESSION_CONFIGURED); | |
| mNewStateCond.signal(); | |
| mLock.unlock(); | |
| } | |
| @Override | |
| public void onConfigureFailed(CameraCaptureSession session) { | |
| if (mFailOnConfigureFailed) { | |
| fail("Configuring a session failed"); | |
| } | |
| } | |
| /** | |
| * Get a number of sessions that have been configured. | |
| */ | |
| public List<CameraCaptureSession> getAllSessions(int numSessions, int timeoutMs) | |
| throws Exception { | |
| long remainingTime = timeoutMs; | |
| mLock.lock(); | |
| try { | |
| while (mSessions.size() < numSessions) { | |
| long startTime = SystemClock.elapsedRealtime(); | |
| boolean ret = mNewStateCond.await(remainingTime, TimeUnit.MILLISECONDS); | |
| remainingTime -= (SystemClock.elapsedRealtime() - startTime); | |
| ret &= remainingTime > 0; | |
| assertTrue("Get " + numSessions + " sessions timed out after " + timeoutMs + | |
| "ms", ret); | |
| } | |
| return mSessions; | |
| } finally { | |
| mLock.unlock(); | |
| } | |
| } | |
| /** | |
| * Wait until a previously-configured sessoin is closed or it times out. | |
| */ | |
| public void waitForSessionClose(CameraCaptureSession session, int timeoutMs) throws Exception { | |
| long remainingTime = timeoutMs; | |
| mLock.lock(); | |
| try { | |
| while (mSessionStates.get(session).equals(SESSION_CLOSED) == false) { | |
| long startTime = SystemClock.elapsedRealtime(); | |
| boolean ret = mNewStateCond.await(remainingTime, TimeUnit.MILLISECONDS); | |
| remainingTime -= (SystemClock.elapsedRealtime() - startTime); | |
| ret &= remainingTime > 0; | |
| assertTrue("Wait for session close timed out after " + timeoutMs + "ms", ret); | |
| } | |
| } finally { | |
| mLock.unlock(); | |
| } | |
| } | |
| } | |
| } |