1 /* 2 * Copyright (C) 2017 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 org.junit.Assert.fail; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.mockito.Mockito.eq; 24 import static org.mockito.Mockito.mock; 25 import static org.mockito.Mockito.reset; 26 import static org.mockito.Mockito.timeout; 27 import static org.mockito.Mockito.verify; 28 import static org.mockito.Mockito.verifyNoMoreInteractions; 29 30 import android.hardware.camera2.CameraAccessException; 31 import android.hardware.camera2.CameraDevice; 32 import android.hardware.camera2.CameraManager; 33 import android.os.Handler; 34 import android.os.HandlerThread; 35 import android.platform.test.annotations.AppModeFull; 36 import android.support.test.InstrumentationRegistry; 37 import android.support.test.runner.AndroidJUnit4; 38 39 import com.android.compatibility.common.util.SystemUtil; 40 41 import org.junit.AfterClass; 42 import org.junit.BeforeClass; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 import org.mockito.ArgumentCaptor; 46 47 import java.io.IOException; 48 49 /** 50 * Test for validating behaviors related to idle UIDs. Idle UIDs cannot 51 * access camera. If the UID has a camera handle and becomes idle it would 52 * get an error callback losing the camera handle. Similarly if the UID is 53 * already idle it cannot obtain a camera handle. 54 */ 55 @AppModeFull 56 @RunWith(AndroidJUnit4.class) 57 public final class IdleUidTest { 58 private static final long CAMERA_OPERATION_TIMEOUT_MILLIS = 5000; // 5 sec 59 60 private static final HandlerThread sCallbackThread = new HandlerThread("Callback thread"); 61 62 @BeforeClass startHandlerThread()63 public static void startHandlerThread() { 64 sCallbackThread.start(); 65 } 66 67 @AfterClass stopHandlerThread()68 public static void stopHandlerThread() { 69 sCallbackThread.quit(); 70 } 71 72 /** 73 * Tests that a UID has access to the camera only in active state. 74 */ 75 @Test testCameraAccessForIdleUid()76 public void testCameraAccessForIdleUid() throws Exception { 77 final CameraManager cameraManager = InstrumentationRegistry.getTargetContext() 78 .getSystemService(CameraManager.class); 79 for (String cameraId : cameraManager.getCameraIdList()) { 80 testCameraAccessForIdleUidByCamera(cameraManager, cameraId, 81 new Handler(sCallbackThread.getLooper())); 82 } 83 } 84 85 /** 86 * Tests that a UID loses access to the camera if it becomes inactive. 87 */ 88 @Test testCameraAccessBecomingInactiveUid()89 public void testCameraAccessBecomingInactiveUid() throws Exception { 90 final CameraManager cameraManager = InstrumentationRegistry.getTargetContext() 91 .getSystemService(CameraManager.class); 92 for (String cameraId : cameraManager.getCameraIdList()) { 93 testCameraAccessBecomingInactiveUidByCamera(cameraManager, cameraId, 94 new Handler(sCallbackThread.getLooper())); 95 } 96 97 } 98 testCameraAccessForIdleUidByCamera(CameraManager cameraManager, String cameraId, Handler handler)99 private void testCameraAccessForIdleUidByCamera(CameraManager cameraManager, 100 String cameraId, Handler handler) throws Exception { 101 // Can access camera from an active UID. 102 assertCameraAccess(cameraManager, cameraId, true, handler); 103 104 // Make our UID idle 105 makeMyPackageIdle(); 106 try { 107 // Can not access camera from an idle UID. 108 assertCameraAccess(cameraManager, cameraId, false, handler); 109 } finally { 110 // Restore our UID as active 111 makeMyPackageActive(); 112 } 113 114 // Can access camera from an active UID. 115 assertCameraAccess(cameraManager, cameraId, true, handler); 116 } 117 assertCameraAccess(CameraManager cameraManager, String cameraId, boolean hasAccess, Handler handler)118 private static void assertCameraAccess(CameraManager cameraManager, 119 String cameraId, boolean hasAccess, Handler handler) { 120 // Mock the callback used to observe camera state. 121 final CameraDevice.StateCallback callback = mock(CameraDevice.StateCallback.class); 122 123 // Open the camera 124 try { 125 cameraManager.openCamera(cameraId, callback, handler); 126 } catch (CameraAccessException e) { 127 if (hasAccess) { 128 fail("Unexpected exception" + e); 129 } else { 130 assertThat(e.getReason()).isSameAs(CameraAccessException.CAMERA_DISABLED); 131 } 132 } 133 134 // Verify access 135 final ArgumentCaptor<CameraDevice> captor = ArgumentCaptor.forClass(CameraDevice.class); 136 try { 137 if (hasAccess) { 138 // The camera should open fine as we are in the foreground 139 verify(callback, timeout(CAMERA_OPERATION_TIMEOUT_MILLIS) 140 .times(1)).onOpened(captor.capture()); 141 verifyNoMoreInteractions(callback); 142 } else { 143 // The camera should not open as we are in the background 144 verify(callback, timeout(CAMERA_OPERATION_TIMEOUT_MILLIS) 145 .times(1)).onError(captor.capture(), 146 eq(CameraDevice.StateCallback.ERROR_CAMERA_DISABLED)); 147 verifyNoMoreInteractions(callback); 148 } 149 } finally { 150 final CameraDevice cameraDevice = captor.getValue(); 151 assertThat(cameraDevice).isNotNull(); 152 cameraDevice.close(); 153 } 154 } 155 testCameraAccessBecomingInactiveUidByCamera(CameraManager cameraManager, String cameraId, Handler handler)156 private void testCameraAccessBecomingInactiveUidByCamera(CameraManager cameraManager, 157 String cameraId, Handler handler) throws Exception { 158 // Mock the callback used to observe camera state. 159 final CameraDevice.StateCallback callback = mock(CameraDevice.StateCallback.class); 160 161 // Open the camera 162 try { 163 cameraManager.openCamera(cameraId, callback, handler); 164 } catch (CameraAccessException e) { 165 fail("Unexpected exception" + e); 166 } 167 168 // Verify access 169 final ArgumentCaptor<CameraDevice> captor = ArgumentCaptor.forClass(CameraDevice.class); 170 try { 171 // The camera should open fine as we are in the foreground 172 verify(callback, timeout(CAMERA_OPERATION_TIMEOUT_MILLIS) 173 .times(1)).onOpened(captor.capture()); 174 verifyNoMoreInteractions(callback); 175 176 // Ready for a new verification 177 reset(callback); 178 179 // Now we are moving in the background 180 makeMyPackageIdle(); 181 182 // The camera should be closed if the UID became idle 183 verify(callback, timeout(CAMERA_OPERATION_TIMEOUT_MILLIS) 184 .times(1)).onError(captor.capture(), 185 eq(CameraDevice.StateCallback.ERROR_CAMERA_DISABLED)); 186 verifyNoMoreInteractions(callback); 187 } finally { 188 // Restore to active state 189 makeMyPackageActive(); 190 191 final CameraDevice cameraDevice = captor.getValue(); 192 assertThat(cameraDevice).isNotNull(); 193 cameraDevice.close(); 194 } 195 } 196 makeMyPackageActive()197 private static void makeMyPackageActive() throws IOException { 198 final String command = "cmd media.camera reset-uid-state " 199 + InstrumentationRegistry.getTargetContext().getPackageName(); 200 SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command); 201 } 202 makeMyPackageIdle()203 private static void makeMyPackageIdle() throws IOException { 204 final String command = "cmd media.camera set-uid-state " 205 + InstrumentationRegistry.getTargetContext().getPackageName() + " idle"; 206 SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command); 207 } 208 } 209