• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.CameraCharacteristics;
21 import android.hardware.camera2.CameraAccessException;
22 import android.hardware.camera2.cts.CameraTestUtils.HandlerExecutor;
23 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
24 import android.hardware.camera2.cts.helpers.StaticMetadata;
25 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
26 import android.util.Log;
27 import android.os.SystemClock;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.concurrent.ArrayBlockingQueue;
31 import java.util.concurrent.Executor;
32 import java.util.concurrent.TimeUnit;
33 
34 import static org.mockito.Mockito.*;
35 import org.junit.runners.Parameterized;
36 import org.junit.runner.RunWith;
37 import org.junit.Test;
38 
39 import static junit.framework.Assert.*;
40 
41 /**
42  * <p>Tests for flashlight API.</p>
43  */
44 
45 @RunWith(Parameterized.class)
46 public class FlashlightTest extends Camera2AndroidTestCase {
47     private static final String TAG = "FlashlightTest";
48     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
49     private static final int TORCH_DURATION_MS = 1000;
50     private static final int TORCH_TIMEOUT_MS = 3000;
51     private static final int NUM_REGISTERS = 10;
52 
53     private ArrayList<String> mFlashCameraIdList;
54     private ArrayList<String> mNoFlashCameraIdList;
55 
56     @Override
setUp()57     public void setUp() throws Exception {
58         //Use all camera ids for system camera testing since we count the number of callbacks here
59         // and when mAdoptShellPerm == true, all camera ids will get callbacks.
60         super.setUp(/*useAll*/true);
61 
62         // initialize the list of cameras that have a flash unit so it won't interfere with
63         // flash tests.
64         mFlashCameraIdList = new ArrayList<String>();
65         mNoFlashCameraIdList = new ArrayList<String>();
66         for (String id : mCameraIdsUnderTest) {
67             StaticMetadata info =
68                     new StaticMetadata(mCameraManager.getCameraCharacteristics(id),
69                                        CheckLevel.ASSERT, /*collector*/ null);
70             if (info.hasFlash()) {
71                 mFlashCameraIdList.add(id);
72             } else  {
73                 mNoFlashCameraIdList.add(id);
74             }
75         }
76     }
77 
78     @Test
testTurnOnTorchWithStrengthLevel()79     public void testTurnOnTorchWithStrengthLevel() throws Exception {
80         if (mNoFlashCameraIdList.size() != 0) {
81             for (String id : mNoFlashCameraIdList) {
82                 CameraCharacteristics pc = mCameraManager.getCameraCharacteristics(id);
83                 assertNull(pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL));
84                 assertNull(pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL));
85             }
86         }
87 
88         if (mFlashCameraIdList.size() == 0)
89             return;
90 
91         for (String id : mFlashCameraIdList) {
92             resetTorchModeStatus(id);
93         }
94 
95         for (String id: mFlashCameraIdList) {
96             int maxLevel = 0;
97             int defaultLevel = 0;
98             int minLevel = 1;
99             CameraCharacteristics pc = mCameraManager.getCameraCharacteristics(id);
100             if (pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL) != null) {
101                 defaultLevel = pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL);
102             }
103             if (pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL) != null) {
104                 maxLevel = pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL);
105             }
106             if (maxLevel > 1) {
107                 assertTrue(minLevel <= defaultLevel);
108                 assertTrue(defaultLevel <= maxLevel);
109                 int torchStrength = 0;
110                 CameraManager.TorchCallback torchListener = mock(CameraManager.TorchCallback.class);
111                 mCameraManager.registerTorchCallback(torchListener, mHandler);
112 
113                 mCameraManager.turnOnTorchWithStrengthLevel(id, maxLevel);
114                 SystemClock.sleep(TORCH_DURATION_MS);
115                 torchStrength = mCameraManager.getTorchStrengthLevel(id);
116                 assertEquals(torchStrength, maxLevel);
117                 // Calling with same value twice to verify onTorchStrengthLevelChanged()
118                 // with maxLevel value is called only once.
119                 mCameraManager.turnOnTorchWithStrengthLevel(id, maxLevel);
120                 torchStrength = mCameraManager.getTorchStrengthLevel(id);
121                 assertEquals(torchStrength, maxLevel);
122 
123                 mCameraManager.turnOnTorchWithStrengthLevel(id, defaultLevel);
124                 torchStrength = mCameraManager.getTorchStrengthLevel(id);
125                 assertEquals(torchStrength, defaultLevel);
126 
127                 mCameraManager.turnOnTorchWithStrengthLevel(id, minLevel);
128                 torchStrength = mCameraManager.getTorchStrengthLevel(id);
129                 assertEquals(torchStrength, minLevel);
130 
131                 try {
132                     mCameraManager.turnOnTorchWithStrengthLevel(id, 0);
133                     fail("turnOnTorchWithStrengthLevel with strengthLevel = 0 must fail.");
134                 } catch (IllegalArgumentException e) {
135                     Log.v(TAG, e.getMessage());
136                 }
137 
138                 try {
139                     mCameraManager.turnOnTorchWithStrengthLevel(id, maxLevel + 1);
140                     fail("turnOnTorchWithStrengthLevel with strengthLevel" + (maxLevel + 1) + " must fail.");
141                 } catch (IllegalArgumentException e) {
142                     Log.v(TAG, e.getMessage());
143                 }
144 
145                 // Turn off the torch and verify if the strength level gets
146                 // reset to default level.
147                 mCameraManager.setTorchMode(id, false);
148                 torchStrength = mCameraManager.getTorchStrengthLevel(id);
149                 assertEquals(torchStrength, defaultLevel);
150 
151                 // verify corrected numbers of callbacks
152                 verify(torchListener, timeout(TORCH_TIMEOUT_MS).
153                         times(1)).onTorchModeChanged(id, true);
154 
155                 verify(torchListener,timeout(TORCH_TIMEOUT_MS).
156                         times(1)).onTorchStrengthLevelChanged(id, maxLevel);
157                 verify(torchListener,timeout(TORCH_TIMEOUT_MS).
158                         times(1)).onTorchStrengthLevelChanged(id, minLevel);
159                 verify(torchListener,timeout(TORCH_TIMEOUT_MS).
160                         times(1)).onTorchStrengthLevelChanged(id, defaultLevel);
161 
162                 verify(torchListener, timeout(TORCH_TIMEOUT_MS).
163                         times(2)).onTorchModeChanged(id, false);
164 
165                 mCameraManager.unregisterTorchCallback(torchListener);
166             } else {
167                 Log.i(TAG, "Torch strength level adjustment is not supported.");
168             }
169         }
170     }
171 
172 
173     @Test
testSetTorchModeOnOff()174     public void testSetTorchModeOnOff() throws Exception {
175         if (mFlashCameraIdList.size() == 0)
176             return;
177 
178         // reset flash status for all devices with a flash unit
179         for (String id : mFlashCameraIdList) {
180             resetTorchModeStatus(id);
181         }
182 
183         // turn on and off torch mode one by one
184         for (String id : mFlashCameraIdList) {
185             CameraManager.TorchCallback torchListener = mock(CameraManager.TorchCallback.class);
186             mCameraManager.registerTorchCallback(torchListener, mHandler); // should get OFF
187 
188             mCameraManager.setTorchMode(id, true); // should get ON
189             SystemClock.sleep(TORCH_DURATION_MS);
190             mCameraManager.setTorchMode(id, false); // should get OFF
191 
192             // verify corrected numbers of callbacks
193             verify(torchListener, timeout(TORCH_TIMEOUT_MS).
194                     times(2)).onTorchModeChanged(id, false);
195             verify(torchListener, timeout(TORCH_TIMEOUT_MS).
196                     times(mFlashCameraIdList.size() + 1)).
197                     onTorchModeChanged(anyString(), eq(false));
198             verify(torchListener, timeout(TORCH_TIMEOUT_MS).
199                     times(1)).onTorchModeChanged(id, true);
200             verify(torchListener, timeout(TORCH_TIMEOUT_MS).
201                     times(1)).onTorchModeChanged(anyString(), eq(true));
202             verify(torchListener, after(TORCH_TIMEOUT_MS).never()).
203                     onTorchModeUnavailable(anyString());
204 
205             mCameraManager.unregisterTorchCallback(torchListener);
206         }
207 
208         // turn on all torch modes at once
209         if (mFlashCameraIdList.size() >= 2) {
210             CameraManager.TorchCallback torchListener = mock(CameraManager.TorchCallback.class);
211             mCameraManager.registerTorchCallback(torchListener, mHandler); // should get OFF.
212 
213             for (String id : mFlashCameraIdList) {
214                 // should get ON for this ID.
215                 // may get OFF for previously-on IDs.
216                 mCameraManager.setTorchMode(id, true);
217             }
218 
219             SystemClock.sleep(TORCH_DURATION_MS);
220 
221             for (String id : mFlashCameraIdList) {
222                 // should get OFF if not turned off previously.
223                 mCameraManager.setTorchMode(id, false);
224             }
225 
226             verify(torchListener, timeout(TORCH_TIMEOUT_MS).times(mFlashCameraIdList.size())).
227                     onTorchModeChanged(anyString(), eq(true));
228             // one more off for each id due to callback registeration.
229             verify(torchListener, timeout(TORCH_TIMEOUT_MS).
230                     times(mFlashCameraIdList.size() * 2)).
231                     onTorchModeChanged(anyString(), eq(false));
232 
233             mCameraManager.unregisterTorchCallback(torchListener);
234         }
235     }
236 
237     @Test
testTorchCallback()238     public void testTorchCallback() throws Exception {
239         testTorchCallback(/*useExecutor*/ false);
240         testTorchCallback(/*useExecutor*/ true);
241     }
242 
testTorchCallback(boolean useExecutor)243     private void testTorchCallback(boolean useExecutor) throws Exception {
244         if (mFlashCameraIdList.size() == 0)
245             return;
246 
247         final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null;
248         // reset torch mode status
249         for (String id : mFlashCameraIdList) {
250             resetTorchModeStatus(id);
251         }
252 
253         CameraManager.TorchCallback torchListener = mock(CameraManager.TorchCallback.class);
254 
255         for (int i = 0; i < NUM_REGISTERS; i++) {
256             // should get OFF for all cameras with a flash unit.
257             if (useExecutor) {
258                 mCameraManager.registerTorchCallback(executor, torchListener);
259             } else {
260                 mCameraManager.registerTorchCallback(torchListener, mHandler);
261             }
262             mCameraManager.unregisterTorchCallback(torchListener);
263         }
264 
265         verify(torchListener, timeout(TORCH_TIMEOUT_MS).
266                 times(NUM_REGISTERS * mFlashCameraIdList.size())).
267                 onTorchModeChanged(anyString(), eq(false));
268         verify(torchListener, after(TORCH_TIMEOUT_MS).never()).
269                 onTorchModeChanged(anyString(), eq(true));
270         verify(torchListener, after(TORCH_TIMEOUT_MS).never()).
271                 onTorchModeUnavailable(anyString());
272 
273         // verify passing a null handler will raise IllegalArgumentException
274         try {
275             mCameraManager.registerTorchCallback(torchListener, null);
276             mCameraManager.unregisterTorchCallback(torchListener);
277             fail("should get IllegalArgumentException due to no handler");
278         } catch (IllegalArgumentException e) {
279             // expected exception
280         }
281     }
282 
283     @Test
testCameraDeviceOpenAfterTorchOn()284     public void testCameraDeviceOpenAfterTorchOn() throws Exception {
285         if (mFlashCameraIdList.size() == 0)
286             return;
287 
288         for (String id : mFlashCameraIdList) {
289             for (String idToOpen : mCameraIdsUnderTest) {
290                 resetTorchModeStatus(id);
291 
292                 CameraManager.TorchCallback torchListener =
293                         mock(CameraManager.TorchCallback.class);
294 
295                 // this will trigger OFF for each id in mFlashCameraIdList
296                 mCameraManager.registerTorchCallback(torchListener, mHandler);
297 
298                 // this will trigger ON for id
299                 mCameraManager.setTorchMode(id, true);
300                 SystemClock.sleep(TORCH_DURATION_MS);
301 
302                 // if id == idToOpen, this will trigger UNAVAILABLE and may trigger OFF.
303                 // this may trigger UNAVAILABLE for any other id in mFlashCameraIdList
304                 openDevice(idToOpen);
305 
306                 // if id == idToOpen, this will trigger OFF.
307                 // this may trigger OFF for any other id in mFlashCameraIdList.
308                 closeDevice(idToOpen);
309 
310                 // this may trigger OFF for id if not received previously.
311                 mCameraManager.setTorchMode(id, false);
312 
313                 verify(torchListener, timeout(TORCH_TIMEOUT_MS).times(1)).
314                         onTorchModeChanged(id, true);
315                 verify(torchListener, timeout(TORCH_TIMEOUT_MS).times(1)).
316                         onTorchModeChanged(anyString(), eq(true));
317 
318                 verify(torchListener, timeout(TORCH_TIMEOUT_MS).atLeast(2)).
319                         onTorchModeChanged(id, false);
320                 verify(torchListener, atMost(3)).onTorchModeChanged(id, false);
321 
322                 verify(torchListener, timeout(TORCH_TIMEOUT_MS).
323                         atLeast(mFlashCameraIdList.size())).
324                         onTorchModeChanged(anyString(), eq(false));
325                 verify(torchListener, atMost(mFlashCameraIdList.size() * 2 + 1)).
326                         onTorchModeChanged(anyString(), eq(false));
327 
328                 if (hasFlash(idToOpen)) {
329                     verify(torchListener, timeout(TORCH_TIMEOUT_MS).times(1)).
330                             onTorchModeUnavailable(idToOpen);
331                 }
332                 verify(torchListener, atMost(mFlashCameraIdList.size())).
333                             onTorchModeUnavailable(anyString());
334 
335                 mCameraManager.unregisterTorchCallback(torchListener);
336             }
337         }
338     }
339 
340     @Test
testTorchModeExceptions()341     public void testTorchModeExceptions() throws Exception {
342         // cameraIdsToTestTorch = all available camera ID + non-existing camera id +
343         //                        non-existing numeric camera id + null
344         String[] cameraIdsToTestTorch = new String[mCameraIdsUnderTest.length + 3];
345         System.arraycopy(mCameraIdsUnderTest, 0, cameraIdsToTestTorch, 0, mCameraIdsUnderTest.length);
346         cameraIdsToTestTorch[mCameraIdsUnderTest.length] = generateNonexistingCameraId();
347         cameraIdsToTestTorch[mCameraIdsUnderTest.length + 1] = generateNonexistingNumericCameraId();
348 
349         for (String idToOpen : mCameraIdsUnderTest) {
350             openDevice(idToOpen);
351             try {
352                 for (String id : cameraIdsToTestTorch) {
353                     try {
354                         mCameraManager.setTorchMode(id, true);
355                         SystemClock.sleep(TORCH_DURATION_MS);
356                         mCameraManager.setTorchMode(id, false);
357                         if (!hasFlash(id)) {
358                             fail("exception should be thrown when turning on torch mode of a " +
359                                     "camera without a flash");
360                         } else if (id.equals(idToOpen)) {
361                             fail("exception should be thrown when turning on torch mode of an " +
362                                     "opened camera");
363                         }
364                     } catch (CameraAccessException e) {
365                         int reason = e.getReason();
366                         if ((hasFlash(id) &&  id.equals(idToOpen) &&
367                                     reason == CameraAccessException.CAMERA_IN_USE) ||
368                             (hasFlash(id) && !id.equals(idToOpen) &&
369                                     reason == CameraAccessException.MAX_CAMERAS_IN_USE)) {
370                             continue;
371                         }
372                         fail("(" + id + ") not expecting: " + e.getMessage() + "reason " + reason);
373                     } catch (IllegalArgumentException e) {
374                         if (hasFlash(id)) {
375                             fail("not expecting IllegalArgumentException");
376                         }
377                     }
378                 }
379             } finally {
380                 closeDevice(idToOpen);
381             }
382         }
383     }
384 
hasFlash(String cameraId)385     private boolean hasFlash(String cameraId) {
386         return mFlashCameraIdList.contains(cameraId);
387     }
388 
389     // make sure the torch status is off.
resetTorchModeStatus(String cameraId)390     private void resetTorchModeStatus(String cameraId) throws Exception {
391         TorchCallbackListener torchListener = new TorchCallbackListener(cameraId);
392 
393         mCameraManager.registerTorchCallback(torchListener, mHandler);
394         mCameraManager.setTorchMode(cameraId, true);
395         mCameraManager.setTorchMode(cameraId, false);
396 
397         torchListener.waitOnStatusChange(TorchCallbackListener.STATUS_ON);
398         torchListener.waitOnStatusChange(TorchCallbackListener.STATUS_OFF);
399 
400         mCameraManager.unregisterTorchCallback(torchListener);
401     }
402 
generateNonexistingCameraId()403     private String generateNonexistingCameraId() {
404         String nonExisting = "none_existing_camera";
405         for (String id : mCameraIdsUnderTest) {
406             if (Arrays.asList(mCameraIdsUnderTest).contains(nonExisting)) {
407                 nonExisting += id;
408             } else {
409                 break;
410             }
411         }
412         return nonExisting;
413     }
414 
415     // return a non-existing and non-negative numeric camera id.
generateNonexistingNumericCameraId()416     private String generateNonexistingNumericCameraId() throws Exception {
417         // We don't rely on mCameraIdsUnderTest to generate a non existing camera id since
418         // mCameraIdsUnderTest doesn't give us an accurate reflection of which camera ids actually
419         // exist. It just tells us the ones we're testing right now.
420         String[] allCameraIds = mCameraManager.getCameraIdListNoLazy();
421         int[] numericCameraIds = new int[allCameraIds.length];
422         int size = 0;
423 
424         for (String cameraId : allCameraIds) {
425             try {
426                 int value = Integer.parseInt(cameraId);
427                 if (value >= 0) {
428                     numericCameraIds[size++] = value;
429                 }
430             } catch (Throwable e) {
431                 // do nothing if camera id isn't an integer
432             }
433         }
434 
435         if (size == 0) {
436             return "0";
437         }
438 
439         Arrays.sort(numericCameraIds, 0, size);
440         if (numericCameraIds[0] != 0) {
441             return "0";
442         }
443 
444         for (int i = 0; i < size - 1; i++) {
445             if (numericCameraIds[i] + 1 < numericCameraIds[i + 1]) {
446                 return String.valueOf(numericCameraIds[i] + 1);
447             }
448         }
449 
450         if (numericCameraIds[size - 1] != Integer.MAX_VALUE) {
451             return String.valueOf(numericCameraIds[size - 1] + 1);
452         }
453 
454         fail("cannot find a non-existing and non-negative numeric camera id");
455         return null;
456     }
457 
458     private final class TorchCallbackListener extends CameraManager.TorchCallback {
459         private static final String TAG = "TorchCallbackListener";
460         private static final int STATUS_WAIT_TIMEOUT_MS = 3000;
461         private static final int QUEUE_CAPACITY = 100;
462 
463         private String mCameraId;
464         private ArrayBlockingQueue<Integer> mStatusQueue =
465                 new ArrayBlockingQueue<Integer>(QUEUE_CAPACITY);
466         private ArrayBlockingQueue<Integer> mTorchStrengthQueue =
467                 new ArrayBlockingQueue<Integer>(QUEUE_CAPACITY);
468 
469         public static final int STATUS_UNAVAILABLE = 0;
470         public static final int STATUS_OFF = 1;
471         public static final int STATUS_ON = 2;
472 
TorchCallbackListener(String cameraId)473         public TorchCallbackListener(String cameraId) {
474             // only care about events for this camera id.
475             mCameraId = cameraId;
476         }
477 
waitOnStatusChange(int status)478         public void waitOnStatusChange(int status) throws Exception {
479             while (true) {
480                 Integer s = mStatusQueue.poll(STATUS_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
481                 if (s == null) {
482                     fail("waiting for status " + status + " timed out");
483                 } else if (s.intValue() == status) {
484                     return;
485                 }
486             }
487         }
488 
489         @Override
onTorchModeUnavailable(String cameraId)490         public void onTorchModeUnavailable(String cameraId) {
491             if (cameraId.equals(mCameraId)) {
492                 Integer s = new Integer(STATUS_UNAVAILABLE);
493                 try {
494                     mStatusQueue.put(s);
495                 } catch (Throwable e) {
496                     fail(e.getMessage());
497                 }
498             }
499         }
500 
501         @Override
onTorchStrengthLevelChanged(String cameraId, int newStrengthLevel)502         public void onTorchStrengthLevelChanged(String cameraId, int newStrengthLevel) {
503             if (cameraId.equals(mCameraId)) {
504                 try {
505                     mTorchStrengthQueue.put(newStrengthLevel);
506                 } catch (Throwable e) {
507                     fail(e.getMessage());
508                 }
509             }
510         }
511 
512         @Override
onTorchModeChanged(String cameraId, boolean enabled)513         public void onTorchModeChanged(String cameraId, boolean enabled) {
514             if (cameraId.equals(mCameraId)) {
515                 Integer s;
516                 if (enabled) {
517                     s = new Integer(STATUS_ON);
518                 } else {
519                     s = new Integer(STATUS_OFF);
520                 }
521                 try {
522                     mStatusQueue.put(s);
523                 } catch (Throwable e) {
524                     fail(e.getMessage());
525                 }
526             }
527         }
528     }
529 }
530