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