mirror of
https://github.com/SimpleMobileTools/Simple-Camera.git
synced 2025-06-27 09:02:59 +02:00
- instead of using the OnImageSavedCallback, we now use the OnImageCaptureCallback and we play the sound in the onCapturedSuccess method to prevent the user from hearing the capture sound before the actual capture takes place - add ImageSaver and ImageUtil based on CameraX implementation - add sealed interfaces ImageCaptureOutput and VideoCaptureOutput to prevent confusion on the supported output format for images and video - ImageSaver supports saving images with all MediaOutput.ImageCaptureOutput that can be saved to disk; MediaStoreOutput, OutputStreamMediaOutput and FileMediaOutput (unused at the moment), it throws an UnsupportedOperation when trying to save a BitmapOutput. - with ImageSaver, we now have control of whether the Exif attributes are written to disk and so we can get rid of ExifRemover - delete the extension method ImageProxy.toJpegByteArray as we have a similar method in ImageUtils class
233 lines
9.2 KiB
Kotlin
233 lines
9.2 KiB
Kotlin
package com.simplemobiletools.camera.helpers
|
|
|
|
import android.content.ContentValues
|
|
import android.net.Uri
|
|
import android.os.Environment
|
|
import android.os.ParcelFileDescriptor
|
|
import android.provider.MediaStore
|
|
import com.simplemobiletools.camera.extensions.config
|
|
import com.simplemobiletools.camera.extensions.getOutputMediaFile
|
|
import com.simplemobiletools.camera.extensions.getRandomMediaName
|
|
import com.simplemobiletools.camera.models.MediaOutput
|
|
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
|
import com.simplemobiletools.commons.extensions.*
|
|
import com.simplemobiletools.commons.helpers.isOreoPlus
|
|
import com.simplemobiletools.commons.helpers.isQPlus
|
|
import java.io.File
|
|
import java.io.OutputStream
|
|
|
|
class MediaOutputHelper(
|
|
private val activity: BaseSimpleActivity,
|
|
private val errorHandler: CameraErrorHandler,
|
|
private val outputUri: Uri?,
|
|
private val is3rdPartyIntent: Boolean,
|
|
) {
|
|
|
|
companion object {
|
|
private const val MODE = "rw"
|
|
private const val EXTERNAL_VOLUME = "external"
|
|
private const val IMAGE_MIME_TYPE = "image/jpeg"
|
|
private const val VIDEO_MIME_TYPE = "video/mp4"
|
|
}
|
|
|
|
private val mediaStorageDir = activity.config.savePhotosFolder
|
|
private val contentResolver = activity.contentResolver
|
|
|
|
fun getImageMediaOutput(): MediaOutput.ImageCaptureOutput {
|
|
return try {
|
|
if (is3rdPartyIntent) {
|
|
if (outputUri != null) {
|
|
val outputStream = openOutputStream(outputUri)
|
|
if (outputStream != null) {
|
|
MediaOutput.OutputStreamMediaOutput(outputStream, outputUri)
|
|
} else {
|
|
errorHandler.showSaveToInternalStorage()
|
|
getMediaStoreOutput(isPhoto = true)
|
|
}
|
|
} else {
|
|
MediaOutput.BitmapOutput
|
|
}
|
|
} else {
|
|
getOutputStreamMediaOutput() ?: getMediaStoreOutput(isPhoto = true)
|
|
}
|
|
} catch (e: Exception) {
|
|
errorHandler.showSaveToInternalStorage()
|
|
getMediaStoreOutput(isPhoto = true)
|
|
}
|
|
}
|
|
|
|
fun getVideoMediaOutput(): MediaOutput.VideoCaptureOutput {
|
|
return try {
|
|
if (is3rdPartyIntent) {
|
|
if (outputUri != null) {
|
|
if (isOreoPlus()) {
|
|
val fileDescriptor = openFileDescriptor(outputUri)
|
|
if (fileDescriptor != null) {
|
|
MediaOutput.FileDescriptorMediaOutput(fileDescriptor, outputUri)
|
|
} else {
|
|
errorHandler.showSaveToInternalStorage()
|
|
getMediaStoreOutput(isPhoto = false)
|
|
}
|
|
} else {
|
|
val path = activity.getRealPathFromURI(outputUri)
|
|
if (path != null) {
|
|
MediaOutput.FileMediaOutput(File(path), outputUri)
|
|
} else {
|
|
errorHandler.showSaveToInternalStorage()
|
|
getMediaStoreOutput(isPhoto = false)
|
|
}
|
|
}
|
|
} else {
|
|
getMediaStoreOutput(isPhoto = false)
|
|
}
|
|
} else {
|
|
if (isOreoPlus()) {
|
|
getFileDescriptorMediaOutput() ?: getMediaStoreOutput(isPhoto = false)
|
|
} else {
|
|
getFileMediaOutput() ?: getMediaStoreOutput(isPhoto = false)
|
|
}
|
|
}
|
|
} catch (e: Exception) {
|
|
errorHandler.showSaveToInternalStorage()
|
|
getMediaStoreOutput(isPhoto = false)
|
|
}
|
|
}
|
|
|
|
private fun getMediaStoreOutput(isPhoto: Boolean): MediaOutput.MediaStoreOutput {
|
|
val contentValues = getContentValues(isPhoto)
|
|
val contentUri = if (isPhoto) {
|
|
MediaStore.Images.Media.getContentUri(EXTERNAL_VOLUME)
|
|
} else {
|
|
MediaStore.Video.Media.getContentUri(EXTERNAL_VOLUME)
|
|
}
|
|
return MediaOutput.MediaStoreOutput(contentValues, contentUri)
|
|
}
|
|
|
|
@Suppress("DEPRECATION")
|
|
private fun getContentValues(isPhoto: Boolean): ContentValues {
|
|
val mimeType = if (isPhoto) IMAGE_MIME_TYPE else VIDEO_MIME_TYPE
|
|
return ContentValues().apply {
|
|
put(MediaStore.MediaColumns.DISPLAY_NAME, getRandomMediaName(isPhoto))
|
|
put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
|
|
if (isQPlus()) {
|
|
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
|
|
} else {
|
|
put(MediaStore.MediaColumns.DATA, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString())
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun getOutputStreamMediaOutput(): MediaOutput.OutputStreamMediaOutput? {
|
|
var mediaOutput: MediaOutput.OutputStreamMediaOutput? = null
|
|
val canWrite = canWriteToFilePath(mediaStorageDir)
|
|
if (canWrite) {
|
|
val path = activity.getOutputMediaFile(true)
|
|
val uri = getUriForFilePath(path)
|
|
val outputStream = activity.getFileOutputStreamSync(path, path.getMimeType())
|
|
if (uri != null && outputStream != null) {
|
|
mediaOutput = MediaOutput.OutputStreamMediaOutput(outputStream, uri)
|
|
}
|
|
}
|
|
return mediaOutput
|
|
}
|
|
|
|
private fun openOutputStream(uri: Uri): OutputStream? {
|
|
return try {
|
|
contentResolver.openOutputStream(uri)
|
|
} catch (e: Exception) {
|
|
activity.showErrorToast(e)
|
|
null
|
|
}
|
|
}
|
|
|
|
private fun getFileDescriptorMediaOutput(): MediaOutput.FileDescriptorMediaOutput? {
|
|
var mediaOutput: MediaOutput.FileDescriptorMediaOutput? = null
|
|
val canWrite = canWriteToFilePath(mediaStorageDir)
|
|
if (canWrite) {
|
|
val path = activity.getOutputMediaFile(false)
|
|
val uri = getUriForFilePath(path)
|
|
if (uri != null) {
|
|
val fileDescriptor = contentResolver.openFileDescriptor(uri, MODE)
|
|
if (fileDescriptor != null) {
|
|
mediaOutput = MediaOutput.FileDescriptorMediaOutput(fileDescriptor, uri)
|
|
}
|
|
}
|
|
}
|
|
return mediaOutput
|
|
}
|
|
|
|
private fun getFileMediaOutput(): MediaOutput.FileMediaOutput? {
|
|
var mediaOutput: MediaOutput.FileMediaOutput? = null
|
|
val canWrite = canWriteToFilePath(mediaStorageDir)
|
|
if (canWrite) {
|
|
val path = activity.getOutputMediaFile(false)
|
|
val uri = getUriForFilePath(path)
|
|
if (uri != null) {
|
|
mediaOutput = MediaOutput.FileMediaOutput(File(path), uri)
|
|
}
|
|
}
|
|
return mediaOutput
|
|
}
|
|
|
|
private fun openFileDescriptor(uri: Uri): ParcelFileDescriptor? {
|
|
return try {
|
|
contentResolver.openFileDescriptor(uri, MODE)
|
|
} catch (e: Exception) {
|
|
activity.showErrorToast(e)
|
|
null
|
|
}
|
|
}
|
|
|
|
private fun canWriteToFilePath(path: String): Boolean {
|
|
return when {
|
|
activity.isRestrictedSAFOnlyRoot(path) -> activity.hasProperStoredAndroidTreeUri(path)
|
|
activity.needsStupidWritePermissions(path) -> activity.hasProperStoredTreeUri(false)
|
|
activity.isAccessibleWithSAFSdk30(path) -> activity.hasProperStoredFirstParentUri(path)
|
|
else -> File(path).canWrite()
|
|
}
|
|
}
|
|
|
|
private fun getUriForFilePath(path: String): Uri? {
|
|
val targetFile = File(path)
|
|
return when {
|
|
activity.isRestrictedSAFOnlyRoot(path) -> activity.getAndroidSAFUri(path)
|
|
activity.needsStupidWritePermissions(path) -> {
|
|
targetFile.parentFile?.let { parentFile ->
|
|
val documentFile =
|
|
if (activity.getDoesFilePathExist(parentFile.absolutePath)) {
|
|
activity.getDocumentFile(parentFile.path)
|
|
} else {
|
|
val parentDocumentFile = parentFile.parent?.let {
|
|
activity.getDocumentFile(it)
|
|
}
|
|
parentDocumentFile?.createDirectory(parentFile.name)
|
|
?: activity.getDocumentFile(parentFile.absolutePath)
|
|
}
|
|
|
|
if (documentFile == null) {
|
|
return Uri.fromFile(targetFile)
|
|
}
|
|
|
|
try {
|
|
if (activity.getDoesFilePathExist(path)) {
|
|
activity.createDocumentUriFromRootTree(path)
|
|
} else {
|
|
documentFile.createFile(path.getMimeType(), path.getFilenameFromPath())?.uri
|
|
}
|
|
} catch (e: Exception) {
|
|
null
|
|
}
|
|
}
|
|
}
|
|
activity.isAccessibleWithSAFSdk30(path) -> {
|
|
try {
|
|
activity.createDocumentUriUsingFirstParentTreeUri(path)
|
|
} catch (e: Exception) {
|
|
null
|
|
} ?: Uri.fromFile(targetFile)
|
|
}
|
|
else -> return Uri.fromFile(targetFile)
|
|
}
|
|
}
|
|
}
|