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 setUp()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 testSetTorchModeOnOff()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 testTorchCallback()127 public void testTorchCallback() throws Exception { 128 testTorchCallback(/*useExecutor*/ false); 129 testTorchCallback(/*useExecutor*/ true); 130 } 131 testTorchCallback(boolean useExecutor)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 testCameraDeviceOpenAfterTorchOn()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 testTorchModeExceptions()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 hasFlash(String cameraId)271 private boolean hasFlash(String cameraId) { 272 return mFlashCameraIdList.contains(cameraId); 273 } 274 275 // make sure the torch status is off. resetTorchModeStatus(String cameraId)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 generateNonexistingCameraId()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. generateNonexistingNumericCameraId()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 TorchCallbackListener(String cameraId)353 public TorchCallbackListener(String cameraId) { 354 // only care about events for this camera id. 355 mCameraId = cameraId; 356 } 357 waitOnStatusChange(int status)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 onTorchModeUnavailable(String cameraId)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 onTorchModeChanged(String cameraId, boolean enabled)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