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