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