Reworked & improved export when usig VFS feature
This commit is contained in:
parent
a2ac7e9f37
commit
0404777c32
4 changed files with 151 additions and 20 deletions
|
@ -21,13 +21,21 @@ package org.linphone.activities.main.files.fragments
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.linphone.R
|
import org.linphone.R
|
||||||
import org.linphone.activities.GenericFragment
|
import org.linphone.activities.GenericFragment
|
||||||
import org.linphone.activities.SnackBarActivity
|
import org.linphone.activities.SnackBarActivity
|
||||||
|
import org.linphone.compatibility.Compatibility
|
||||||
import org.linphone.core.Content
|
import org.linphone.core.Content
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
import org.linphone.databinding.FileViewerTopBarFragmentBinding
|
import org.linphone.databinding.FileViewerTopBarFragmentBinding
|
||||||
|
import org.linphone.mediastream.Version
|
||||||
import org.linphone.utils.FileUtils
|
import org.linphone.utils.FileUtils
|
||||||
|
import org.linphone.utils.PermissionHelper
|
||||||
|
|
||||||
class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
|
class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
|
||||||
private var content: Content? = null
|
private var content: Content? = null
|
||||||
|
@ -46,20 +54,9 @@ class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.setExportClickListener {
|
binding.setExportClickListener {
|
||||||
if (content != null) {
|
val contentToExport = content
|
||||||
val filePath = content?.plainFilePath.orEmpty()
|
if (contentToExport != null) {
|
||||||
plainFilePath = if (filePath.isEmpty()) content?.filePath.orEmpty() else filePath
|
exportContent(contentToExport)
|
||||||
Log.i("[File Viewer] Plain file path is: $plainFilePath")
|
|
||||||
if (plainFilePath.isNotEmpty()) {
|
|
||||||
if (!FileUtils.openFileInThirdPartyApp(requireActivity(), plainFilePath)) {
|
|
||||||
(requireActivity() as SnackBarActivity).showSnackBar(R.string.chat_message_no_app_found_to_handle_file_mime_type)
|
|
||||||
if (plainFilePath != content?.filePath.orEmpty()) {
|
|
||||||
Log.i("[File Viewer] No app to open plain file path: $plainFilePath, destroying it")
|
|
||||||
FileUtils.deleteFile(plainFilePath)
|
|
||||||
}
|
|
||||||
plainFilePath = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Log.e("[File Viewer] No Content set!")
|
Log.e("[File Viewer] No Content set!")
|
||||||
}
|
}
|
||||||
|
@ -89,4 +86,77 @@ class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
|
||||||
content = c
|
content = c
|
||||||
binding.fileName.text = c.name
|
binding.fileName.text = c.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun exportContent(content: Content) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
var mediaStoreFilePath = ""
|
||||||
|
if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10) || PermissionHelper.get().hasWriteExternalStoragePermission()) {
|
||||||
|
Log.i("[File Viewer] Exporting image through Media Store API")
|
||||||
|
when (content.type) {
|
||||||
|
"image" -> {
|
||||||
|
val export = lifecycleScope.async {
|
||||||
|
Compatibility.addImageToMediaStore(requireContext(), content)
|
||||||
|
}
|
||||||
|
if (export.await()) {
|
||||||
|
Log.i("[File Viewer] Adding image ${content.name} to Media Store terminated: ${content.userData}")
|
||||||
|
mediaStoreFilePath = content.userData.toString()
|
||||||
|
} else {
|
||||||
|
Log.e("[File Viewer] Something went wrong while copying file to Media Store...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"video" -> {
|
||||||
|
val export = lifecycleScope.async {
|
||||||
|
Compatibility.addVideoToMediaStore(requireContext(), content)
|
||||||
|
}
|
||||||
|
if (export.await()) {
|
||||||
|
Log.i("[File Viewer] Adding video ${content.name} to Media Store terminated: ${content.userData}")
|
||||||
|
mediaStoreFilePath = content.userData.toString()
|
||||||
|
} else {
|
||||||
|
Log.e("[File Viewer] Something went wrong while copying file to Media Store...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"audio" -> {
|
||||||
|
val export = lifecycleScope.async {
|
||||||
|
Compatibility.addAudioToMediaStore(requireContext(), content)
|
||||||
|
}
|
||||||
|
if (export.await()) {
|
||||||
|
Log.i("[File Viewer] Adding audio ${content.name} to Media Store terminated: ${content.userData}")
|
||||||
|
mediaStoreFilePath = content.userData.toString()
|
||||||
|
} else {
|
||||||
|
Log.e("[File Viewer] Something went wrong while copying file to Media Store...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.w("[File Viewer] File ${content.name} isn't either an image, an audio file or a video, can't add it to the Media Store")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w("[File Viewer] Can't export image through Media Store API (requires Android 10 or WRITE_EXTERNAL permission, using fallback method...")
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (mediaStoreFilePath.isEmpty()) {
|
||||||
|
Log.w("[File Viewer] Media store file path is empty, media store export failed?")
|
||||||
|
|
||||||
|
val filePath = content.plainFilePath.orEmpty()
|
||||||
|
plainFilePath = if (filePath.isEmpty()) content.filePath.orEmpty() else filePath
|
||||||
|
Log.i("[File Viewer] Plain file path is: $plainFilePath")
|
||||||
|
if (plainFilePath.isNotEmpty()) {
|
||||||
|
if (!FileUtils.openFileInThirdPartyApp(requireActivity(), plainFilePath)) {
|
||||||
|
(requireActivity() as SnackBarActivity).showSnackBar(R.string.chat_message_no_app_found_to_handle_file_mime_type)
|
||||||
|
if (plainFilePath != content.filePath.orEmpty()) {
|
||||||
|
Log.i("[File Viewer] No app to open plain file path: $plainFilePath, destroying it")
|
||||||
|
FileUtils.deleteFile(plainFilePath)
|
||||||
|
}
|
||||||
|
plainFilePath = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plainFilePath = ""
|
||||||
|
Log.i("[File Viewer] Media store file path is: $mediaStoreFilePath")
|
||||||
|
FileUtils.openMediaStoreFile(requireActivity(), mediaStoreFilePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,9 @@ class Api21Compatibility {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val filePath = content.filePath
|
val plainFilePath = content.plainFilePath.orEmpty()
|
||||||
|
val isVfsEncrypted = plainFilePath.isNotEmpty()
|
||||||
|
val filePath = if (isVfsEncrypted) plainFilePath else content.filePath
|
||||||
if (filePath == null) {
|
if (filePath == null) {
|
||||||
Log.e("[Media Store] Content doesn't have a file path!")
|
Log.e("[Media Store] Content doesn't have a file path!")
|
||||||
return false
|
return false
|
||||||
|
@ -95,6 +97,10 @@ class Api21Compatibility {
|
||||||
}
|
}
|
||||||
val collection = MediaStore.Images.Media.getContentUri("external")
|
val collection = MediaStore.Images.Media.getContentUri("external")
|
||||||
val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values)
|
val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values)
|
||||||
|
if (isVfsEncrypted) {
|
||||||
|
Log.w("[Media Store] Content was encrypted, delete plain version: $plainFilePath")
|
||||||
|
FileUtils.deleteFile(plainFilePath)
|
||||||
|
}
|
||||||
if (mediaStoreFilePath.isNotEmpty()) {
|
if (mediaStoreFilePath.isNotEmpty()) {
|
||||||
content.userData = mediaStoreFilePath
|
content.userData = mediaStoreFilePath
|
||||||
return true
|
return true
|
||||||
|
@ -108,7 +114,9 @@ class Api21Compatibility {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val filePath = content.filePath
|
val plainFilePath = content.plainFilePath.orEmpty()
|
||||||
|
val isVfsEncrypted = plainFilePath.isNotEmpty()
|
||||||
|
val filePath = if (isVfsEncrypted) plainFilePath else content.filePath
|
||||||
if (filePath == null) {
|
if (filePath == null) {
|
||||||
Log.e("[Media Store] Content doesn't have a file path!")
|
Log.e("[Media Store] Content doesn't have a file path!")
|
||||||
return false
|
return false
|
||||||
|
@ -127,6 +135,10 @@ class Api21Compatibility {
|
||||||
}
|
}
|
||||||
val collection = MediaStore.Video.Media.getContentUri("external")
|
val collection = MediaStore.Video.Media.getContentUri("external")
|
||||||
val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values)
|
val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values)
|
||||||
|
if (isVfsEncrypted) {
|
||||||
|
Log.w("[Media Store] Content was encrypted, delete plain version: $plainFilePath")
|
||||||
|
FileUtils.deleteFile(plainFilePath)
|
||||||
|
}
|
||||||
if (mediaStoreFilePath.isNotEmpty()) {
|
if (mediaStoreFilePath.isNotEmpty()) {
|
||||||
content.userData = mediaStoreFilePath
|
content.userData = mediaStoreFilePath
|
||||||
return true
|
return true
|
||||||
|
@ -140,7 +152,9 @@ class Api21Compatibility {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val filePath = content.filePath
|
val plainFilePath = content.plainFilePath.orEmpty()
|
||||||
|
val isVfsEncrypted = plainFilePath.isNotEmpty()
|
||||||
|
val filePath = if (isVfsEncrypted) plainFilePath else content.filePath
|
||||||
if (filePath == null) {
|
if (filePath == null) {
|
||||||
Log.e("[Media Store] Content doesn't have a file path!")
|
Log.e("[Media Store] Content doesn't have a file path!")
|
||||||
return false
|
return false
|
||||||
|
@ -160,6 +174,10 @@ class Api21Compatibility {
|
||||||
val collection = MediaStore.Audio.Media.getContentUri("external")
|
val collection = MediaStore.Audio.Media.getContentUri("external")
|
||||||
|
|
||||||
val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values)
|
val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values)
|
||||||
|
if (isVfsEncrypted) {
|
||||||
|
Log.w("[Media Store] Content was encrypted, delete plain version: $plainFilePath")
|
||||||
|
FileUtils.deleteFile(plainFilePath)
|
||||||
|
}
|
||||||
if (mediaStoreFilePath.isNotEmpty()) {
|
if (mediaStoreFilePath.isNotEmpty()) {
|
||||||
content.userData = mediaStoreFilePath
|
content.userData = mediaStoreFilePath
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -99,7 +99,9 @@ class Api29Compatibility {
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
|
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
|
||||||
val filePath = content.filePath
|
val plainFilePath = content.plainFilePath.orEmpty()
|
||||||
|
val isVfsEncrypted = plainFilePath.isNotEmpty()
|
||||||
|
val filePath = if (isVfsEncrypted) plainFilePath else content.filePath
|
||||||
if (filePath == null) {
|
if (filePath == null) {
|
||||||
Log.e("[Media Store] Content doesn't have a file path!")
|
Log.e("[Media Store] Content doesn't have a file path!")
|
||||||
return false
|
return false
|
||||||
|
@ -119,6 +121,11 @@ class Api29Compatibility {
|
||||||
}
|
}
|
||||||
val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||||
val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values, MediaStore.Images.Media.IS_PENDING)
|
val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values, MediaStore.Images.Media.IS_PENDING)
|
||||||
|
|
||||||
|
if (isVfsEncrypted) {
|
||||||
|
Log.w("[Media Store] Content was encrypted, delete plain version: $plainFilePath")
|
||||||
|
FileUtils.deleteFile(plainFilePath)
|
||||||
|
}
|
||||||
if (mediaStoreFilePath.isNotEmpty()) {
|
if (mediaStoreFilePath.isNotEmpty()) {
|
||||||
content.userData = mediaStoreFilePath
|
content.userData = mediaStoreFilePath
|
||||||
return true
|
return true
|
||||||
|
@ -127,7 +134,9 @@ class Api29Compatibility {
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean {
|
suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean {
|
||||||
val filePath = content.filePath
|
val plainFilePath = content.plainFilePath.orEmpty()
|
||||||
|
val isVfsEncrypted = plainFilePath.isNotEmpty()
|
||||||
|
val filePath = if (isVfsEncrypted) plainFilePath else content.filePath
|
||||||
if (filePath == null) {
|
if (filePath == null) {
|
||||||
Log.e("[Media Store] Content doesn't have a file path!")
|
Log.e("[Media Store] Content doesn't have a file path!")
|
||||||
return false
|
return false
|
||||||
|
@ -148,6 +157,11 @@ class Api29Compatibility {
|
||||||
}
|
}
|
||||||
val collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
val collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||||
val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values, MediaStore.Video.Media.IS_PENDING)
|
val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values, MediaStore.Video.Media.IS_PENDING)
|
||||||
|
|
||||||
|
if (isVfsEncrypted) {
|
||||||
|
Log.w("[Media Store] Content was encrypted, delete plain version: $plainFilePath")
|
||||||
|
FileUtils.deleteFile(plainFilePath)
|
||||||
|
}
|
||||||
if (mediaStoreFilePath.isNotEmpty()) {
|
if (mediaStoreFilePath.isNotEmpty()) {
|
||||||
content.userData = mediaStoreFilePath
|
content.userData = mediaStoreFilePath
|
||||||
return true
|
return true
|
||||||
|
@ -156,7 +170,9 @@ class Api29Compatibility {
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean {
|
suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean {
|
||||||
val filePath = content.filePath
|
val plainFilePath = content.plainFilePath.orEmpty()
|
||||||
|
val isVfsEncrypted = plainFilePath.isNotEmpty()
|
||||||
|
val filePath = if (isVfsEncrypted) plainFilePath else content.filePath
|
||||||
if (filePath == null) {
|
if (filePath == null) {
|
||||||
Log.e("[Media Store] Content doesn't have a file path!")
|
Log.e("[Media Store] Content doesn't have a file path!")
|
||||||
return false
|
return false
|
||||||
|
@ -178,6 +194,11 @@ class Api29Compatibility {
|
||||||
val collection = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
val collection = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||||
|
|
||||||
val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values, MediaStore.Audio.Media.IS_PENDING)
|
val mediaStoreFilePath = addContentValuesToCollection(context, filePath, collection, values, MediaStore.Audio.Media.IS_PENDING)
|
||||||
|
|
||||||
|
if (isVfsEncrypted) {
|
||||||
|
Log.w("[Media Store] Content was encrypted, delete plain version: $plainFilePath")
|
||||||
|
FileUtils.deleteFile(plainFilePath)
|
||||||
|
}
|
||||||
if (mediaStoreFilePath.isNotEmpty()) {
|
if (mediaStoreFilePath.isNotEmpty()) {
|
||||||
content.userData = mediaStoreFilePath
|
content.userData = mediaStoreFilePath
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -470,5 +470,27 @@ class FileUtils {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openMediaStoreFile(
|
||||||
|
activity: Activity,
|
||||||
|
contentFilePath: String,
|
||||||
|
newTask: Boolean = false
|
||||||
|
): Boolean {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
val contentUri: Uri = Uri.parse(contentFilePath)
|
||||||
|
intent.data = contentUri
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
if (newTask) {
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
activity.startActivity(intent)
|
||||||
|
return true
|
||||||
|
} catch (anfe: ActivityNotFoundException) {
|
||||||
|
Log.e("[File Viewer] Can't open media store export in third party app: $anfe")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue