blob: bbb5643aa4b924c46ab239ccf4857cee41d06dd3 [file] [log] [blame]
/*
* Copyright (C) 2008 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.cts;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.Area;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.ErrorCallback;
import android.hardware.Camera.Face;
import android.hardware.Camera.FaceDetectionListener;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.ShutterCallback;
import android.hardware.Camera.Size;
import android.media.CamcorderProfile;
import android.media.ExifInterface;
import android.media.MediaRecorder;
import android.os.ConditionVariable;
import android.os.Environment;
import android.os.Looper;
import android.test.ActivityInstrumentationTestCase2;
import android.test.MoreAsserts;
import android.test.UiThreadTest;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.view.SurfaceHolder;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
/**
* This test case must run with hardware. It can't be tested in emulator
*/
@LargeTest
public class CameraTest extends ActivityInstrumentationTestCase2<CameraStubActivity> {
private static String TAG = "CameraTest";
private static final String PACKAGE = "com.android.cts.stub";
private static final boolean LOGV = false;
private final String JPEG_PATH = Environment.getExternalStorageDirectory().getPath() +
"/test.jpg";
private byte[] mJpegData;
private static final int PREVIEW_CALLBACK_NOT_RECEIVED = 0;
private static final int PREVIEW_CALLBACK_RECEIVED = 1;
private static final int PREVIEW_CALLBACK_DATA_NULL = 2;
private static final int PREVIEW_CALLBACK_INVALID_FRAME_SIZE = 3;
private int mPreviewCallbackResult = PREVIEW_CALLBACK_NOT_RECEIVED;
private boolean mShutterCallbackResult = false;
private boolean mRawPictureCallbackResult = false;
private boolean mJpegPictureCallbackResult = false;
private static final int NO_ERROR = -1;
private int mCameraErrorCode = NO_ERROR;
private boolean mAutoFocusSucceeded = false;
private static final int WAIT_FOR_COMMAND_TO_COMPLETE = 5000; // Milliseconds.
private static final int WAIT_FOR_FOCUS_TO_COMPLETE = 5000;
private static final int WAIT_FOR_SNAPSHOT_TO_COMPLETE = 5000;
private static final int FOCUS_AREA = 0;
private static final int METERING_AREA = 1;
private static final int AUTOEXPOSURE_LOCK = 0;
private static final int AUTOWHITEBALANCE_LOCK = 1;
private PreviewCallback mPreviewCallback = new PreviewCallback();
private TestShutterCallback mShutterCallback = new TestShutterCallback();
private RawPictureCallback mRawPictureCallback = new RawPictureCallback();
private JpegPictureCallback mJpegPictureCallback = new JpegPictureCallback();
private TestErrorCallback mErrorCallback = new TestErrorCallback();
private AutoFocusCallback mAutoFocusCallback = new AutoFocusCallback();
private AutoFocusMoveCallback mAutoFocusMoveCallback = new AutoFocusMoveCallback();
private Looper mLooper = null;
private final ConditionVariable mPreviewDone = new ConditionVariable();
private final ConditionVariable mFocusDone = new ConditionVariable();
private final ConditionVariable mSnapshotDone = new ConditionVariable();
Camera mCamera;
public CameraTest() {
super(PACKAGE, CameraStubActivity.class);
if (LOGV) Log.v(TAG, "Camera Constructor");
}
@Override
protected void setUp() throws Exception {
super.setUp();
// to start CameraStubActivity.
getActivity();
}
@Override
protected void tearDown() throws Exception {
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
super.tearDown();
}
/*
* Initializes the message looper so that the Camera object can
* receive the callback messages.
*/
private void initializeMessageLooper(final int cameraId) throws IOException {
final ConditionVariable startDone = new ConditionVariable();
new Thread() {
@Override
public void run() {
Log.v(TAG, "start loopRun");
// Set up a looper to be used by camera.
Looper.prepare();
// Save the looper so that we can terminate this thread
// after we are done with it.
mLooper = Looper.myLooper();
try {
mCamera = Camera.open(cameraId);
mCamera.setErrorCallback(mErrorCallback);
} catch (RuntimeException e) {
Log.e(TAG, "Fail to open camera." + e);
}
Log.v(TAG, "camera is opened");
startDone.open();
Looper.loop(); // Blocks forever until Looper.quit() is called.
if (LOGV) Log.v(TAG, "initializeMessageLooper: quit.");
}
}.start();
Log.v(TAG, "start waiting for looper");
if (!startDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
Log.v(TAG, "initializeMessageLooper: start timeout");
fail("initializeMessageLooper: start timeout");
}
assertNotNull("Fail to open camera.", mCamera);
mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
}
/*
* Terminates the message looper thread.
*/
private void terminateMessageLooper() throws Exception {
mLooper.quit();
// Looper.quit() is asynchronous. The looper may still has some
// preview callbacks in the queue after quit is called. The preview
// callback still uses the camera object (setHasPreviewCallback).
// After camera is released, RuntimeException will be thrown from
// the method. So we need to join the looper thread here.
mLooper.getThread().join();
mCamera.release();
mCamera = null;
assertEquals("Got camera error callback.", NO_ERROR, mCameraErrorCode);
}
// Align 'x' to 'to', which should be a power of 2
private static int align(int x, int to) {
return (x + (to-1)) & ~(to - 1);
}
private static int calculateBufferSize(int width, int height,
int format, int bpp) {
if (LOGV) {
Log.v(TAG, "calculateBufferSize: w=" + width + ",h=" + height
+ ",f=" + format + ",bpp=" + bpp);
}
if (format == ImageFormat.YV12) {
/*
http://developer.android.com/reference/android/graphics/ImageFormat.html#YV12
*/
int stride = align(width, 16);
int y_size = stride * height;
int c_stride = align(stride/2, 16);
int c_size = c_stride * height/2;
int size = y_size + c_size * 2;
if (LOGV) {
Log.v(TAG, "calculateBufferSize: YV12 size= " + size);
}
return size;
}
else {
return width * height * bpp / 8;
}
}
//Implement the previewCallback
private final class PreviewCallback
implements android.hardware.Camera.PreviewCallback {
public void onPreviewFrame(byte [] data, Camera camera) {
if (data == null) {
mPreviewCallbackResult = PREVIEW_CALLBACK_DATA_NULL;
mPreviewDone.open();
return;
}
Size size = camera.getParameters().getPreviewSize();
int format = camera.getParameters().getPreviewFormat();
int bitsPerPixel = ImageFormat.getBitsPerPixel(format);
if (calculateBufferSize(size.width, size.height,
format, bitsPerPixel) != data.length) {
Log.e(TAG, "Invalid frame size " + data.length + ". width=" + size.width
+ ". height=" + size.height + ". bitsPerPixel=" + bitsPerPixel);
mPreviewCallbackResult = PREVIEW_CALLBACK_INVALID_FRAME_SIZE;
mPreviewDone.open();
return;
}
mPreviewCallbackResult = PREVIEW_CALLBACK_RECEIVED;
mCamera.stopPreview();
if (LOGV) Log.v(TAG, "notify the preview callback");
mPreviewDone.open();
if (LOGV) Log.v(TAG, "Preview callback stop");
}
}
//Implement the shutterCallback
private final class TestShutterCallback implements ShutterCallback {
public void onShutter() {
mShutterCallbackResult = true;
if (LOGV) Log.v(TAG, "onShutter called");
}
}
//Implement the RawPictureCallback
private final class RawPictureCallback implements PictureCallback {
public void onPictureTaken(byte [] rawData, Camera camera) {
mRawPictureCallbackResult = true;
if (LOGV) Log.v(TAG, "RawPictureCallback callback");
}
}
// Implement the JpegPictureCallback
private final class JpegPictureCallback implements PictureCallback {
public void onPictureTaken(byte[] rawData, Camera camera) {
try {
mJpegData = rawData;
if (rawData != null) {
// try to store the picture on the SD card
File rawoutput = new File(JPEG_PATH);
FileOutputStream outStream = new FileOutputStream(rawoutput);
outStream.write(rawData);
outStream.close();
mJpegPictureCallbackResult = true;
if (LOGV) {
Log.v(TAG, "JpegPictureCallback rawDataLength = " + rawData.length);
}
} else {
mJpegPictureCallbackResult = false;
}
mSnapshotDone.open();
if (LOGV) Log.v(TAG, "Jpeg Picture callback");
} catch (IOException e) {
// no need to fail here; callback worked fine
Log.w(TAG, "Error writing picture to sd card.");
}
}
}
// Implement the ErrorCallback
private final class TestErrorCallback implements ErrorCallback {
public void onError(int error, Camera camera) {
Log.e(TAG, "Got camera error=" + error);
mCameraErrorCode = error;
}
}
private final class AutoFocusCallback
implements android.hardware.Camera.AutoFocusCallback {
public void onAutoFocus(boolean success, Camera camera) {
mAutoFocusSucceeded = success;
Log.v(TAG, "AutoFocusCallback success=" + success);
mFocusDone.open();
}
}
private final class AutoFocusMoveCallback
implements android.hardware.Camera.AutoFocusMoveCallback {
@Override
public void onAutoFocusMoving(boolean start, Camera camera) {
}
}
private void waitForPreviewDone() {
if (LOGV) Log.v(TAG, "Wait for preview callback");
if (!mPreviewDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
// timeout could be expected or unexpected. The caller will decide.
Log.v(TAG, "waitForPreviewDone: timeout");
}
mPreviewDone.close();
}
private boolean waitForFocusDone() {
boolean result = mFocusDone.block(WAIT_FOR_FOCUS_TO_COMPLETE);
if (!result) {
// timeout could be expected or unexpected. The caller will decide.
Log.v(TAG, "waitForFocusDone: timeout");
}
mFocusDone.close();
return result;
}
private void waitForSnapshotDone() {
if (!mSnapshotDone.block(WAIT_FOR_SNAPSHOT_TO_COMPLETE)) {
// timeout could be expected or unexpected. The caller will decide.
Log.v(TAG, "waitForSnapshotDone: timeout");
}
mSnapshotDone.close();
}
private void checkPreviewCallback() throws Exception {
if (LOGV) Log.v(TAG, "check preview callback");
mCamera.startPreview();
waitForPreviewDone();
mCamera.setPreviewCallback(null);
}
/*
* Test case 1: Take a picture and verify all the callback
* functions are called properly.
*/
@UiThreadTest
public void testTakePicture() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
initializeMessageLooper(id);
mCamera.startPreview();
subtestTakePictureByCamera(false, 0, 0);
terminateMessageLooper();
}
}
private void subtestTakePictureByCamera(boolean isVideoSnapshot,
int videoWidth, int videoHeight) throws Exception {
int videoSnapshotMinArea =
videoWidth * videoHeight; // Temporary until new API definitions
Size pictureSize = mCamera.getParameters().getPictureSize();
mCamera.autoFocus(mAutoFocusCallback);
assertTrue(waitForFocusDone());
mJpegData = null;
mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
waitForSnapshotDone();
assertTrue("Shutter callback not received", mShutterCallbackResult);
assertTrue("Raw picture callback not received", mRawPictureCallbackResult);
assertTrue("Jpeg picture callback not recieved", mJpegPictureCallbackResult);
assertNotNull(mJpegData);
BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
bmpOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(mJpegData, 0, mJpegData.length, bmpOptions);
if (!isVideoSnapshot) {
assertEquals(pictureSize.width, bmpOptions.outWidth);
assertEquals(pictureSize.height, bmpOptions.outHeight);
} else {
int realArea = bmpOptions.outWidth * bmpOptions.outHeight;
if (LOGV) Log.v(TAG, "Video snapshot is " +
bmpOptions.outWidth + " x " + bmpOptions.outHeight +
", video size is " + videoWidth + " x " + videoHeight);
assertTrue ("Video snapshot too small! Expected at least " +
videoWidth + " x " + videoHeight + " (" +
videoSnapshotMinArea/1000000. + " MP)",
realArea >= videoSnapshotMinArea);
}
}
@UiThreadTest
public void testPreviewCallback() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testPreviewCallbackByCamera(id);
}
}
private void testPreviewCallbackByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
mCamera.setPreviewCallback(mPreviewCallback);
checkPreviewCallback();
terminateMessageLooper();
assertEquals(PREVIEW_CALLBACK_RECEIVED, mPreviewCallbackResult);
mPreviewCallbackResult = PREVIEW_CALLBACK_NOT_RECEIVED;
initializeMessageLooper(cameraId);
checkPreviewCallback();
terminateMessageLooper();
assertEquals(PREVIEW_CALLBACK_NOT_RECEIVED, mPreviewCallbackResult);
// Test all preview sizes.
initializeMessageLooper(cameraId);
Parameters parameters = mCamera.getParameters();
for (Size size: parameters.getSupportedPreviewSizes()) {
mPreviewCallbackResult = PREVIEW_CALLBACK_NOT_RECEIVED;
mCamera.setPreviewCallback(mPreviewCallback);
parameters.setPreviewSize(size.width, size.height);
mCamera.setParameters(parameters);
assertEquals(size, mCamera.getParameters().getPreviewSize());
checkPreviewCallback();
assertEquals(PREVIEW_CALLBACK_RECEIVED, mPreviewCallbackResult);
try {
// Wait for a while to throw away the remaining preview frames.
Thread.sleep(1000);
} catch(Exception e) {
// ignore
}
mPreviewDone.close();
}
terminateMessageLooper();
}
@UiThreadTest
public void testSetOneShotPreviewCallback() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testSetOneShotPreviewCallbackByCamera(id);
}
}
private void testSetOneShotPreviewCallbackByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
mCamera.setOneShotPreviewCallback(mPreviewCallback);
checkPreviewCallback();
terminateMessageLooper();
assertEquals(PREVIEW_CALLBACK_RECEIVED, mPreviewCallbackResult);
mPreviewCallbackResult = PREVIEW_CALLBACK_NOT_RECEIVED;
initializeMessageLooper(cameraId);
checkPreviewCallback();
terminateMessageLooper();
assertEquals(PREVIEW_CALLBACK_NOT_RECEIVED, mPreviewCallbackResult);
}
@UiThreadTest
public void testSetPreviewDisplay() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testSetPreviewDisplayByCamera(id);
}
}
private void testSetPreviewDisplayByCamera(int cameraId) throws Exception {
SurfaceHolder holder = getActivity().getSurfaceView().getHolder();
initializeMessageLooper(cameraId);
// Check the order: startPreview->setPreviewDisplay.
mCamera.setOneShotPreviewCallback(mPreviewCallback);
mCamera.startPreview();
mCamera.setPreviewDisplay(holder);
waitForPreviewDone();
terminateMessageLooper();
assertEquals(PREVIEW_CALLBACK_RECEIVED, mPreviewCallbackResult);
// Check the order: setPreviewDisplay->startPreview.
initializeMessageLooper(cameraId);
mPreviewCallbackResult = PREVIEW_CALLBACK_NOT_RECEIVED;
mCamera.setOneShotPreviewCallback(mPreviewCallback);
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
waitForPreviewDone();
mCamera.stopPreview();
assertEquals(PREVIEW_CALLBACK_RECEIVED, mPreviewCallbackResult);
// Check the order: setting preview display to null->startPreview->
// setPreviewDisplay.
mPreviewCallbackResult = PREVIEW_CALLBACK_NOT_RECEIVED;
mCamera.setOneShotPreviewCallback(mPreviewCallback);
mCamera.setPreviewDisplay(null);
mCamera.startPreview();
mCamera.setPreviewDisplay(holder);
waitForPreviewDone();
terminateMessageLooper();
assertEquals(PREVIEW_CALLBACK_RECEIVED, mPreviewCallbackResult);
}
@UiThreadTest
public void testDisplayOrientation() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testDisplayOrientationByCamera(id);
}
}
private void testDisplayOrientationByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
// Check valid arguments.
mCamera.setDisplayOrientation(0);
mCamera.setDisplayOrientation(90);
mCamera.setDisplayOrientation(180);
mCamera.setDisplayOrientation(270);
// Check invalid arguments.
try {
mCamera.setDisplayOrientation(45);
fail("Should throw exception for invalid arguments");
} catch (RuntimeException ex) {
// expected
}
// Start preview.
mCamera.startPreview();
// Check setting orientation during preview is allowed.
mCamera.setDisplayOrientation(90);
mCamera.setDisplayOrientation(180);
mCamera.setDisplayOrientation(270);
mCamera.setDisplayOrientation(00);
terminateMessageLooper();
}
@UiThreadTest
public void testParameters() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testParametersByCamera(id);
}
}
private void testParametersByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
// we can get parameters just by getxxx method due to the private constructor
Parameters pSet = mCamera.getParameters();
assertParameters(pSet);
terminateMessageLooper();
}
// Also test Camera.Parameters
private void assertParameters(Parameters parameters) {
// Parameters constants
final int PICTURE_FORMAT = ImageFormat.JPEG;
final int PREVIEW_FORMAT = ImageFormat.NV21;
// Before setting Parameters
final int origPictureFormat = parameters.getPictureFormat();
final int origPictureWidth = parameters.getPictureSize().width;
final int origPictureHeight = parameters.getPictureSize().height;
final int origPreviewFormat = parameters.getPreviewFormat();
final int origPreviewWidth = parameters.getPreviewSize().width;
final int origPreviewHeight = parameters.getPreviewSize().height;
final int origPreviewFrameRate = parameters.getPreviewFrameRate();
assertTrue(origPictureWidth > 0);
assertTrue(origPictureHeight > 0);
assertTrue(origPreviewWidth > 0);
assertTrue(origPreviewHeight > 0);
assertTrue(origPreviewFrameRate > 0);
// The default preview format must be yuv420 (NV21).
assertEquals(ImageFormat.NV21, origPreviewFormat);
// The default picture format must be Jpeg.
assertEquals(ImageFormat.JPEG, origPictureFormat);
// If camera supports flash, the default flash mode must be off.
String flashMode = parameters.getFlashMode();
assertTrue(flashMode == null || flashMode.equals(parameters.FLASH_MODE_OFF));
String wb = parameters.getWhiteBalance();
assertTrue(wb == null || wb.equals(parameters.WHITE_BALANCE_AUTO));
String effect = parameters.getColorEffect();
assertTrue(effect == null || effect.equals(parameters.EFFECT_NONE));
// Some parameters must be supported.
List<Size> previewSizes = parameters.getSupportedPreviewSizes();
List<Size> pictureSizes = parameters.getSupportedPictureSizes();
List<Integer> previewFormats = parameters.getSupportedPreviewFormats();
List<Integer> pictureFormats = parameters.getSupportedPictureFormats();
List<Integer> frameRates = parameters.getSupportedPreviewFrameRates();
List<String> focusModes = parameters.getSupportedFocusModes();
String focusMode = parameters.getFocusMode();
float focalLength = parameters.getFocalLength();
float horizontalViewAngle = parameters.getHorizontalViewAngle();
float verticalViewAngle = parameters.getVerticalViewAngle();
int jpegQuality = parameters.getJpegQuality();
int jpegThumnailQuality = parameters.getJpegThumbnailQuality();
assertTrue(previewSizes != null && previewSizes.size() != 0);
assertTrue(pictureSizes != null && pictureSizes.size() != 0);
assertTrue(previewFormats != null && previewFormats.size() >= 2);
assertTrue(previewFormats.contains(ImageFormat.NV21));
assertTrue(previewFormats.contains(ImageFormat.YV12));
assertTrue(pictureFormats != null && pictureFormats.size() != 0);
assertTrue(frameRates != null && frameRates.size() != 0);
assertTrue(focusModes != null && focusModes.size() != 0);
assertNotNull(focusMode);
// The default focus mode must be auto if it exists.
if (focusModes.contains(Parameters.FOCUS_MODE_AUTO)) {
assertEquals(Parameters.FOCUS_MODE_AUTO, focusMode);
}
assertTrue(focalLength > 0);
assertTrue(horizontalViewAngle > 0 && horizontalViewAngle <= 360);
assertTrue(verticalViewAngle > 0 && verticalViewAngle <= 360);
Size previewSize = previewSizes.get(0);
Size pictureSize = pictureSizes.get(0);
assertTrue(jpegQuality >= 1 && jpegQuality <= 100);
assertTrue(jpegThumnailQuality >= 1 && jpegThumnailQuality <= 100);
// If a parameter is supported, both getXXX and getSupportedXXX have to
// be non null.
if (parameters.getWhiteBalance() != null) {
assertNotNull(parameters.getSupportedWhiteBalance());
}
if (parameters.getSupportedWhiteBalance() != null) {
assertNotNull(parameters.getWhiteBalance());
}
if (parameters.getColorEffect() != null) {
assertNotNull(parameters.getSupportedColorEffects());
}
if (parameters.getSupportedColorEffects() != null) {
assertNotNull(parameters.getColorEffect());
}
if (parameters.getAntibanding() != null) {
assertNotNull(parameters.getSupportedAntibanding());
}
if (parameters.getSupportedAntibanding() != null) {
assertNotNull(parameters.getAntibanding());
}
if (parameters.getSceneMode() != null) {
assertNotNull(parameters.getSupportedSceneModes());
}
if (parameters.getSupportedSceneModes() != null) {
assertNotNull(parameters.getSceneMode());
}
if (parameters.getFlashMode() != null) {
assertNotNull(parameters.getSupportedFlashModes());
}
if (parameters.getSupportedFlashModes() != null) {
assertNotNull(parameters.getFlashMode());
}
// Check if the sizes value contain invalid characters.
assertNoLetters(parameters.get("preview-size-values"), "preview-size-values");
assertNoLetters(parameters.get("picture-size-values"), "picture-size-values");
assertNoLetters(parameters.get("jpeg-thumbnail-size-values"),
"jpeg-thumbnail-size-values");
// Set the parameters.
parameters.setPictureFormat(PICTURE_FORMAT);
assertEquals(PICTURE_FORMAT, parameters.getPictureFormat());
parameters.setPictureSize(pictureSize.width, pictureSize.height);
assertEquals(pictureSize.width, parameters.getPictureSize().width);
assertEquals(pictureSize.height, parameters.getPictureSize().height);
parameters.setPreviewFormat(PREVIEW_FORMAT);
assertEquals(PREVIEW_FORMAT, parameters.getPreviewFormat());
parameters.setPreviewFrameRate(frameRates.get(0));
assertEquals(frameRates.get(0).intValue(), parameters.getPreviewFrameRate());
parameters.setPreviewSize(previewSize.width, previewSize.height);
assertEquals(previewSize.width, parameters.getPreviewSize().width);
assertEquals(previewSize.height, parameters.getPreviewSize().height);
mCamera.setParameters(parameters);
Parameters paramActual = mCamera.getParameters();
assertTrue(isValidPixelFormat(paramActual.getPictureFormat()));
assertEquals(pictureSize.width, paramActual.getPictureSize().width);
assertEquals(pictureSize.height, paramActual.getPictureSize().height);
assertTrue(isValidPixelFormat(paramActual.getPreviewFormat()));
assertEquals(previewSize.width, paramActual.getPreviewSize().width);
assertEquals(previewSize.height, paramActual.getPreviewSize().height);
assertTrue(paramActual.getPreviewFrameRate() > 0);
checkExposureCompensation(parameters);
checkPreferredPreviewSizeForVideo(parameters);
}
private void checkPreferredPreviewSizeForVideo(Parameters parameters) {
List<Size> videoSizes = parameters.getSupportedVideoSizes();
Size preferredPreviewSize = parameters.getPreferredPreviewSizeForVideo();
// If getSupportedVideoSizes() returns null,
// getPreferredPreviewSizeForVideo() will return null;
// otherwise, if getSupportedVideoSizes() does not return null,
// getPreferredPreviewSizeForVideo() will not return null.
if (videoSizes == null) {
assertNull(preferredPreviewSize);
} else {
assertNotNull(preferredPreviewSize);
}
// If getPreferredPreviewSizeForVideo() returns null,
// getSupportedVideoSizes() will return null;
// otherwise, if getPreferredPreviewSizeForVideo() does not return null,
// getSupportedVideoSizes() will not return null.
if (preferredPreviewSize == null) {
assertNull(videoSizes);
} else {
assertNotNull(videoSizes);
}
if (videoSizes != null) { // implies: preferredPreviewSize != null
// If getSupportedVideoSizes() does not return null,
// the returned list will contain at least one size.
assertTrue(videoSizes.size() > 0);
// In addition, getPreferredPreviewSizeForVideo() returns a size
// that is among the supported preview sizes.
List<Size> previewSizes = parameters.getSupportedPreviewSizes();
assertNotNull(previewSizes);
assertTrue(previewSizes.size() > 0);
assertTrue(previewSizes.contains(preferredPreviewSize));
}
}
private void checkExposureCompensation(Parameters parameters) {
assertEquals(0, parameters.getExposureCompensation());
int max = parameters.getMaxExposureCompensation();
int min = parameters.getMinExposureCompensation();
float step = parameters.getExposureCompensationStep();
if (max == 0 && min == 0) {
assertEquals(0f, step, 0.000001f);
return;
}
assertTrue(step > 0);
assertTrue(max >= 0);
assertTrue(min <= 0);
}
private boolean isValidPixelFormat(int format) {
return (format == ImageFormat.RGB_565) || (format == ImageFormat.NV21)
|| (format == ImageFormat.JPEG) || (format == ImageFormat.YUY2);
}
@UiThreadTest
public void testJpegThumbnailSize() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
initializeMessageLooper(id);
testJpegThumbnailSizeByCamera(false, 0, 0);
terminateMessageLooper();
}
}
private void testJpegThumbnailSizeByCamera(boolean recording,
int recordingWidth, int recordingHeight) throws Exception {
// Thumbnail size parameters should have valid values.
Parameters p = mCamera.getParameters();
Size size = p.getJpegThumbnailSize();
assertTrue(size.width > 0 && size.height > 0);
List<Size> sizes = p.getSupportedJpegThumbnailSizes();
assertTrue(sizes.size() >= 2);
assertTrue(sizes.contains(size));
assertTrue(sizes.contains(mCamera.new Size(0, 0)));
// Test if the thumbnail size matches the setting.
if (!recording) mCamera.startPreview();
mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
waitForSnapshotDone();
assertTrue(mJpegPictureCallbackResult);
ExifInterface exif = new ExifInterface(JPEG_PATH);
assertTrue(exif.hasThumbnail());
byte[] thumb = exif.getThumbnail();
BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
bmpOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(thumb, 0, thumb.length, bmpOptions);
if (!recording) {
assertEquals(size.width, bmpOptions.outWidth);
assertEquals(size.height, bmpOptions.outHeight);
} else {
assertTrue(bmpOptions.outWidth >= recordingWidth ||
bmpOptions.outWidth == size.width);
assertTrue(bmpOptions.outHeight >= recordingHeight ||
bmpOptions.outHeight == size.height);
}
// Test no thumbnail case.
p.setJpegThumbnailSize(0, 0);
mCamera.setParameters(p);
Size actual = mCamera.getParameters().getJpegThumbnailSize();
assertEquals(0, actual.width);
assertEquals(0, actual.height);
if (!recording) mCamera.startPreview();
mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
waitForSnapshotDone();
assertTrue(mJpegPictureCallbackResult);
exif = new ExifInterface(JPEG_PATH);
assertFalse(exif.hasThumbnail());
}
@UiThreadTest
public void testJpegExif() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
initializeMessageLooper(id);
testJpegExifByCamera(false);
terminateMessageLooper();
}
}
private void testJpegExifByCamera(boolean recording) throws Exception {
Camera.Parameters parameters = mCamera.getParameters();
if (!recording) mCamera.startPreview();
double focalLength = parameters.getFocalLength();
Date date = new Date(System.currentTimeMillis());
String localDatetime = new SimpleDateFormat("yyyy:MM:dd HH:").format(date);
mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
waitForSnapshotDone();
// Test various exif tags.
ExifInterface exif = new ExifInterface(JPEG_PATH);
assertNotNull(exif.getAttribute(ExifInterface.TAG_MAKE));
assertNotNull(exif.getAttribute(ExifInterface.TAG_MODEL));
String datetime = exif.getAttribute(ExifInterface.TAG_DATETIME);
assertNotNull(datetime);
// Datetime should be local time.
assertTrue(datetime.startsWith(localDatetime));
assertTrue(datetime.length() == 19); // EXIF spec is "YYYY:MM:DD HH:MM:SS".
checkGpsDataNull(exif);
double exifFocalLength = exif.getAttributeDouble(ExifInterface.TAG_FOCAL_LENGTH, -1);
assertEquals(focalLength, exifFocalLength, 0.001);
// Test image width and height exif tags. They should match the jpeg.
assertBitmapAndJpegSizeEqual(mJpegData, exif);
// Test gps exif tags.
testGpsExifValues(parameters, 37.736071, -122.441983, 21, 1199145600,
"GPS NETWORK HYBRID ARE ALL FINE.");
testGpsExifValues(parameters, 0.736071, 0.441983, 1, 1199145601, "GPS");
testGpsExifValues(parameters, -89.736071, -179.441983, 100000, 1199145602, "NETWORK");
// Test gps tags do not exist after calling removeGpsData. Also check if
// image width and height exif match the jpeg when jpeg rotation is set.
if (!recording) mCamera.startPreview();
parameters.removeGpsData();
parameters.setRotation(90); // For testing image width and height exif.
mCamera.setParameters(parameters);
mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
waitForSnapshotDone();
exif = new ExifInterface(JPEG_PATH);
checkGpsDataNull(exif);
assertBitmapAndJpegSizeEqual(mJpegData, exif);
// Reset the rotation to prevent from affecting other tests.
parameters.setRotation(0);
mCamera.setParameters(parameters);
}
private void assertBitmapAndJpegSizeEqual(byte[] jpegData, ExifInterface exif) {
int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0);
int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0);
assertTrue(exifWidth != 0 && exifHeight != 0);
BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
bmpOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, bmpOptions);
assertEquals(bmpOptions.outWidth, exifWidth);
assertEquals(bmpOptions.outHeight, exifHeight);
}
private void testGpsExifValues(Parameters parameters, double latitude,
double longitude, double altitude, long timestamp, String method)
throws IOException {
mCamera.startPreview();
parameters.setGpsLatitude(latitude);
parameters.setGpsLongitude(longitude);
parameters.setGpsAltitude(altitude);
parameters.setGpsTimestamp(timestamp);
parameters.setGpsProcessingMethod(method);
mCamera.setParameters(parameters);
mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
waitForSnapshotDone();
ExifInterface exif = new ExifInterface(JPEG_PATH);
assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF));
assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_DATESTAMP));
assertEquals(method, exif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD));
float[] latLong = new float[2];
assertTrue(exif.getLatLong(latLong));
assertEquals((float)latitude, latLong[0], 0.0001f);
assertEquals((float)longitude, latLong[1], 0.0001f);
assertEquals(altitude, exif.getAltitude(-1), 1);
assertEquals(timestamp, exif.getGpsDateTime() / 1000);
}
private void checkGpsDataNull(ExifInterface exif) {
assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF));
assertNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
assertNull(exif.getAttribute(ExifInterface.TAG_GPS_DATESTAMP));
assertNull(exif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD));
}
@UiThreadTest
public void testLockUnlock() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testLockUnlockByCamera(id);
}
}
private void testLockUnlockByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
Camera.Parameters parameters = mCamera.getParameters();
SurfaceHolder surfaceHolder;
surfaceHolder = getActivity().getSurfaceView().getHolder();
CamcorderProfile profile = CamcorderProfile.get(cameraId,
CamcorderProfile.QUALITY_LOW);
// Set the preview size.
setPreviewSizeByProfile(parameters, profile);
mCamera.setParameters(parameters);
mCamera.setPreviewDisplay(surfaceHolder);
mCamera.startPreview();
mCamera.lock(); // Locking again from the same process has no effect.
try {
recordVideo(profile, surfaceHolder);
fail("Recording should not succeed because camera is locked.");
} catch (Exception e) {
// expected
}
mCamera.unlock(); // Unlock the camera so media recorder can use it.
try {
mCamera.setParameters(parameters);
fail("setParameters should not succeed because camera is unlocked.");
} catch (RuntimeException e) {
// expected
}
recordVideo(profile, surfaceHolder); // should not throw exception
// Media recorder already releases the camera so the test application
// can lock and use the camera now.
mCamera.lock(); // should not fail
mCamera.setParameters(parameters); // should not fail
terminateMessageLooper();
}
private void setPreviewSizeByProfile(Parameters parameters, CamcorderProfile profile) {
if (parameters.getSupportedVideoSizes() == null) {
parameters.setPreviewSize(profile.videoFrameWidth,
profile.videoFrameHeight);
} else { // Driver supports separates outputs for preview and video.
List<Size> sizes = parameters.getSupportedPreviewSizes();
Size preferred = parameters.getPreferredPreviewSizeForVideo();
int product = preferred.width * preferred.height;
for (Size size: sizes) {
if (size.width * size.height <= product) {
parameters.setPreviewSize(size.width, size.height);
break;
}
}
}
}
private void recordVideo(CamcorderProfile profile,
SurfaceHolder holder) throws Exception {
MediaRecorder recorder = new MediaRecorder();
try {
// Pass the camera from the test application to media recorder.
recorder.setCamera(mCamera);
recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
recorder.setProfile(profile);
recorder.setOutputFile("/dev/null");
recorder.setPreviewDisplay(holder.getSurface());
recorder.prepare();
recorder.start();
// Apps can use the camera after start since API level 13.
Parameters parameters = mCamera.getParameters();
if (parameters.isZoomSupported()) {
if (parameters.getMaxZoom() > 0) {
parameters.setZoom(1);
mCamera.setParameters(parameters);
parameters.setZoom(0);
mCamera.setParameters(parameters);
}
}
if (parameters.isSmoothZoomSupported()) {
if (parameters.getMaxZoom() > 0) {
ZoomListener zoomListener = new ZoomListener();
mCamera.setZoomChangeListener(zoomListener);
mCamera.startSmoothZoom(1);
assertTrue(zoomListener.mZoomDone.block(1000));
}
}
try {
mCamera.unlock();
fail("unlock should not succeed during recording.");
} catch(RuntimeException e) {
// expected
}
Thread.sleep(2000);
recorder.stop();
} finally {
recorder.release();
}
}
@UiThreadTest
public void testPreviewCallbackWithBuffer() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testPreviewCallbackWithBufferByCamera(id);
}
}
private void testPreviewCallbackWithBufferByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
SurfaceHolder surfaceHolder;
surfaceHolder = getActivity().getSurfaceView().getHolder();
mCamera.setPreviewDisplay(surfaceHolder);
Parameters parameters = mCamera.getParameters();
PreviewCallbackWithBuffer callback = new PreviewCallbackWithBuffer();
// Test all preview sizes.
for (Size size: parameters.getSupportedPreviewSizes()) {
parameters.setPreviewSize(size.width, size.height);
mCamera.setParameters(parameters);
assertEquals(size, mCamera.getParameters().getPreviewSize());
callback.mNumCbWithBuffer1 = 0;
callback.mNumCbWithBuffer2 = 0;
callback.mNumCbWithBuffer3 = 0;
int format = mCamera.getParameters().getPreviewFormat();
int bitsPerPixel = ImageFormat.getBitsPerPixel(format);
callback.mBuffer1 = new byte[size.width * size.height * bitsPerPixel / 8];
callback.mBuffer2 = new byte[size.width * size.height * bitsPerPixel / 8];
callback.mBuffer3 = new byte[size.width * size.height * bitsPerPixel / 8];
// Test if we can get the preview callbacks with specified buffers.
mCamera.addCallbackBuffer(callback.mBuffer1);
mCamera.addCallbackBuffer(callback.mBuffer2);
mCamera.setPreviewCallbackWithBuffer(callback);
mCamera.startPreview();
waitForPreviewDone();
assertFalse(callback.mPreviewDataNull);
assertFalse(callback.mInvalidData);
assertEquals(1, callback.mNumCbWithBuffer1);
assertEquals(1, callback.mNumCbWithBuffer2);
assertEquals(0, callback.mNumCbWithBuffer3);
// Test if preview callback with buffer still works during preview.
mCamera.addCallbackBuffer(callback.mBuffer3);
waitForPreviewDone();
assertFalse(callback.mPreviewDataNull);
assertFalse(callback.mInvalidData);
assertEquals(1, callback.mNumCbWithBuffer1);
assertEquals(1, callback.mNumCbWithBuffer2);
assertEquals(1, callback.mNumCbWithBuffer3);
mCamera.setPreviewCallbackWithBuffer(null);
mCamera.stopPreview();
}
terminateMessageLooper();
}
private final class PreviewCallbackWithBuffer
implements android.hardware.Camera.PreviewCallback {
public int mNumCbWithBuffer1, mNumCbWithBuffer2, mNumCbWithBuffer3;
public byte[] mBuffer1, mBuffer2, mBuffer3;
public boolean mPreviewDataNull, mInvalidData;
public void onPreviewFrame(byte[] data, Camera camera) {
if (data == null) {
Log.e(TAG, "Preview data is null!");
mPreviewDataNull = true;
mPreviewDone.open();
return;
}
if (data == mBuffer1) {
mNumCbWithBuffer1++;
} else if (data == mBuffer2) {
mNumCbWithBuffer2++;
} else if (data == mBuffer3) {
mNumCbWithBuffer3++;
} else {
Log.e(TAG, "Invalid byte array.");
mInvalidData = true;
mPreviewDone.open();
return;
}
if ((mNumCbWithBuffer1 == 1 && mNumCbWithBuffer2 == 1)
|| mNumCbWithBuffer3 == 1) {
mPreviewDone.open();
}
}
}
@UiThreadTest
public void testImmediateZoom() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testImmediateZoomByCamera(id);
}
}
private void testImmediateZoomByCamera(int id) throws Exception {
initializeMessageLooper(id);
Parameters parameters = mCamera.getParameters();
if (!parameters.isZoomSupported()) {
terminateMessageLooper();
return;
}
// Test the zoom parameters.
assertEquals(0, parameters.getZoom()); // default zoom should be 0.
for (Size size: parameters.getSupportedPreviewSizes()) {
parameters = mCamera.getParameters();
parameters.setPreviewSize(size.width, size.height);
mCamera.setParameters(parameters);
parameters = mCamera.getParameters();
int maxZoom = parameters.getMaxZoom();
assertTrue(maxZoom >= 0);
// Zoom ratios should be sorted from small to large.
List<Integer> ratios = parameters.getZoomRatios();
assertEquals(maxZoom + 1, ratios.size());
assertEquals(100, ratios.get(0).intValue());
for (int i = 0; i < ratios.size() - 1; i++) {
assertTrue(ratios.get(i) < ratios.get(i + 1));
}
mCamera.startPreview();
waitForPreviewDone();
// Test each zoom step.
for (int i = 0; i <= maxZoom; i++) {
parameters.setZoom(i);
mCamera.setParameters(parameters);
assertEquals(i, mCamera.getParameters().getZoom());
}
// It should throw exception if an invalid value is passed.
try {
parameters.setZoom(maxZoom + 1);
mCamera.setParameters(parameters);
fail("setZoom should throw exception.");
} catch (RuntimeException e) {
// expected
}
assertEquals(maxZoom, mCamera.getParameters().getZoom());
mCamera.takePicture(mShutterCallback, mRawPictureCallback,
mJpegPictureCallback);
waitForSnapshotDone();
}
terminateMessageLooper();
}
@UiThreadTest
public void testSmoothZoom() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testSmoothZoomByCamera(id);
}
}
private void testSmoothZoomByCamera(int id) throws Exception {
initializeMessageLooper(id);
Parameters parameters = mCamera.getParameters();
if (!parameters.isSmoothZoomSupported()) {
terminateMessageLooper();
return;
}
assertTrue(parameters.isZoomSupported());
ZoomListener zoomListener = new ZoomListener();
mCamera.setZoomChangeListener(zoomListener);
mCamera.startPreview();
waitForPreviewDone();
// Immediate zoom should not generate callbacks.
int maxZoom = parameters.getMaxZoom();
parameters.setZoom(maxZoom);
mCamera.setParameters(parameters);
assertEquals(maxZoom, mCamera.getParameters().getZoom());
parameters.setZoom(0);
mCamera.setParameters(parameters);
assertEquals(0, mCamera.getParameters().getZoom());
assertFalse(zoomListener.mZoomDone.block(500));
// Nothing will happen if zoom is not moving.
mCamera.stopSmoothZoom();
// It should not generate callbacks if zoom value is not changed.
mCamera.startSmoothZoom(0);
assertFalse(zoomListener.mZoomDone.block(500));
assertEquals(0, mCamera.getParameters().getZoom());
// Test startSmoothZoom.
mCamera.startSmoothZoom(maxZoom);
assertEquals(true, zoomListener.mZoomDone.block(5000));
assertEquals(maxZoom, mCamera.getParameters().getZoom());
assertEquals(maxZoom, zoomListener.mValues.size());
for(int i = 0; i < maxZoom; i++) {
int value = zoomListener.mValues.get(i);
boolean stopped = zoomListener.mStopped.get(i);
// Make sure we get all the zoom values in order.
assertEquals(i + 1, value);
// All "stopped" except the last should be false.
assertEquals(i == maxZoom - 1, stopped);
}
// Test startSmoothZoom. Make sure we get all the callbacks.
if (maxZoom > 1) {
zoomListener.mValues.clear();
zoomListener.mStopped.clear();
Log.e(TAG, "zoomListener.mStopped = " + zoomListener.mStopped);
zoomListener.mZoomDone.close();
mCamera.startSmoothZoom(maxZoom / 2);
assertTrue(zoomListener.mZoomDone.block(5000));
assertEquals(maxZoom / 2, mCamera.getParameters().getZoom());
assertEquals(maxZoom - (maxZoom / 2), zoomListener.mValues.size());
for(int i = 0; i < zoomListener.mValues.size(); i++) {
int value = zoomListener.mValues.get(i);
boolean stopped = zoomListener.mStopped.get(i);
// Make sure we get all the zoom values in order.
assertEquals(maxZoom - 1 - i, value);
// All "stopped" except the last should be false.
assertEquals(i == zoomListener.mValues.size() - 1, stopped);
}
}
// It should throw exception if an invalid value is passed.
try {
mCamera.startSmoothZoom(maxZoom + 1);
fail("startSmoothZoom should throw exception.");
} catch (IllegalArgumentException e) {
// expected
}
// Test stopSmoothZoom.
zoomListener.mValues.clear();
zoomListener.mStopped.clear();
zoomListener.mZoomDone.close();
parameters.setZoom(0);
mCamera.setParameters(parameters);
assertEquals(0, mCamera.getParameters().getZoom());
mCamera.startSmoothZoom(maxZoom);
mCamera.stopSmoothZoom();
assertTrue(zoomListener.mZoomDone.block(5000));
assertEquals(zoomListener.mValues.size(), mCamera.getParameters().getZoom());
for(int i = 0; i < zoomListener.mValues.size() - 1; i++) {
int value = zoomListener.mValues.get(i);
boolean stopped = zoomListener.mStopped.get(i);
// Make sure we get all the callbacks in order (except the last).
assertEquals(i + 1, value);
// All "stopped" except the last should be false. stopSmoothZoom has been called. So the
// last "stopped" can be true or false.
if (i != zoomListener.mValues.size() - 1) {
assertFalse(stopped);
}
}
terminateMessageLooper();
}
private final class ZoomListener
implements android.hardware.Camera.OnZoomChangeListener {
public ArrayList<Integer> mValues = new ArrayList<Integer>();
public ArrayList<Boolean> mStopped = new ArrayList<Boolean>();
public final ConditionVariable mZoomDone = new ConditionVariable();
public void onZoomChange(int value, boolean stopped, Camera camera) {
mValues.add(value);
mStopped.add(stopped);
if (stopped) {
mZoomDone.open();
}
}
}
@UiThreadTest
public void testFocusDistances() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testFocusDistancesByCamera(id);
}
}
private void testFocusDistancesByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
mCamera.startPreview();
waitForPreviewDone();
Parameters parameters = mCamera.getParameters();
// Test every supported focus mode.
for (String focusMode: parameters.getSupportedFocusModes()) {
parameters.setFocusMode(focusMode);
mCamera.setParameters(parameters);
parameters = mCamera.getParameters();
assertEquals(focusMode, parameters.getFocusMode());
checkFocusDistances(parameters);
if (Parameters.FOCUS_MODE_AUTO.equals(focusMode)
|| Parameters.FOCUS_MODE_MACRO.equals(focusMode)
|| Parameters.FOCUS_MODE_CONTINUOUS_VIDEO.equals(focusMode)
|| Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(focusMode)) {
Log.v(TAG, "Focus mode=" + focusMode);
mCamera.autoFocus(mAutoFocusCallback);
assertTrue(waitForFocusDone());
parameters = mCamera.getParameters();
checkFocusDistances(parameters);
float[] initialFocusDistances = new float[3];
parameters.getFocusDistances(initialFocusDistances);
// Focus position should not change after autoFocus call.
// Continuous autofocus should have stopped. Sleep some time and
// check. Make sure continuous autofocus is not working. If the
// focus mode is auto or macro, it is no harm to do the extra
// test.
Thread.sleep(500);
parameters = mCamera.getParameters();
float[] currentFocusDistances = new float[3];
parameters.getFocusDistances(currentFocusDistances);
assertEquals(initialFocusDistances, currentFocusDistances);
// Focus position should not change after stopping preview.
mCamera.stopPreview();
parameters = mCamera.getParameters();
parameters.getFocusDistances(currentFocusDistances);
assertEquals(initialFocusDistances, currentFocusDistances);
// Focus position should not change after taking a picture.
mCamera.startPreview();
mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
waitForSnapshotDone();
parameters = mCamera.getParameters();
parameters.getFocusDistances(currentFocusDistances);
assertEquals(initialFocusDistances, currentFocusDistances);
mCamera.startPreview();
}
}
// Test if the method throws exception if the argument is invalid.
try {
parameters.getFocusDistances(null);
fail("getFocusDistances should not accept null.");
} catch (IllegalArgumentException e) {
// expected
}
try {
parameters.getFocusDistances(new float[2]);
fail("getFocusDistances should not accept a float array with two elements.");
} catch (IllegalArgumentException e) {
// expected
}
try {
parameters.getFocusDistances(new float[4]);
fail("getFocusDistances should not accept a float array with four elements.");
} catch (IllegalArgumentException e) {
// expected
}
terminateMessageLooper();
}
private void checkFocusDistances(Parameters parameters) {
float[] distances = new float[3];
parameters.getFocusDistances(distances);
// Focus distances should be greater than 0.
assertTrue(distances[Parameters.FOCUS_DISTANCE_NEAR_INDEX] > 0);
assertTrue(distances[Parameters.FOCUS_DISTANCE_OPTIMAL_INDEX] > 0);
assertTrue(distances[Parameters.FOCUS_DISTANCE_FAR_INDEX] > 0);
// Make sure far focus distance >= optimal focus distance >= near focus distance.
assertTrue(distances[Parameters.FOCUS_DISTANCE_FAR_INDEX] >=
distances[Parameters.FOCUS_DISTANCE_OPTIMAL_INDEX]);
assertTrue(distances[Parameters.FOCUS_DISTANCE_OPTIMAL_INDEX] >=
distances[Parameters.FOCUS_DISTANCE_NEAR_INDEX]);
// Far focus distance should be infinity in infinity focus mode.
if (Parameters.FOCUS_MODE_INFINITY.equals(parameters.getFocusMode())) {
assertEquals(Float.POSITIVE_INFINITY,
distances[Parameters.FOCUS_DISTANCE_FAR_INDEX]);
}
}
@UiThreadTest
public void testCancelAutofocus() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testCancelAutofocusByCamera(id);
}
}
private void testCancelAutofocusByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
Parameters parameters = mCamera.getParameters();
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains(Parameters.FOCUS_MODE_AUTO)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
} else if (focusModes.contains(Parameters.FOCUS_MODE_MACRO)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_MACRO);
} else {
terminateMessageLooper();
return;
}
mCamera.setParameters(parameters);
// Valid to call outside of preview; should just reset lens or
// be a no-op.
mCamera.cancelAutoFocus();
mCamera.startPreview();
// No op if autofocus is not in progress.
mCamera.cancelAutoFocus();
// Try to cancel autofocus immediately.
mCamera.autoFocus(mAutoFocusCallback);
mCamera.cancelAutoFocus();
checkFocusDistanceNotChanging();
// Try to cancel autofocus after it starts for some time.
mCamera.autoFocus(mAutoFocusCallback);
Thread.sleep(500);
mCamera.cancelAutoFocus();
checkFocusDistanceNotChanging();
// Try to cancel autofocus after it completes. It should be no op.
mCamera.autoFocus(mAutoFocusCallback);
assertTrue(waitForFocusDone());
mCamera.cancelAutoFocus();
// Test the case calling cancelAutoFocus and release in a row.
mCamera.autoFocus(mAutoFocusCallback);
mCamera.cancelAutoFocus();
mCamera.release();
// Ensure the camera can be opened if release is called right after AF.
mCamera = Camera.open(cameraId);
mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
mCamera.startPreview();
mCamera.autoFocus(mAutoFocusCallback);
mCamera.release();
terminateMessageLooper();
}
private void checkFocusDistanceNotChanging() throws Exception {
float[] distances1 = new float[3];
float[] distances2 = new float[3];
Parameters parameters = mCamera.getParameters();
parameters.getFocusDistances(distances1);
Thread.sleep(100);
parameters = mCamera.getParameters();
parameters.getFocusDistances(distances2);
assertEquals(distances1[Parameters.FOCUS_DISTANCE_NEAR_INDEX],
distances2[Parameters.FOCUS_DISTANCE_NEAR_INDEX]);
assertEquals(distances1[Parameters.FOCUS_DISTANCE_OPTIMAL_INDEX],
distances2[Parameters.FOCUS_DISTANCE_OPTIMAL_INDEX]);
assertEquals(distances1[Parameters.FOCUS_DISTANCE_FAR_INDEX],
distances2[Parameters.FOCUS_DISTANCE_FAR_INDEX]);
}
@UiThreadTest
public void testMultipleCameras() throws Exception {
int nCameras = Camera.getNumberOfCameras();
Log.v(TAG, "total " + nCameras + " cameras");
assertTrue(nCameras >= 0);
boolean backCameraExist = false;
CameraInfo info = new CameraInfo();
for (int i = 0; i < nCameras; i++) {
Camera.getCameraInfo(i, info);
if (info.facing == CameraInfo.CAMERA_FACING_BACK) {
backCameraExist = true;
break;
}
}
// Make sure original open still works. It must return a back-facing
// camera.
mCamera = Camera.open();
if (mCamera != null) {
mCamera.release();
assertTrue(backCameraExist);
} else {
assertFalse(backCameraExist);
}
for (int id = -1; id <= nCameras; id++) {
Log.v(TAG, "testing camera #" + id);
boolean isBadId = (id < 0 || id >= nCameras);
try {
Camera.getCameraInfo(id, info);
if (isBadId) {
fail("getCameraInfo should not accept bad cameraId (" + id + ")");
}
} catch (RuntimeException e) {
if (!isBadId) throw e;
}
int facing = info.facing;
int orientation = info.orientation;
assertTrue(facing == CameraInfo.CAMERA_FACING_BACK ||
facing == CameraInfo.CAMERA_FACING_FRONT);
assertTrue(orientation == 0 || orientation == 90 ||
orientation == 180 || orientation == 270);
Camera camera = null;
try {
camera = Camera.open(id);
if (isBadId) {
fail("open() should not accept bad cameraId (" + id + ")");
}
} catch (RuntimeException e) {
if (!isBadId) throw e;
} finally {
if (camera != null) {
camera.release();
}
}
}
}
@UiThreadTest
public void testPreviewPictureSizesCombination() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testPreviewPictureSizesCombinationByCamera(id);
}
}
private void testPreviewPictureSizesCombinationByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
Parameters parameters = mCamera.getParameters();
PreviewCbForPreviewPictureSizesCombination callback =
new PreviewCbForPreviewPictureSizesCombination();
// Test combination of preview sizes and picture sizes. Pick four of each to test.
// Do not test all combinations because it will timeout. Four is just a small number
// and the test will not timeout.
List<Size> previewSizes = parameters.getSupportedPreviewSizes();
List<Size> pictureSizes = parameters.getSupportedPictureSizes();
int previewSizeTestCount = Math.min(previewSizes.size(), 4);
int pictureSizeTestCount = Math.min(pictureSizes.size(), 4);
// Calculate the step so that the first one and the last one are always tested.
float previewSizeIndexStep = (float) (previewSizes.size() - 1) / (previewSizeTestCount - 1);
float pictureSizeIndexStep = (float) (pictureSizes.size() - 1) / (pictureSizeTestCount - 1);
for (int i = 0; i < previewSizeTestCount; i++) {
for (int j = 0; j < pictureSizeTestCount; j++) {
Size previewSize = previewSizes.get(Math.round(previewSizeIndexStep * i));
Size pictureSize = pictureSizes.get(Math.round(pictureSizeIndexStep * j));
Log.v(TAG, "Test previewSize=(" + previewSize.width + "," +
previewSize.height + ") pictureSize=(" +
pictureSize.width + "," + pictureSize.height + ")");
mPreviewCallbackResult = PREVIEW_CALLBACK_NOT_RECEIVED;
mCamera.setPreviewCallback(callback);
callback.expectedPreviewSize = previewSize;
parameters.setPreviewSize(previewSize.width, previewSize.height);
parameters.setPictureSize(pictureSize.width, pictureSize.height);
mCamera.setParameters(parameters);
assertEquals(previewSize, mCamera.getParameters().getPreviewSize());
assertEquals(pictureSize, mCamera.getParameters().getPictureSize());
// Check if the preview size is the same as requested.
mCamera.startPreview();
waitForPreviewDone();
assertEquals(PREVIEW_CALLBACK_RECEIVED, mPreviewCallbackResult);
// Check if the picture size is the same as requested.
mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
waitForSnapshotDone();
assertTrue(mJpegPictureCallbackResult);
assertNotNull(mJpegData);
BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
bmpOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(mJpegData, 0, mJpegData.length, bmpOptions);
assertEquals(pictureSize.width, bmpOptions.outWidth);
assertEquals(pictureSize.height, bmpOptions.outHeight);
}
}
terminateMessageLooper();
}
private final class PreviewCbForPreviewPictureSizesCombination
implements android.hardware.Camera.PreviewCallback {
public Size expectedPreviewSize;
public void onPreviewFrame(byte[] data, Camera camera) {
if (data == null) {
mPreviewCallbackResult = PREVIEW_CALLBACK_DATA_NULL;
mPreviewDone.open();
return;
}
Size size = camera.getParameters().getPreviewSize();
int format = camera.getParameters().getPreviewFormat();
int bitsPerPixel = ImageFormat.getBitsPerPixel(format);
if (!expectedPreviewSize.equals(size) ||
calculateBufferSize(size.width, size.height,
format, bitsPerPixel) != data.length) {
Log.e(TAG, "Expected preview width=" + expectedPreviewSize.width + ", height="
+ expectedPreviewSize.height + ". Actual width=" + size.width + ", height="
+ size.height);
Log.e(TAG, "Frame data length=" + data.length + ". bitsPerPixel=" + bitsPerPixel);
mPreviewCallbackResult = PREVIEW_CALLBACK_INVALID_FRAME_SIZE;
mPreviewDone.open();
return;
}
camera.setPreviewCallback(null);
mPreviewCallbackResult = PREVIEW_CALLBACK_RECEIVED;
mPreviewDone.open();
}
}
@UiThreadTest
public void testPreviewFpsRange() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testPreviewFpsRangeByCamera(id);
}
}
private void testPreviewFpsRangeByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
// Test if the parameters exists and minimum fps <= maximum fps.
int[] defaultFps = new int[2];
Parameters parameters = mCamera.getParameters();
parameters.getPreviewFpsRange(defaultFps);
List<int[]> fpsList = parameters.getSupportedPreviewFpsRange();
assertTrue(fpsList.size() > 0);
boolean found = false;
for(int[] fps: fpsList) {
assertTrue(fps[Parameters.PREVIEW_FPS_MIN_INDEX] > 0);
assertTrue(fps[Parameters.PREVIEW_FPS_MIN_INDEX] <=
fps[Parameters.PREVIEW_FPS_MAX_INDEX]);
if (!found && Arrays.equals(defaultFps, fps)) {
found = true;
}
}
assertTrue("Preview fps range must be in the supported list.", found);
// Test if the list is properly sorted.
for (int i = 0; i < fpsList.size() - 1; i++) {
int minFps1 = fpsList.get(i)[Parameters.PREVIEW_FPS_MIN_INDEX];
int maxFps1 = fpsList.get(i)[Parameters.PREVIEW_FPS_MAX_INDEX];
int minFps2 = fpsList.get(i + 1)[Parameters.PREVIEW_FPS_MIN_INDEX];
int maxFps2 = fpsList.get(i + 1)[Parameters.PREVIEW_FPS_MAX_INDEX];
assertTrue(maxFps1 < maxFps2
|| (maxFps1 == maxFps2 && minFps1 < minFps2));
}
// Test if the actual fps is within fps range.
Size size = parameters.getPreviewSize();
int format = mCamera.getParameters().getPreviewFormat();
int bitsPerPixel = ImageFormat.getBitsPerPixel(format);
byte[] buffer1 = new byte[size.width * size.height * bitsPerPixel / 8];
byte[] buffer2 = new byte[size.width * size.height * bitsPerPixel / 8];
byte[] buffer3 = new byte[size.width * size.height * bitsPerPixel / 8];
FpsRangePreviewCb callback = new FpsRangePreviewCb();
int[] readBackFps = new int[2];
for (int[] fps: fpsList) {
parameters = mCamera.getParameters();
parameters.setPreviewFpsRange(fps[Parameters.PREVIEW_FPS_MIN_INDEX],
fps[Parameters.PREVIEW_FPS_MAX_INDEX]);
callback.reset(fps[Parameters.PREVIEW_FPS_MIN_INDEX] / 1000.0,
fps[Parameters.PREVIEW_FPS_MAX_INDEX] / 1000.0);
mCamera.setParameters(parameters);
parameters = mCamera.getParameters();
parameters.getPreviewFpsRange(readBackFps);
MoreAsserts.assertEquals(fps, readBackFps);
mCamera.addCallbackBuffer(buffer1);
mCamera.addCallbackBuffer(buffer2);
mCamera.addCallbackBuffer(buffer3);
mCamera.setPreviewCallbackWithBuffer(callback);
mCamera.startPreview();
try {
// Test the frame rate for a while.
Thread.sleep(3000);
} catch(Exception e) {
// ignore
}
mCamera.stopPreview();
}
// Test the invalid fps cases.
parameters = mCamera.getParameters();
parameters.setPreviewFpsRange(-1, -1);
try {
mCamera.setParameters(parameters);
fail("Should throw an exception if fps range is negative.");
} catch (RuntimeException e) {
// expected
}
parameters.setPreviewFpsRange(10, 5);
try {
mCamera.setParameters(parameters);
fail("Should throw an exception if fps range is invalid.");
} catch (RuntimeException e) {
// expected
}
terminateMessageLooper();
}
private final class FpsRangePreviewCb
implements android.hardware.Camera.PreviewCallback {
private double mMinFps, mMaxFps, mMaxFrameInterval, mMinFrameInterval;
// An array storing the arrival time of the frames in the last second.
private ArrayList<Long> mFrames = new ArrayList<Long>();
private long firstFrameArrivalTime;
public void reset(double minFps, double maxFps) {
this.mMinFps = minFps;
this.mMaxFps = maxFps;
mMaxFrameInterval = 1000.0 / mMinFps;
mMinFrameInterval = 1000.0 / mMaxFps;
Log.v(TAG, "Min fps=" + mMinFps + ". Max fps=" + mMaxFps
+ ". Min frame interval=" + mMinFrameInterval
+ ". Max frame interval=" + mMaxFrameInterval);
mFrames.clear();
firstFrameArrivalTime = 0;
}
// This method tests if the actual fps is between minimum and maximum.
// It also tests if the frame interval is too long.
public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
long arrivalTime = System.currentTimeMillis();
camera.addCallbackBuffer(data);
if (firstFrameArrivalTime == 0) firstFrameArrivalTime = arrivalTime;
// Remove the frames that arrived before the last second.
Iterator<Long> it = mFrames.iterator();
while(it.hasNext()) {
long time = it.next();
if (arrivalTime - time > 1000 && mFrames.size() > 2) {
it.remove();
} else {
break;
}
}
// Start the test after one second.
if (arrivalTime - firstFrameArrivalTime > 1000) {
assertTrue(mFrames.size() >= 2);
// Check the frame interval and fps. The interval check
// considers the time variance passing frames from the camera
// hardware to the callback. It should be a constant time, not a
// ratio. The fps check is more strict because individual
// variance is averaged out.
// Check if the frame interval is too large or too small.
// x100 = percent, intervalMargin should be bigger than fpsMargin considering that
// fps will be in the order of 10.
double intervalMargin = 0.9;
long lastArrivalTime = mFrames.get(mFrames.size() - 1);
double interval = arrivalTime - lastArrivalTime;
if (LOGV) Log.v(TAG, "Frame interval=" + interval);
assertTrue("Frame interval (" + interval + "ms) is too large." +
" mMaxFrameInterval=" + mMaxFrameInterval + "ms",
interval < mMaxFrameInterval * (1.0 + intervalMargin));
assertTrue("Frame interval (" + interval + "ms) is too small." +
" mMinFrameInterval=" + mMinFrameInterval + "ms",
interval > mMinFrameInterval * (1.0 - intervalMargin));
// Check if the fps is within range.
double fpsMargin = 0.5; // x100 = percent
double avgInterval = (double)(arrivalTime - mFrames.get(0))
/ mFrames.size();
double fps = 1000.0 / avgInterval;
assertTrue("Actual fps (" + fps + ") should be larger than " +
"min fps (" + mMinFps + ")",
fps >= mMinFps * (1.0 - fpsMargin));
assertTrue("Actual fps (" + fps + ") should be smaller than " +
"max fps (" + mMaxFps + ")",
fps <= mMaxFps * (1.0 + fpsMargin));
}
// Add the arrival time of this frame to the list.
mFrames.add(arrivalTime);
}
}
private void assertEquals(Size expected, Size actual) {
assertEquals(expected.width, actual.width);
assertEquals(expected.height, actual.height);
}
private void assertEquals(float[] expected, float[] actual) {
assertEquals(expected.length, actual.length);
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], actual[i], 0.000001f);
}
}
private void assertNoLetters(String value, String key) {
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
assertFalse("Parameter contains invalid characters. key,value=("
+ key + "," + value + ")",
Character.isLetter(c) && c != 'x');
}
}
@UiThreadTest
public void testSceneMode() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testSceneModeByCamera(id);
}
}
private class SceneModeSettings {
public String mScene, mFlash, mFocus, mWhiteBalance;
public List<String> mSupportedFlash, mSupportedFocus, mSupportedWhiteBalance;
public SceneModeSettings(Parameters parameters) {
mScene = parameters.getSceneMode();
mFlash = parameters.getFlashMode();
mFocus = parameters.getFocusMode();
mWhiteBalance = parameters.getWhiteBalance();
mSupportedFlash = parameters.getSupportedFlashModes();
mSupportedFocus = parameters.getSupportedFocusModes();
mSupportedWhiteBalance = parameters.getSupportedWhiteBalance();
}
}
private void testSceneModeByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
Parameters parameters = mCamera.getParameters();
List<String> supportedSceneModes = parameters.getSupportedSceneModes();
if (supportedSceneModes != null) {
assertEquals(Parameters.SCENE_MODE_AUTO, parameters.getSceneMode());
SceneModeSettings autoSceneMode = new SceneModeSettings(parameters);
// Store all scene mode affected settings.
SceneModeSettings[] settings = new SceneModeSettings[supportedSceneModes.size()];
for (int i = 0; i < supportedSceneModes.size(); i++) {
parameters.setSceneMode(supportedSceneModes.get(i));
mCamera.setParameters(parameters);
parameters = mCamera.getParameters();
settings[i] = new SceneModeSettings(parameters);
}
// Make sure scene mode settings are consistent before preview and
// after preview.
mCamera.startPreview();
waitForPreviewDone();
for (int i = 0; i < supportedSceneModes.size(); i++) {
String sceneMode = supportedSceneModes.get(i);
parameters.setSceneMode(sceneMode);
mCamera.setParameters(parameters);
parameters = mCamera.getParameters();
// In auto scene mode, camera HAL will not remember the previous
// flash, focus, and white-balance. It will just take values set
// by parameters. But the supported flash, focus, and
// white-balance should still be restored in auto scene mode.
if (!Parameters.SCENE_MODE_AUTO.equals(sceneMode)) {
assertEquals("Flash is inconsistent in scene mode " + sceneMode,
settings[i].mFlash, parameters.getFlashMode());
assertEquals("Focus is inconsistent in scene mode " + sceneMode,
settings[i].mFocus, parameters.getFocusMode());
assertEquals("White balance is inconsistent in scene mode " + sceneMode,
settings[i].mWhiteBalance, parameters.getWhiteBalance());
}
assertEquals("Suppported flash modes are inconsistent in scene mode " + sceneMode,
settings[i].mSupportedFlash, parameters.getSupportedFlashModes());
assertEquals("Suppported focus modes are inconsistent in scene mode " + sceneMode,
settings[i].mSupportedFocus, parameters.getSupportedFocusModes());
assertEquals("Suppported white balance are inconsistent in scene mode " + sceneMode,
settings[i].mSupportedWhiteBalance, parameters.getSupportedWhiteBalance());
}
for (int i = 0; i < settings.length; i++) {
if (Parameters.SCENE_MODE_AUTO.equals(settings[i].mScene)) continue;
// Both the setting and the supported settings may change. It is
// allowed to have more than one supported settings in scene
// modes. For example, in night scene mode, supported flash
// modes can have on and off.
if (autoSceneMode.mSupportedFlash != null) {
assertTrue(settings[i].mSupportedFlash.contains(settings[i].mFlash));
for (String mode: settings[i].mSupportedFlash) {
assertTrue(autoSceneMode.mSupportedFlash.contains(mode));
}
}
if (autoSceneMode.mSupportedFocus != null) {
assertTrue(settings[i].mSupportedFocus.contains(settings[i].mFocus));
for (String mode: settings[i].mSupportedFocus) {
assertTrue(autoSceneMode.mSupportedFocus.contains(mode));
}
}
if (autoSceneMode.mSupportedWhiteBalance != null) {
assertTrue(settings[i].mSupportedWhiteBalance.contains(settings[i].mWhiteBalance));
for (String mode: settings[i].mSupportedWhiteBalance) {
assertTrue(autoSceneMode.mSupportedWhiteBalance.contains(mode));
}
}
}
}
terminateMessageLooper();
}
@UiThreadTest
public void testInvalidParameters() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testInvalidParametersByCamera(id);
}
}
private void testInvalidParametersByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
// Test flash mode.
Parameters parameters = mCamera.getParameters();
List<String> list = parameters.getSupportedFlashModes();
if (list != null && list.size() > 0) {
String original = parameters.getFlashMode();
parameters.setFlashMode("invalid");
try {
mCamera.setParameters(parameters);
fail("Should throw exception for invalid parameters");
} catch (RuntimeException e) {
// expected
}
parameters = mCamera.getParameters();
assertEquals(original, parameters.getFlashMode());
}
// Test focus mode.
String originalFocus = parameters.getFocusMode();
parameters.setFocusMode("invalid");
try {
mCamera.setParameters(parameters);
fail("Should throw exception for invalid parameters");
} catch (RuntimeException e) {
// expected
}
parameters = mCamera.getParameters();
assertEquals(originalFocus, parameters.getFocusMode());
// Test preview size.
Size originalSize = parameters.getPreviewSize();
parameters.setPreviewSize(-1, -1);
try {
mCamera.setParameters(parameters);
fail("Should throw exception for invalid parameters");
} catch (RuntimeException e) {
// expected
}
parameters = mCamera.getParameters();
assertEquals(originalSize, parameters.getPreviewSize());
terminateMessageLooper();
}
@UiThreadTest
public void testGetParameterDuringFocus() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testGetParameterDuringFocusByCamera(id);
}
}
private void testGetParameterDuringFocusByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
mCamera.startPreview();
Parameters parameters = mCamera.getParameters();
for (String focusMode: parameters.getSupportedFocusModes()) {
if (focusMode.equals(parameters.FOCUS_MODE_AUTO)
|| focusMode.equals(parameters.FOCUS_MODE_MACRO)) {
parameters.setFocusMode(focusMode);
mCamera.setParameters(parameters);
mCamera.autoFocus(mAutoFocusCallback);
// This should not crash or throw exception.
mCamera.getParameters();
waitForFocusDone();
mCamera.autoFocus(mAutoFocusCallback);
// Add a small delay to make sure focus has started.
Thread.sleep(100);
// This should not crash or throw exception.
mCamera.getParameters();
waitForFocusDone();
}
}
terminateMessageLooper();
}
@UiThreadTest
public void testPreviewFormats() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testPreviewFormatsByCamera(id);
}
}
private void testPreviewFormatsByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
Parameters parameters = mCamera.getParameters();
for (int format: parameters.getSupportedPreviewFormats()) {
Log.v(TAG, "Test preview format " + format);
parameters.setPreviewFormat(format);
mCamera.setParameters(parameters);
mCamera.setOneShotPreviewCallback(mPreviewCallback);
mCamera.startPreview();
waitForPreviewDone();
assertEquals(PREVIEW_CALLBACK_RECEIVED, mPreviewCallbackResult);
}
terminateMessageLooper();
}
@UiThreadTest
public void testMultiCameraRelease() throws Exception {
// Verify that multiple cameras exist, and that they can be opened at the same time
if (LOGV) Log.v(TAG, "testMultiCameraRelease: Checking pre-conditions.");
int nCameras = Camera.getNumberOfCameras();
if (nCameras < 2) {
Log.i(TAG, "Test multi-camera release: Skipping test because only 1 camera available");
return;
}
Camera testCamera0 = Camera.open(0);
Camera testCamera1 = null;
try {
testCamera1 = Camera.open(1);
} catch (RuntimeException e) {
// Can't open two cameras at once
Log.i(TAG, "testMultiCameraRelease: Skipping test because only 1 camera "+
"could be opened at once. Second open threw: " + e);
testCamera0.release();
return;
}
testCamera0.release();
testCamera1.release();
// Start first camera
if (LOGV) Log.v(TAG, "testMultiCameraRelease: Opening camera 0");
initializeMessageLooper(0);
SimplePreviewStreamCb callback0 = new SimplePreviewStreamCb(0);
mCamera.setPreviewCallback(callback0);
if (LOGV) Log.v(TAG, "testMultiCameraRelease: Starting preview on camera 0");
mCamera.startPreview();
// Run preview for a bit
for (int f = 0; f < 100; f++) {
mPreviewDone.close();
assertTrue("testMultiCameraRelease: First camera preview timed out on frame " + f + "!",
mPreviewDone.block( WAIT_FOR_COMMAND_TO_COMPLETE));
}
if (LOGV) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 0");
mCamera.stopPreview();
// Save message looper and camera to deterministically release them, instead
// of letting GC do it at some point.
Camera firstCamera = mCamera;
Looper firstLooper = mLooper;
//terminateMessageLooper(); // Intentionally not calling this
// Preview surface should be released though!
mCamera.setPreviewDisplay(null);
// Start second camera without releasing the first one (will
// set mCamera and mLooper to new objects)
if (LOGV) Log.v(TAG, "testMultiCameraRelease: Opening camera 1");
initializeMessageLooper(1);
SimplePreviewStreamCb callback1 = new SimplePreviewStreamCb(1);
mCamera.setPreviewCallback(callback1);
if (LOGV) Log.v(TAG, "testMultiCameraRelease: Starting preview on camera 1");
mCamera.startPreview();
// Run preview for a bit - GC of first camera instance should not impact the second's
// operation.
for (int f = 0; f < 100; f++) {
mPreviewDone.close();
assertTrue("testMultiCameraRelease: Second camera preview timed out on frame " + f + "!",
mPreviewDone.block( WAIT_FOR_COMMAND_TO_COMPLETE));
if (f == 50) {
// Release first camera mid-preview, should cause no problems
if (LOGV) Log.v(TAG, "testMultiCameraRelease: Releasing camera 0");
firstCamera.release();
}
}
if (LOGV) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 0");
mCamera.stopPreview();
firstLooper.quit();
terminateMessageLooper();
}
// This callback just signals on the condition variable, making it useful for checking that
// preview callbacks don't stop unexpectedly
private final class SimplePreviewStreamCb
implements android.hardware.Camera.PreviewCallback {
private int mId;
public SimplePreviewStreamCb(int id) {
mId = id;
}
public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
if (LOGV) Log.v(TAG, "Preview frame callback, id " + mId + ".");
mPreviewDone.open();
}
}
@UiThreadTest
public void testFocusAreas() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
initializeMessageLooper(id);
Parameters parameters = mCamera.getParameters();
int maxNumFocusAreas = parameters.getMaxNumFocusAreas();
assertTrue(maxNumFocusAreas >= 0);
if (maxNumFocusAreas > 0) {
List<String> focusModes = parameters.getSupportedFocusModes();
assertTrue(focusModes.contains(Parameters.FOCUS_MODE_AUTO));
testAreas(FOCUS_AREA, maxNumFocusAreas);
}
terminateMessageLooper();
}
}
@UiThreadTest
public void testMeteringAreas() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
initializeMessageLooper(id);
Parameters parameters = mCamera.getParameters();
int maxNumMeteringAreas = parameters.getMaxNumMeteringAreas();
assertTrue(maxNumMeteringAreas >= 0);
if (maxNumMeteringAreas > 0) {
testAreas(METERING_AREA, maxNumMeteringAreas);
}
terminateMessageLooper();
}
}
private void testAreas(int type, int maxNumAreas) throws Exception {
mCamera.startPreview();
// Test various valid cases.
testValidAreas(type, null); // the default area
testValidAreas(type, makeAreas(-1000, -1000, 1000, 1000, 1)); // biggest area
testValidAreas(type, makeAreas(-500, -500, 500, 500, 1000)); // medium area & biggest weight
testValidAreas(type, makeAreas(0, 0, 1, 1, 1)); // smallest area
ArrayList<Area> areas = new ArrayList();
if (maxNumAreas > 1) {
// Test overlapped areas.
testValidAreas(type, makeAreas(-250, -250, 250, 250, 1, 0, 0, 500, 500, 2));
// Test completely disjoint areas.
testValidAreas(type, makeAreas(-250, -250, 0, 0, 1, 900, 900, 1000, 1000, 1));
// Test the maximum number of areas.
testValidAreas(type, makeAreas(-1000, -1000, 1000, 1000, 1000, maxNumAreas));
}
// Test various invalid cases.
testInvalidAreas(type, makeAreas(-1001, -1000, 1000, 1000, 1)); // left should >= -1000
testInvalidAreas(type, makeAreas(-1000, -1001, 1000, 1000, 1)); // top should >= -1000
testInvalidAreas(type, makeAreas(-1000, -1000, 1001, 1000, 1)); // right should <= 1000
testInvalidAreas(type, makeAreas(-1000, -1000, 1000, 1001, 1)); // bottom should <= 1000
testInvalidAreas(type, makeAreas(-1000, -1000, 1000, 1000, 0)); // weight should >= 1
testInvalidAreas(type, makeAreas(-1000, -1000, 1000, 1001, 1001)); // weight should <= 1000
testInvalidAreas(type, makeAreas(500, -1000, 500, 1000, 1)); // left should < right
testInvalidAreas(type, makeAreas(-1000, 500, 1000, 500, 1)); // top should < bottom
testInvalidAreas(type, makeAreas(500, -1000, 499, 1000, 1)); // left should < right
testInvalidAreas(type, makeAreas(-1000, 500, 100, 499, 1)); // top should < bottom
testInvalidAreas(type, makeAreas(-250, -250, 250, 250, -1)); // weight should >= 1
// Test when the number of areas exceeds maximum.
testInvalidAreas(type, makeAreas(-1000, -1000, 1000, 1000, 1000, maxNumAreas + 1));
}
private static ArrayList<Area> makeAreas(int left, int top, int right, int bottom, int weight) {
ArrayList<Area> areas = new ArrayList<Area>();
areas.add(new Area(new Rect(left, top, right, bottom), weight));
return areas;
}
private static ArrayList<Area> makeAreas(int left, int top, int right, int bottom,
int weight, int number) {
ArrayList<Area> areas = new ArrayList<Area>();
for (int i = 0; i < number; i++) {
areas.add(new Area(new Rect(left, top, right, bottom), weight));
}
return areas;
}
private static ArrayList<Area> makeAreas(int left1, int top1, int right1,
int bottom1, int weight1, int left2, int top2, int right2,
int bottom2, int weight2) {
ArrayList<Area> areas = new ArrayList<Area>();
areas.add(new Area(new Rect(left1, top1, right1, bottom1), weight1));
areas.add(new Area(new Rect(left2, top2, right2, bottom2), weight2));
return areas;
}
private void testValidAreas(int areaType, ArrayList<Area> areas) {
if (areaType == FOCUS_AREA) {
testValidFocusAreas(areas);
} else {
testValidMeteringAreas(areas);
}
}
private void testInvalidAreas(int areaType, ArrayList<Area> areas) {
if (areaType == FOCUS_AREA) {
testInvalidFocusAreas(areas);
} else {
testInvalidMeteringAreas(areas);
}
}
private void testValidFocusAreas(ArrayList<Area> areas) {
Parameters parameters = mCamera.getParameters();
parameters.setFocusAreas(areas);
mCamera.setParameters(parameters);
parameters = mCamera.getParameters();
assertEquals(areas, parameters.getFocusAreas());
mCamera.autoFocus(mAutoFocusCallback);
waitForFocusDone();
}
private void testInvalidFocusAreas(ArrayList<Area> areas) {
Parameters parameters = mCamera.getParameters();
List<Area> originalAreas = parameters.getFocusAreas();
try {
parameters.setFocusAreas(areas);
mCamera.setParameters(parameters);
fail("Should throw exception when focus area is invalid.");
} catch (RuntimeException e) {
parameters = mCamera.getParameters();
assertEquals(originalAreas, parameters.getFocusAreas());
}
}
private void testValidMeteringAreas(ArrayList<Area> areas) {
Parameters parameters = mCamera.getParameters();
parameters.setMeteringAreas(areas);
mCamera.setParameters(parameters);
parameters = mCamera.getParameters();
assertEquals(areas, parameters.getMeteringAreas());
}
private void testInvalidMeteringAreas(ArrayList<Area> areas) {
Parameters parameters = mCamera.getParameters();
List<Area> originalAreas = parameters.getMeteringAreas();
try {
parameters.setMeteringAreas(areas);
mCamera.setParameters(parameters);
fail("Should throw exception when metering area is invalid.");
} catch (RuntimeException e) {
parameters = mCamera.getParameters();
assertEquals(originalAreas, parameters.getMeteringAreas());
}
}
// Apps should be able to call startPreview in jpeg callback.
@UiThreadTest
public void testJpegCallbackStartPreview() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testJpegCallbackStartPreviewByCamera(id);
}
}
private void testJpegCallbackStartPreviewByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
mCamera.startPreview();
mCamera.takePicture(mShutterCallback, mRawPictureCallback, new JpegStartPreviewCallback());
waitForSnapshotDone();
terminateMessageLooper();
assertTrue(mJpegPictureCallbackResult);
}
private final class JpegStartPreviewCallback implements PictureCallback {
public void onPictureTaken(byte[] rawData, Camera camera) {
try {
camera.startPreview();
mJpegPictureCallbackResult = true;
} catch (Exception e) {
}
mSnapshotDone.open();
}
}
@UiThreadTest
public void testRecordingHint() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testRecordingHintByCamera(id);
}
}
private void testRecordingHintByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
Parameters parameters = mCamera.getParameters();
SurfaceHolder holder = getActivity().getSurfaceView().getHolder();
CamcorderProfile profile = CamcorderProfile.get(cameraId,
CamcorderProfile.QUALITY_LOW);
setPreviewSizeByProfile(parameters, profile);
// Test recording videos and taking pictures when the hint is off and on.
for (int i = 0; i < 2; i++) {
parameters.setRecordingHint(i == 0 ? false : true);
mCamera.setParameters(parameters);
mCamera.startPreview();
recordVideoSimple(profile, holder);
mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
waitForSnapshotDone();
assertTrue(mJpegPictureCallbackResult);
}
// Can change recording hint when the preview is active.
mCamera.startPreview();
parameters.setRecordingHint(false);
mCamera.setParameters(parameters);
parameters.setRecordingHint(true);
mCamera.setParameters(parameters);
terminateMessageLooper();
}
private void recordVideoSimple(CamcorderProfile profile,
SurfaceHolder holder) throws Exception {
mCamera.unlock();
MediaRecorder recorder = new MediaRecorder();
try {
recorder.setCamera(mCamera);
recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
recorder.setProfile(profile);
recorder.setOutputFile("/dev/null");
recorder.setPreviewDisplay(holder.getSurface());
recorder.prepare();
recorder.start();
Thread.sleep(2000);
recorder.stop();
} finally {
recorder.release();
mCamera.lock();
}
}
@UiThreadTest
public void testAutoExposureLock() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
initializeMessageLooper(id);
Parameters parameters = mCamera.getParameters();
boolean aeLockSupported = parameters.isAutoExposureLockSupported();
if (aeLockSupported) {
subtestLockCommon(AUTOEXPOSURE_LOCK);
subtestLockAdditionalAE();
}
terminateMessageLooper();
}
}
@UiThreadTest
public void testAutoWhiteBalanceLock() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
initializeMessageLooper(id);
Parameters parameters = mCamera.getParameters();
boolean awbLockSupported = parameters.isAutoWhiteBalanceLockSupported();
if (awbLockSupported) {
subtestLockCommon(AUTOWHITEBALANCE_LOCK);
subtestLockAdditionalAWB();
}
terminateMessageLooper();
}
}
@UiThreadTest
public void test3ALockInteraction() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
initializeMessageLooper(id);
Parameters parameters = mCamera.getParameters();
boolean locksSupported =
parameters.isAutoWhiteBalanceLockSupported() &&
parameters.isAutoExposureLockSupported();
if (locksSupported) {
subtestLockInteractions();
}
terminateMessageLooper();
}
}
private void subtestLockCommon(int type) {
// Verify lock is not set on open()
assert3ALockState("Lock not released after open()", type, false);
// Verify lock can be set, unset before preview
set3ALockState(true, type);
assert3ALockState("Lock could not be set before 1st preview!",
type, true);
set3ALockState(false, type);
assert3ALockState("Lock could not be unset before 1st preview!",
type, false);
// Verify preview start does not set lock
mCamera.startPreview();
assert3ALockState("Lock state changed by preview start!", type, false);
// Verify lock can be set, unset during preview
set3ALockState(true, type);
assert3ALockState("Lock could not be set during preview!", type, true);
set3ALockState(false, type);
assert3ALockState("Lock could not be unset during preview!",
type, false);
// Verify lock is not cleared by stop preview
set3ALockState(true, type);
mCamera.stopPreview();
assert3ALockState("Lock was cleared by stopPreview!", type, true);
// Verify that preview start does not clear lock
set3ALockState(true, type);
mCamera.startPreview();
assert3ALockState("Lock state changed by preview start!", type, true);
// Verify that taking a picture does not clear the lock
set3ALockState(true, type);
mCamera.takePicture(mShutterCallback, mRawPictureCallback,
mJpegPictureCallback);
waitForSnapshotDone();
assert3ALockState("Lock state was cleared by takePicture!", type, true);
mCamera.startPreview();
Parameters parameters = mCamera.getParameters();
for (String focusMode: parameters.getSupportedFocusModes()) {
// TODO: Test this for other focus modes as well, once agreement is
// reached on which ones it should apply to
if (!Parameters.FOCUS_MODE_AUTO.equals(focusMode) ) {
continue;
}
parameters.setFocusMode(focusMode);
mCamera.setParameters(parameters);
// Verify that autoFocus does not change the lock
set3ALockState(false, type);
mCamera.autoFocus(mAutoFocusCallback);
assert3ALockState("Lock was set by autoFocus in mode: " + focusMode, type, false);
assertTrue(waitForFocusDone());
assert3ALockState("Lock was set by autoFocus in mode: " + focusMode, type, false);
// Verify that cancelAutoFocus does not change the lock
mCamera.cancelAutoFocus();
assert3ALockState("Lock was set by cancelAutoFocus!", type, false);
// Verify that autoFocus does not change the lock
set3ALockState(true, type);
mCamera.autoFocus(mAutoFocusCallback);
assert3ALockState("Lock was cleared by autoFocus in mode: " + focusMode, type, true);
assertTrue(waitForFocusDone());
assert3ALockState("Lock was cleared by autoFocus in mode: " + focusMode, type, true);
// Verify that cancelAutoFocus does not change the lock
mCamera.cancelAutoFocus();
assert3ALockState("Lock was cleared by cancelAutoFocus!", type, true);
}
mCamera.stopPreview();
}
private void subtestLockAdditionalAE() {
// Verify that exposure compensation can be used while
// AE lock is active
mCamera.startPreview();
Parameters parameters = mCamera.getParameters();
parameters.setAutoExposureLock(true);
mCamera.setParameters(parameters);
parameters.setExposureCompensation(parameters.getMaxExposureCompensation());
mCamera.setParameters(parameters);
parameters = mCamera.getParameters();
assertTrue("Could not adjust exposure compensation with AE locked!",
parameters.getExposureCompensation() ==
parameters.getMaxExposureCompensation() );
parameters.setExposureCompensation(parameters.getMinExposureCompensation());
mCamera.setParameters(parameters);
parameters = mCamera.getParameters();
assertTrue("Could not adjust exposure compensation with AE locked!",
parameters.getExposureCompensation() ==
parameters.getMinExposureCompensation() );
mCamera.stopPreview();
}
private void subtestLockAdditionalAWB() {
// Verify that switching AWB modes clears AWB lock
mCamera.startPreview();
Parameters parameters = mCamera.getParameters();
String firstWb = null;
for ( String wbMode: parameters.getSupportedWhiteBalance() ) {
if (firstWb == null) {
firstWb = wbMode;
}
parameters.setWhiteBalance(firstWb);
mCamera.setParameters(parameters);
parameters.setAutoWhiteBalanceLock(true);
mCamera.setParameters(parameters);
parameters.setWhiteBalance(wbMode);
mCamera.setParameters(parameters);
if (firstWb == wbMode) {
assert3ALockState("AWB lock was cleared when WB mode was unchanged!",
AUTOWHITEBALANCE_LOCK, true);
} else {
assert3ALockState("Changing WB mode did not clear AWB lock!",
AUTOWHITEBALANCE_LOCK, false);
}
}
mCamera.stopPreview();
}
private void subtestLockInteractions() {
// Verify that toggling AE does not change AWB lock state
set3ALockState(false, AUTOWHITEBALANCE_LOCK);
set3ALockState(false, AUTOEXPOSURE_LOCK);
set3ALockState(true, AUTOEXPOSURE_LOCK);
assert3ALockState("Changing AE lock affected AWB lock!",
AUTOWHITEBALANCE_LOCK, false);
set3ALockState(false, AUTOEXPOSURE_LOCK);
assert3ALockState("Changing AE lock affected AWB lock!",
AUTOWHITEBALANCE_LOCK, false);
set3ALockState(true, AUTOWHITEBALANCE_LOCK);
set3ALockState(true, AUTOEXPOSURE_LOCK);
assert3ALockState("Changing AE lock affected AWB lock!",
AUTOWHITEBALANCE_LOCK, true);
set3ALockState(false, AUTOEXPOSURE_LOCK);
assert3ALockState("Changing AE lock affected AWB lock!",
AUTOWHITEBALANCE_LOCK, true);
// Verify that toggling AWB does not change AE lock state
set3ALockState(false, AUTOWHITEBALANCE_LOCK);
set3ALockState(false, AUTOEXPOSURE_LOCK);
set3ALockState(true, AUTOWHITEBALANCE_LOCK);
assert3ALockState("Changing AWB lock affected AE lock!",
AUTOEXPOSURE_LOCK, false);
set3ALockState(false, AUTOWHITEBALANCE_LOCK);
assert3ALockState("Changing AWB lock affected AE lock!",
AUTOEXPOSURE_LOCK, false);
set3ALockState(true, AUTOEXPOSURE_LOCK);
set3ALockState(true, AUTOWHITEBALANCE_LOCK);
assert3ALockState("Changing AWB lock affected AE lock!",
AUTOEXPOSURE_LOCK, true);
set3ALockState(false, AUTOWHITEBALANCE_LOCK);
assert3ALockState("Changing AWB lock affected AE lock!",
AUTOEXPOSURE_LOCK, true);
}
private void assert3ALockState(String msg, int type, boolean state) {
Parameters parameters = mCamera.getParameters();
switch (type) {
case AUTOEXPOSURE_LOCK:
assertTrue(msg, state == parameters.getAutoExposureLock());
break;
case AUTOWHITEBALANCE_LOCK:
assertTrue(msg, state == parameters.getAutoWhiteBalanceLock());
break;
default:
assertTrue("Unknown lock type " + type, false);
break;
}
}
private void set3ALockState(boolean state, int type) {
Parameters parameters = mCamera.getParameters();
switch (type) {
case AUTOEXPOSURE_LOCK:
parameters.setAutoExposureLock(state);
break;
case AUTOWHITEBALANCE_LOCK:
parameters.setAutoWhiteBalanceLock(state);
break;
default:
assertTrue("Unknown lock type "+type, false);
break;
}
mCamera.setParameters(parameters);
}
@UiThreadTest
public void testFaceDetection() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testFaceDetectionByCamera(id);
}
}
private void testFaceDetectionByCamera(int cameraId) throws Exception {
final int FACE_DETECTION_TEST_DURATION = 3000;
initializeMessageLooper(cameraId);
mCamera.startPreview();
Parameters parameters = mCamera.getParameters();
int maxNumOfFaces = parameters.getMaxNumDetectedFaces();
assertTrue(maxNumOfFaces >= 0);
if (maxNumOfFaces == 0) {
try {
mCamera.startFaceDetection();
fail("Should throw an exception if face detection is not supported.");
} catch (IllegalArgumentException e) {
// expected
}
terminateMessageLooper();
return;
}
mCamera.startFaceDetection();
try {
mCamera.startFaceDetection();
fail("Starting face detection twice should throw an exception");
} catch (RuntimeException e) {
// expected
}
FaceListener listener = new FaceListener();
mCamera.setFaceDetectionListener(listener);
// Sleep some time so the camera has chances to detect faces.
Thread.sleep(FACE_DETECTION_TEST_DURATION);
// The face callback runs in another thread. Release the camera and stop
// the looper. So we do not access the face array from two threads at
// the same time.
terminateMessageLooper();
// Check if the optional fields are supported.
boolean optionalFieldSupported = false;
Face firstFace = null;
for (Face[] faces: listener.mFacesArray) {
for (Face face: faces) {
if (face != null) firstFace = face;
}
}
if (firstFace != null) {
if (firstFace.id != -1 || firstFace.leftEye != null
|| firstFace.rightEye != null || firstFace.mouth != null) {
optionalFieldSupported = true;
}
}
// Verify the faces array.
for (Face[] faces: listener.mFacesArray) {
testFaces(faces, maxNumOfFaces, optionalFieldSupported);
}
// After taking a picture, face detection should be started again.
// Also make sure autofocus move callback is supported.
initializeMessageLooper(cameraId);
mCamera.setAutoFocusMoveCallback(mAutoFocusMoveCallback);
mCamera.startPreview();
mCamera.startFaceDetection();
mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
waitForSnapshotDone();
mCamera.startPreview();
mCamera.startFaceDetection();
terminateMessageLooper();
}
private class FaceListener implements FaceDetectionListener {
public ArrayList<Face[]> mFacesArray = new ArrayList<Face[]>();
@Override
public void onFaceDetection(Face[] faces, Camera camera) {
mFacesArray.add(faces);
}
}
private void testFaces(Face[] faces, int maxNumOfFaces,
boolean optionalFieldSupported) {
Rect bounds = new Rect(-1000, -1000, 1000, 1000);
assertNotNull(faces);
assertTrue(faces.length <= maxNumOfFaces);
for (int i = 0; i < faces.length; i++) {
Face face = faces[i];
Rect rect = face.rect;
// Check the bounds.
assertNotNull(rect);
assertTrue(rect.width() > 0);
assertTrue(rect.height() > 0);
assertTrue("Coordinates out of bounds. rect=" + rect,
bounds.contains(rect) || Rect.intersects(bounds, rect));
// Check the score.
assertTrue(face.score >= 1 && face.score <= 100);
// Check id, left eye, right eye, and the mouth.
// Optional fields should be all valid or none of them.
if (!optionalFieldSupported) {
assertEquals(-1, face.id);
assertNull(face.leftEye);
assertNull(face.rightEye);
assertNull(face.mouth);
} else {
assertTrue(face.id != -1);
assertNotNull(face.leftEye);
assertNotNull(face.rightEye);
assertNotNull(face.mouth);
assertTrue(bounds.contains(face.leftEye.x, face.leftEye.y));
assertTrue(bounds.contains(face.rightEye.x, face.rightEye.y));
assertTrue(bounds.contains(face.mouth.x, face.mouth.y));
// ID should be unique.
if (i != faces.length - 1) {
assertTrue(face.id != faces[i + 1].id);
}
}
}
}
@UiThreadTest
public void testVideoSnapshot() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testVideoSnapshotByCamera(id);
}
}
private static final int[] mCamcorderProfileList = {
CamcorderProfile.QUALITY_1080P,
CamcorderProfile.QUALITY_480P,
CamcorderProfile.QUALITY_720P,
CamcorderProfile.QUALITY_CIF,
CamcorderProfile.QUALITY_HIGH,
CamcorderProfile.QUALITY_LOW,
CamcorderProfile.QUALITY_QCIF,
CamcorderProfile.QUALITY_QVGA,
};
private void testVideoSnapshotByCamera(int cameraId) throws Exception {
initializeMessageLooper(cameraId);
Camera.Parameters parameters = mCamera.getParameters();
terminateMessageLooper();
if (!parameters.isVideoSnapshotSupported()) {
return;
}
SurfaceHolder holder = getActivity().getSurfaceView().getHolder();
for (int profileId: mCamcorderProfileList) {
if (!CamcorderProfile.hasProfile(cameraId, profileId)) {
continue;
}
initializeMessageLooper(cameraId);
// Set the preview size.
CamcorderProfile profile = CamcorderProfile.get(cameraId,
profileId);
setPreviewSizeByProfile(parameters, profile);
// Set the biggest picture size.
Size biggestSize = mCamera.new Size(-1, -1);
for (Size size: parameters.getSupportedPictureSizes()) {
if (biggestSize.width < size.width) {
biggestSize = size;
}
}
parameters.setPictureSize(biggestSize.width, biggestSize.height);
mCamera.setParameters(parameters);
mCamera.startPreview();
mCamera.unlock();
MediaRecorder recorder = new MediaRecorder();
try {
recorder.setCamera(mCamera);
recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
recorder.setProfile(profile);
recorder.setOutputFile("/dev/null");
recorder.setPreviewDisplay(holder.getSurface());
recorder.prepare();
recorder.start();
subtestTakePictureByCamera(true,
profile.videoFrameWidth, profile.videoFrameHeight);
testJpegExifByCamera(true);
testJpegThumbnailSizeByCamera(true,
profile.videoFrameWidth, profile.videoFrameHeight);
Thread.sleep(2000);
recorder.stop();
} finally {
recorder.release();
mCamera.lock();
}
mCamera.stopPreview();
terminateMessageLooper();
}
}
public void testPreviewCallbackWithPicture() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testPreviewCallbackWithPictureByCamera(id);
}
}
private void testPreviewCallbackWithPictureByCamera(int cameraId)
throws Exception {
initializeMessageLooper(cameraId);
SimplePreviewStreamCb callback = new SimplePreviewStreamCb(1);
mCamera.setPreviewCallback(callback);
Log.v(TAG, "Starting preview");
mCamera.startPreview();
// Wait until callbacks are flowing
for (int i = 0; i < 30; i++) {
assertTrue("testPreviewCallbackWithPicture: Not receiving preview callbacks!",
mPreviewDone.block( WAIT_FOR_COMMAND_TO_COMPLETE ) );
mPreviewDone.close();
}
// Now take a picture
Log.v(TAG, "Taking picture now");
Size pictureSize = mCamera.getParameters().getPictureSize();
mCamera.takePicture(mShutterCallback, mRawPictureCallback,
mJpegPictureCallback);
waitForSnapshotDone();
assertTrue("Shutter callback not received", mShutterCallbackResult);
assertTrue("Raw picture callback not received", mRawPictureCallbackResult);
assertTrue("Jpeg picture callback not received", mJpegPictureCallbackResult);
assertNotNull(mJpegData);
BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
bmpOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(mJpegData, 0, mJpegData.length, bmpOptions);
assertEquals(pictureSize.width, bmpOptions.outWidth);
assertEquals(pictureSize.height, bmpOptions.outHeight);
// Restart preview, confirm callbacks still happen
Log.v(TAG, "Restarting preview");
mCamera.startPreview();
for (int i = 0; i < 30; i++) {
assertTrue("testPreviewCallbackWithPicture: Not receiving preview callbacks!",
mPreviewDone.block( WAIT_FOR_COMMAND_TO_COMPLETE ) );
mPreviewDone.close();
}
mCamera.stopPreview();
terminateMessageLooper();
}
public void testEnableShutterSound() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
Log.v(TAG, "Camera id=" + id);
testEnableShutterSoundByCamera(id);
}
}
private void testEnableShutterSoundByCamera(int id) throws Exception {
CameraInfo info = new CameraInfo();
Camera.getCameraInfo(id, info);
initializeMessageLooper(id);
boolean result;
Log.v(TAG, "testEnableShutterSoundByCamera: canDisableShutterSound: " +
info.canDisableShutterSound);
result = mCamera.enableShutterSound(false);
assertTrue(result == info.canDisableShutterSound);
result = mCamera.enableShutterSound(true);
assertTrue(result);
terminateMessageLooper();
}
}