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