| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.hardware.camera2.cts; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.junit.Assert.fail; |
| import static org.mockito.Mockito.eq; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.timeout; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyNoMoreInteractions; |
| |
| import android.hardware.camera2.CameraAccessException; |
| import android.hardware.camera2.CameraDevice; |
| import android.hardware.camera2.CameraManager; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.platform.test.annotations.AppModeFull; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.compatibility.common.util.SystemUtil; |
| |
| import org.junit.AfterClass; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| |
| import java.io.IOException; |
| |
| /** |
| * Test for validating behaviors related to idle UIDs. Idle UIDs cannot |
| * access camera. If the UID has a camera handle and becomes idle it would |
| * get an error callback losing the camera handle. Similarly if the UID is |
| * already idle it cannot obtain a camera handle. |
| */ |
| @AppModeFull |
| @RunWith(AndroidJUnit4.class) |
| public final class IdleUidTest { |
| private static final long CAMERA_OPERATION_TIMEOUT_MILLIS = 5000; // 5 sec |
| |
| private static final HandlerThread sCallbackThread = new HandlerThread("Callback thread"); |
| |
| @BeforeClass |
| public static void startHandlerThread() { |
| sCallbackThread.start(); |
| } |
| |
| @AfterClass |
| public static void stopHandlerThread() { |
| sCallbackThread.quit(); |
| } |
| |
| /** |
| * Tests that a UID has access to the camera only in active state. |
| */ |
| @Test |
| public void testCameraAccessForIdleUid() throws Exception { |
| final CameraManager cameraManager = InstrumentationRegistry.getTargetContext() |
| .getSystemService(CameraManager.class); |
| for (String cameraId : cameraManager.getCameraIdList()) { |
| testCameraAccessForIdleUidByCamera(cameraManager, cameraId, |
| new Handler(sCallbackThread.getLooper())); |
| } |
| } |
| |
| /** |
| * Tests that a UID loses access to the camera if it becomes inactive. |
| */ |
| @Test |
| public void testCameraAccessBecomingInactiveUid() throws Exception { |
| final CameraManager cameraManager = InstrumentationRegistry.getTargetContext() |
| .getSystemService(CameraManager.class); |
| for (String cameraId : cameraManager.getCameraIdList()) { |
| testCameraAccessBecomingInactiveUidByCamera(cameraManager, cameraId, |
| new Handler(sCallbackThread.getLooper())); |
| } |
| |
| } |
| |
| private void testCameraAccessForIdleUidByCamera(CameraManager cameraManager, |
| String cameraId, Handler handler) throws Exception { |
| // Can access camera from an active UID. |
| assertCameraAccess(cameraManager, cameraId, true, handler); |
| |
| // Make our UID idle |
| makeMyPackageIdle(); |
| try { |
| // Can not access camera from an idle UID. |
| assertCameraAccess(cameraManager, cameraId, false, handler); |
| } finally { |
| // Restore our UID as active |
| makeMyPackageActive(); |
| } |
| |
| // Can access camera from an active UID. |
| assertCameraAccess(cameraManager, cameraId, true, handler); |
| } |
| |
| private static void assertCameraAccess(CameraManager cameraManager, |
| String cameraId, boolean hasAccess, Handler handler) { |
| // Mock the callback used to observe camera state. |
| final CameraDevice.StateCallback callback = mock(CameraDevice.StateCallback.class); |
| |
| // Open the camera |
| try { |
| cameraManager.openCamera(cameraId, callback, handler); |
| } catch (CameraAccessException e) { |
| if (hasAccess) { |
| fail("Unexpected exception" + e); |
| } else { |
| assertThat(e.getReason()).isSameAs(CameraAccessException.CAMERA_DISABLED); |
| } |
| } |
| |
| // Verify access |
| final ArgumentCaptor<CameraDevice> captor = ArgumentCaptor.forClass(CameraDevice.class); |
| try { |
| if (hasAccess) { |
| // The camera should open fine as we are in the foreground |
| verify(callback, timeout(CAMERA_OPERATION_TIMEOUT_MILLIS) |
| .times(1)).onOpened(captor.capture()); |
| verifyNoMoreInteractions(callback); |
| } else { |
| // The camera should not open as we are in the background |
| verify(callback, timeout(CAMERA_OPERATION_TIMEOUT_MILLIS) |
| .times(1)).onError(captor.capture(), |
| eq(CameraDevice.StateCallback.ERROR_CAMERA_DISABLED)); |
| verifyNoMoreInteractions(callback); |
| } |
| } finally { |
| final CameraDevice cameraDevice = captor.getValue(); |
| assertThat(cameraDevice).isNotNull(); |
| cameraDevice.close(); |
| } |
| } |
| |
| private void testCameraAccessBecomingInactiveUidByCamera(CameraManager cameraManager, |
| String cameraId, Handler handler) throws Exception { |
| // Mock the callback used to observe camera state. |
| final CameraDevice.StateCallback callback = mock(CameraDevice.StateCallback.class); |
| |
| // Open the camera |
| try { |
| cameraManager.openCamera(cameraId, callback, handler); |
| } catch (CameraAccessException e) { |
| fail("Unexpected exception" + e); |
| } |
| |
| // Verify access |
| final ArgumentCaptor<CameraDevice> captor = ArgumentCaptor.forClass(CameraDevice.class); |
| try { |
| // The camera should open fine as we are in the foreground |
| verify(callback, timeout(CAMERA_OPERATION_TIMEOUT_MILLIS) |
| .times(1)).onOpened(captor.capture()); |
| verifyNoMoreInteractions(callback); |
| |
| // Ready for a new verification |
| reset(callback); |
| |
| // Now we are moving in the background |
| makeMyPackageIdle(); |
| |
| // The camera should be closed if the UID became idle |
| verify(callback, timeout(CAMERA_OPERATION_TIMEOUT_MILLIS) |
| .times(1)).onError(captor.capture(), |
| eq(CameraDevice.StateCallback.ERROR_CAMERA_DISABLED)); |
| verifyNoMoreInteractions(callback); |
| } finally { |
| // Restore to active state |
| makeMyPackageActive(); |
| |
| final CameraDevice cameraDevice = captor.getValue(); |
| assertThat(cameraDevice).isNotNull(); |
| cameraDevice.close(); |
| } |
| } |
| |
| private static void makeMyPackageActive() throws IOException { |
| final String command = "cmd media.camera reset-uid-state " |
| + InstrumentationRegistry.getTargetContext().getPackageName(); |
| SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command); |
| } |
| |
| private static void makeMyPackageIdle() throws IOException { |
| final String command = "cmd media.camera set-uid-state " |
| + InstrumentationRegistry.getTargetContext().getPackageName() + " idle"; |
| SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command); |
| } |
| } |