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