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