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