| /* |
| * Copyright 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.hardware.camera2.cts; |
| |
| import android.hardware.camera2.CameraManager; |
| import android.hardware.camera2.CameraAccessException; |
| import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase; |
| import android.hardware.camera2.cts.helpers.StaticMetadata; |
| import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel; |
| import android.util.Log; |
| import android.os.SystemClock; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.concurrent.ArrayBlockingQueue; |
| import java.util.concurrent.TimeUnit; |
| |
| import static org.mockito.Mockito.*; |
| |
| /** |
| * <p>Tests for flashlight API.</p> |
| */ |
| public class FlashlightTest extends Camera2AndroidTestCase { |
| private static final String TAG = "FlashlightTest"; |
| private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); |
| private static final int TORCH_DURATION_MS = 1000; |
| private static final int TORCH_TIMEOUT_MS = 3000; |
| private static final int NUM_REGISTERS = 10; |
| |
| private ArrayList<String> mFlashCameraIdList; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| |
| // initialize the list of cameras that have a flash unit so it won't interfere with |
| // flash tests. |
| mFlashCameraIdList = new ArrayList<String>(); |
| for (String id : mCameraIds) { |
| StaticMetadata info = |
| new StaticMetadata(mCameraManager.getCameraCharacteristics(id), |
| CheckLevel.ASSERT, /*collector*/ null); |
| if (info.hasFlash()) { |
| mFlashCameraIdList.add(id); |
| } |
| } |
| } |
| |
| public void testSetTorchModeOnOff() throws Exception { |
| if (mFlashCameraIdList.size() == 0) |
| return; |
| |
| // reset flash status for all devices with a flash unit |
| for (String id : mFlashCameraIdList) { |
| resetTorchModeStatus(id); |
| } |
| |
| // turn on and off torch mode one by one |
| for (String id : mFlashCameraIdList) { |
| CameraManager.TorchCallback torchListener = mock(CameraManager.TorchCallback.class); |
| mCameraManager.registerTorchCallback(torchListener, mHandler); // should get OFF |
| |
| mCameraManager.setTorchMode(id, true); // should get ON |
| SystemClock.sleep(TORCH_DURATION_MS); |
| mCameraManager.setTorchMode(id, false); // should get OFF |
| |
| // verify corrected numbers of callbacks |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS). |
| times(2)).onTorchModeChanged(id, false); |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS). |
| times(mFlashCameraIdList.size() + 1)). |
| onTorchModeChanged(anyString(), eq(false)); |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS). |
| times(1)).onTorchModeChanged(id, true); |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS). |
| times(1)).onTorchModeChanged(anyString(), eq(true)); |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS).never()). |
| onTorchModeUnavailable(anyString()); |
| |
| mCameraManager.unregisterTorchCallback(torchListener); |
| } |
| |
| // turn on all torch modes at once |
| if (mFlashCameraIdList.size() >= 2) { |
| CameraManager.TorchCallback torchListener = mock(CameraManager.TorchCallback.class); |
| mCameraManager.registerTorchCallback(torchListener, mHandler); // should get OFF. |
| |
| for (String id : mFlashCameraIdList) { |
| // should get ON for this ID. |
| // may get OFF for previously-on IDs. |
| mCameraManager.setTorchMode(id, true); |
| } |
| |
| SystemClock.sleep(TORCH_DURATION_MS); |
| |
| for (String id : mFlashCameraIdList) { |
| // should get OFF if not turned off previously. |
| mCameraManager.setTorchMode(id, false); |
| } |
| |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS).times(mFlashCameraIdList.size())). |
| onTorchModeChanged(anyString(), eq(true)); |
| // one more off for each id due to callback registeration. |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS). |
| times(mFlashCameraIdList.size() * 2)). |
| onTorchModeChanged(anyString(), eq(false)); |
| |
| mCameraManager.unregisterTorchCallback(torchListener); |
| } |
| } |
| |
| public void testTorchCallback() throws Exception { |
| if (mFlashCameraIdList.size() == 0) |
| return; |
| |
| // reset torch mode status |
| for (String id : mFlashCameraIdList) { |
| resetTorchModeStatus(id); |
| } |
| |
| CameraManager.TorchCallback torchListener = mock(CameraManager.TorchCallback.class); |
| |
| for (int i = 0; i < NUM_REGISTERS; i++) { |
| // should get OFF for all cameras with a flash unit. |
| mCameraManager.registerTorchCallback(torchListener, mHandler); |
| mCameraManager.unregisterTorchCallback(torchListener); |
| } |
| |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS). |
| times(NUM_REGISTERS * mFlashCameraIdList.size())). |
| onTorchModeChanged(anyString(), eq(false)); |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS).never()). |
| onTorchModeChanged(anyString(), eq(true)); |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS).never()). |
| onTorchModeUnavailable(anyString()); |
| |
| // verify passing a null handler will raise IllegalArgumentException |
| try { |
| mCameraManager.registerTorchCallback(torchListener, null); |
| mCameraManager.unregisterTorchCallback(torchListener); |
| fail("should get IllegalArgumentException due to no handler"); |
| } catch (IllegalArgumentException e) { |
| // expected exception |
| } |
| } |
| |
| public void testCameraDeviceOpenAfterTorchOn() throws Exception { |
| if (mFlashCameraIdList.size() == 0) |
| return; |
| |
| for (String id : mFlashCameraIdList) { |
| for (String idToOpen : mCameraIds) { |
| resetTorchModeStatus(id); |
| |
| CameraManager.TorchCallback torchListener = |
| mock(CameraManager.TorchCallback.class); |
| |
| // this will trigger OFF for each id in mFlashCameraIdList |
| mCameraManager.registerTorchCallback(torchListener, mHandler); |
| |
| // this will trigger ON for id |
| mCameraManager.setTorchMode(id, true); |
| SystemClock.sleep(TORCH_DURATION_MS); |
| |
| // if id == idToOpen, this will trigger UNAVAILABLE and may trigger OFF. |
| // this may trigger UNAVAILABLE for any other id in mFlashCameraIdList |
| openDevice(idToOpen); |
| |
| // if id == idToOpen, this will trigger OFF. |
| // this may trigger OFF for any other id in mFlashCameraIdList. |
| closeDevice(idToOpen); |
| |
| // this may trigger OFF for id if not received previously. |
| mCameraManager.setTorchMode(id, false); |
| |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS).times(1)). |
| onTorchModeChanged(id, true); |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS).times(1)). |
| onTorchModeChanged(anyString(), eq(true)); |
| |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS).atLeast(2)). |
| onTorchModeChanged(id, false); |
| verify(torchListener, atMost(3)).onTorchModeChanged(id, false); |
| |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS). |
| atLeast(mFlashCameraIdList.size())). |
| onTorchModeChanged(anyString(), eq(false)); |
| verify(torchListener, atMost(mFlashCameraIdList.size() * 2 + 1)). |
| onTorchModeChanged(anyString(), eq(false)); |
| |
| if (hasFlash(idToOpen)) { |
| verify(torchListener, timeout(TORCH_TIMEOUT_MS).times(1)). |
| onTorchModeUnavailable(idToOpen); |
| } |
| verify(torchListener, atMost(mFlashCameraIdList.size())). |
| onTorchModeUnavailable(anyString()); |
| |
| mCameraManager.unregisterTorchCallback(torchListener); |
| } |
| } |
| } |
| |
| public void testTorchModeExceptions() throws Exception { |
| // cameraIdsToTestTorch = all available camera ID + non-existing camera id + |
| // non-existing numeric camera id + null |
| String[] cameraIdsToTestTorch = new String[mCameraIds.length + 3]; |
| System.arraycopy(mCameraIds, 0, cameraIdsToTestTorch, 0, mCameraIds.length); |
| cameraIdsToTestTorch[mCameraIds.length] = generateNonexistingCameraId(); |
| cameraIdsToTestTorch[mCameraIds.length + 1] = generateNonexistingNumericCameraId(); |
| |
| for (String idToOpen : mCameraIds) { |
| openDevice(idToOpen); |
| try { |
| for (String id : cameraIdsToTestTorch) { |
| try { |
| mCameraManager.setTorchMode(id, true); |
| SystemClock.sleep(TORCH_DURATION_MS); |
| mCameraManager.setTorchMode(id, false); |
| if (!hasFlash(id)) { |
| fail("exception should be thrown when turning on torch mode of a " + |
| "camera without a flash"); |
| } else if (id.equals(idToOpen)) { |
| fail("exception should be thrown when turning on torch mode of an " + |
| "opened camera"); |
| } |
| } catch (CameraAccessException e) { |
| if ((hasFlash(id) && id.equals(idToOpen) && |
| e.getReason() == CameraAccessException.CAMERA_IN_USE) || |
| (hasFlash(id) && !id.equals(idToOpen) && |
| e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE)) { |
| continue; |
| } |
| fail("(" + id + ") not expecting: " + e.getMessage()); |
| } catch (IllegalArgumentException e) { |
| if (hasFlash(id)) { |
| fail("not expecting IllegalArgumentException"); |
| } |
| } |
| } |
| } finally { |
| closeDevice(idToOpen); |
| } |
| } |
| } |
| |
| private boolean hasFlash(String cameraId) { |
| return mFlashCameraIdList.contains(cameraId); |
| } |
| |
| // make sure the torch status is off. |
| private void resetTorchModeStatus(String cameraId) throws Exception { |
| TorchCallbackListener torchListener = new TorchCallbackListener(cameraId); |
| |
| mCameraManager.registerTorchCallback(torchListener, mHandler); |
| mCameraManager.setTorchMode(cameraId, true); |
| mCameraManager.setTorchMode(cameraId, false); |
| |
| torchListener.waitOnStatusChange(TorchCallbackListener.STATUS_ON); |
| torchListener.waitOnStatusChange(TorchCallbackListener.STATUS_OFF); |
| |
| mCameraManager.unregisterTorchCallback(torchListener); |
| } |
| |
| private String generateNonexistingCameraId() { |
| String nonExisting = "none_existing_camera"; |
| for (String id : mCameraIds) { |
| if (Arrays.asList(mCameraIds).contains(nonExisting)) { |
| nonExisting += id; |
| } else { |
| break; |
| } |
| } |
| return nonExisting; |
| } |
| |
| // return a non-existing and non-negative numeric camera id. |
| private String generateNonexistingNumericCameraId() { |
| int[] numericCameraIds = new int[mCameraIds.length]; |
| int size = 0; |
| |
| for (String cameraId : mCameraIds) { |
| try { |
| int value = Integer.parseInt(cameraId); |
| if (value >= 0) { |
| numericCameraIds[size++] = value; |
| } |
| } catch (Throwable e) { |
| // do nothing if camera id isn't an integer |
| } |
| } |
| |
| if (size == 0) { |
| return "0"; |
| } |
| |
| Arrays.sort(numericCameraIds, 0, size); |
| if (numericCameraIds[0] != 0) { |
| return "0"; |
| } |
| |
| for (int i = 0; i < size - 1; i++) { |
| if (numericCameraIds[i] + 1 < numericCameraIds[i + 1]) { |
| return String.valueOf(numericCameraIds[i] + 1); |
| } |
| } |
| |
| if (numericCameraIds[size - 1] != Integer.MAX_VALUE) { |
| return String.valueOf(numericCameraIds[size - 1] + 1); |
| } |
| |
| fail("cannot find a non-existing and non-negative numeric camera id"); |
| return null; |
| } |
| |
| private final class TorchCallbackListener extends CameraManager.TorchCallback { |
| private static final String TAG = "TorchCallbackListener"; |
| private static final int STATUS_WAIT_TIMEOUT_MS = 3000; |
| private static final int QUEUE_CAPACITY = 100; |
| |
| private String mCameraId; |
| private ArrayBlockingQueue<Integer> mStatusQueue = |
| new ArrayBlockingQueue<Integer>(QUEUE_CAPACITY); |
| |
| public static final int STATUS_UNAVAILABLE = 0; |
| public static final int STATUS_OFF = 1; |
| public static final int STATUS_ON = 2; |
| |
| public TorchCallbackListener(String cameraId) { |
| // only care about events for this camera id. |
| mCameraId = cameraId; |
| } |
| |
| public void waitOnStatusChange(int status) throws Exception { |
| while (true) { |
| Integer s = mStatusQueue.poll(STATUS_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS); |
| if (s == null) { |
| fail("waiting for status " + status + " timed out"); |
| } else if (s.intValue() == status) { |
| return; |
| } |
| } |
| } |
| |
| @Override |
| public void onTorchModeUnavailable(String cameraId) { |
| if (cameraId.equals(mCameraId)) { |
| Integer s = new Integer(STATUS_UNAVAILABLE); |
| try { |
| mStatusQueue.put(s); |
| } catch (Throwable e) { |
| fail(e.getMessage()); |
| } |
| } |
| } |
| |
| @Override |
| public void onTorchModeChanged(String cameraId, boolean enabled) { |
| if (cameraId.equals(mCameraId)) { |
| Integer s; |
| if (enabled) { |
| s = new Integer(STATUS_ON); |
| } else { |
| s = new Integer(STATUS_OFF); |
| } |
| try { |
| mStatusQueue.put(s); |
| } catch (Throwable e) { |
| fail(e.getMessage()); |
| } |
| } |
| } |
| } |
| } |