diff --git a/app/src/main/java/org/linphone/activities/main/MainActivity.kt b/app/src/main/java/org/linphone/activities/main/MainActivity.kt index bedc39a33..66e89d2b1 100644 --- a/app/src/main/java/org/linphone/activities/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/activities/main/MainActivity.kt @@ -30,12 +30,14 @@ import android.view.inputmethod.InputMethodManager import androidx.databinding.DataBindingUtil import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.NavDestination import androidx.navigation.findNavController import com.google.android.material.snackbar.Snackbar import java.io.UnsupportedEncodingException import java.net.URLDecoder +import kotlinx.coroutines.* import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R @@ -147,10 +149,14 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin private fun handleIntentParams(intent: Intent) { when (intent.action) { Intent.ACTION_SEND -> { - handleSendImage(intent) + lifecycleScope.launch { + handleSendImage(intent) + } } Intent.ACTION_SEND_MULTIPLE -> { - handleSendMultipleImages(intent) + lifecycleScope.launch { + handleSendMultipleImages(intent) + } } Intent.ACTION_VIEW -> { if (intent.type == AppUtils.getString(R.string.linphone_address_mime_type)) { @@ -218,13 +224,18 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin } } - private fun handleSendImage(intent: Intent) { + private suspend fun handleSendImage(intent: Intent) { (intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { val list = arrayListOf() - val path = FileUtils.getFilePath(this, it) - if (path != null) { - list.add(path) - Log.i("[Main Activity] Found single file to share: $path") + coroutineScope { + val deferred = async { + FileUtils.getFilePath(this@MainActivity, it) + } + val path = deferred.await() + if (path != null) { + list.add(path) + Log.i("[Main Activity] Found single file to share: $path") + } } sharedViewModel.filesToShare.value = list @@ -234,14 +245,20 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin } } - private fun handleSendMultipleImages(intent: Intent) { + private suspend fun handleSendMultipleImages(intent: Intent) { intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.let { val list = arrayListOf() - for (parcelable in it) { - val uri = parcelable as Uri - val path = FileUtils.getFilePath(this, uri) - Log.i("[Main Activity] Found file to share: $path") - if (path != null) list.add(path) + coroutineScope { + val deferred = arrayListOf>() + for (parcelable in it) { + val uri = parcelable as Uri + deferred.add(async { FileUtils.getFilePath(this@MainActivity, uri) }) + } + val paths = deferred.awaitAll() + for (path in paths) { + Log.i("[Main Activity] Found file to share: $path") + if (path != null) list.add(path) + } } sharedViewModel.filesToShare.value = list diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index ed96dc37c..683da1e52 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -35,12 +35,14 @@ import androidx.appcompat.view.menu.MenuPopupHelper import androidx.core.content.FileProvider import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.navigation.Navigation import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import java.io.File import kotlinx.android.synthetic.main.tabs_fragment.* +import kotlinx.coroutines.launch import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R @@ -299,40 +301,41 @@ class DetailChatRoomFragment : MasterFragment() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (resultCode == Activity.RESULT_OK) { - var fileToUploadPath: String? = null - - val temporaryFileUploadPath = chatSendingViewModel.temporaryFileUploadPath - if (temporaryFileUploadPath != null) { - if (data != null) { - val dataUri = data.data - if (dataUri != null) { - fileToUploadPath = dataUri.toString() - Log.i("[Chat Room] Using data URI $fileToUploadPath") + lifecycleScope.launch { + var fileToUploadPath: String? = null + val temporaryFileUploadPath = chatSendingViewModel.temporaryFileUploadPath + if (temporaryFileUploadPath != null) { + if (data != null) { + val dataUri = data.data + if (dataUri != null) { + fileToUploadPath = dataUri.toString() + Log.i("[Chat Room] Using data URI $fileToUploadPath") + } else if (temporaryFileUploadPath.exists()) { + fileToUploadPath = temporaryFileUploadPath.absolutePath + Log.i("[Chat Room] Data URI is null, using $fileToUploadPath") + } } else if (temporaryFileUploadPath.exists()) { fileToUploadPath = temporaryFileUploadPath.absolutePath - Log.i("[Chat Room] Data URI is null, using $fileToUploadPath") - } - } else if (temporaryFileUploadPath.exists()) { - fileToUploadPath = temporaryFileUploadPath.absolutePath - Log.i("[Chat Room] Data is null, using $fileToUploadPath") - } - } - - if (fileToUploadPath != null) { - if (fileToUploadPath.startsWith("content://") || - fileToUploadPath.startsWith("file://") - ) { - val uriToParse = Uri.parse(fileToUploadPath) - fileToUploadPath = FileUtils.getFilePath(requireContext(), uriToParse) - Log.i("[Chat] Path was using a content or file scheme, real path is: $fileToUploadPath") - if (fileToUploadPath == null) { - Log.e("[Chat] Failed to get access to file $uriToParse") + Log.i("[Chat Room] Data is null, using $fileToUploadPath") } } - } - if (fileToUploadPath != null) { - chatSendingViewModel.addAttachment(fileToUploadPath) + if (fileToUploadPath != null) { + if (fileToUploadPath.startsWith("content://") || + fileToUploadPath.startsWith("file://") + ) { + val uriToParse = Uri.parse(fileToUploadPath) + fileToUploadPath = FileUtils.getFilePath(requireContext(), uriToParse) + Log.i("[Chat] Path was using a content or file scheme, real path is: $fileToUploadPath") + if (fileToUploadPath == null) { + Log.e("[Chat] Failed to get access to file $uriToParse") + } + } + } + + if (fileToUploadPath != null) { + chatSendingViewModel.addAttachment(fileToUploadPath) + } } } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageViewModel.kt index 949f3dccf..169a62aaf 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageViewModel.kt @@ -22,6 +22,8 @@ package org.linphone.activities.main.chat.viewmodels import android.os.CountDownTimer import android.text.Spanned import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R @@ -205,30 +207,32 @@ class ChatMessageViewModel( } private fun addContentToMediaStore(content: Content) { - when (content.type) { - "image" -> { - if (Compatibility.addImageToMediaStore(coreContext.context, content)) { - Log.i("[Chat Message] Adding image ${content.name} terminated") - } else { - Log.e("[Chat Message] Something went wrong while copying file...") + viewModelScope.launch { + when (content.type) { + "image" -> { + if (Compatibility.addImageToMediaStore(coreContext.context, content)) { + Log.i("[Chat Message] Adding image ${content.name} terminated") + } else { + Log.e("[Chat Message] Something went wrong while copying file...") + } } - } - "video" -> { - if (Compatibility.addVideoToMediaStore(coreContext.context, content)) { - Log.i("[Chat Message] Adding video ${content.name} terminated") - } else { - Log.e("[Chat Message] Something went wrong while copying file...") + "video" -> { + if (Compatibility.addVideoToMediaStore(coreContext.context, content)) { + Log.i("[Chat Message] Adding video ${content.name} terminated") + } else { + Log.e("[Chat Message] Something went wrong while copying file...") + } } - } - "audio" -> { - if (Compatibility.addAudioToMediaStore(coreContext.context, content)) { - Log.i("[Chat Message] Adding audio ${content.name} terminated") - } else { - Log.e("[Chat Message] Something went wrong while copying file...") + "audio" -> { + if (Compatibility.addAudioToMediaStore(coreContext.context, content)) { + Log.i("[Chat Message] Adding audio ${content.name} terminated") + } else { + Log.e("[Chat Message] Something went wrong while copying file...") + } + } + else -> { + Log.w("[Chat Message] 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("[Chat Message] File ${content.name} isn't either an image, an audio file or a video, can't add it to the Media Store") } } } diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt index ec56410d0..4487da829 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt @@ -31,8 +31,10 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import java.io.File +import kotlinx.coroutines.launch import org.linphone.R import org.linphone.activities.main.MainActivity import org.linphone.activities.main.contact.viewmodels.* @@ -130,40 +132,42 @@ class ContactEditorFragment : Fragment() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (resultCode == Activity.RESULT_OK) { - var fileToUploadPath: String? = null + lifecycleScope.launch { + var fileToUploadPath: String? = null - val temporaryFileUploadPath = temporaryPicturePath - if (temporaryFileUploadPath != null) { - if (data != null) { - val dataUri = data.data - if (dataUri != null) { - fileToUploadPath = dataUri.toString() - Log.i("[Chat Room] Using data URI $fileToUploadPath") + val temporaryFileUploadPath = temporaryPicturePath + if (temporaryFileUploadPath != null) { + if (data != null) { + val dataUri = data.data + if (dataUri != null) { + fileToUploadPath = dataUri.toString() + Log.i("[Chat Room] Using data URI $fileToUploadPath") + } else if (temporaryFileUploadPath.exists()) { + fileToUploadPath = temporaryFileUploadPath.absolutePath + Log.i("[Chat Room] Data URI is null, using $fileToUploadPath") + } } else if (temporaryFileUploadPath.exists()) { fileToUploadPath = temporaryFileUploadPath.absolutePath - Log.i("[Chat Room] Data URI is null, using $fileToUploadPath") - } - } else if (temporaryFileUploadPath.exists()) { - fileToUploadPath = temporaryFileUploadPath.absolutePath - Log.i("[Chat Room] Data is null, using $fileToUploadPath") - } - } - - if (fileToUploadPath != null) { - if (fileToUploadPath.startsWith("content://") || - fileToUploadPath.startsWith("file://") - ) { - val uriToParse = Uri.parse(fileToUploadPath) - fileToUploadPath = FileUtils.getFilePath(requireContext(), uriToParse) - Log.i("[Chat] Path was using a content or file scheme, real path is: $fileToUploadPath") - if (fileToUploadPath == null) { - Log.e("[Chat] Failed to get access to file $uriToParse") + Log.i("[Chat Room] Data is null, using $fileToUploadPath") } } - } - if (fileToUploadPath != null) { - viewModel.setPictureFromPath(fileToUploadPath) + if (fileToUploadPath != null) { + if (fileToUploadPath.startsWith("content://") || + fileToUploadPath.startsWith("file://") + ) { + val uriToParse = Uri.parse(fileToUploadPath) + fileToUploadPath = FileUtils.getFilePath(requireContext(), uriToParse) + Log.i("[Chat] Path was using a content or file scheme, real path is: $fileToUploadPath") + if (fileToUploadPath == null) { + Log.e("[Chat] Failed to get access to file $uriToParse") + } + } + } + + if (fileToUploadPath != null) { + viewModel.setPictureFromPath(fileToUploadPath) + } } } } diff --git a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt index bd8d61d60..804ac10af 100644 --- a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt @@ -57,7 +57,7 @@ class Api21Compatibility { vibrator.vibrate(pattern, 1) } - fun addImageToMediaStore(context: Context, content: Content): Boolean { + suspend fun addImageToMediaStore(context: Context, content: Content): Boolean { val filePath = content.filePath val appName = AppUtils.getString(R.string.app_name) val relativePath = "${Environment.DIRECTORY_PICTURES}/$appName" @@ -88,7 +88,7 @@ class Api21Compatibility { return copyOk } - fun addVideoToMediaStore(context: Context, content: Content): Boolean { + suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean { val filePath = content.filePath val appName = AppUtils.getString(R.string.app_name) val relativePath = "${Environment.DIRECTORY_MOVIES}/$appName" @@ -120,7 +120,7 @@ class Api21Compatibility { return copyOk } - fun addAudioToMediaStore(context: Context, content: Content): Boolean { + suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean { val filePath = content.filePath val appName = AppUtils.getString(R.string.app_name) val relativePath = "${Environment.DIRECTORY_MUSIC}/$appName" diff --git a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt index fd4ab0fe3..e4cb1c22c 100644 --- a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt @@ -33,7 +33,7 @@ import org.linphone.utils.FileUtils @TargetApi(29) class Api29Compatibility { companion object { - fun addImageToMediaStore(context: Context, content: Content): Boolean { + suspend fun addImageToMediaStore(context: Context, content: Content): Boolean { val filePath = content.filePath val appName = AppUtils.getString(R.string.app_name) val relativePath = "${Environment.DIRECTORY_PICTURES}/$appName" @@ -70,7 +70,7 @@ class Api29Compatibility { return copyOk } - fun addVideoToMediaStore(context: Context, content: Content): Boolean { + suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean { val filePath = content.filePath val appName = AppUtils.getString(R.string.app_name) val relativePath = "${Environment.DIRECTORY_MOVIES}/$appName" @@ -108,7 +108,7 @@ class Api29Compatibility { return copyOk } - fun addAudioToMediaStore(context: Context, content: Content): Boolean { + suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean { val filePath = content.filePath val appName = AppUtils.getString(R.string.app_name) val relativePath = "${Environment.DIRECTORY_MUSIC}/$appName" diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index b1678a7d0..cc44a3db5 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -115,21 +115,21 @@ class Compatibility { // TODO Use removeLongLivedShortcuts() starting Android R (API 30) } - fun addImageToMediaStore(context: Context, content: Content): Boolean { + suspend fun addImageToMediaStore(context: Context, content: Content): Boolean { if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) { return Api29Compatibility.addImageToMediaStore(context, content) } return Api21Compatibility.addImageToMediaStore(context, content) } - fun addVideoToMediaStore(context: Context, content: Content): Boolean { + suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean { if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) { return Api29Compatibility.addVideoToMediaStore(context, content) } return Api21Compatibility.addVideoToMediaStore(context, content) } - fun addAudioToMediaStore(context: Context, content: Content): Boolean { + suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean { if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) { return Api29Compatibility.addAudioToMediaStore(context, content) } diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt index c9ffca3f6..5aa02a69c 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ b/app/src/main/java/org/linphone/utils/FileUtils.kt @@ -26,6 +26,10 @@ import android.provider.OpenableColumns import java.io.* import java.text.SimpleDateFormat import java.util.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.withContext import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.core.tools.Log @@ -109,7 +113,7 @@ class FileUtils { } } - fun getFilePath(context: Context, uri: Uri): String? { + suspend fun getFilePath(context: Context, uri: Uri): String? { var result: String? = null val name: String = getNameFromUri(uri, context) @@ -123,13 +127,16 @@ class FileUtils { " to local file " + localFile.absolutePath ) - if (copyToFile(remoteFile, localFile)) { - Log.i("[File Utils] Copy successful") - result = localFile.absolutePath - } else { - Log.e("[File Utils] Copy failed") + coroutineScope { + val deferred = async { copyToFile(remoteFile, localFile) } + if (deferred.await()) { + Log.i("[File Utils] Copy successful") + result = localFile.absolutePath + } else { + Log.e("[File Utils] Copy failed") + } + remoteFile?.close() } - remoteFile?.close() } catch (e: IOException) { Log.e("[File Utils] getFilePath exception: ", e) } @@ -158,7 +165,7 @@ class FileUtils { return name } - fun copyFileTo(filePath: String, outputStream: OutputStream?): Boolean { + suspend fun copyFileTo(filePath: String, outputStream: OutputStream?): Boolean { if (outputStream == null) { Log.e("[File Utils] Can't copy file $filePath to given null output stream") return false @@ -171,11 +178,13 @@ class FileUtils { } try { - val inputStream = FileInputStream(file) - val buffer = ByteArray(4096) - var bytesRead: Int - while (inputStream.read(buffer).also { bytesRead = it } >= 0) { - outputStream.write(buffer, 0, bytesRead) + withContext(Dispatchers.IO) { + val inputStream = FileInputStream(file) + val buffer = ByteArray(4096) + var bytesRead: Int + while (inputStream.read(buffer).also { bytesRead = it } >= 0) { + outputStream.write(buffer, 0, bytesRead) + } } return true } catch (e: IOException) { @@ -184,14 +193,16 @@ class FileUtils { return false } - private fun copyToFile(inputStream: InputStream?, destFile: File?): Boolean { + private suspend fun copyToFile(inputStream: InputStream?, destFile: File?): Boolean { if (inputStream == null || destFile == null) return false try { - FileOutputStream(destFile).use { out -> - val buffer = ByteArray(4096) - var bytesRead: Int - while (inputStream.read(buffer).also { bytesRead = it } >= 0) { - out.write(buffer, 0, bytesRead) + withContext(Dispatchers.IO) { + FileOutputStream(destFile).use { out -> + val buffer = ByteArray(4096) + var bytesRead: Int + while (inputStream.read(buffer).also { bytesRead = it } >= 0) { + out.write(buffer, 0, bytesRead) + } } } return true