package de.jeisfeld.augendiagnoselib.util; import android.graphics.ImageFormat; import android.hardware.Camera; import android.hardware.Camera.CameraInfo; import android.hardware.Camera.Parameters; import android.hardware.Camera.PictureCallback; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup.LayoutParams; import android.widget.FrameLayout; import java.util.ArrayList; import java.util.List; import de.jeisfeld.augendiagnoselib.Application; import de.jeisfeld.augendiagnoselib.activities.CameraActivity.CameraCallback; import de.jeisfeld.augendiagnoselib.activities.CameraActivity.FlashMode; import de.jeisfeld.augendiagnoselib.activities.CameraActivity.FocusMode; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * A handler to take pictures with the camera via the old Camera interface. */ @SuppressWarnings("deprecation") public class Camera1Handler implements CameraHandler { /** * The camera used by the activity. */ @Nullable private Camera mCamera; /** * A flag indicating if the preview is active. */ private boolean mIsInPreview = false; /** * A flag indicating if preview is requested. */ private boolean mIsPreviewRequested = false; /** * A flag indicating if the camera is configured. */ private boolean mIsCameraConfigured = false; /** * A flag indicating if the surface is created. */ private boolean mIsSurfaceCreated = false; /** * The current flashlight mode. */ @Nullable private String mCurrentFlashlightMode = null; /** * The current focus mode. */ @Nullable private String mCurrentFocusMode = null; /** * The FrameLayout holding the preview. */ private final FrameLayout mPreviewFrame; /** * The surface of the preview. */ @Nullable private SurfaceHolder mPreviewHolder = null; /** * The handler called when the picture is taken. */ private final CameraCallback mCameraCallback; /** * The current relative zoom. */ private float mCurrentRelativeZoom = 0; /** * The maximal digital zoom. */ private float mMaxDigitalZoom = 1; /** * Constructor of the Camera1Handler. * * @param previewFrame The FrameLayout holding the preview. * @param preview The view holding the preview. * @param cameraCallback The handler called when the picture is taken. */ public Camera1Handler(final FrameLayout previewFrame, @NonNull final SurfaceView preview, final CameraCallback cameraCallback) { this.mPreviewFrame = previewFrame; this.mCameraCallback = cameraCallback; mPreviewHolder = preview.getHolder(); mPreviewHolder.addCallback(getSurfaceCallback()); mPreviewHolder.setKeepScreenOn(true); } @Override public final void setFlashlightMode(@Nullable final FlashMode flashlightMode) { if (flashlightMode == null) { mCurrentFlashlightMode = null; return; } switch (flashlightMode) { case OFF: mCurrentFlashlightMode = Parameters.FLASH_MODE_OFF; break; case ON: mCurrentFlashlightMode = Parameters.FLASH_MODE_ON; break; case TORCH: mCurrentFlashlightMode = Parameters.FLASH_MODE_TORCH; break; default: mCurrentFlashlightMode = null; break; } updateFlashlight(); } @Override public final void setFocusMode(@Nullable final FocusMode focusMode) { if (focusMode == null) { mCurrentFocusMode = null; return; } switch (focusMode) { case AUTO: mCurrentFocusMode = Parameters.FOCUS_MODE_AUTO; break; case MACRO: mCurrentFocusMode = Parameters.FOCUS_MODE_MACRO; break; case CONTINUOUS: mCurrentFocusMode = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE; break; default: mCurrentFocusMode = null; break; } updateFocus(); } @Override public final void setRelativeZoom(final float relativeZoom) { mCurrentRelativeZoom = relativeZoom; updateZoom(); } /** * Update the zoom. */ private void updateZoom() { if (mCamera != null) { Parameters parameters = mCamera.getParameters(); if (parameters.isZoomSupported()) { int zoomFactor = (int) ((mMaxDigitalZoom + 0.99999) * mCurrentRelativeZoom); // MAGIC_NUMBER parameters.setZoom(zoomFactor); } mCamera.setParameters(parameters); } } /** * Update the flashlight. */ private void updateFlashlight() { if (mCamera != null) { Parameters parameters = mCamera.getParameters(); if (parameters.getSupportedFlashModes().contains(mCurrentFlashlightMode)) { parameters.setFlashMode(mCurrentFlashlightMode); } mCamera.setParameters(parameters); } } /** * Update the focus. */ private void updateFocus() { if (mCamera != null) { Parameters parameters = mCamera.getParameters(); parameters.setFocusMode(mCurrentFocusMode); mCamera.setParameters(parameters); } } /** * Initialize the camera. */ private void initPreview() { if (mCamera != null && mPreviewHolder.getSurface() != null) { try { mCamera.setPreviewDisplay(mPreviewHolder); } catch (Exception e) { mCameraCallback.onCameraError("Cannot set preview", "pre1", e); return; } if (!mIsCameraConfigured) { Parameters parameters = mCamera.getParameters(); Camera.Size pictureSize = getBiggestPictureSize(parameters); if (pictureSize == null) { return; } Camera.Size previewSize = getBestPreviewSize(((float) pictureSize.width) / pictureSize.height, parameters); if (previewSize == null) { return; } parameters.setPreviewSize(previewSize.width, previewSize.height); parameters.setPictureSize(pictureSize.width, pictureSize.height); parameters.setPictureFormat(ImageFormat.JPEG); updateAvailableModes(parameters.getSupportedFocusModes()); mCameraCallback.updateAvailableZoom(parameters.isZoomSupported()); mMaxDigitalZoom = parameters.getMaxZoom(); try { parameters.setFocusMode(mCurrentFocusMode == null ? Camera.Parameters.FOCUS_MODE_AUTO : mCurrentFocusMode); mCamera.setParameters(parameters); } catch (Exception e) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); mCamera.setParameters(parameters); } if (mCurrentFlashlightMode != null) { updateFlashlight(); } if (parameters.isZoomSupported()) { updateZoom(); } // Resize frame to match aspect ratio float aspectRatio = ((float) pictureSize.width) / pictureSize.height; LayoutParams layoutParams = mPreviewFrame.getLayoutParams(); if (mPreviewFrame.getWidth() > aspectRatio * mPreviewFrame.getHeight()) { layoutParams.width = Math.round(mPreviewFrame.getHeight() * aspectRatio); layoutParams.height = mPreviewFrame.getHeight(); } else { layoutParams.width = mPreviewFrame.getWidth(); layoutParams.height = Math.round(mPreviewFrame.getWidth() / aspectRatio); } mPreviewFrame.setLayoutParams(layoutParams); mIsCameraConfigured = true; } } } @Override public final void startPreview() { if (Parameters.FLASH_MODE_ON.equals(mCurrentFlashlightMode)) { stopPreview(); } mIsPreviewRequested = true; if (mIsInPreview) { return; } if (mCamera == null) { mCamera = getCameraInstance(); if (mCamera == null) { mCameraCallback.onCameraError("Cannot open camera", "ope1", null); return; } } if (mIsSurfaceCreated && !mIsCameraConfigured) { initPreview(); } if (mIsCameraConfigured) { mCamera.startPreview(); mIsInPreview = true; } } @Override public final void stopPreview() { mIsPreviewRequested = false; if (mCamera != null) { if (mIsInPreview) { mCamera.stopPreview(); } mCamera.release(); mCamera = null; mIsCameraConfigured = false; mIsInPreview = false; } } @Override public final void takePicture() { mCamera.takePicture(null, null, mPhotoCallback); mCameraCallback.onTakingPicture(); } /** * Creage the callback client for the preview. * * @return the callback client. */ @NonNull private SurfaceHolder.Callback getSurfaceCallback() { return new SurfaceHolder.Callback() { @Override public void surfaceCreated(final SurfaceHolder holder) { mIsSurfaceCreated = true; try { if (mIsPreviewRequested) { startPreview(); } } catch (Exception e) { mCameraCallback.onCameraError("Failed to start preview", "pre2", e); } } @Override public void surfaceChanged(final SurfaceHolder holder, final int format, final int width, final int height) { // do nothing. } @Override public void surfaceDestroyed(final SurfaceHolder holder) { stopPreview(); mIsSurfaceCreated = false; } }; } /** * Update the available focus modes. * * @param supportedFocusModes the supported focus modes. */ private void updateAvailableModes(@NonNull final List<String> supportedFocusModes) { List<FocusMode> focusModes = new ArrayList<>(); for (String focusMode : supportedFocusModes) { if (Camera.Parameters.FOCUS_MODE_AUTO.equals(focusMode)) { focusModes.add(FocusMode.AUTO); } else if (Camera.Parameters.FOCUS_MODE_MACRO.equals(focusMode)) { focusModes.add(FocusMode.MACRO); } else if (Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(focusMode)) { focusModes.add(FocusMode.CONTINUOUS); } } mCameraCallback.updateAvailableModes(focusModes); } /** * The callback called when pictures are taken. */ private final PictureCallback mPhotoCallback = new PictureCallback() { @Override @SuppressFBWarnings(value = "VA_PRIMITIVE_ARRAY_PASSED_TO_OBJECT_VARARG", justification = "Intentionally sending byte array") public void onPictureTaken(final byte[] data, final Camera photoCamera) { mIsInPreview = false; mCameraCallback.onPictureTaken(data); } }; /** * Get a camera instance. * * @return The camera instance. */ private static Camera getCameraInstance() { int cameraId = findBackFacingCamera(); if (cameraId < 0) { return null; } try { return Camera.open(cameraId); } catch (Exception e) { Log.e(Application.TAG, "Could not open camera: " + e.getMessage(), e); return null; } } /** * Find the id of a front facing camera. * * @return The camera id. */ private static int findBackFacingCamera() { int numberOfCameras = Camera.getNumberOfCameras(); if (numberOfCameras == 0) { return -1; } for (int cameraId = 0; cameraId < numberOfCameras; cameraId++) { CameraInfo info = new CameraInfo(); Camera.getCameraInfo(cameraId, info); if (info.facing == CameraInfo.CAMERA_FACING_BACK) { return cameraId; } } // no back facing camera found. return 0; } /** * Get the best preview size. * * @param aspectRatio The aspect ratio of the picture. * @param parameters The camera parameters. * @return The best preview size. */ @Nullable private static Camera.Size getBestPreviewSize(final float aspectRatio, @NonNull final Camera.Parameters parameters) { Camera.Size result = null; float bestAspectRatioDifference = 0; for (Camera.Size size : parameters.getSupportedPreviewSizes()) { float newAspectRatioDifference = Math.abs(((float) size.width) / size.height - aspectRatio); if (result == null) { result = size; bestAspectRatioDifference = newAspectRatioDifference; } else { if ((newAspectRatioDifference < bestAspectRatioDifference - 0.01f) // MAGIC_NUMBER || (size.width > result.width && newAspectRatioDifference < bestAspectRatioDifference + 0.01f)) { // MAGIC_NUMBER result = size; bestAspectRatioDifference = newAspectRatioDifference; } } } return result; } /** * Get the biggest possible picture size. * * @param parameters The camera parameters. * @return The biggest picture size. */ @Nullable private static Camera.Size getBiggestPictureSize(@NonNull final Camera.Parameters parameters) { Camera.Size result = null; for (Camera.Size size : parameters.getSupportedPictureSizes()) { if (result == null) { result = size; } else { int resultSize = Math.min(result.width, result.height); int newSize = Math.min(size.width, size.height); if (newSize > resultSize || (newSize == resultSize && size.width > size.height)) { result = size; } } } return result; } }