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