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