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