1 /*
2  * Copyright 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.hardware.camera2.cts;
18 
19 import static junit.framework.Assert.*;
20 
21 import static org.junit.Assume.assumeFalse;
22 import static org.junit.Assume.assumeTrue;
23 import static org.mockito.Mockito.*;
24 
25 import android.app.Instrumentation;
26 import android.app.NotificationManager;
27 import android.app.UiAutomation;
28 import android.content.pm.PackageManager;
29 import android.hardware.camera2.CameraAccessException;
30 import android.hardware.camera2.CameraCharacteristics;
31 import android.hardware.camera2.CameraDevice;
32 import android.hardware.camera2.CameraManager;
33 import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
34 import android.hardware.camera2.cts.CameraTestUtils.HandlerExecutor;
35 import android.hardware.camera2.cts.CameraTestUtils.MockStateCallback;
36 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
37 import android.os.Build;
38 import android.os.Handler;
39 import android.os.HandlerThread;
40 import android.os.ParcelFileDescriptor;
41 import android.platform.test.annotations.AppModeFull;
42 import android.util.Log;
43 import android.util.Pair;
44 
45 import androidx.test.InstrumentationRegistry;
46 
47 import com.android.compatibility.common.util.PropertyUtil;
48 import com.android.ex.camera2.blocking.BlockingStateCallback;
49 
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.junit.runners.Parameterized;
53 import org.mockito.ArgumentCaptor;
54 
55 import java.io.FileInputStream;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.HashMap;
61 import java.util.HashSet;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Set;
65 import java.util.concurrent.Executor;
66 import java.util.concurrent.LinkedBlockingQueue;
67 
68 /**
69  * <p>Basic test for CameraManager class.</p>
70  */
71 
72 @RunWith(Parameterized.class)
73 public class CameraManagerTest extends Camera2ParameterizedTestCase {
74     private static final String TAG = "CameraManagerTest";
75     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
76     private static final int NUM_CAMERA_REOPENS = 10;
77     private static final int AVAILABILITY_TIMEOUT_MS = 10;
78 
79     private PackageManager mPackageManager;
80     private NoopCameraListener mListener;
81     private HandlerThread mHandlerThread;
82     private Handler mHandler;
83     private BlockingStateCallback mCameraListener;
84     private CameraErrorCollector mCollector;
85     private Set<Set<String>> mConcurrentCameraIdCombinations;
86 
87     /** Load validation jni on initialization. */
88     static {
89         System.loadLibrary("ctscamera2_jni");
90     }
91 
92     @Override
setUp()93     public void setUp() throws Exception {
94         super.setUp();
95         mPackageManager = mContext.getPackageManager();
96         assertNotNull("Can't get package manager", mPackageManager);
97         mListener = new NoopCameraListener();
98 
99         /**
100          * Workaround for mockito and JB-MR2 incompatibility
101          *
102          * Avoid java.lang.IllegalArgumentException: dexcache == null
103          * https://code.google.com/p/dexmaker/issues/detail?id=2
104          */
105         System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
106 
107         mCameraListener = spy(new BlockingStateCallback());
108 
109         mHandlerThread = new HandlerThread(TAG);
110         mHandlerThread.start();
111         mHandler = new Handler(mHandlerThread.getLooper());
112         mCollector = new CameraErrorCollector();
113         mConcurrentCameraIdCombinations =
114                 CameraTestUtils.getConcurrentCameraIds(mCameraManager, mAdoptShellPerm);
115     }
116 
117     @Override
tearDown()118     public void tearDown() throws Exception {
119         mHandlerThread.quitSafely();
120         mHandler = null;
121 
122         try {
123             mCollector.verify();
124         } catch (Throwable e) {
125             // When new Exception(e) is used, exception info will be printed twice.
126             throw new Exception(e.getMessage());
127         } finally {
128             super.tearDown();
129         }
130     }
131 
132     /**
133      * Verifies that the reason is in the range of public-only codes.
134      */
checkCameraAccessExceptionReason(CameraAccessException e)135     private static int checkCameraAccessExceptionReason(CameraAccessException e) {
136         int reason = e.getReason();
137 
138         switch (reason) {
139             case CameraAccessException.CAMERA_DISABLED:
140             case CameraAccessException.CAMERA_DISCONNECTED:
141             case CameraAccessException.CAMERA_ERROR:
142             case CameraAccessException.CAMERA_IN_USE:
143             case CameraAccessException.MAX_CAMERAS_IN_USE:
144                 return reason;
145         }
146 
147         fail("Invalid CameraAccessException code: " + reason);
148 
149         return -1; // unreachable
150     }
151 
152     @Test
testCameraManagerGetDeviceIdList()153     public void testCameraManagerGetDeviceIdList() throws Exception {
154         String[] ids = getCameraIdsUnderTest();
155         if (VERBOSE) Log.v(TAG, "CameraManager ids: " + Arrays.toString(ids));
156 
157         assumeFalse("Camera related features may not be accurate for system cameras, skipping",
158                 mAdoptShellPerm);
159         assumeTrue(
160                 "Camera related features may not be accurent when test is run with single "
161                         + "camera under test specified by cameraId override",
162                 mOverrideCameraId == null);
163 
164         /**
165          * Test: that if there is at least one reported id, then the system must have
166          * the FEATURE_CAMERA_ANY feature.
167          */
168         assertTrue("System camera feature and camera id list don't match",
169                 ids.length == 0 ||
170                 mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY));
171 
172         /**
173          * Test: that if the device has front or rear facing cameras, then there
174          * must be matched system features.
175          */
176         boolean externalCameraConnected = false;
177         String mainBackId = null, mainFrontId = null;
178         Map<String, Integer> lensFacingMap = new HashMap<String, Integer>();
179         for (int i = 0; i < ids.length; i++) {
180             CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
181             assertNotNull("Can't get camera characteristics for camera " + ids[i], props);
182             Integer lensFacing = props.get(CameraCharacteristics.LENS_FACING);
183             lensFacingMap.put(ids[i], lensFacing);
184             assertNotNull("Can't get lens facing info", lensFacing);
185             if (lensFacing == CameraCharacteristics.LENS_FACING_FRONT) {
186                 assertTrue("System doesn't have front camera feature",
187                         mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT));
188                 if (mainFrontId == null) {
189                     mainFrontId = ids[i];
190                 }
191             } else if (lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
192                 assertTrue("System doesn't have back camera feature",
193                         mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA));
194                 if (mainBackId == null) {
195                     mainBackId = ids[i];
196                 }
197             } else if (lensFacing == CameraCharacteristics.LENS_FACING_EXTERNAL) {
198                 externalCameraConnected = true;
199                 assertTrue("System doesn't have external camera feature",
200                         mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL));
201             } else {
202                 fail("Unknown camera lens facing " + lensFacing.toString());
203             }
204         }
205 
206         // Test an external camera is connected if FEATURE_CAMERA_EXTERNAL is advertised
207         if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL)) {
208             assertTrue("External camera is not connected on device with FEATURE_CAMERA_EXTERNAL",
209                     externalCameraConnected);
210         }
211 
212         /**
213          * Test: that if there is one camera device, then the system must have some
214          * specific features.
215          */
216         assertTrue("Missing system feature: FEATURE_CAMERA_ANY",
217                ids.length == 0
218             || mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY));
219         assertTrue("Missing system feature: FEATURE_CAMERA, FEATURE_CAMERA_FRONT or FEATURE_CAMERA_EXTERNAL",
220                ids.length == 0
221             || mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
222             || mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)
223             || mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL));
224 
225         testConcurrentCameraFeature(mainFrontId, mainBackId);
226     }
227 
228     /**
229      * Returns true if mConcurrentCameraIdCombinations has at least one combination containing both
230      * mainFrontId and mainBackId.
231      * Returns false otherwise.
232      */
containsMainFrontBackConcurrentCombination(String mainFrontId, String mainBackId)233     private boolean containsMainFrontBackConcurrentCombination(String mainFrontId,
234             String mainBackId) {
235         if (mainFrontId == null || mainBackId == null) {
236             return false;
237         }
238         boolean combinationFound = false;
239 
240         // Go through all combinations and see that at least one combination has a main
241         // front + main back camera.
242         for (Set<String> cameraIdCombination : mConcurrentCameraIdCombinations) {
243             boolean frontFacingFound = false, backFacingFound = false;
244             for (String cameraId : cameraIdCombination) {
245                 if (cameraId.equals(mainFrontId)) {
246                     frontFacingFound = true;
247                 } else if (cameraId.equals(mainBackId)) {
248                     backFacingFound = true;
249                 }
250                 if (frontFacingFound && backFacingFound) {
251                     combinationFound = true;
252                     break;
253                 }
254             }
255             if (combinationFound) {
256                 break;
257             }
258         }
259         return combinationFound;
260     }
261 
262     /**
263      * Test the consistency of the statement: If FEATURE_CAMERA_CONCURRENT is advertised,
264      * CameraManager.getConcurrentCameraIds()
265      * returns a combination which contains the main front id and main back id, and vice versa.
266      */
testConcurrentCameraFeature(String mainFrontId, String mainBackId)267     private void testConcurrentCameraFeature(String mainFrontId, String mainBackId)
268             throws Exception {
269         boolean frontBackFeatureAdvertised =
270                   mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_CONCURRENT);
271         if (frontBackFeatureAdvertised) {
272             assertTrue("FEATURE_CAMERA_CONCURRENT advertised but main front id is null",
273                         mainFrontId != null);
274             assertTrue("FEATURE_CAMERA_CONCURRENT advertised but main back id is null",
275                         mainBackId != null);
276         }
277 
278         boolean concurrentMainFrontBackCombinationFound =
279                 containsMainFrontBackConcurrentCombination(mainFrontId, mainBackId);
280 
281         String[] ids = getCameraIdsUnderTest();
282         if (ids.length > 0) {
283             assertTrue("System camera feature FEATURE_CAMERA_CONCURRENT = "
284                     + frontBackFeatureAdvertised
285                     + " and device actually having a main front back combination which can operate "
286                     + "concurrently = " + concurrentMainFrontBackCombinationFound
287                     +  " do not match",
288                     frontBackFeatureAdvertised == concurrentMainFrontBackCombinationFound);
289         }
290     }
291 
292     // Test: that properties can be queried from each device, without exceptions.
293     @Test
testCameraManagerGetCameraCharacteristics()294     public void testCameraManagerGetCameraCharacteristics() throws Exception {
295         String[] ids = getCameraIdsUnderTest();
296         for (int i = 0; i < ids.length; i++) {
297             CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
298             assertNotNull(
299                     String.format("Can't get camera characteristics from: ID %s", ids[i]), props);
300         }
301     }
302 
303     // Test: that properties queried between the Java SDK and the C++ NDK are equivalent.
304     @Test
testCameraCharacteristicsNdkFromSdk()305     public void testCameraCharacteristicsNdkFromSdk() throws Exception {
306         String[] ids = getCameraIdsUnderTest();
307         for (int i = 0; i < ids.length; i++) {
308             CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
309             Integer lensFacing = props.get(CameraCharacteristics.LENS_FACING);
310             assertNotNull("Can't get lens facing info", lensFacing);
311 
312             assertTrue(validateACameraMetadataFromCameraMetadataCriticalTagsNative(
313                 props, lensFacing.intValue()));
314         }
315     }
316 
317     // Returns true if `props` has lens facing `lensFacing` when queried from the NDK via
318     // ACameraMetadata_fromCameraMetadata().
validateACameraMetadataFromCameraMetadataCriticalTagsNative( CameraCharacteristics props, int lensFacing)319     private static native boolean validateACameraMetadataFromCameraMetadataCriticalTagsNative(
320         CameraCharacteristics props, int lensFacing);
321 
322     // Test: that an exception is thrown if an invalid device id is passed down.
323     @Test
testCameraManagerInvalidDevice()324     public void testCameraManagerInvalidDevice() throws Exception {
325         String[] ids = getCameraIdsUnderTest();
326         // Create an invalid id by concatenating all the valid ids together.
327         StringBuilder invalidId = new StringBuilder();
328         invalidId.append("INVALID");
329         for (int i = 0; i < ids.length; i++) {
330             invalidId.append(ids[i]);
331         }
332 
333         try {
334             mCameraManager.getCameraCharacteristics(
335                 invalidId.toString());
336             fail(String.format("Accepted invalid camera ID: %s", invalidId.toString()));
337         } catch (IllegalArgumentException e) {
338             // This is the exception that should be thrown in this case.
339         }
340     }
341 
342     // Test: that each camera device can be opened one at a time, several times.
343     @Test
testCameraManagerOpenCamerasSerially()344     public void testCameraManagerOpenCamerasSerially() throws Exception {
345         testCameraManagerOpenCamerasSerially(/*useExecutor*/ false);
346         testCameraManagerOpenCamerasSerially(/*useExecutor*/ true);
347     }
348 
testCameraManagerOpenCamerasSerially(boolean useExecutor)349     private void testCameraManagerOpenCamerasSerially(boolean useExecutor) throws Exception {
350         final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null;
351         String[] ids = getCameraIdsUnderTest();
352         for (int i = 0; i < ids.length; i++) {
353             for (int j = 0; j < NUM_CAMERA_REOPENS; j++) {
354                 CameraDevice camera = null;
355                 try {
356                     MockStateCallback mockListener = MockStateCallback.mock();
357                     mCameraListener = new BlockingStateCallback(mockListener);
358 
359                     if (useExecutor) {
360                         mCameraManager.openCamera(ids[i], executor, mCameraListener);
361                     } else {
362                         mCameraManager.openCamera(ids[i], mCameraListener, mHandler);
363                     }
364 
365                     // Block until unConfigured
366                     mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED,
367                             CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
368 
369                     // Ensure state transitions are in right order:
370                     // -- 1) Opened
371                     // Ensure no other state transitions have occurred:
372                     camera = CameraTestUtils.verifyCameraStateOpened(ids[i], mockListener);
373                 } finally {
374                     if (camera != null) {
375                         camera.close();
376                     }
377                 }
378             }
379         }
380     }
381 
382     /**
383      * Test: one or more camera devices can be open at the same time, or the right error state
384      * is set if this can't be done.
385      */
386     @Test
testCameraManagerOpenAllCameras()387     public void testCameraManagerOpenAllCameras() throws Exception {
388         testCameraManagerOpenAllCameras(/*useExecutor*/ false);
389         testCameraManagerOpenAllCameras(/*useExecutor*/ true);
390     }
391 
testCameraManagerOpenAllCameras(boolean useExecutor)392     private void testCameraManagerOpenAllCameras(boolean useExecutor) throws Exception {
393         String[] ids = getCameraIdsUnderTest();
394         assertNotNull("Camera ids shouldn't be null", ids);
395 
396         // Skip test if the device doesn't have multiple cameras.
397         if (ids.length <= 1) {
398             return;
399         }
400 
401         final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null;
402         List<CameraDevice> cameraList = new ArrayList<CameraDevice>();
403         List<MockStateCallback> listenerList = new ArrayList<MockStateCallback>();
404         List<BlockingStateCallback> blockingListenerList = new ArrayList<BlockingStateCallback>();
405         try {
406             for (int i = 0; i < ids.length; i++) {
407                 // Ignore state changes from other cameras
408                 MockStateCallback mockListener = MockStateCallback.mock();
409                 mCameraListener = new BlockingStateCallback(mockListener);
410 
411                 /**
412                  * Track whether or not we got a synchronous error from openCamera.
413                  *
414                  * A synchronous error must also be accompanied by an asynchronous
415                  * StateCallback#onError callback.
416                  */
417                 boolean expectingError = false;
418 
419                 String cameraId = ids[i];
420                 try {
421                     if (useExecutor) {
422                         mCameraManager.openCamera(cameraId, executor, mCameraListener);
423                     } else {
424                         mCameraManager.openCamera(cameraId, mCameraListener, mHandler);
425                     }
426                 } catch (CameraAccessException e) {
427                     int reason = checkCameraAccessExceptionReason(e);
428                     if (reason == CameraAccessException.CAMERA_DISCONNECTED ||
429                             reason == CameraAccessException.CAMERA_DISABLED) {
430                         // TODO: We should handle a Disabled camera by passing here and elsewhere
431                         fail("Camera must not be disconnected or disabled for this test" + ids[i]);
432                     } else {
433                         expectingError = true;
434                     }
435                 }
436 
437                 List<Integer> expectedStates = new ArrayList<Integer>();
438                 expectedStates.add(BlockingStateCallback.STATE_OPENED);
439                 expectedStates.add(BlockingStateCallback.STATE_ERROR);
440                 int state = mCameraListener.waitForAnyOfStates(
441                         expectedStates, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
442 
443                 // It's possible that we got an asynchronous error transition only. This is ok.
444                 if (expectingError) {
445                     assertEquals("Throwing a CAMERA_ERROR exception must be accompanied with a " +
446                             "StateCallback#onError callback",
447                             BlockingStateCallback.STATE_ERROR, state);
448                 }
449 
450                 /**
451                  * Two situations are considered passing:
452                  * 1) The camera opened successfully.
453                  *     => No error must be set.
454                  * 2) The camera did not open because there were too many other cameras opened.
455                  *     => Only MAX_CAMERAS_IN_USE error must be set.
456                  *
457                  * Any other situation is considered a failure.
458                  *
459                  * For simplicity we treat disconnecting asynchronously as a failure, so
460                  * camera devices should not be physically unplugged during this test.
461                  */
462 
463                 CameraDevice camera;
464                 if (state == BlockingStateCallback.STATE_ERROR) {
465                     // Camera did not open because too many other cameras were opened
466                     // => onError called exactly once with a non-null camera
467                     assertTrue("At least one camera must be opened successfully",
468                             cameraList.size() > 0);
469 
470                     ArgumentCaptor<CameraDevice> argument =
471                             ArgumentCaptor.forClass(CameraDevice.class);
472 
473                     verify(mockListener)
474                             .onError(
475                                     argument.capture(),
476                                     eq(CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE)
477                             );
478                     verifyNoMoreInteractions(mockListener);
479 
480                     camera = argument.getValue();
481                     assertNotNull("Expected a non-null camera for the error transition for ID: "
482                             + ids[i], camera);
483                 } else if (state == BlockingStateCallback.STATE_OPENED) {
484                     // Camera opened successfully.
485                     // => onOpened called exactly once
486                     camera = CameraTestUtils.verifyCameraStateOpened(cameraId,
487                             mockListener);
488                 } else {
489                     fail("Unexpected state " + state);
490                     camera = null; // unreachable. but need this for java compiler
491                 }
492 
493                 // Keep track of cameras so we can close it later
494                 cameraList.add(camera);
495                 listenerList.add(mockListener);
496                 blockingListenerList.add(mCameraListener);
497             }
498         } finally {
499             for (int i = 0; i < cameraList.size(); i++) {
500                 // With conflicting devices, opening of one camera could result in the other camera
501                 // being disconnected. To handle such case, reset the mock before close.
502                 reset(listenerList.get(i));
503                 cameraList.get(i).close();
504             }
505             for (BlockingStateCallback blockingListener : blockingListenerList) {
506                 blockingListener.waitForState(
507                         BlockingStateCallback.STATE_CLOSED,
508                         CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
509             }
510         }
511 
512         /*
513          * Ensure that no state transitions have bled through from one camera to another
514          * after closing the cameras.
515          */
516         int i = 0;
517         for (MockStateCallback listener : listenerList) {
518             CameraDevice camera = cameraList.get(i);
519 
520             verify(listener).onClosed(eq(camera));
521             verifyNoMoreInteractions(listener);
522             i++;
523             // Only a #close can happen on the camera since we were done with it.
524             // Also nothing else should've happened between the close and the open.
525         }
526     }
527 
528     /**
529      * Test: that opening the same device multiple times and make sure the right
530      * error state is set.
531      */
532     @Test
testCameraManagerOpenCameraTwice()533     public void testCameraManagerOpenCameraTwice() throws Exception {
534         testCameraManagerOpenCameraTwice(/*useExecutor*/ false);
535         testCameraManagerOpenCameraTwice(/*useExecutor*/ true);
536     }
537 
testCameraManagerOpenCameraTwice(boolean useExecutor)538     private void testCameraManagerOpenCameraTwice(boolean useExecutor) throws Exception {
539         String[] ids = getCameraIdsUnderTest();
540         final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null;
541 
542         // Test across every camera device.
543         for (int i = 0; i < ids.length; ++i) {
544             CameraDevice successCamera = null;
545             mCollector.setCameraId(ids[i]);
546 
547             try {
548                 MockStateCallback mockSuccessListener = MockStateCallback.mock();
549                 MockStateCallback mockFailListener = MockStateCallback.mock();
550 
551                 BlockingStateCallback successListener =
552                         new BlockingStateCallback(mockSuccessListener);
553                 BlockingStateCallback failListener =
554                         new BlockingStateCallback(mockFailListener);
555 
556                 if (useExecutor) {
557                     mCameraManager.openCamera(ids[i], executor, successListener);
558                     mCameraManager.openCamera(ids[i], executor, failListener);
559                 } else {
560                     mCameraManager.openCamera(ids[i], successListener, mHandler);
561                     mCameraManager.openCamera(ids[i], failListener, mHandler);
562                 }
563 
564                 successListener.waitForState(BlockingStateCallback.STATE_OPENED,
565                         CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
566                 ArgumentCaptor<CameraDevice> argument =
567                         ArgumentCaptor.forClass(CameraDevice.class);
568                 verify(mockSuccessListener, atLeastOnce()).onOpened(argument.capture());
569                 verify(mockSuccessListener, atLeastOnce()).onDisconnected(argument.capture());
570 
571                 failListener.waitForState(BlockingStateCallback.STATE_OPENED,
572                         CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
573                 verify(mockFailListener, atLeastOnce()).onOpened(argument.capture());
574 
575                 successCamera = CameraTestUtils.verifyCameraStateOpened(
576                         ids[i], mockFailListener);
577 
578                 verifyNoMoreInteractions(mockFailListener);
579             } finally {
580                 if (successCamera != null) {
581                     successCamera.close();
582                 }
583             }
584         }
585     }
586 
587     private class NoopCameraListener extends CameraManager.AvailabilityCallback {
588         @Override
onCameraAvailable(String cameraId)589         public void onCameraAvailable(String cameraId) {
590             // No-op
591         }
592 
593         @Override
onCameraUnavailable(String cameraId)594         public void onCameraUnavailable(String cameraId) {
595             // No-op
596         }
597     }
598 
599     /**
600      * Test: that the APIs to register and unregister a listener run successfully;
601      * doesn't test that the listener actually gets invoked at the right time.
602      * Registering a listener multiple times should have no effect, and unregistering
603      * a listener that isn't registered should have no effect.
604      */
605     @Test
testCameraManagerListener()606     public void testCameraManagerListener() throws Exception {
607         mCameraManager.unregisterAvailabilityCallback(mListener);
608         // Test Handler API
609         mCameraManager.registerAvailabilityCallback(mListener, mHandler);
610         mCameraManager.registerAvailabilityCallback(mListener, mHandler);
611         mCameraManager.unregisterAvailabilityCallback(mListener);
612         mCameraManager.unregisterAvailabilityCallback(mListener);
613         // Test Executor API
614         Executor executor = new HandlerExecutor(mHandler);
615         mCameraManager.registerAvailabilityCallback(executor, mListener);
616         mCameraManager.registerAvailabilityCallback(executor, mListener);
617         mCameraManager.unregisterAvailabilityCallback(mListener);
618         mCameraManager.unregisterAvailabilityCallback(mListener);
619     }
620 
621     /**
622      * Test that the availability callbacks fire when expected
623      */
624     @Test
testCameraManagerListenerCallbacks()625     public void testCameraManagerListenerCallbacks() throws Exception {
626         if (mOverrideCameraId != null) {
627             // Testing is done for individual camera. Skip.
628             return;
629         }
630         testCameraManagerListenerCallbacks(/*useExecutor*/ false);
631         testCameraManagerListenerCallbacks(/*useExecutor*/ true);
632     }
633 
634 
testCameraManagerListenerCallbacks(boolean useExecutor)635     private void testCameraManagerListenerCallbacks(boolean useExecutor) throws Exception {
636 
637         final LinkedBlockingQueue<String> availableEventQueue = new LinkedBlockingQueue<>();
638         final LinkedBlockingQueue<String> unavailableEventQueue = new LinkedBlockingQueue<>();
639         final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null;
640 
641         final LinkedBlockingQueue<Pair<String, String>> availablePhysicalCamEventQueue =
642                 new LinkedBlockingQueue<>();
643         final LinkedBlockingQueue<Pair<String, String>> unavailablePhysicalCamEventQueue =
644                 new LinkedBlockingQueue<>();
645 
646         final LinkedBlockingQueue<String> onCameraOpenedEventQueue = new LinkedBlockingQueue<>();
647         final LinkedBlockingQueue<String> onCameraClosedEventQueue = new LinkedBlockingQueue<>();
648 
649         CameraManager.AvailabilityCallback ac = new CameraManager.AvailabilityCallback() {
650             @Override
651             public void onCameraAvailable(String cameraId) {
652                 // We allow this callback irrespective of mAdoptShellPerm since for this particular
653                 // test, in the case when shell permissions are adopted we test all cameras, for
654                 // simplicity. This is since when mAdoptShellPerm is false, we can't test for
655                 // onCameraOpened/Closed callbacks (no CAMERA_OPEN_CLOSE_LISTENER permissions).
656                 // So, to test all cameras, we test them when we adopt shell permission identity.
657                 super.onCameraAvailable(cameraId);
658                 availableEventQueue.offer(cameraId);
659             }
660 
661             @Override
662             public void onCameraUnavailable(String cameraId) {
663                 super.onCameraUnavailable(cameraId);
664                 unavailableEventQueue.offer(cameraId);
665             }
666 
667             @Override
668             public void onPhysicalCameraAvailable(String cameraId, String physicalCameraId) {
669                 super.onPhysicalCameraAvailable(cameraId, physicalCameraId);
670                 availablePhysicalCamEventQueue.offer(new Pair<>(cameraId, physicalCameraId));
671             }
672 
673             @Override
674             public void onPhysicalCameraUnavailable(String cameraId, String physicalCameraId) {
675                 super.onPhysicalCameraUnavailable(cameraId, physicalCameraId);
676                 unavailablePhysicalCamEventQueue.offer(new Pair<>(cameraId, physicalCameraId));
677             }
678 
679             @Override
680             public void onCameraOpened(String cameraId, String packageId) {
681                 super.onCameraOpened(cameraId, packageId);
682                 String curPackageId = mContext.getPackageName();
683                 assertTrue("Opening package should be " + curPackageId + ", was " + packageId,
684                         curPackageId.equals(packageId));
685                 onCameraOpenedEventQueue.offer(cameraId);
686             }
687 
688             @Override
689             public void onCameraClosed(String cameraId) {
690                 super.onCameraClosed(cameraId);
691                 onCameraClosedEventQueue.offer(cameraId);
692             }
693 
694         };
695 
696         if (useExecutor) {
697             mCameraManager.registerAvailabilityCallback(executor, ac);
698         } else {
699             mCameraManager.registerAvailabilityCallback(ac, mHandler);
700         }
701         String[] cameras = getCameraIdsUnderTest();
702         if (mAdoptShellPerm) {
703             //when mAdoptShellPerm is false, we can't test for
704             // onCameraOpened/Closed callbacks (no CAMERA_OPEN_CLOSE_LISTENER permissions).
705             // So, to test all cameras, we test them when we adopt shell permission identity.
706             cameras = mCameraManager.getCameraIdListNoLazy();
707         }
708 
709         if (cameras.length == 0) {
710             Log.i(TAG, "No cameras present, skipping test mAdoprPerm");
711             return;
712         }
713 
714         // Verify we received available for all cameras' initial state in a reasonable amount of time
715         HashSet<String> expectedAvailableCameras = new HashSet<String>(Arrays.asList(cameras));
716         CameraTestUtils.verifyAvailabilityCbsReceived(expectedAvailableCameras, availableEventQueue,
717                 unavailableEventQueue, true /*available*/);
718 
719         // Clear physical camera callback queue in case the initial state of certain physical
720         // cameras are unavailable.
721         unavailablePhysicalCamEventQueue.clear();
722 
723         // Verify transitions for individual cameras
724         for (String id : cameras) {
725             MockStateCallback mockListener = MockStateCallback.mock();
726             mCameraListener = new BlockingStateCallback(mockListener);
727 
728             if (useExecutor) {
729                 mCameraManager.openCamera(id, executor, mCameraListener);
730             } else {
731                 mCameraManager.openCamera(id, mCameraListener, mHandler);
732             }
733 
734             // Block until opened
735             mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED,
736                     CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
737             // Then verify only open happened, and get the camera handle
738             CameraDevice camera = CameraTestUtils.verifyCameraStateOpened(id, mockListener);
739 
740             CameraTestUtils.verifySingleAvailabilityCbsReceived(unavailableEventQueue,
741                         availableEventQueue, id, "unavailability", "Availability");
742             if (mAdoptShellPerm) {
743                 // Verify that we see the expected 'onCameraOpened' event.
744                 CameraTestUtils.verifySingleAvailabilityCbsReceived(onCameraOpenedEventQueue,
745                         onCameraClosedEventQueue, id, "onCameraOpened", "onCameraClosed");
746             }
747 
748             // Verify that we see the expected 'unavailable' events if this camera is a physical
749             // camera of another logical multi-camera
750             HashSet<Pair<String, String>> relatedLogicalCameras = new HashSet<>();
751             for (String multiCamId : cameras) {
752                 CameraCharacteristics props = mCameraManager.getCameraCharacteristics(multiCamId);
753                 Set<String> physicalIds = props.getPhysicalCameraIds();
754                 if (physicalIds.contains(id)) {
755                     relatedLogicalCameras.add(new Pair<String, String>(multiCamId, id));
756                 }
757             }
758 
759             HashSet<Pair<String, String>> expectedLogicalCameras =
760                     new HashSet<>(relatedLogicalCameras);
761             CameraTestUtils.verifyAvailabilityCbsReceived(expectedLogicalCameras,
762                     unavailablePhysicalCamEventQueue, availablePhysicalCamEventQueue,
763                     false /*available*/);
764 
765             // Verify that we see the expected 'available' event after closing the camera
766 
767             camera.close();
768             mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED,
769                     CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS);
770 
771             CameraTestUtils.verifySingleAvailabilityCbsReceived(availableEventQueue,
772                     unavailableEventQueue, id, "availability", "Unavailability");
773 
774             if (mAdoptShellPerm) {
775                 CameraTestUtils.verifySingleAvailabilityCbsReceived(onCameraClosedEventQueue,
776                         onCameraOpenedEventQueue, id, "onCameraClosed", "onCameraOpened");
777             }
778 
779             expectedLogicalCameras = new HashSet<Pair<String, String>>(relatedLogicalCameras);
780             CameraTestUtils.verifyAvailabilityCbsReceived(expectedLogicalCameras,
781                     availablePhysicalCamEventQueue,
782                     null /*unExpectedEventQueue*/,
783                     true /*available*/);
784 
785             // Clear physical camera callback queue in case the initial state of certain physical
786             // cameras are unavailable.
787             unavailablePhysicalCamEventQueue.clear();
788         }
789 
790         // Verify that we can unregister the listener and see no more events
791         assertTrue("Availability events received unexpectedly",
792                 availableEventQueue.size() == 0);
793         assertTrue("Unavailability events received unexpectedly",
794                     unavailableEventQueue.size() == 0);
795 
796         mCameraManager.unregisterAvailabilityCallback(ac);
797 
798         {
799             // Open an arbitrary camera and make sure we don't hear about it
800 
801             MockStateCallback mockListener = MockStateCallback.mock();
802             mCameraListener = new BlockingStateCallback(mockListener);
803 
804             if (useExecutor) {
805                 mCameraManager.openCamera(cameras[0], executor, mCameraListener);
806             } else {
807                 mCameraManager.openCamera(cameras[0], mCameraListener, mHandler);
808             }
809 
810             // Block until opened
811             mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED,
812                     CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
813             // Then verify only open happened, and close the camera
814             CameraDevice camera = CameraTestUtils.verifyCameraStateOpened(cameras[0], mockListener);
815 
816             camera.close();
817 
818             mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED,
819                     CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS);
820 
821             // No unavailability or availability callback should have occured
822             String candidateId = unavailableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
823                     java.util.concurrent.TimeUnit.MILLISECONDS);
824             assertTrue(String.format("Received unavailability notice for ID %s unexpectedly ",
825                             candidateId),
826                     candidateId == null);
827 
828             candidateId = availableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
829                     java.util.concurrent.TimeUnit.MILLISECONDS);
830             assertTrue(String.format("Received availability notice for ID %s unexpectedly ",
831                             candidateId),
832                     candidateId == null);
833 
834             Pair<String, String> candidatePhysicalIds = unavailablePhysicalCamEventQueue.poll(
835                     AVAILABILITY_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS);
836             assertTrue("Received unavailability physical camera notice unexpectedly ",
837                     candidatePhysicalIds == null);
838 
839             candidatePhysicalIds = availablePhysicalCamEventQueue.poll(
840                     AVAILABILITY_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS);
841             assertTrue("Received availability notice for physical camera unexpectedly ",
842                     candidatePhysicalIds == null);
843         }
844 
845         if (mAdoptShellPerm) {
846             // Open an arbitrary camera and make sure subsequently subscribed listener receives
847             // correct onCameraOpened/onCameraClosed callbacks
848 
849             MockStateCallback mockListener = MockStateCallback.mock();
850             mCameraListener = new BlockingStateCallback(mockListener);
851 
852             if (useExecutor) {
853                 mCameraManager.openCamera(cameras[0], executor, mCameraListener);
854             } else {
855                 mCameraManager.openCamera(cameras[0], mCameraListener, mHandler);
856             }
857 
858             // Block until opened
859             mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED,
860                     CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
861             // Then verify only open happened, and close the camera
862             CameraDevice camera = CameraTestUtils.verifyCameraStateOpened(cameras[0], mockListener);
863 
864             if (useExecutor) {
865                 mCameraManager.registerAvailabilityCallback(executor, ac);
866             } else {
867                 mCameraManager.registerAvailabilityCallback(ac, mHandler);
868             }
869 
870             // Verify that we see the expected 'onCameraOpened' event.
871             CameraTestUtils.verifySingleAvailabilityCbsReceived(onCameraOpenedEventQueue,
872                     onCameraClosedEventQueue, cameras[0], "onCameraOpened", "onCameraClosed");
873 
874             camera.close();
875 
876             mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED,
877                     CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS);
878 
879             CameraTestUtils.verifySingleAvailabilityCbsReceived(onCameraClosedEventQueue,
880                     onCameraOpenedEventQueue, cameras[0], "onCameraClosed", "onCameraOpened");
881 
882             mCameraManager.unregisterAvailabilityCallback(ac);
883         }
884     } // testCameraManagerListenerCallbacks
885 
886     /**
887      * Test that the physical camera available/unavailable callback behavior is consistent
888      * between:
889      *
890      * - No camera is open,
891      * - After camera is opened, and
892      * - After camera is closed,
893      */
894     @Test
testPhysicalCameraAvailabilityConsistency()895     public void testPhysicalCameraAvailabilityConsistency() throws Throwable {
896         CameraTestUtils.testPhysicalCameraAvailabilityConsistencyHelper(getCameraIdsUnderTest(),
897                 mCameraManager, mHandler, true /*expectInitialCallbackAfterOpen*/);
898     }
899 
900     // Verify no LEGACY-level devices appear on devices first launched in the Q release or newer
901     @Test
902     @AppModeFull(reason = "Instant apps can't access Test API")
testNoLegacyOnQ()903     public void testNoLegacyOnQ() throws Exception {
904         if(PropertyUtil.getFirstApiLevel() < Build.VERSION_CODES.Q){
905             // LEGACY still allowed for devices upgrading to Q
906             return;
907         }
908         String[] ids = getCameraIdsUnderTest();
909         for (int i = 0; i < ids.length; i++) {
910             CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
911             assertNotNull(
912                     String.format("Can't get camera characteristics from: ID %s", ids[i]), props);
913             Integer hardwareLevel = props.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
914             assertNotNull(
915                     String.format("Can't get hardware level from: ID %s", ids[i]), hardwareLevel);
916             assertTrue(String.format(
917                             "Camera device %s cannot be LEGACY level for devices launching on Q",
918                             ids[i]),
919                     hardwareLevel != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
920         }
921     }
922 
923     @Test
testCameraManagerWithDnD()924     public void testCameraManagerWithDnD() throws Exception {
925         String[] cameras = getCameraIdsUnderTest();
926         if (cameras.length == 0) {
927             Log.i(TAG, "No cameras present, skipping test");
928             return;
929         }
930         // Allow the test package to adjust notification policy
931         toggleNotificationPolicyAccess(mContext.getPackageName(),
932                 InstrumentationRegistry.getInstrumentation(), true);
933 
934         // Enable DnD filtering
935 
936         NotificationManager nm = mContext.getSystemService(NotificationManager.class);
937         try {
938             nm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
939 
940             // Try to use the camera API
941 
942             for (String cameraId : cameras) {
943                 try {
944                     CameraCharacteristics c = mCameraManager.getCameraCharacteristics(cameraId);
945                     assertTrue("Unable to get camera characteristics when DnD is enabled",
946                             c != null);
947                 } catch (RuntimeException e) {
948                     fail("RuntimeException thrown when attempting to access camera " +
949                             "characteristics with DnD enabled. " +
950                             "https://android-review.googlesource.com/c/platform/frameworks/base/+" +
951                             "/747089/ may be missing.");
952                 }
953             }
954         } finally {
955             // Restore notifications to normal
956 
957             nm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
958         }
959     }
960 
961     @Test
testCameraManagerAutomotiveCameras()962     public void testCameraManagerAutomotiveCameras() throws Exception {
963         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
964             // Execute this test only on the automotive device implementations
965             Log.i(TAG, "Skips this test on non automotive device implementations");
966             return;
967         }
968 
969         String[] cameraIds = getCameraIdsUnderTest();
970         if (cameraIds.length < 1) {
971             Log.i(TAG, "No cameras present, skipping test");
972             return;
973         }
974 
975         /**
976          * On automotive device implementations, all cameras must have android.automotive.location
977          * and android.automotive.lens.facing in their static metadata.  Also,
978          * android.lens.poseTranslation and android.lens.poseRotation must present in a camera's
979          * static metadata, and android.lens.poseReference should be set as
980          * LENS_POSE_REFERENCE_AUTOMOTIVE in following conditions.
981          *
982          * - android.automotive.location has AUTOMOTIVE_LOCATION_EXTERIOR_OTHER or
983          *   AUTOMOTIVE_LOCATION_EXTRA_OTHER
984          * - android.automotive.lens.facing has AUTOMOTIVE_LENS_FACING_EXTERIOR_OTHER or
985          *   AUTOMOTIVE_LENS_FACING_INTERIOR_OTHER
986          * - One or more camera has the same android.automotive.location and
987          *   android.automotive.lens.facing values
988          */
989         Map<Pair<Integer, Integer>, ArrayList<String>> cameraGroup = new HashMap<>();
990         for (String cameraId : cameraIds) {
991             CameraCharacteristics props = mCameraManager.getCameraCharacteristics(cameraId);
992             assertNotNull(
993                     String.format("Can't get camera characteristics from: ID %s", cameraId), props);
994 
995             Integer lensFacing = props.get(CameraCharacteristics.LENS_FACING);
996             if (lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_EXTERNAL) {
997                 // Automotive device implementations may have external cameras but they are exempted
998                 // from this test case.
999                 continue;
1000             }
1001 
1002             Integer cameraLocation = props.get(CameraCharacteristics.AUTOMOTIVE_LOCATION);
1003             assertNotNull(
1004                     String.format("Can't get a camera location from: ID %s", cameraId),
1005                     cameraLocation);
1006 
1007             int[] automotiveLensFacing = props.get(CameraCharacteristics.AUTOMOTIVE_LENS_FACING);
1008             assertNotNull(
1009                     String.format("Can't get a lens facing direction from: ID %s", cameraId),
1010                     automotiveLensFacing);
1011 
1012             if (cameraLocation == CameraCharacteristics.AUTOMOTIVE_LOCATION_EXTERIOR_OTHER ||
1013                     cameraLocation == CameraCharacteristics.AUTOMOTIVE_LOCATION_EXTRA_OTHER ||
1014                     automotiveLensFacing[0] ==
1015                             CameraCharacteristics.AUTOMOTIVE_LENS_FACING_EXTERIOR_OTHER ||
1016                     automotiveLensFacing[0] ==
1017                             CameraCharacteristics.AUTOMOTIVE_LENS_FACING_INTERIOR_OTHER) {
1018                 checkAutomotiveLensPoseCharacteristics(cameraId, props);
1019             } else {
1020                 Pair<Integer, Integer> key = new Pair<>(cameraLocation, automotiveLensFacing[0]);
1021                 if (cameraGroup.containsKey(key)) {
1022                     cameraGroup.get(key).add(cameraId);
1023                 } else {
1024                     cameraGroup.put(key, new ArrayList<>(Arrays.asList(cameraId)));
1025                 }
1026             }
1027         }
1028 
1029         for (Map.Entry<Pair<Integer, Integer>, ArrayList<String>> entry : cameraGroup.entrySet()) {
1030             ArrayList<String> cameraIdsToVerify = entry.getValue();
1031             if (cameraIdsToVerify.size() > 1) {
1032                 for (String id : cameraIdsToVerify) {
1033                     CameraCharacteristics props = mCameraManager.getCameraCharacteristics(id);
1034                     checkAutomotiveLensPoseCharacteristics(id, props);
1035                 }
1036             }
1037         }
1038     }
1039 
checkAutomotiveLensPoseCharacteristics(String cameraId, CameraCharacteristics props)1040     private void checkAutomotiveLensPoseCharacteristics(String cameraId,
1041             CameraCharacteristics props) {
1042         Integer reference = props.get(CameraCharacteristics.LENS_POSE_REFERENCE);
1043         assertNotNull(
1044                 String.format("Can't get a lens pose reference from: ID %s", cameraId),
1045                 reference);
1046         assertTrue("Lens pose reference must be AUTOMOTIVE",
1047                 reference == CameraCharacteristics.LENS_POSE_REFERENCE_AUTOMOTIVE);
1048         float[] translation = props.get(CameraCharacteristics.LENS_POSE_TRANSLATION);
1049         assertNotNull(
1050                 String.format("Can't get a lens pose translation from: ID %s", cameraId),
1051                 translation);
1052         float[] rotation = props.get(CameraCharacteristics.LENS_POSE_ROTATION);
1053         assertNotNull(
1054                 String.format("Can't get a lens pose rotation from: ID %s", cameraId),
1055                 rotation);
1056     }
1057 
1058 
toggleNotificationPolicyAccess(String packageName, Instrumentation instrumentation, boolean on)1059     private void toggleNotificationPolicyAccess(String packageName,
1060             Instrumentation instrumentation, boolean on) throws IOException {
1061 
1062         String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName;
1063 
1064         runCommand(command, instrumentation);
1065 
1066         NotificationManager nm = mContext.getSystemService(NotificationManager.class);
1067         assertEquals("Notification Policy Access Grant is " +
1068                 nm.isNotificationPolicyAccessGranted() + " not " + on, on,
1069                 nm.isNotificationPolicyAccessGranted());
1070     }
1071 
runCommand(String command, Instrumentation instrumentation)1072     private void runCommand(String command, Instrumentation instrumentation) throws IOException {
1073         UiAutomation uiAutomation = instrumentation.getUiAutomation();
1074         // Execute command
1075         ParcelFileDescriptor fd = mUiAutomation.executeShellCommand(command);
1076         assertNotNull("Failed to execute shell command: " + command, fd);
1077         // Wait for the command to finish by reading until EOF
1078         try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
1079             byte[] buffer = new byte[4096];
1080             while (in.read(buffer) > 0) {}
1081         } catch (IOException e) {
1082             throw new IOException("Could not read stdout of command: " + command, e);
1083         }
1084     }
1085 
1086 }
1087