1 /*
2  * Copyright (C) 2020 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.testcases;
18 
19 import static android.hardware.camera2.cts.CameraTestUtils.*;
20 import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
21 
22 import static org.junit.Assume.assumeTrue;
23 
24 import android.content.Context;
25 import android.graphics.ImageFormat;
26 import android.graphics.Rect;
27 import android.hardware.camera2.CameraCaptureSession;
28 import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
29 import android.hardware.camera2.CameraCharacteristics;
30 import android.hardware.camera2.CameraDevice;
31 import android.hardware.camera2.CameraManager;
32 import android.hardware.camera2.CaptureRequest;
33 import android.hardware.camera2.params.MandatoryStreamCombination;
34 import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
35 import android.hardware.camera2.params.OutputConfiguration;
36 import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
37 import android.hardware.camera2.cts.CameraTestUtils;
38 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
39 import android.hardware.camera2.cts.helpers.StaticMetadata;
40 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
41 import android.os.Handler;
42 import android.os.HandlerThread;
43 import android.test.AndroidTestCase;
44 import android.util.Log;
45 import android.view.Surface;
46 import android.view.WindowManager;
47 
48 import com.android.ex.camera2.blocking.BlockingSessionCallback;
49 import com.android.ex.camera2.blocking.BlockingStateCallback;
50 
51 import java.io.File;
52 import java.nio.ByteBuffer;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.HashMap;
56 import java.util.List;
57 import java.util.Set;
58 
59 public class Camera2ConcurrentAndroidTestCase extends Camera2ParameterizedTestCase {
60     private static final String TAG = "Camera2ConcurrentAndroidTestCase";
61     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
62 
63     // include both standalone camera IDs and "hidden" physical camera IDs
64     private String[] mAllCameraIds;
65 
66     public static class CameraTestInfo {
67         public String mCameraId;
68         public CameraDevice mCamera;
69         public StaticMetadata mStaticInfo;
70         public MandatoryStreamCombination[] mMandatoryStreamCombinations;
71         public CameraCaptureSession mCameraSession;
72         public BlockingSessionCallback mCameraSessionListener;
73         public BlockingStateCallback mCameraListener;
CameraTestInfo(String cameraId, StaticMetadata staticInfo, MandatoryStreamCombination[] mandatoryStreamCombinations, BlockingStateCallback cameraListener)74         public CameraTestInfo(String cameraId, StaticMetadata staticInfo,
75                 MandatoryStreamCombination[] mandatoryStreamCombinations,
76                 BlockingStateCallback cameraListener) {
77             mCameraId = cameraId;
78             mStaticInfo = staticInfo;
79             mMandatoryStreamCombinations = mandatoryStreamCombinations;
80             mCameraListener = cameraListener;
81         }
82     };
83     protected Set<Set<String>> mConcurrentCameraIdCombinations;
84     protected HashMap<String, CameraTestInfo> mCameraTestInfos;
85     protected HashMap<String, StaticMetadata> mAllStaticInfo;
86     protected Handler mHandler;
87     protected HandlerThread mHandlerThread;
88     protected CameraErrorCollector mCollector;
89     protected String mDebugFileNameBase;
90 
91     protected WindowManager mWindowManager;
92 
93     /**
94      * Set up the camera2 test case required environments, including CameraManager,
95      * HandlerThread, Camera IDs, and CameraStateCallback etc.
96      */
97     @Override
setUp()98     public void setUp() throws Exception {
99         super.setUp();
100 
101         assumeTrue(
102                 "Camera2ConcurrentAndroidTestCase tests can't be run with cameraId "
103                         + "override set, restricting the test to single camera",
104                 mOverrideCameraId == null);
105 
106         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
107         mHandlerThread = new HandlerThread(TAG);
108         mHandlerThread.start();
109         mHandler = new Handler(mHandlerThread.getLooper());
110         mCollector = new CameraErrorCollector();
111 
112         File filesDir = mContext.getPackageManager().isInstantApp()
113                 ? mContext.getFilesDir()
114                 : mContext.getExternalFilesDir(null);
115 
116         mDebugFileNameBase = filesDir.getPath();
117         mAllStaticInfo = new HashMap<String, StaticMetadata>();
118         List<String> hiddenPhysicalIds = new ArrayList<>();
119         String[] cameraIdsUnderTest = getCameraIdsUnderTest();
120         for (String cameraId : cameraIdsUnderTest) {
121             CameraCharacteristics props = mCameraManager.getCameraCharacteristics(cameraId);
122             StaticMetadata staticMetadata = new StaticMetadata(props,
123                     CheckLevel.ASSERT, /*collector*/null);
124             mAllStaticInfo.put(cameraId, staticMetadata);
125             for (String physicalId : props.getPhysicalCameraIds()) {
126                 if (!Arrays.asList(cameraIdsUnderTest).contains(physicalId) &&
127                         !hiddenPhysicalIds.contains(physicalId)) {
128                     hiddenPhysicalIds.add(physicalId);
129                     props = mCameraManager.getCameraCharacteristics(physicalId);
130                     staticMetadata = new StaticMetadata(
131                             mCameraManager.getCameraCharacteristics(physicalId),
132                             CheckLevel.ASSERT, /*collector*/null);
133                     mAllStaticInfo.put(physicalId, staticMetadata);
134                 }
135             }
136         }
137         mConcurrentCameraIdCombinations =
138                 CameraTestUtils.getConcurrentCameraIds(mCameraManager, mAdoptShellPerm);
139         assertNotNull("Unable to get concurrent camera combinations",
140                 mConcurrentCameraIdCombinations);
141         mCameraTestInfos = new HashMap<String, CameraTestInfo>();
142         for (Set<String> cameraIdComb : mConcurrentCameraIdCombinations) {
143             for (String cameraId : cameraIdComb) {
144                 if (!mCameraTestInfos.containsKey(cameraId)) {
145                     StaticMetadata staticMetadata = mAllStaticInfo.get(cameraId);
146                     assertTrue("camera id" + cameraId + "'s metadata not found in mAllStaticInfo",
147                             staticMetadata != null);
148                     CameraCharacteristics.Key<MandatoryStreamCombination[]> mandatoryStreamsKey =
149                             CameraCharacteristics.SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS;
150                     MandatoryStreamCombination[] combinations =
151                             staticMetadata.getCharacteristics().get(mandatoryStreamsKey);
152                     assertTrue("Concurrent streaming camera id " + cameraId +
153                             "  MUST have mandatory stream combinations",
154                             (combinations != null) && (combinations.length > 0));
155                     mCameraTestInfos.put(cameraId,
156                             new CameraTestInfo(cameraId, staticMetadata, combinations,
157                                   new BlockingStateCallback()));
158                 }
159             }
160         }
161 
162         mAllCameraIds = new String[cameraIdsUnderTest.length + hiddenPhysicalIds.size()];
163         System.arraycopy(cameraIdsUnderTest, 0, mAllCameraIds, 0, cameraIdsUnderTest.length);
164         for (int i = 0; i < hiddenPhysicalIds.size(); i++) {
165             mAllCameraIds[cameraIdsUnderTest.length + i] = hiddenPhysicalIds.get(i);
166         }
167     }
168 
169     @Override
tearDown()170     public void tearDown() throws Exception {
171         try {
172             if (mHandlerThread != null) {
173                 mHandlerThread.quitSafely();
174             }
175             mHandler = null;
176 
177             if (mCollector != null) {
178                 mCollector.verify();
179             }
180         } catch (Throwable e) {
181             // When new Exception(e) is used, exception info will be printed twice.
182             throw new Exception(e.getMessage());
183         } finally {
184             super.tearDown();
185         }
186     }
187 
188     /**
189      * Start capture with given {@link #CaptureRequest}.
190      *
191      * @param request The {@link #CaptureRequest} to be captured.
192      * @param repeating If the capture is single capture or repeating.
193      * @param listener The {@link #CaptureCallback} camera device used to notify callbacks.
194      * @param handler The handler camera device used to post callbacks.
195      */
startCapture(String cameraId, CaptureRequest request, boolean repeating, CaptureCallback listener, Handler handler)196     protected void startCapture(String cameraId, CaptureRequest request, boolean repeating,
197             CaptureCallback listener, Handler handler) throws Exception {
198         if (VERBOSE) Log.v(TAG, "Starting capture from device");
199         CameraTestInfo info = mCameraTestInfos.get(cameraId);
200         assertTrue("CameraTestInfo not found for camera id " + cameraId, info != null);
201         if (repeating) {
202             info.mCameraSession.setRepeatingRequest(request, listener, handler);
203         } else {
204             info.mCameraSession.capture(request, listener, handler);
205         }
206     }
207 
208     /**
209      * Stop the current active capture.
210      *
211      * @param fast When it is true, {@link CameraDevice#flush} is called, the stop capture
212      * could be faster.
213      */
stopCapture(String cameraId, boolean fast)214     protected void stopCapture(String cameraId, boolean fast) throws Exception {
215         if (VERBOSE) Log.v(TAG, "Stopping capture");
216 
217         CameraTestInfo info = mCameraTestInfos.get(cameraId);
218         assertTrue("CameraTest info not found for camera id " + cameraId, info != null);
219         if (fast) {
220             /**
221              * Flush is useful for canceling long exposure single capture, it also could help
222              * to make the streaming capture stop sooner.
223              */
224             info.mCameraSession.abortCaptures();
225             info.mCameraSessionListener.getStateWaiter().
226                     waitForState(BlockingSessionCallback.SESSION_READY, CAMERA_IDLE_TIMEOUT_MS);
227         } else {
228             info.mCameraSession.close();
229             info.mCameraSessionListener.getStateWaiter().
230                     waitForState(BlockingSessionCallback.SESSION_CLOSED, CAMERA_IDLE_TIMEOUT_MS);
231         }
232     }
233 
234     /**
235      * Open a {@link #CameraDevice camera device} and get the StaticMetadata for a given camera id.
236      * The default mCameraListener is used to wait for states.
237      *
238      * @param cameraId The id of the camera device to be opened.
239      */
openDevice(String cameraId)240     protected void openDevice(String cameraId) throws Exception {
241         CameraTestInfo info = mCameraTestInfos.get(cameraId);
242         assertTrue("CameraTest info not found for camera id " + cameraId, info != null);
243         openDevice(cameraId, info.mCameraListener);
244     }
245 
246     /**
247      * Open a {@link #CameraDevice} and get the StaticMetadata for a given camera id and listener.
248      *
249      * @param cameraId The id of the camera device to be opened.
250      * @param listener The {@link #BlockingStateCallback} used to wait for states.
251      */
openDevice(String cameraId, BlockingStateCallback listener)252     protected void openDevice(String cameraId, BlockingStateCallback listener) throws Exception {
253         CameraTestInfo info = mCameraTestInfos.get(cameraId);
254         assertTrue("CameraTest info not found for camera id " + cameraId, info != null);
255 
256         info.mCamera = CameraTestUtils.openCamera(
257                 mCameraManager, cameraId, listener, mHandler);
258         mCollector.setCameraId(cameraId);
259         if (VERBOSE) {
260             Log.v(TAG, "Camera " + cameraId + " is opened");
261         }
262     }
263 
264     /**
265      * Create a {@link #CameraCaptureSession} using the currently open camera with
266      * OutputConfigurations.
267      *
268      * @param outputSurfaces The set of output surfaces to configure for this session
269      */
createSessionByConfigs(String cameraId, List<OutputConfiguration> outputConfigs)270     protected void createSessionByConfigs(String cameraId,
271             List<OutputConfiguration> outputConfigs) throws Exception {
272         CameraTestInfo info = mCameraTestInfos.get(cameraId);
273         assertTrue("CameraTest info not found for camera id " + cameraId, info != null);
274 
275         info.mCameraSessionListener = new BlockingSessionCallback();
276         info.mCameraSession = CameraTestUtils.configureCameraSessionWithConfig(info.mCamera,
277                 outputConfigs, info.mCameraSessionListener, mHandler);
278     }
279 
280     /**
281      * Close a {@link #CameraDevice camera device} and clear the associated StaticInfo field for a
282      * given camera id. The default mCameraListener is used to wait for states.
283      * <p>
284      * This function must be used along with the {@link #openDevice} for the
285      * same camera id.
286      * </p>
287      *
288      * @param cameraId The id of the {@link #CameraDevice camera device} to be closed.
289      */
closeDevice(String cameraId)290     protected void closeDevice(String cameraId) {
291         CameraTestInfo info = mCameraTestInfos.get(cameraId);
292         assertTrue("CameraTest info not found for camera id " + cameraId, info != null);
293         closeDevice(cameraId, info.mCameraListener);
294     }
295 
296     /**
297      * Close a {@link #CameraDevice camera device} and clear the associated StaticInfo field for a
298      * given camera id and listener.
299      * <p>
300      * This function must be used along with the {@link #openDevice} for the
301      * same camera id.
302      * </p>
303      *
304      * @param cameraId The id of the camera device to be closed.
305      * @param listener The BlockingStateCallback used to wait for states.
306      */
closeDevice(String cameraId, BlockingStateCallback listener)307     protected void closeDevice(String cameraId, BlockingStateCallback listener) {
308         CameraTestInfo info = mCameraTestInfos.get(cameraId);
309         assertTrue("CameraTest info not found for camera id " + cameraId, info != null);
310 
311         if (info.mCamera != null) {
312             info.mCamera.close();
313             listener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
314             info.mCamera = null;
315             info.mCameraSession = null;
316             info.mCameraSessionListener = null;
317             if (VERBOSE) {
318                 Log.v(TAG, "Camera " + cameraId + " is closed");
319             }
320         }
321     }
322 
323 }
324