1 /*
2  * Copyright 2015 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 android.hardware.camera2.CameraManager;
20 import android.hardware.camera2.CameraAccessException;
21 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
22 import android.hardware.camera2.cts.helpers.StaticMetadata;
23 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
24 import android.util.Log;
25 import android.os.SystemClock;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.concurrent.ArrayBlockingQueue;
29 import java.util.concurrent.TimeUnit;
30 
31 import static org.mockito.Mockito.*;
32 
33 /**
34  * <p>Tests for flashlight API.</p>
35  */
36 public class FlashlightTest extends Camera2AndroidTestCase {
37     private static final String TAG = "FlashlightTest";
38     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
39     private static final int TORCH_DURATION_MS = 1000;
40     private static final int TORCH_TIMEOUT_MS = 3000;
41     private static final int NUM_REGISTERS = 10;
42 
43     private ArrayList<String> mFlashCameraIdList;
44 
45     @Override
setUp()46     protected void setUp() throws Exception {
47         super.setUp();
48 
49         // initialize the list of cameras that have a flash unit so it won't interfere with
50         // flash tests.
51         mFlashCameraIdList = new ArrayList<String>();
52         for (String id : mCameraIds) {
53             StaticMetadata info =
54                     new StaticMetadata(mCameraManager.getCameraCharacteristics(id),
55                                        CheckLevel.ASSERT, /*collector*/ null);
56             if (info.hasFlash()) {
57                 mFlashCameraIdList.add(id);
58             }
59         }
60     }
61 
testSetTorchModeOnOff()62     public void testSetTorchModeOnOff() throws Exception {
63         if (mFlashCameraIdList.size() == 0)
64             return;
65 
66         // reset flash status for all devices with a flash unit
67         for (String id : mFlashCameraIdList) {
68             resetTorchModeStatus(id);
69         }
70 
71         // turn on and off torch mode one by one
72         for (String id : mFlashCameraIdList) {
73             CameraManager.TorchCallback torchListener = mock(CameraManager.TorchCallback.class);
74             mCameraManager.registerTorchCallback(torchListener, mHandler); // should get OFF
75 
76             mCameraManager.setTorchMode(id, true); // should get ON
77             SystemClock.sleep(TORCH_DURATION_MS);
78             mCameraManager.setTorchMode(id, false); // should get OFF
79 
80             // verify corrected numbers of callbacks
81             verify(torchListener, timeout(TORCH_TIMEOUT_MS).
82                     times(2)).onTorchModeChanged(id, false);
83             verify(torchListener, timeout(TORCH_TIMEOUT_MS).
84                     times(mFlashCameraIdList.size() + 1)).
85                     onTorchModeChanged(anyString(), eq(false));
86             verify(torchListener, timeout(TORCH_TIMEOUT_MS).
87                     times(1)).onTorchModeChanged(id, true);
88             verify(torchListener, timeout(TORCH_TIMEOUT_MS).
89                     times(1)).onTorchModeChanged(anyString(), eq(true));
90             verify(torchListener, timeout(TORCH_TIMEOUT_MS).never()).
91                     onTorchModeUnavailable(anyString());
92 
93             mCameraManager.unregisterTorchCallback(torchListener);
94         }
95 
96         // turn on all torch modes at once
97         if (mFlashCameraIdList.size() >= 2) {
98             CameraManager.TorchCallback torchListener = mock(CameraManager.TorchCallback.class);
99             mCameraManager.registerTorchCallback(torchListener, mHandler); // should get OFF.
100 
101             for (String id : mFlashCameraIdList) {
102                 // should get ON for this ID.
103                 // may get OFF for previously-on IDs.
104                 mCameraManager.setTorchMode(id, true);
105             }
106 
107             SystemClock.sleep(TORCH_DURATION_MS);
108 
109             for (String id : mFlashCameraIdList) {
110                 // should get OFF if not turned off previously.
111                 mCameraManager.setTorchMode(id, false);
112             }
113 
114             verify(torchListener, timeout(TORCH_TIMEOUT_MS).times(mFlashCameraIdList.size())).
115                     onTorchModeChanged(anyString(), eq(true));
116             // one more off for each id due to callback registeration.
117             verify(torchListener, timeout(TORCH_TIMEOUT_MS).
118                     times(mFlashCameraIdList.size() * 2)).
119                     onTorchModeChanged(anyString(), eq(false));
120 
121             mCameraManager.unregisterTorchCallback(torchListener);
122         }
123     }
124 
testTorchCallback()125     public void testTorchCallback() throws Exception {
126         if (mFlashCameraIdList.size() == 0)
127             return;
128 
129         // reset torch mode status
130         for (String id : mFlashCameraIdList) {
131             resetTorchModeStatus(id);
132         }
133 
134         CameraManager.TorchCallback torchListener = mock(CameraManager.TorchCallback.class);
135 
136         for (int i = 0; i < NUM_REGISTERS; i++) {
137             // should get OFF for all cameras with a flash unit.
138             mCameraManager.registerTorchCallback(torchListener, mHandler);
139             mCameraManager.unregisterTorchCallback(torchListener);
140         }
141 
142         verify(torchListener, timeout(TORCH_TIMEOUT_MS).
143                 times(NUM_REGISTERS * mFlashCameraIdList.size())).
144                 onTorchModeChanged(anyString(), eq(false));
145         verify(torchListener, timeout(TORCH_TIMEOUT_MS).never()).
146                 onTorchModeChanged(anyString(), eq(true));
147         verify(torchListener, timeout(TORCH_TIMEOUT_MS).never()).
148                 onTorchModeUnavailable(anyString());
149 
150         // verify passing a null handler will raise IllegalArgumentException
151         try {
152             mCameraManager.registerTorchCallback(torchListener, null);
153             mCameraManager.unregisterTorchCallback(torchListener);
154             fail("should get IllegalArgumentException due to no handler");
155         } catch (IllegalArgumentException e) {
156             // expected exception
157         }
158     }
159 
testCameraDeviceOpenAfterTorchOn()160     public void testCameraDeviceOpenAfterTorchOn() throws Exception {
161         if (mFlashCameraIdList.size() == 0)
162             return;
163 
164         for (String id : mFlashCameraIdList) {
165             for (String idToOpen : mCameraIds) {
166                 resetTorchModeStatus(id);
167 
168                 CameraManager.TorchCallback torchListener =
169                         mock(CameraManager.TorchCallback.class);
170 
171                 // this will trigger OFF for each id in mFlashCameraIdList
172                 mCameraManager.registerTorchCallback(torchListener, mHandler);
173 
174                 // this will trigger ON for id
175                 mCameraManager.setTorchMode(id, true);
176                 SystemClock.sleep(TORCH_DURATION_MS);
177 
178                 // if id == idToOpen, this will trigger UNAVAILABLE and may trigger OFF.
179                 // this may trigger UNAVAILABLE for any other id in mFlashCameraIdList
180                 openDevice(idToOpen);
181 
182                 // if id == idToOpen, this will trigger OFF.
183                 // this may trigger OFF for any other id in mFlashCameraIdList.
184                 closeDevice(idToOpen);
185 
186                 // this may trigger OFF for id if not received previously.
187                 mCameraManager.setTorchMode(id, false);
188 
189                 verify(torchListener, timeout(TORCH_TIMEOUT_MS).times(1)).
190                         onTorchModeChanged(id, true);
191                 verify(torchListener, timeout(TORCH_TIMEOUT_MS).times(1)).
192                         onTorchModeChanged(anyString(), eq(true));
193 
194                 verify(torchListener, timeout(TORCH_TIMEOUT_MS).atLeast(2)).
195                         onTorchModeChanged(id, false);
196                 verify(torchListener, atMost(3)).onTorchModeChanged(id, false);
197 
198                 verify(torchListener, timeout(TORCH_TIMEOUT_MS).
199                         atLeast(mFlashCameraIdList.size())).
200                         onTorchModeChanged(anyString(), eq(false));
201                 verify(torchListener, atMost(mFlashCameraIdList.size() * 2 + 1)).
202                         onTorchModeChanged(anyString(), eq(false));
203 
204                 if (hasFlash(idToOpen)) {
205                     verify(torchListener, timeout(TORCH_TIMEOUT_MS).times(1)).
206                             onTorchModeUnavailable(idToOpen);
207                 }
208                 verify(torchListener, atMost(mFlashCameraIdList.size())).
209                             onTorchModeUnavailable(anyString());
210 
211                 mCameraManager.unregisterTorchCallback(torchListener);
212             }
213         }
214     }
215 
testTorchModeExceptions()216     public void testTorchModeExceptions() throws Exception {
217         // cameraIdsToTestTorch = all available camera ID + non-existing camera id +
218         //                        non-existing numeric camera id + null
219         String[] cameraIdsToTestTorch = new String[mCameraIds.length + 3];
220         System.arraycopy(mCameraIds, 0, cameraIdsToTestTorch, 0, mCameraIds.length);
221         cameraIdsToTestTorch[mCameraIds.length] = generateNonexistingCameraId();
222         cameraIdsToTestTorch[mCameraIds.length + 1] = generateNonexistingNumericCameraId();
223 
224         for (String idToOpen : mCameraIds) {
225             openDevice(idToOpen);
226             try {
227                 for (String id : cameraIdsToTestTorch) {
228                     try {
229                         mCameraManager.setTorchMode(id, true);
230                         SystemClock.sleep(TORCH_DURATION_MS);
231                         mCameraManager.setTorchMode(id, false);
232                         if (!hasFlash(id)) {
233                             fail("exception should be thrown when turning on torch mode of a " +
234                                     "camera without a flash");
235                         } else if (id.equals(idToOpen)) {
236                             fail("exception should be thrown when turning on torch mode of an " +
237                                     "opened camera");
238                         }
239                     } catch (CameraAccessException e) {
240                         if ((hasFlash(id) &&  id.equals(idToOpen) &&
241                                     e.getReason() == CameraAccessException.CAMERA_IN_USE) ||
242                             (hasFlash(id) && !id.equals(idToOpen) &&
243                                     e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE)) {
244                             continue;
245                         }
246                         fail("(" + id + ") not expecting: " + e.getMessage());
247                     } catch (IllegalArgumentException e) {
248                         if (hasFlash(id)) {
249                             fail("not expecting IllegalArgumentException");
250                         }
251                     }
252                 }
253             } finally {
254                 closeDevice(idToOpen);
255             }
256         }
257     }
258 
hasFlash(String cameraId)259     private boolean hasFlash(String cameraId) {
260         return mFlashCameraIdList.contains(cameraId);
261     }
262 
263     // make sure the torch status is off.
resetTorchModeStatus(String cameraId)264     private void resetTorchModeStatus(String cameraId) throws Exception {
265         TorchCallbackListener torchListener = new TorchCallbackListener(cameraId);
266 
267         mCameraManager.registerTorchCallback(torchListener, mHandler);
268         mCameraManager.setTorchMode(cameraId, true);
269         mCameraManager.setTorchMode(cameraId, false);
270 
271         torchListener.waitOnStatusChange(TorchCallbackListener.STATUS_ON);
272         torchListener.waitOnStatusChange(TorchCallbackListener.STATUS_OFF);
273 
274         mCameraManager.unregisterTorchCallback(torchListener);
275     }
276 
generateNonexistingCameraId()277     private String generateNonexistingCameraId() {
278         String nonExisting = "none_existing_camera";
279         for (String id : mCameraIds) {
280             if (Arrays.asList(mCameraIds).contains(nonExisting)) {
281                 nonExisting += id;
282             } else {
283                 break;
284             }
285         }
286         return nonExisting;
287     }
288 
289     // return a non-existing and non-negative numeric camera id.
generateNonexistingNumericCameraId()290     private String generateNonexistingNumericCameraId() {
291         int[] numericCameraIds = new int[mCameraIds.length];
292         int size = 0;
293 
294         for (String cameraId : mCameraIds) {
295             try {
296                 int value = Integer.parseInt(cameraId);
297                 if (value >= 0) {
298                     numericCameraIds[size++] = value;
299                 }
300             } catch (Throwable e) {
301                 // do nothing if camera id isn't an integer
302             }
303         }
304 
305         if (size == 0) {
306             return "0";
307         }
308 
309         Arrays.sort(numericCameraIds, 0, size);
310         if (numericCameraIds[0] != 0) {
311             return "0";
312         }
313 
314         for (int i = 0; i < size - 1; i++) {
315             if (numericCameraIds[i] + 1 < numericCameraIds[i + 1]) {
316                 return String.valueOf(numericCameraIds[i] + 1);
317             }
318         }
319 
320         if (numericCameraIds[size - 1] != Integer.MAX_VALUE) {
321             return String.valueOf(numericCameraIds[size - 1] + 1);
322         }
323 
324         fail("cannot find a non-existing and non-negative numeric camera id");
325         return null;
326     }
327 
328     private final class TorchCallbackListener extends CameraManager.TorchCallback {
329         private static final String TAG = "TorchCallbackListener";
330         private static final int STATUS_WAIT_TIMEOUT_MS = 3000;
331         private static final int QUEUE_CAPACITY = 100;
332 
333         private String mCameraId;
334         private ArrayBlockingQueue<Integer> mStatusQueue =
335                 new ArrayBlockingQueue<Integer>(QUEUE_CAPACITY);
336 
337         public static final int STATUS_UNAVAILABLE = 0;
338         public static final int STATUS_OFF = 1;
339         public static final int STATUS_ON = 2;
340 
TorchCallbackListener(String cameraId)341         public TorchCallbackListener(String cameraId) {
342             // only care about events for this camera id.
343             mCameraId = cameraId;
344         }
345 
waitOnStatusChange(int status)346         public void waitOnStatusChange(int status) throws Exception {
347             while (true) {
348                 Integer s = mStatusQueue.poll(STATUS_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
349                 if (s == null) {
350                     fail("waiting for status " + status + " timed out");
351                 } else if (s.intValue() == status) {
352                     return;
353                 }
354             }
355         }
356 
357         @Override
onTorchModeUnavailable(String cameraId)358         public void onTorchModeUnavailable(String cameraId) {
359             if (cameraId.equals(mCameraId)) {
360                 Integer s = new Integer(STATUS_UNAVAILABLE);
361                 try {
362                     mStatusQueue.put(s);
363                 } catch (Throwable e) {
364                     fail(e.getMessage());
365                 }
366             }
367         }
368 
369         @Override
onTorchModeChanged(String cameraId, boolean enabled)370         public void onTorchModeChanged(String cameraId, boolean enabled) {
371             if (cameraId.equals(mCameraId)) {
372                 Integer s;
373                 if (enabled) {
374                     s = new Integer(STATUS_ON);
375                 } else {
376                     s = new Integer(STATUS_OFF);
377                 }
378                 try {
379                     mStatusQueue.put(s);
380                 } catch (Throwable e) {
381                     fail(e.getMessage());
382                 }
383             }
384         }
385     }
386 }
387