/* * Catroid: An on-device visual programming system for Android devices * Copyright (C) 2010-2016 The Catrobat Team * (<http://developer.catrobat.org/credits>) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * An additional term exception under section 7 of the GNU Affero * General Public License, version 3, is available at * http://developer.catrobat.org/license_additional_term * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.catrobat.catroid.camera; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.graphics.YuvImage; import android.hardware.Camera; import android.hardware.Camera.Parameters; import android.util.Log; import android.view.ViewGroup; import android.view.ViewParent; import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.common.ScreenValues; import org.catrobat.catroid.facedetection.FaceDetectionHandler; import org.catrobat.catroid.stage.CameraSurface; import org.catrobat.catroid.stage.DeviceCameraControl; import org.catrobat.catroid.stage.StageActivity; import org.catrobat.catroid.utils.FlashUtil; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; public final class CameraManager implements DeviceCameraControl, Camera.PreviewCallback { public class CameraInformation { protected int cameraId; protected boolean flashAvailable = false; protected CameraInformation(int cameraId, boolean flashAvailable) { this.cameraId = cameraId; this.flashAvailable = flashAvailable; } } public static final int TEXTURE_NAME = 1; private static final String TAG = CameraManager.class.getSimpleName(); private static CameraManager instance; private Camera currentCamera; private SurfaceTexture texture; private List<JpgPreviewCallback> callbacks = new ArrayList<>(); private int previewFormat; private int previewWidth; private int previewHeight; private CameraInformation defaultCameraInformation = null; private CameraInformation currentCameraInformation = null; private CameraInformation frontCameraInformation = null; private CameraInformation backCameraInformation = null; private int cameraCount = 0; StageActivity stageActivity = null; CameraSurface cameraSurface = null; public final Object cameraChangeLock = new Object(); private final Object cameraBaseLock = new Object(); private boolean wasRunning = false; //Mode used for CameraPreview public enum CameraState { notUsed, prepare, previewRunning, previewPaused, stopped } private CameraState state = CameraState.notUsed; public static CameraManager getInstance() { if (instance == null) { instance = new CameraManager(); } return instance; } private CameraManager() { cameraCount = Camera.getNumberOfCameras(); for (int id = 0; id < cameraCount; id++) { Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); Camera.getCameraInfo(id, cameraInfo); if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { backCameraInformation = new CameraInformation(id, hasCameraFlash(id)); currentCameraInformation = backCameraInformation; } if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { frontCameraInformation = new CameraInformation(id, hasCameraFlash(id)); currentCameraInformation = frontCameraInformation; } } defaultCameraInformation = currentCameraInformation; createTexture(); } public boolean hasBackCamera() { if (backCameraInformation == null) { return false; } return true; } public boolean hasFrontCamera() { if (frontCameraInformation == null) { return false; } return true; } private boolean hasCameraFlash(int cameraId) { try { Camera camera; camera = Camera.open(cameraId); if (camera == null) { return false; } Camera.Parameters parameters = camera.getParameters(); if (parameters.getFlashMode() == null) { camera.release(); return false; } List<String> supportedFlashModes = parameters.getSupportedFlashModes(); if (supportedFlashModes == null || supportedFlashModes.isEmpty() || (supportedFlashModes.size() == 1 && supportedFlashModes.get(0).equals(Camera.Parameters.FLASH_MODE_OFF))) { camera.release(); return false; } camera.release(); return true; } catch (Exception exception) { Log.e(TAG, "failed checking for flash", exception); return false; } } public boolean isCurrentCameraFacingBack() { return currentCameraInformation == backCameraInformation; } public boolean isCurrentCameraFacingFront() { return currentCameraInformation == frontCameraInformation; } public boolean isCameraActive() { return state == CameraState.previewRunning; } public void setToDefaultCamera() { updateCamera(defaultCameraInformation); } public boolean setToBackCamera() { if (!hasBackCamera()) { return false; } updateCamera(backCameraInformation); return true; } public boolean setToFrontCamera() { if (!hasFrontCamera()) { return false; } updateCamera(frontCameraInformation); return true; } private void updateCamera(CameraInformation cameraInformation) { synchronized (cameraChangeLock) { CameraState currentState = state; if (cameraInformation == currentCameraInformation) { return; } currentCameraInformation = cameraInformation; if (FlashUtil.isOn() && !currentCameraInformation.flashAvailable) { Log.w(TAG, "destroy Stage because flash isOn while changing camera"); CameraManager.getInstance().destroyStage(); return; } FlashUtil.pauseFlash(); FaceDetectionHandler.pauseFaceDetection(); releaseCamera(); startCamera(); FaceDetectionHandler.resumeFaceDetection(); FlashUtil.resumeFlash(); if (currentState == CameraState.prepare || currentState == CameraState.previewRunning) { changeCameraAsync(); } } } public boolean startCamera() { synchronized (cameraBaseLock) { if (currentCamera == null) { boolean success = createCamera(); if (!success) { return false; } } Camera.Parameters cameraParameters = currentCamera.getParameters(); previewFormat = cameraParameters.getPreviewFormat(); previewWidth = cameraParameters.getPreviewSize().width; previewHeight = cameraParameters.getPreviewSize().height; try { currentCamera.startPreview(); } catch (Exception e) { Log.e(TAG, e.getMessage()); return false; } return true; } } public void releaseCamera() { synchronized (cameraBaseLock) { if (currentCamera == null) { return; } currentCamera.setPreviewCallback(null); currentCamera.stopPreview(); currentCamera.release(); currentCamera = null; } } private boolean createCamera() { if (currentCamera != null) { return false; } try { currentCamera = Camera.open(currentCameraInformation.cameraId); if (ProjectManager.getInstance().isCurrentProjectLandscapeMode()) { currentCamera.setDisplayOrientation(0); } else { currentCamera.setDisplayOrientation(90); } Camera.Parameters cameraParameters = currentCamera.getParameters(); List<Camera.Size> previewSizes = cameraParameters.getSupportedPreviewSizes(); int previewHeight = 0; int previewWidth = 0; for (int i = 0; i < previewSizes.size() && previewSizes.get(i).height <= ScreenValues.SCREEN_HEIGHT; i++) { if (previewSizes.get(i).height > previewHeight) { previewHeight = previewSizes.get(i).height; previewWidth = previewSizes.get(i).width; } } cameraParameters.setPreviewSize(previewWidth, previewHeight); currentCamera.setParameters(cameraParameters); } catch (RuntimeException runtimeException) { Log.e(TAG, "Creating camera caused an exception", runtimeException); return false; } currentCamera.setPreviewCallbackWithBuffer(this); if (texture != null) { try { setTexture(); } catch (IOException iOException) { Log.e(TAG, "Setting preview texture failed!", iOException); return false; } } return true; } public boolean hasCurrentCameraFlash() { return currentCameraInformation.flashAvailable; } public CameraState getState() { return state; } public Camera getCurrentCamera() { return currentCamera; } public void addOnJpgPreviewFrameCallback(JpgPreviewCallback callback) { if (callbacks.contains(callback)) { return; } callbacks.add(callback); } public void removeOnJpgPreviewFrameCallback(JpgPreviewCallback callback) { callbacks.remove(callback); } @Override public void onPreviewFrame(byte[] data, Camera camera) { if (callbacks.size() == 0) { return; } byte[] jpgData = getDecodeableBytesFromCameraFrame(data); for (JpgPreviewCallback callback : callbacks) { callback.onFrame(jpgData); } } private byte[] getDecodeableBytesFromCameraFrame(byte[] cameraData) { byte[] decodableBytes; YuvImage image = new YuvImage(cameraData, previewFormat, previewWidth, previewHeight, null); ByteArrayOutputStream out = new ByteArrayOutputStream(); image.compressToJpeg(new Rect(0, 0, previewWidth, previewHeight), 50, out); decodableBytes = out.toByteArray(); return decodableBytes; } private void createTexture() { texture = new SurfaceTexture(TEXTURE_NAME); } private void setTexture() throws IOException { currentCamera.setPreviewTexture(texture); } public void setFlashParams(Parameters flash) { Log.d(TAG, flash.toString()); if (currentCamera != null && flash != null) { Parameters current = currentCamera.getParameters(); current.setFlashMode(flash.getFlashMode()); currentCamera.setParameters(current); } } @Override public void prepareCamera() { state = CameraState.previewRunning; if (cameraSurface == null) { cameraSurface = new CameraSurface(stageActivity); } ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup .LayoutParams.WRAP_CONTENT); //java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. ViewGroup parent = (ViewGroup) cameraSurface.getParent(); if (parent != null) { parent.removeView(cameraSurface); } stageActivity.addContentView(cameraSurface, params); startCamera(); } @Override public void stopPreview() { state = CameraState.notUsed; if (cameraSurface != null) { ViewParent parentView = cameraSurface.getParent(); if (parentView instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) parentView; viewGroup.removeView(cameraSurface); } cameraSurface = null; try { if (currentCamera != null) { currentCamera.stopPreview(); setTexture(); } if (FaceDetectionHandler.isFaceDetectionRunning() || FlashUtil.isAvailable()) { currentCamera.startPreview(); } } catch (IOException e) { Log.e(TAG, "reset Texture failed at stopPreview"); Log.e(TAG, e.getMessage()); } } } @Override public void pausePreview() { if (state == CameraState.previewRunning) { wasRunning = true; } stopPreview(); state = CameraState.previewPaused; } @Override public void resumePreview() { prepareCamera(); wasRunning = false; } public void pauseForScene() { Runnable r = new Runnable() { public void run() { pausePreview(); } }; stageActivity.post(r); wasRunning = false; state = CameraState.notUsed; } public void resumeForScene() { Runnable r = new Runnable() { public void run() { resumePreview(); } }; stageActivity.post(r); } @Override public void prepareCameraAsync() { Runnable r = new Runnable() { public void run() { prepareCamera(); } }; stageActivity.post(r); } @Override public void stopPreviewAsync() { Runnable r = new Runnable() { public void run() { stopPreview(); } }; stageActivity.post(r); } @Override public void pausePreviewAsync() { if (state == CameraState.previewPaused || state == CameraState.stopped || state == CameraState.notUsed) { return; } Runnable r = new Runnable() { public void run() { pausePreview(); } }; stageActivity.post(r); } @Override public void resumePreviewAsync() { if (state != CameraState.previewPaused || !wasRunning) { return; } Runnable r = new Runnable() { public void run() { resumePreview(); } }; stageActivity.post(r); } @Override public boolean isReady() { if (currentCamera != null) { return true; } return false; } public void updatePreview(CameraState newState) { synchronized (cameraChangeLock) { if (state == CameraState.previewRunning && newState != CameraState.prepare) { stopPreviewAsync(); } else if (state == CameraState.notUsed && newState != CameraState.stopped) { prepareCameraAsync(); } } } public void changeCameraAsync() { Runnable r = new Runnable() { public void run() { changeCamera(); } }; stageActivity.post(r); } public void changeCamera() { stopPreview(); prepareCamera(); } public void setStageActivity(StageActivity stageActivity) { this.stageActivity = stageActivity; } public void destroyStage() { if (this.stageActivity != null) { stageActivity.destroy(); } } public boolean switchToCameraWithFlash() { if (hasCurrentCameraFlash()) { return true; } if (frontCameraInformation.flashAvailable) { updateCamera(frontCameraInformation); return true; } if (backCameraInformation.flashAvailable) { updateCamera(backCameraInformation); return true; } return false; } }