mirror of
https://github.com/SimpleMobileTools/Simple-Camera.git
synced 2025-06-27 09:02:59 +02:00
handle 3rd party image/video capture intents
- in MediaOutputHelper, - add support for specifying the output URI if present in the intent - when the output URI is specified, - for Image Capture, we return a `Bitmap` as a `data` extra and also the URI as the Intent data - for Video Capture we return the `Uri` as the Intent data - if no output URI is specified in the capture intent or if there is an error while trying to access the URI, use the default location with MediaStore, so we do not return inconsistent URIs (eg, SAF tree URIs) - add CameraXInitializer to abstract CameraXPreview initialisation logic
This commit is contained in:
@ -1,15 +1,18 @@
|
||||
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 android.util.Log
|
||||
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.createAndroidSAFFile
|
||||
import com.simplemobiletools.commons.extensions.createDocumentUriFromRootTree
|
||||
import com.simplemobiletools.commons.extensions.createDocumentUriUsingFirstParentTreeUri
|
||||
import com.simplemobiletools.commons.extensions.createSAFFileSdk30
|
||||
import com.simplemobiletools.commons.extensions.getAndroidSAFUri
|
||||
import com.simplemobiletools.commons.extensions.getDocumentFile
|
||||
import com.simplemobiletools.commons.extensions.getDoesFilePathExist
|
||||
@ -22,189 +25,186 @@ import com.simplemobiletools.commons.extensions.hasProperStoredTreeUri
|
||||
import com.simplemobiletools.commons.extensions.isAccessibleWithSAFSdk30
|
||||
import com.simplemobiletools.commons.extensions.isRestrictedSAFOnlyRoot
|
||||
import com.simplemobiletools.commons.extensions.needsStupidWritePermissions
|
||||
import com.simplemobiletools.commons.extensions.showFileCreateError
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
|
||||
class MediaOutputHelper(private val activity: BaseSimpleActivity) {
|
||||
class MediaOutputHelper(
|
||||
private val activity: BaseSimpleActivity,
|
||||
private val errorHandler: CameraErrorHandler,
|
||||
private val outputUri: Uri?,
|
||||
private val is3rdPartyIntent: Boolean,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MediaOutputHelper"
|
||||
private const val MODE = "rw"
|
||||
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 getOutputStreamMediaOutput(): MediaOutput.OutputStreamMediaOutput? {
|
||||
val canWrite = activity.canWrite(mediaStorageDir)
|
||||
fun getImageMediaOutput(): MediaOutput {
|
||||
return if (is3rdPartyIntent) {
|
||||
if (outputUri != null) {
|
||||
val outputStream = openOutputStream(outputUri)
|
||||
if (outputStream != null) {
|
||||
MediaOutput.OutputStreamMediaOutput(outputStream, outputUri)
|
||||
} else {
|
||||
errorHandler.showSaveToInternalStorage()
|
||||
getMediaStoreOutput(isPhoto = true)
|
||||
}
|
||||
} else {
|
||||
getMediaStoreOutput(isPhoto = true)
|
||||
}
|
||||
} else {
|
||||
getOutputStreamMediaOutput() ?: getMediaStoreOutput(isPhoto = true)
|
||||
}
|
||||
}
|
||||
|
||||
fun getVideoMediaOutput(): MediaOutput {
|
||||
return if (is3rdPartyIntent) {
|
||||
if (outputUri != null) {
|
||||
val fileDescriptor = openFileDescriptor(outputUri)
|
||||
if (fileDescriptor != null) {
|
||||
MediaOutput.FileDescriptorMediaOutput(fileDescriptor, outputUri)
|
||||
} else {
|
||||
errorHandler.showSaveToInternalStorage()
|
||||
getMediaStoreOutput(isPhoto = false)
|
||||
}
|
||||
} else {
|
||||
getMediaStoreOutput(isPhoto = false)
|
||||
}
|
||||
} else {
|
||||
getFileDescriptorMediaOutput() ?: getMediaStoreOutput(isPhoto = false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMediaStoreOutput(isPhoto: Boolean): MediaOutput.MediaStoreOutput {
|
||||
val contentValues = getContentValues(isPhoto)
|
||||
val contentUri = if (isPhoto) {
|
||||
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
} else {
|
||||
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
}
|
||||
return MediaOutput.MediaStoreOutput(contentValues, contentUri)
|
||||
}
|
||||
|
||||
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)
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOutputStreamMediaOutput(): MediaOutput.OutputStreamMediaOutput? {
|
||||
var mediaOutput: MediaOutput.OutputStreamMediaOutput? = null
|
||||
val canWrite = canWriteToFilePath(mediaStorageDir)
|
||||
Log.i(TAG, "getMediaOutput: canWrite=${canWrite}")
|
||||
return if (canWrite) {
|
||||
if (canWrite) {
|
||||
val path = activity.getOutputMediaFile(true)
|
||||
val uri = activity.getUri(path)
|
||||
uri?.let {
|
||||
activity.getFileOutputStreamSync(path, path.getMimeType())?.let {
|
||||
MediaOutput.OutputStreamMediaOutput(it, uri)
|
||||
}
|
||||
val uri = getUriForFilePath(path)
|
||||
val outputStream = activity.getFileOutputStreamSync(path, path.getMimeType())
|
||||
if (uri != null && outputStream != null) {
|
||||
mediaOutput = MediaOutput.OutputStreamMediaOutput(outputStream, uri)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}.also {
|
||||
Log.i(TAG, "output stream: $it")
|
||||
}
|
||||
Log.i(TAG, "OutputStreamMediaOutput: $mediaOutput")
|
||||
return mediaOutput
|
||||
}
|
||||
|
||||
fun getFileDescriptorMediaOutput(): MediaOutput.FileDescriptorMediaOutput? {
|
||||
val canWrite = activity.canWrite(mediaStorageDir)
|
||||
Log.i(TAG, "getMediaOutput: canWrite=${canWrite}")
|
||||
return if (canWrite) {
|
||||
val path = activity.getOutputMediaFile(false)
|
||||
val uri = activity.getUri(path)
|
||||
uri?.let {
|
||||
activity.getFileDescriptorSync(path, path.getMimeType())?.let {
|
||||
MediaOutput.FileDescriptorMediaOutput(it, uri)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}.also {
|
||||
Log.i(TAG, "descriptor: $it")
|
||||
}
|
||||
}
|
||||
|
||||
private fun BaseSimpleActivity.canWrite(path: String): Boolean {
|
||||
return when {
|
||||
isRestrictedSAFOnlyRoot(path) -> hasProperStoredAndroidTreeUri(path)
|
||||
needsStupidWritePermissions(path) -> hasProperStoredTreeUri(false)
|
||||
isAccessibleWithSAFSdk30(path) -> hasProperStoredFirstParentUri(path)
|
||||
else -> File(path).canWrite()
|
||||
}
|
||||
}
|
||||
|
||||
private fun BaseSimpleActivity.getUri(path: String): Uri? {
|
||||
val targetFile = File(path)
|
||||
return when {
|
||||
isRestrictedSAFOnlyRoot(path) -> {
|
||||
getAndroidSAFUri(path)
|
||||
}
|
||||
needsStupidWritePermissions(path) -> {
|
||||
val parentFile = targetFile.parentFile ?: return null
|
||||
val documentFile =
|
||||
if (getDoesFilePathExist(parentFile.absolutePath ?: return null)) {
|
||||
getDocumentFile(parentFile.path)
|
||||
} else {
|
||||
val parentDocumentFile = parentFile.parent?.let { getDocumentFile(it) }
|
||||
parentDocumentFile?.createDirectory(parentFile.name) ?: getDocumentFile(parentFile.absolutePath)
|
||||
}
|
||||
|
||||
if (documentFile == null) {
|
||||
return Uri.fromFile(targetFile)
|
||||
}
|
||||
|
||||
try {
|
||||
if (getDoesFilePathExist(path)) {
|
||||
createDocumentUriFromRootTree(path)
|
||||
} else {
|
||||
documentFile.createFile(path.getMimeType(), path.getFilenameFromPath())!!.uri
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
isAccessibleWithSAFSdk30(path) -> {
|
||||
try {
|
||||
createDocumentUriUsingFirstParentTreeUri(path)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
} ?: Uri.fromFile(targetFile)
|
||||
}
|
||||
else -> return Uri.fromFile(targetFile)
|
||||
}
|
||||
}
|
||||
|
||||
private fun BaseSimpleActivity.getFileDescriptorSync(path: String, mimeType: String): ParcelFileDescriptor? {
|
||||
val targetFile = File(path)
|
||||
|
||||
return when {
|
||||
isRestrictedSAFOnlyRoot(path) -> {
|
||||
val uri = getAndroidSAFUri(path)
|
||||
if (!getDoesFilePathExist(path)) {
|
||||
createAndroidSAFFile(path)
|
||||
}
|
||||
applicationContext.contentResolver.openFileDescriptor(uri, MODE)
|
||||
}
|
||||
needsStupidWritePermissions(path) -> {
|
||||
val parentFile = targetFile.parentFile ?: return null
|
||||
val documentFile =
|
||||
if (getDoesFilePathExist(parentFile.absolutePath ?: return null)) {
|
||||
getDocumentFile(parentFile.path)
|
||||
} else {
|
||||
val parentDocumentFile = parentFile.parent?.let { getDocumentFile(it) }
|
||||
parentDocumentFile?.createDirectory(parentFile.name) ?: getDocumentFile(parentFile.absolutePath)
|
||||
}
|
||||
|
||||
|
||||
if (documentFile == null) {
|
||||
val casualOutputStream = createCasualFileDescriptor(targetFile)
|
||||
return if (casualOutputStream == null) {
|
||||
showFileCreateError(parentFile.path)
|
||||
null
|
||||
} else {
|
||||
casualOutputStream
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val uri = if (getDoesFilePathExist(path)) {
|
||||
createDocumentUriFromRootTree(path)
|
||||
} else {
|
||||
documentFile.createFile(mimeType, path.getFilenameFromPath())!!.uri
|
||||
}
|
||||
applicationContext.contentResolver.openFileDescriptor(uri, MODE)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
isAccessibleWithSAFSdk30(path) -> {
|
||||
try {
|
||||
val uri = createDocumentUriUsingFirstParentTreeUri(path)
|
||||
if (!getDoesFilePathExist(path)) {
|
||||
createSAFFileSdk30(path)
|
||||
}
|
||||
applicationContext.contentResolver.openFileDescriptor(uri, MODE)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
} ?: createCasualFileDescriptor(targetFile)
|
||||
}
|
||||
else -> return createCasualFileDescriptor(targetFile)
|
||||
}
|
||||
}
|
||||
|
||||
private fun BaseSimpleActivity.createCasualFileDescriptor(targetFile: File): ParcelFileDescriptor? {
|
||||
if (targetFile.parentFile?.exists() == false) {
|
||||
targetFile.parentFile?.mkdirs()
|
||||
}
|
||||
|
||||
private fun openOutputStream(uri: Uri): OutputStream? {
|
||||
return try {
|
||||
contentResolver.openFileDescriptor(Uri.fromFile(targetFile), MODE)
|
||||
Log.i(TAG, "uri: $uri")
|
||||
contentResolver.openOutputStream(uri)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
sealed class MediaOutput(
|
||||
open val uri: Uri,
|
||||
) {
|
||||
data class OutputStreamMediaOutput(
|
||||
val outputStream: OutputStream,
|
||||
override val uri: Uri,
|
||||
) : MediaOutput(uri)
|
||||
private fun getFileDescriptorMediaOutput(): MediaOutput.FileDescriptorMediaOutput? {
|
||||
var mediaOutput: MediaOutput.FileDescriptorMediaOutput? = null
|
||||
val canWrite = canWriteToFilePath(mediaStorageDir)
|
||||
Log.i(TAG, "getMediaOutput: canWrite=${canWrite}")
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "FileDescriptorMediaOutput: $mediaOutput")
|
||||
return mediaOutput
|
||||
}
|
||||
|
||||
data class FileDescriptorMediaOutput(
|
||||
val fileDescriptor: ParcelFileDescriptor,
|
||||
override val uri: Uri,
|
||||
) : MediaOutput(uri)
|
||||
private fun openFileDescriptor(uri: Uri): ParcelFileDescriptor? {
|
||||
return try {
|
||||
Log.i(TAG, "uri: $uri")
|
||||
contentResolver.openFileDescriptor(uri, MODE)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
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) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
activity.isAccessibleWithSAFSdk30(path) -> {
|
||||
try {
|
||||
activity.createDocumentUriUsingFirstParentTreeUri(path)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
} ?: Uri.fromFile(targetFile)
|
||||
}
|
||||
else -> return Uri.fromFile(targetFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user