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