package com.lemenzo.camera.capturer import android.annotation.SuppressLint import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.content.ClipData import android.content.ClipboardManager import android.graphics.Bitmap import android.os.Build import android.util.Log import android.view.View import android.view.animation.AlphaAnimation import android.view.animation.Animation import android.view.animation.LinearInterpolator import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCapture import androidx.camera.core.ImageCaptureException import com.lemenzo.camera.App import com.lemenzo.camera.CapturedItem import com.lemenzo.camera.R import com.lemenzo.camera.ui.activities.MainActivity import com.lemenzo.camera.ui.activities.SecureMainActivity import com.lemenzo.camera.ui.showIgnoringShortEdgeMode import com.lemenzo.camera.util.printStackTraceToString import com.google.android.material.dialog.MaterialAlertDialogBuilder private const val imageFileFormat = ".jpg" class ImageCapturer(val mActivity: MainActivity) { val camConfig = mActivity.camConfig val isTakingPicture: Boolean get() = currentImageSaver != null private var currentImageSaver : ImageSaver? = null private fun fadeCaptureButton() { mActivity.captureButton.isEnabled = false val animation: Animation = AlphaAnimation(mActivity.captureButton.alpha, 0.6f) animation.duration = 200 animation.interpolator = LinearInterpolator() animation.fillAfter = true mActivity.captureButton.startAnimation(animation) } private fun unfadeCaptureButton() { mActivity.captureButton.isEnabled = true val animation: Animation = AlphaAnimation(mActivity.captureButton.alpha, 1f) animation.duration = 200 animation.interpolator = LinearInterpolator() animation.fillAfter = true mActivity.captureButton.startAnimation(animation) } @SuppressLint("RestrictedApi") fun takePicture() { if (camConfig.camera == null) { return } if (!camConfig.canTakePicture) { mActivity.showMessage(R.string.unsupported_taking_picture_while_recording) return } if (isTakingPicture) { return } val imageMetadata = ImageCapture.Metadata() imageMetadata.isReversedHorizontal = camConfig.lensFacing == CameraSelector.LENS_FACING_FRONT && camConfig.saveImageAsPreviewed if (camConfig.requireLocation) { val location = (mActivity.applicationContext as App).getLocation() if (location == null) { mActivity.showMessage(R.string.location_unavailable) } else { imageMetadata.location = location } } val preview = mActivity.imagePreview val imageCapture = camConfig.imageCapture!! val imageSaver = ImageSaver( this, mActivity.applicationContext, imageCapture.jpegQuality, camConfig.storageLocation, imageFileFormat, imageMetadata, camConfig.removeExifAfterCapture, targetThumbnailWidth = preview.width, targetThumbnailHeight = preview.height, ) currentImageSaver = imageSaver imageCapture.takePicture(ImageSaver.imageCaptureCallbackExecutor, imageSaver) fadeCaptureButton() } fun cancelPendingCaptureRequest() { if (isTakingPicture) { currentImageSaver?.cancelCaptureRequest() unfadeCaptureButton() currentImageSaver = null } } fun onCaptureSuccess() { unfadeCaptureButton() currentImageSaver = null camConfig.mPlayer.playShutterSound() camConfig.snapPreview() mActivity.previewLoader.visibility = View.VISIBLE if (camConfig.selfIlluminate) { val animation: Animation = AlphaAnimation(0.8f, 0f) animation.duration = 200 animation.interpolator = LinearInterpolator() animation.fillAfter = true mActivity.mainOverlay.setImageResource(android.R.color.white) animation.setAnimationListener( object : Animation.AnimationListener { override fun onAnimationStart(p0: Animation?) { mActivity.mainOverlay.visibility = View.VISIBLE } override fun onAnimationEnd(p0: Animation?) { mActivity.mainOverlay.visibility = View.INVISIBLE mActivity.mainOverlay.setImageResource(android.R.color.transparent) } override fun onAnimationRepeat(p0: Animation?) {} } ) mActivity.mainOverlay.startAnimation(animation) } } fun onCaptureError(exception: ImageCaptureException) { Log.e(TAG, "onCaptureError", exception) unfadeCaptureButton() currentImageSaver = null mActivity.previewLoader.visibility = View.GONE if (mActivity.isStarted) { val msg = mActivity.getString(R.string.unable_to_capture_image_verbose, exception.imageCaptureError) showErrorDialog(msg, exception) } } fun onImageSaverSuccess(item: CapturedItem) { camConfig.updateLastCapturedItem(item) if (mActivity is SecureMainActivity) { mActivity.capturedItems.add(item) } } fun onStorageLocationNotFound() { camConfig.onStorageLocationNotFound() } fun onImageSaverError(exception: ImageSaverException, skipErrorDialog: Boolean) { Log.e(TAG, "onImageSaverError", exception) mActivity.previewLoader.visibility = View.GONE if (!mActivity.isStarted) { val channelId = "image_saver_error" val channel = NotificationChannel(channelId, mActivity.getString(R.string.unable_to_save_image), NotificationManager.IMPORTANCE_HIGH) val notif = Notification.Builder(mActivity, channelId).apply { setSmallIcon(R.drawable.info) setContentTitle(mActivity.getString(R.string.unable_to_save_image)) }.build() mActivity.getSystemService(NotificationManager::class.java).let { it.createNotificationChannel(channel) it.notify(1, notif) } return } if (skipErrorDialog) { mActivity.showMessage(R.string.unable_to_save_image) } else { val msg = mActivity.getString(R.string.unable_to_save_image_verbose, exception.place.name) showErrorDialog(msg, exception) } } private fun showErrorDialog(message: String, exception: Throwable) { val ctx = mActivity MaterialAlertDialogBuilder(ctx).apply { setMessage(message) setPositiveButton(R.string.show_details) { _, _ -> val pkgName = ctx.packageName val pkgVersion = ctx.packageManager.getPackageInfo(pkgName, 0).longVersionCode val text = "osVersion: ${Build.FINGERPRINT}" + "\npackage: $pkgName:$pkgVersion" + "\n\n${exception.printStackTraceToString()}" MaterialAlertDialogBuilder(ctx).apply { setItems(text.lines().toTypedArray(), null) setNeutralButton(R.string.copy_to_clipboard) { _, _ -> val clipData = ClipData.newPlainText(exception.javaClass.name, text) val cm = mActivity.getSystemService(ClipboardManager::class.java) cm.setPrimaryClip(clipData) ctx.showMessage(R.string.copied_text_to_clipboard) } showIgnoringShortEdgeMode() } } showIgnoringShortEdgeMode() } } fun onThumbnailGenerated(thumbnail: Bitmap) { mActivity.previewLoader.visibility = View.GONE mActivity.imagePreview.setImageBitmap(thumbnail) } companion object { private const val TAG = "ImageCapturer" } }