From 1095ed56e58396838dea331a246c3736073ffbf1 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 19 Mar 2017 21:21:21 +0100 Subject: [PATCH] convert Preview to kotlin --- .../com/simplemobiletools/camera/Preview.java | 666 ------------------ .../com/simplemobiletools/camera/Preview.kt | 636 +++++++++++++++++ 2 files changed, 636 insertions(+), 666 deletions(-) delete mode 100644 app/src/main/java/com/simplemobiletools/camera/Preview.java create mode 100644 app/src/main/kotlin/com/simplemobiletools/camera/Preview.kt diff --git a/app/src/main/java/com/simplemobiletools/camera/Preview.java b/app/src/main/java/com/simplemobiletools/camera/Preview.java deleted file mode 100644 index f7db6a05..00000000 --- a/app/src/main/java/com/simplemobiletools/camera/Preview.java +++ /dev/null @@ -1,666 +0,0 @@ -package com.simplemobiletools.camera; - -import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.Camera; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.MediaRecorder; -import android.media.MediaScannerConnection; -import android.net.Uri; -import android.os.Environment; -import android.os.Handler; -import android.os.ParcelFileDescriptor; -import android.support.v4.provider.DocumentFile; -import android.util.Log; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.View; -import android.view.ViewGroup; - -import com.simplemobiletools.camera.activities.MainActivity; - -import java.io.File; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -public class Preview extends ViewGroup - implements SurfaceHolder.Callback, View.OnTouchListener, View.OnClickListener, MediaScannerConnection.OnScanCompletedListener { - public static final int PHOTO_PREVIEW_LENGTH = 1000; - private static final String TAG = Preview.class.getSimpleName(); - private static final int FOCUS_AREA_SIZE = 100; - - private static SurfaceHolder mSurfaceHolder; - private static Camera mCamera; - private static List mSupportedPreviewSizes; - private static SurfaceView mSurfaceView; - private static Camera.Size mPreviewSize; - private static MainActivity mActivity; - private static Camera.Parameters mParameters; - private static PreviewListener mCallback; - private static MediaRecorder mRecorder; - private static String mCurrVideoPath; - private static Point mScreenSize; - private static Uri mTargetUri; - private static Context mContext; - private static ScaleGestureDetector mScaleGestureDetector; - private static List mZoomRatios; - private static Config mConfig; - - private static boolean mCanTakePicture; - private static boolean mIsFlashEnabled; - private static boolean mIsRecording; - private static boolean mIsVideoMode; - private static boolean mIsSurfaceCreated; - private static boolean mSwitchToVideoAsap; - private static boolean mSetupPreviewAfterMeasure; - private static boolean mForceAspectRatio; - private static boolean mWasZooming; - private static int mLastClickX; - private static int mLastClickY; - private static int mInitVideoRotation; - private static int mCurrCameraId; - private static int mMaxZoom; - - public Preview(Context context) { - super(context); - } - - public Preview(MainActivity activity, SurfaceView surfaceView, PreviewListener previewListener) { - super(activity); - - mActivity = activity; - mCallback = previewListener; - mSurfaceView = surfaceView; - mSurfaceHolder = mSurfaceView.getHolder(); - mSurfaceHolder.addCallback(this); - mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - mCanTakePicture = false; - mSurfaceView.setOnTouchListener(this); - mSurfaceView.setOnClickListener(this); - mIsFlashEnabled = false; - mIsVideoMode = false; - mIsSurfaceCreated = false; - mSetupPreviewAfterMeasure = false; - mCurrVideoPath = ""; - mScreenSize = Utils.Companion.getScreenSize(mActivity); - mContext = getContext(); - initGestureDetector(); - } - - public void trySwitchToVideo() { - if (mIsSurfaceCreated) { - initRecorder(); - } else { - mSwitchToVideoAsap = true; - } - } - - public boolean setCamera(int cameraId) { - mCurrCameraId = cameraId; - Camera newCamera; - try { - newCamera = Camera.open(cameraId); - mCallback.setIsCameraAvailable(true); - } catch (Exception e) { - Utils.Companion.showToast(mContext, R.string.camera_open_error); - Log.e(TAG, "setCamera open " + e.getMessage()); - mCallback.setIsCameraAvailable(false); - return false; - } - - if (mCamera == newCamera) { - return false; - } - - releaseCamera(); - mCamera = newCamera; - if (mCamera != null) { - mParameters = mCamera.getParameters(); - mMaxZoom = mParameters.getMaxZoom(); - mZoomRatios = mParameters.getZoomRatios(); - mSupportedPreviewSizes = mParameters.getSupportedPreviewSizes(); - Collections.sort(mSupportedPreviewSizes, new SizesComparator()); - requestLayout(); - invalidate(); - mSetupPreviewAfterMeasure = true; - - final List focusModes = mParameters.getSupportedFocusModes(); - if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) - mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); - - final int rotation = Utils.Companion.getPreviewRotation(mActivity, cameraId); - mCamera.setDisplayOrientation(rotation); - mCamera.setParameters(mParameters); - - if (mCanTakePicture) { - try { - mCamera.setPreviewDisplay(mSurfaceHolder); - } catch (IOException e) { - Log.e(TAG, "setCamera setPreviewDisplay " + e.getMessage()); - return false; - } - } - - mCallback.setFlashAvailable(Utils.Companion.hasFlash(mCamera)); - } - - if (mIsVideoMode) { - initRecorder(); - } - - mConfig = Config.Companion.newInstance(mContext); - - return true; - } - - public void setTargetUri(Uri uri) { - mTargetUri = uri; - } - - private void initGestureDetector() { - mScaleGestureDetector = new ScaleGestureDetector(mContext, new ScaleGestureDetector.SimpleOnScaleGestureListener() { - @Override - public boolean onScale(ScaleGestureDetector detector) { - int zoomFactor = mParameters.getZoom(); - float zoomRatio = mZoomRatios.get(zoomFactor) / 100.f; - zoomRatio *= detector.getScaleFactor(); - - int newZoomFactor = zoomFactor; - if (zoomRatio <= 1.f) { - newZoomFactor = 0; - } else if (zoomRatio >= mZoomRatios.get(mMaxZoom) / 100.f) { - newZoomFactor = mMaxZoom; - } else { - if (detector.getScaleFactor() > 1.f) { - for (int i = zoomFactor; i < mZoomRatios.size(); i++) { - if (mZoomRatios.get(i) / 100.0f >= zoomRatio) { - newZoomFactor = i; - break; - } - } - } else { - for (int i = zoomFactor; i >= 0; i--) { - if (mZoomRatios.get(i) / 100.0f <= zoomRatio) { - newZoomFactor = i; - break; - } - } - } - } - - newZoomFactor = Math.max(newZoomFactor, 0); - newZoomFactor = Math.min(mMaxZoom, newZoomFactor); - - mParameters.setZoom(newZoomFactor); - if (mCamera != null) - mCamera.setParameters(mParameters); - - return true; - } - - @Override - public void onScaleEnd(ScaleGestureDetector detector) { - super.onScaleEnd(detector); - mWasZooming = true; - mSurfaceView.setSoundEffectsEnabled(false); - mParameters.setFocusAreas(null); - } - }); - } - - public void takePicture() { - if (mCanTakePicture) { - int rotation = Utils.Companion.getMediaRotation(mActivity, mCurrCameraId); - rotation += Utils.Companion.compensateDeviceRotation(mCurrCameraId, mCallback.getCurrentOrientation()); - - /*final Camera.Size maxSize = getOptimalPictureSize(); - mParameters.setPictureSize(maxSize.width, maxSize.height);*/ - mParameters.setRotation(rotation % 360); - - if (mConfig.isSoundEnabled()) { - final AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); - final int volume = audioManager.getStreamVolume(AudioManager.STREAM_SYSTEM); - if (volume != 0) { - final MediaPlayer mp = MediaPlayer.create(getContext(), Uri.parse("file:///system/media/audio/ui/camera_click.ogg")); - if (mp != null) - mp.start(); - } - } - - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { - mCamera.enableShutterSound(false); - } - - mCamera.setParameters(mParameters); - mCamera.takePicture(null, null, takePictureCallback); - } - mCanTakePicture = false; - } - - private Camera.PictureCallback takePictureCallback = new Camera.PictureCallback() { - @Override - public void onPictureTaken(byte[] data, Camera cam) { - if (mConfig.isShowPreviewEnabled()) { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - resumePreview(); - } - }, PHOTO_PREVIEW_LENGTH); - } else { - resumePreview(); - } - - new PhotoProcessor(mActivity, mTargetUri).execute(data); - } - }; - - private void resumePreview() { - if (mCamera != null) { - mCamera.startPreview(); - } - - mCanTakePicture = true; - } - - public List getSupportedVideoSizes() { - if (mParameters.getSupportedVideoSizes() != null) { - return mParameters.getSupportedVideoSizes(); - } else { - return mParameters.getSupportedPreviewSizes(); - } - } - - private void focusArea(final boolean takePictureAfter) { - if (mCamera == null) - return; - - mCamera.cancelAutoFocus(); - final Rect focusRect = calculateFocusArea(mLastClickX, mLastClickY); - if (mParameters.getMaxNumFocusAreas() > 0) { - final List focusAreas = new ArrayList<>(1); - focusAreas.add(new Camera.Area(focusRect, 1000)); - mParameters.setFocusAreas(focusAreas); - mCallback.drawFocusRect(mLastClickX, mLastClickY); - } - - mCamera.setParameters(mParameters); - mCamera.autoFocus(new Camera.AutoFocusCallback() { - @Override - public void onAutoFocus(boolean success, Camera camera) { - camera.cancelAutoFocus(); - final List focusModes = mParameters.getSupportedFocusModes(); - if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) - mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); - - camera.setParameters(mParameters); - if (takePictureAfter) { - takePicture(); - } - } - }); - } - - private Rect calculateFocusArea(float x, float y) { - int left = Float.valueOf((x / mSurfaceView.getWidth()) * 2000 - 1000).intValue(); - int top = Float.valueOf((y / mSurfaceView.getHeight()) * 2000 - 1000).intValue(); - - int tmp = left; - left = top; - top = -tmp; - - final int rectLeft = Math.max(left - FOCUS_AREA_SIZE / 2, -1000); - final int rectTop = Math.max(top - FOCUS_AREA_SIZE / 2, -1000); - final int rectRight = Math.min(left + FOCUS_AREA_SIZE / 2, 1000); - final int rectBottom = Math.min(top + FOCUS_AREA_SIZE / 2, 1000); - return new Rect(rectLeft, rectTop, rectRight, rectBottom); - } - - public void releaseCamera() { - stopRecording(); - - if (mCamera != null) { - mCamera.stopPreview(); - mCamera.release(); - mCamera = null; - } - - cleanupRecorder(); - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - mIsSurfaceCreated = true; - try { - if (mCamera != null) { - mCamera.setPreviewDisplay(mSurfaceHolder); - } - - if (mSwitchToVideoAsap) - initRecorder(); - } catch (IOException e) { - Log.e(TAG, "surfaceCreated IOException " + e.getMessage()); - } - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - mIsSurfaceCreated = true; - - if (mIsVideoMode) { - initRecorder(); - } - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - mIsSurfaceCreated = false; - if (mCamera != null) { - mCamera.stopPreview(); - } - - cleanupRecorder(); - } - - private void setupPreview() { - mCanTakePicture = true; - if (mCamera != null && mPreviewSize != null) { - mParameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height); - mCamera.setParameters(mParameters); - mCamera.startPreview(); - } - } - - private void cleanupRecorder() { - if (mRecorder != null) { - if (mIsRecording) { - stopRecording(); - } - - mRecorder.release(); - mRecorder = null; - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - - } - - private Camera.Size getOptimalPreviewSize(List sizes, int width, int height) { - Camera.Size result = null; - for (Camera.Size size : sizes) { - if (size.width <= width && size.height <= height) { - if (result == null) { - result = size; - } else { - int resultArea = result.width * result.height; - int newArea = size.width * size.height; - - if (newArea > resultArea) { - result = size; - } - } - } - } - - return result; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(mScreenSize.x, mScreenSize.y); - - if (mSupportedPreviewSizes != null) { - // for simplicity lets assume that most displays are 16:9 and the remaining ones are 4:3 - // always set 16:9 for videos as many devices support 4:3 only in low quality - if (mForceAspectRatio || mIsVideoMode) { - mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, mScreenSize.y, mScreenSize.x); - } else { - final int newRatioHeight = (int) (mScreenSize.x * ((double) 4 / 3)); - setMeasuredDimension(mScreenSize.x, newRatioHeight); - mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, newRatioHeight, mScreenSize.x); - } - final LayoutParams lp = mSurfaceView.getLayoutParams(); - - // make sure to occupy whole width in every case - if (mScreenSize.x > mPreviewSize.height) { - final float ratio = (float) mScreenSize.x / mPreviewSize.height; - lp.width = (int) (mPreviewSize.height * ratio); - if (mForceAspectRatio || mIsVideoMode) { - lp.height = mScreenSize.y; - } else { - lp.height = (int) (mPreviewSize.width * ratio); - } - } else { - lp.width = mPreviewSize.height; - lp.height = mPreviewSize.width; - } - - if (mSetupPreviewAfterMeasure) { - mSetupPreviewAfterMeasure = false; - if (mCamera != null) - mCamera.stopPreview(); - - setupPreview(); - } - } - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - mLastClickX = (int) event.getX(); - mLastClickY = (int) event.getY(); - - if (mMaxZoom > 0) - mScaleGestureDetector.onTouchEvent(event); - return false; - } - - public void enableFlash() { - mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); - mCamera.setParameters(mParameters); - mIsFlashEnabled = true; - } - - public void disableFlash() { - mIsFlashEnabled = false; - mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); - mCamera.setParameters(mParameters); - } - - public void initPhotoMode() { - stopRecording(); - cleanupRecorder(); - mIsRecording = false; - mIsVideoMode = false; - recheckAspectRatio(); - } - - private void recheckAspectRatio() { - if (!mForceAspectRatio) { - mSetupPreviewAfterMeasure = true; - invalidate(); - requestLayout(); - } - } - - // VIDEO RECORDING - public boolean initRecorder() { - if (mCamera == null || mRecorder != null || !mIsSurfaceCreated) - return false; - - mSwitchToVideoAsap = false; - final List previewSizes = mParameters.getSupportedPreviewSizes(); - Collections.sort(previewSizes, new SizesComparator()); - Camera.Size preferred = previewSizes.get(0); - - mParameters.setPreviewSize(preferred.width, preferred.height); - mCamera.setParameters(mParameters); - - mIsRecording = false; - mIsVideoMode = true; - recheckAspectRatio(); - mRecorder = new MediaRecorder(); - mRecorder.setCamera(mCamera); - mRecorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT); - mRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); - - mCurrVideoPath = Utils.Companion.getOutputMediaFile(mContext, false); - if (mCurrVideoPath.isEmpty()) { - Utils.Companion.showToast(mContext, R.string.video_creating_error); - return false; - } - - /*final Camera.Size videoSize = getOptimalVideoSize(); - final CamcorderProfile cpHigh = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); - cpHigh.videoFrameWidth = videoSize.width; - cpHigh.videoFrameHeight = videoSize.height; - mRecorder.setProfile(cpHigh);*/ - - if (Utils.Companion.needsStupidWritePermissions(getContext(), mCurrVideoPath)) { - if (mConfig.getTreeUri().isEmpty()) { - Utils.Companion.showToast(mContext, R.string.save_error_internal_storage); - mConfig.setSavePhotosFolder(Environment.getExternalStorageDirectory().toString()); - releaseCamera(); - return false; - } - try { - DocumentFile document = Utils.Companion.getFileDocument(getContext(), mCurrVideoPath, mConfig.getTreeUri()); - document = document.createFile("", mCurrVideoPath.substring(mCurrVideoPath.lastIndexOf('/') + 1)); - final Uri uri = document.getUri(); - final ParcelFileDescriptor fileDescriptor = getContext().getContentResolver().openFileDescriptor(uri, "rw"); - mRecorder.setOutputFile(fileDescriptor.getFileDescriptor()); - } catch (Exception e) { - setupFailed(e); - } - } else { - mRecorder.setOutputFile(mCurrVideoPath); - } - mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); - - int rotation = Utils.Companion.getFinalRotation(mActivity, mCurrCameraId, mCallback.getCurrentOrientation()); - mInitVideoRotation = rotation; - mRecorder.setOrientationHint(rotation); - - try { - mRecorder.prepare(); - } catch (Exception e) { - setupFailed(e); - return false; - } - return true; - } - - private void setupFailed(Exception e) { - Utils.Companion.showToast(mContext, R.string.video_setup_error); - Log.e(TAG, "initRecorder " + e.getMessage()); - releaseCamera(); - } - - public boolean toggleRecording() { - if (mIsRecording) { - stopRecording(); - initRecorder(); - } else { - startRecording(); - } - return mIsRecording; - } - - private void startRecording() { - if (mInitVideoRotation != Utils.Companion.getFinalRotation(mActivity, mCurrCameraId, mCallback.getCurrentOrientation())) { - cleanupRecorder(); - initRecorder(); - } - - try { - mCamera.unlock(); - toggleShutterSound(true); - mRecorder.start(); - toggleShutterSound(false); - mIsRecording = true; - } catch (Exception e) { - Utils.Companion.showToast(mContext, R.string.video_setup_error); - Log.e(TAG, "toggleRecording " + e.getMessage()); - releaseCamera(); - } - } - - private void stopRecording() { - if (mRecorder != null && mIsRecording) { - try { - toggleShutterSound(true); - mRecorder.stop(); - final String[] paths = {mCurrVideoPath}; - MediaScannerConnection.scanFile(mContext, paths, null, this); - } catch (RuntimeException e) { - toggleShutterSound(false); - new File(mCurrVideoPath).delete(); - Utils.Companion.showToast(mContext, R.string.video_saving_error); - Log.e(TAG, "stopRecording " + e.getMessage()); - mRecorder = null; - mIsRecording = false; - releaseCamera(); - } - } - - mRecorder = null; - mIsRecording = false; - - final File file = new File(mCurrVideoPath); - if (file.exists() && file.length() == 0) { - file.delete(); - } - } - - private void toggleShutterSound(Boolean mute) { - if (!mConfig.isSoundEnabled()) { - ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)).setStreamMute(AudioManager.STREAM_SYSTEM, mute); - } - } - - @Override - public void onClick(View v) { - if (!mWasZooming) - focusArea(false); - - mWasZooming = false; - mSurfaceView.setSoundEffectsEnabled(true); - } - - @Override - public void onScanCompleted(String path, Uri uri) { - mCallback.videoSaved(uri); - toggleShutterSound(false); - } - - private static class SizesComparator implements Comparator, Serializable { - private static final long serialVersionUID = 5431278455314658485L; - - @Override - public int compare(final Camera.Size a, final Camera.Size b) { - return b.width * b.height - a.width * a.height; - } - } - - public interface PreviewListener { - void setFlashAvailable(boolean available); - - void setIsCameraAvailable(boolean available); - - int getCurrentOrientation(); - - void videoSaved(Uri uri); - - void drawFocusRect(int x, int y); - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/Preview.kt b/app/src/main/kotlin/com/simplemobiletools/camera/Preview.kt new file mode 100644 index 00000000..31029940 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/camera/Preview.kt @@ -0,0 +1,636 @@ +package com.simplemobiletools.camera + +import android.content.Context +import android.graphics.Point +import android.graphics.Rect +import android.hardware.Camera +import android.media.AudioManager +import android.media.MediaPlayer +import android.media.MediaRecorder +import android.media.MediaScannerConnection +import android.net.Uri +import android.os.Environment +import android.os.Handler +import android.util.Log +import android.view.* +import com.simplemobiletools.camera.activities.MainActivity +import com.simplemobiletools.commons.extensions.scanPaths +import com.simplemobiletools.commons.extensions.toast +import java.io.File +import java.io.IOException +import java.io.Serializable +import java.util.* + +class Preview : ViewGroup, SurfaceHolder.Callback, View.OnTouchListener, View.OnClickListener, MediaScannerConnection.OnScanCompletedListener { + companion object { + val PHOTO_PREVIEW_LENGTH = 1000 + private val TAG = Preview::class.java.simpleName + private val FOCUS_AREA_SIZE = 100 + + lateinit var mSurfaceHolder: SurfaceHolder + lateinit var mSurfaceView: SurfaceView + lateinit var mActivity: MainActivity + lateinit var mCallback: PreviewListener + lateinit var mScreenSize: Point + private var mCamera: Camera? = null + private var mSupportedPreviewSizes: List? = null + private var mPreviewSize: Camera.Size? = null + private var mParameters: Camera.Parameters? = null + private var mRecorder: MediaRecorder? = null + private var mCurrVideoPath: String? = null + private var mTargetUri: Uri? = null + private var mScaleGestureDetector: ScaleGestureDetector? = null + private var mZoomRatios: List? = null + private var mConfig: Config? = null + + private var mCanTakePicture: Boolean = false + private var mIsFlashEnabled: Boolean = false + private var mIsRecording: Boolean = false + private var mIsVideoMode: Boolean = false + private var mIsSurfaceCreated: Boolean = false + private var mSwitchToVideoAsap: Boolean = false + private var mSetupPreviewAfterMeasure: Boolean = false + private val mForceAspectRatio: Boolean = false + private var mWasZooming: Boolean = false + private var mLastClickX: Int = 0 + private var mLastClickY: Int = 0 + private var mInitVideoRotation: Int = 0 + private var mCurrCameraId: Int = 0 + private var mMaxZoom: Int = 0 + } + + constructor(context: Context) : super(context) + + constructor(activity: MainActivity, surfaceView: SurfaceView, previewListener: PreviewListener) : super(activity) { + mActivity = activity + mCallback = previewListener + mSurfaceView = surfaceView + mSurfaceHolder = mSurfaceView.holder + mSurfaceHolder.addCallback(this) + mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS) + mCanTakePicture = false + mSurfaceView.setOnTouchListener(this) + mSurfaceView.setOnClickListener(this) + mIsFlashEnabled = false + mIsVideoMode = false + mIsSurfaceCreated = false + mSetupPreviewAfterMeasure = false + mCurrVideoPath = "" + mScreenSize = Utils.getScreenSize(mActivity) + initGestureDetector() + } + + fun trySwitchToVideo() { + if (mIsSurfaceCreated) { + initRecorder() + } else { + mSwitchToVideoAsap = true + } + } + + fun setCamera(cameraId: Int): Boolean { + mCurrCameraId = cameraId + val newCamera: Camera + try { + newCamera = Camera.open(cameraId) + mCallback.setIsCameraAvailable(true) + } catch (e: Exception) { + Utils.showToast(mActivity, R.string.camera_open_error) + Log.e(TAG, "setCamera open " + e.message) + mCallback.setIsCameraAvailable(false) + return false + } + + if (mCamera === newCamera) { + return false + } + + releaseCamera() + mCamera = newCamera + if (mCamera != null) { + mParameters = mCamera!!.parameters + mMaxZoom = mParameters!!.maxZoom + mZoomRatios = mParameters!!.zoomRatios + mSupportedPreviewSizes = mParameters!!.supportedPreviewSizes + Collections.sort(mSupportedPreviewSizes!!, SizesComparator()) + requestLayout() + invalidate() + mSetupPreviewAfterMeasure = true + + val focusModes = mParameters!!.supportedFocusModes + if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) + mParameters!!.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE + + val rotation = Utils.getPreviewRotation(mActivity, cameraId) + mCamera!!.setDisplayOrientation(rotation) + mCamera!!.parameters = mParameters + + if (mCanTakePicture) { + try { + mCamera!!.setPreviewDisplay(mSurfaceHolder) + } catch (e: IOException) { + Log.e(TAG, "setCamera setPreviewDisplay " + e.message) + return false + } + + } + + mCallback.setFlashAvailable(Utils.hasFlash(mCamera)) + } + + if (mIsVideoMode) { + initRecorder() + } + + return true + } + + fun setTargetUri(uri: Uri) { + mTargetUri = uri + } + + private fun initGestureDetector() { + mScaleGestureDetector = ScaleGestureDetector(mActivity, object : ScaleGestureDetector.SimpleOnScaleGestureListener() { + override fun onScale(detector: ScaleGestureDetector): Boolean { + val zoomFactor = mParameters!!.zoom + var zoomRatio = mZoomRatios!![zoomFactor] / 100f + zoomRatio *= detector.scaleFactor + + var newZoomFactor = zoomFactor + if (zoomRatio <= 1f) { + newZoomFactor = 0 + } else if (zoomRatio >= mZoomRatios!![mMaxZoom] / 100f) { + newZoomFactor = mMaxZoom + } else { + if (detector.scaleFactor > 1f) { + for (i in zoomFactor..mZoomRatios!!.size - 1) { + if (mZoomRatios!![i] / 100.0f >= zoomRatio) { + newZoomFactor = i + break + } + } + } else { + for (i in zoomFactor downTo 0) { + if (mZoomRatios!![i] / 100.0f <= zoomRatio) { + newZoomFactor = i + break + } + } + } + } + + newZoomFactor = Math.max(newZoomFactor, 0) + newZoomFactor = Math.min(mMaxZoom, newZoomFactor) + + mParameters!!.zoom = newZoomFactor + if (mCamera != null) + mCamera!!.parameters = mParameters + + return true + } + + override fun onScaleEnd(detector: ScaleGestureDetector) { + super.onScaleEnd(detector) + mWasZooming = true + mSurfaceView.isSoundEffectsEnabled = false + mParameters!!.focusAreas = null + } + }) + } + + fun takePicture() { + if (mCanTakePicture) { + var rotation = Utils.getMediaRotation(mActivity, mCurrCameraId) + rotation += Utils.compensateDeviceRotation(mCurrCameraId, mCallback.getCurrentOrientation()) + + /*final Camera.Size maxSize = getOptimalPictureSize(); + mParameters.setPictureSize(maxSize.width, maxSize.height);*/ + mParameters!!.setRotation(rotation % 360) + + if (mConfig!!.isSoundEnabled) { + val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + val volume = audioManager.getStreamVolume(AudioManager.STREAM_SYSTEM) + if (volume != 0) { + val mp = MediaPlayer.create(context, Uri.parse("file:///system/media/audio/ui/camera_click.ogg")) + mp?.start() + } + } + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { + mCamera!!.enableShutterSound(false) + } + + mCamera!!.parameters = mParameters + mCamera!!.takePicture(null, null, takePictureCallback) + } + mCanTakePicture = false + } + + private val takePictureCallback = Camera.PictureCallback { data, cam -> + if (mConfig!!.isShowPreviewEnabled) { + Handler().postDelayed({ resumePreview() }, PHOTO_PREVIEW_LENGTH.toLong()) + } else { + resumePreview() + } + + PhotoProcessor(mActivity, mTargetUri).execute(data) + } + + private fun resumePreview() { + if (mCamera != null) { + mCamera!!.startPreview() + } + + mCanTakePicture = true + } + + val supportedVideoSizes: List + get() { + if (mParameters!!.supportedVideoSizes != null) { + return mParameters!!.supportedVideoSizes + } else { + return mParameters!!.supportedPreviewSizes + } + } + + private fun focusArea(takePictureAfter: Boolean) { + if (mCamera == null) + return + + mCamera!!.cancelAutoFocus() + val focusRect = calculateFocusArea(mLastClickX.toFloat(), mLastClickY.toFloat()) + if (mParameters!!.maxNumFocusAreas > 0) { + val focusAreas = ArrayList(1) + focusAreas.add(Camera.Area(focusRect, 1000)) + mParameters!!.focusAreas = focusAreas + mCallback.drawFocusRect(mLastClickX, mLastClickY) + } + + mCamera!!.parameters = mParameters + mCamera!!.autoFocus { success, camera -> + camera.cancelAutoFocus() + val focusModes = mParameters!!.supportedFocusModes + if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) + mParameters!!.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE + + camera.parameters = mParameters + if (takePictureAfter) { + takePicture() + } + } + } + + private fun calculateFocusArea(x: Float, y: Float): Rect { + var left = java.lang.Float.valueOf(x / mSurfaceView.width * 2000 - 1000)!!.toInt() + var top = java.lang.Float.valueOf(y / mSurfaceView.height * 2000 - 1000)!!.toInt() + + val tmp = left + left = top + top = -tmp + + val rectLeft = Math.max(left - FOCUS_AREA_SIZE / 2, -1000) + val rectTop = Math.max(top - FOCUS_AREA_SIZE / 2, -1000) + val rectRight = Math.min(left + FOCUS_AREA_SIZE / 2, 1000) + val rectBottom = Math.min(top + FOCUS_AREA_SIZE / 2, 1000) + return Rect(rectLeft, rectTop, rectRight, rectBottom) + } + + fun releaseCamera() { + stopRecording() + + if (mCamera != null) { + mCamera!!.stopPreview() + mCamera!!.release() + mCamera = null + } + + cleanupRecorder() + } + + override fun surfaceCreated(holder: SurfaceHolder) { + mIsSurfaceCreated = true + try { + if (mCamera != null) { + mCamera!!.setPreviewDisplay(mSurfaceHolder) + } + + if (mSwitchToVideoAsap) + initRecorder() + } catch (e: IOException) { + Log.e(TAG, "surfaceCreated IOException " + e.message) + } + + } + + override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { + mIsSurfaceCreated = true + + if (mIsVideoMode) { + initRecorder() + } + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + mIsSurfaceCreated = false + if (mCamera != null) { + mCamera!!.stopPreview() + } + + cleanupRecorder() + } + + private fun setupPreview() { + mCanTakePicture = true + if (mCamera != null && mPreviewSize != null) { + mParameters!!.setPreviewSize(mPreviewSize!!.width, mPreviewSize!!.height) + mCamera!!.parameters = mParameters + mCamera!!.startPreview() + } + } + + private fun cleanupRecorder() { + if (mRecorder != null) { + if (mIsRecording) { + stopRecording() + } + + mRecorder!!.release() + mRecorder = null + } + } + + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + + } + + private fun getOptimalPreviewSize(sizes: List, width: Int, height: Int): Camera.Size { + var result: Camera.Size? = null + for (size in sizes) { + if (size.width <= width && size.height <= height) { + if (result == null) { + result = size + } else { + val resultArea = result.width * result.height + val newArea = size.width * size.height + + if (newArea > resultArea) { + result = size + } + } + } + } + + return result!! + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + setMeasuredDimension(mScreenSize.x, mScreenSize.y) + + if (mSupportedPreviewSizes != null) { + // for simplicity lets assume that most displays are 16:9 and the remaining ones are 4:3 + // always set 16:9 for videos as many devices support 4:3 only in low quality + if (mForceAspectRatio || mIsVideoMode) { + mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes!!, mScreenSize.y, mScreenSize.x) + } else { + val newRatioHeight = (mScreenSize.x * (4.toDouble() / 3)).toInt() + setMeasuredDimension(mScreenSize.x, newRatioHeight) + mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes!!, newRatioHeight, mScreenSize.x) + } + val lp = mSurfaceView.layoutParams + + // make sure to occupy whole width in every case + if (mScreenSize.x > mPreviewSize!!.height) { + val ratio = mScreenSize.x.toFloat() / mPreviewSize!!.height + lp.width = (mPreviewSize!!.height * ratio).toInt() + if (mForceAspectRatio || mIsVideoMode) { + lp.height = mScreenSize.y + } else { + lp.height = (mPreviewSize!!.width * ratio).toInt() + } + } else { + lp.width = mPreviewSize!!.height + lp.height = mPreviewSize!!.width + } + + if (mSetupPreviewAfterMeasure) { + mSetupPreviewAfterMeasure = false + if (mCamera != null) + mCamera!!.stopPreview() + + setupPreview() + } + } + } + + override fun onTouch(v: View, event: MotionEvent): Boolean { + mLastClickX = event.x.toInt() + mLastClickY = event.y.toInt() + + if (mMaxZoom > 0) + mScaleGestureDetector!!.onTouchEvent(event) + return false + } + + fun enableFlash() { + mParameters!!.flashMode = Camera.Parameters.FLASH_MODE_TORCH + mCamera!!.parameters = mParameters + mIsFlashEnabled = true + } + + fun disableFlash() { + mIsFlashEnabled = false + mParameters!!.flashMode = Camera.Parameters.FLASH_MODE_OFF + mCamera!!.parameters = mParameters + } + + fun initPhotoMode() { + stopRecording() + cleanupRecorder() + mIsRecording = false + mIsVideoMode = false + recheckAspectRatio() + } + + private fun recheckAspectRatio() { + if (!mForceAspectRatio) { + mSetupPreviewAfterMeasure = true + invalidate() + requestLayout() + } + } + + // VIDEO RECORDING + fun initRecorder(): Boolean { + if (mCamera == null || mRecorder != null || !mIsSurfaceCreated) + return false + + mSwitchToVideoAsap = false + val previewSizes = mParameters!!.supportedPreviewSizes + Collections.sort(previewSizes, SizesComparator()) + val preferred = previewSizes[0] + + mParameters!!.setPreviewSize(preferred.width, preferred.height) + mCamera!!.parameters = mParameters + + mIsRecording = false + mIsVideoMode = true + recheckAspectRatio() + mRecorder = MediaRecorder() + mRecorder!!.setCamera(mCamera) + mRecorder!!.setVideoSource(MediaRecorder.VideoSource.DEFAULT) + mRecorder!!.setAudioSource(MediaRecorder.AudioSource.DEFAULT) + + mCurrVideoPath = Utils.getOutputMediaFile(mActivity, false) + if (mCurrVideoPath!!.isEmpty()) { + mActivity.toast(R.string.video_creating_error) + return false + } + + /*final Camera.Size videoSize = getOptimalVideoSize(); + final CamcorderProfile cpHigh = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); + cpHigh.videoFrameWidth = videoSize.width; + cpHigh.videoFrameHeight = videoSize.height; + mRecorder.setProfile(cpHigh);*/ + + if (Utils.needsStupidWritePermissions(context, mCurrVideoPath!!)) { + if (mConfig!!.treeUri.isEmpty()) { + mActivity.toast(R.string.save_error_internal_storage) + mConfig!!.savePhotosFolder = Environment.getExternalStorageDirectory().toString() + releaseCamera() + return false + } + try { + /*var document: DocumentFile = Utils.getFileDocument(context, mCurrVideoPath!!, mConfig!!.treeUri) + document = document.createFile("", mCurrVideoPath!!.substring(mCurrVideoPath!!.lastIndexOf('/') + 1)) + val uri = document.uri + val fileDescriptor = context.contentResolver.openFileDescriptor(uri, "rw") + mRecorder!!.setOutputFile(fileDescriptor!!.fileDescriptor)*/ + } catch (e: Exception) { + setupFailed(e) + } + + } else { + mRecorder!!.setOutputFile(mCurrVideoPath) + } + mRecorder!!.setPreviewDisplay(mSurfaceHolder.surface) + + val rotation = Utils.getFinalRotation(mActivity, mCurrCameraId, mCallback.getCurrentOrientation()) + mInitVideoRotation = rotation + mRecorder!!.setOrientationHint(rotation) + + try { + mRecorder!!.prepare() + } catch (e: Exception) { + setupFailed(e) + return false + } + + return true + } + + private fun setupFailed(e: Exception) { + mActivity.toast(R.string.video_setup_error) + Log.e(TAG, "initRecorder " + e.message) + releaseCamera() + } + + fun toggleRecording(): Boolean { + if (mIsRecording) { + stopRecording() + initRecorder() + } else { + startRecording() + } + return mIsRecording + } + + private fun startRecording() { + if (mInitVideoRotation != Utils.getFinalRotation(mActivity, mCurrCameraId, mCallback.getCurrentOrientation())) { + cleanupRecorder() + initRecorder() + } + + try { + mCamera!!.unlock() + toggleShutterSound(true) + mRecorder!!.start() + toggleShutterSound(false) + mIsRecording = true + } catch (e: Exception) { + mActivity.toast(R.string.video_setup_error) + Log.e(TAG, "toggleRecording " + e.message) + releaseCamera() + } + + } + + private fun stopRecording() { + if (mRecorder != null && mIsRecording) { + try { + toggleShutterSound(true) + mRecorder!!.stop() + val paths = arrayListOf(mCurrVideoPath!!) + mActivity.scanPaths(paths) {} + } catch (e: RuntimeException) { + toggleShutterSound(false) + File(mCurrVideoPath!!).delete() + mActivity.toast(R.string.video_saving_error) + Log.e(TAG, "stopRecording " + e.message) + mRecorder = null + mIsRecording = false + releaseCamera() + } + + } + + mRecorder = null + mIsRecording = false + + val file = File(mCurrVideoPath!!) + if (file.exists() && file.length() == 0L) { + file.delete() + } + } + + private fun toggleShutterSound(mute: Boolean?) { + if (!mConfig!!.isSoundEnabled) { + (mActivity.getSystemService(Context.AUDIO_SERVICE) as AudioManager).setStreamMute(AudioManager.STREAM_SYSTEM, mute!!) + } + } + + override fun onClick(v: View) { + if (!mWasZooming) + focusArea(false) + + mWasZooming = false + mSurfaceView.isSoundEffectsEnabled = true + } + + override fun onScanCompleted(path: String, uri: Uri) { + mCallback.videoSaved(uri) + toggleShutterSound(false) + } + + private class SizesComparator : Comparator, Serializable { + + override fun compare(a: Camera.Size, b: Camera.Size): Int { + return b.width * b.height - a.width * a.height + } + + companion object { + private const val serialVersionUID = 5431278455314658485L + } + } + + interface PreviewListener { + fun setFlashAvailable(available: Boolean) + + fun setIsCameraAvailable(available: Boolean) + + fun getCurrentOrientation(): Int + + fun videoSaved(uri: Uri) + + fun drawFocusRect(x: Int, y: Int) + } +}