diff --git a/app/src/main/java/org/linphone/ui/main/viewer/fragment/FileViewerFragment.kt b/app/src/main/java/org/linphone/ui/main/viewer/fragment/FileViewerFragment.kt index 305803c0a..97a914964 100644 --- a/app/src/main/java/org/linphone/ui/main/viewer/fragment/FileViewerFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/viewer/fragment/FileViewerFragment.kt @@ -1,5 +1,7 @@ package org.linphone.ui.main.viewer.fragment +import android.app.Activity +import android.content.Intent import android.os.Bundle import android.util.DisplayMetrics import android.view.LayoutInflater @@ -21,6 +23,8 @@ import org.linphone.ui.main.viewer.viewmodel.FileViewModel class FileViewerFragment : GenericFragment() { companion object { private const val TAG = "[File Viewer Fragment]" + + private const val EXPORT_PDF = 10 } private lateinit var binding: FileViewerFragmentBinding @@ -73,6 +77,17 @@ class FileViewerFragment : GenericFragment() { } } + viewModel.exportPdfEvent.observe(viewLifecycleOwner) { + it.consume { name -> + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/pdf" + putExtra(Intent.EXTRA_TITLE, name) + } + startActivityForResult(intent, EXPORT_PDF) + } + } + viewModel.isVideo.observe(viewLifecycleOwner) { isVideo -> if (isVideo) { binding.videoPlayer.setVideoPath(path) @@ -134,6 +149,17 @@ class FileViewerFragment : GenericFragment() { super.onDestroyView() } + @Deprecated("Deprecated in Java") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == EXPORT_PDF && resultCode == Activity.RESULT_OK) { + data?.data?.also { documentUri -> + Log.i("$TAG Exported PDF should be stored in URI [$documentUri]") + viewModel.copyPdfToUri(documentUri) + } + } + super.onActivityResult(requestCode, resultCode, data) + } + private fun updateScreenSize() { val displayMetrics = DisplayMetrics() requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) diff --git a/app/src/main/java/org/linphone/ui/main/viewer/viewmodel/FileViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewer/viewmodel/FileViewModel.kt index 74e51b898..b056f6585 100644 --- a/app/src/main/java/org/linphone/ui/main/viewer/viewmodel/FileViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewer/viewmodel/FileViewModel.kt @@ -48,6 +48,10 @@ class FileViewModel @UiThread constructor() : ViewModel() { MutableLiveData>() } + val exportPdfEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + val toggleVideoPlayPauseEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -67,7 +71,7 @@ class FileViewModel @UiThread constructor() : ViewModel() { var screenWidth: Int = 0 var screenHeight: Int = 0 - var currentPdfPage: PdfRenderer.Page? = null + private var currentPdfPage: PdfRenderer.Page? = null // End of PDF viewer required variables override fun onCleared() { @@ -180,22 +184,29 @@ class FileViewModel @UiThread constructor() : ViewModel() { @UiThread fun exportToMediaStore() { if (::filePath.isInitialized) { - viewModelScope.launch { - withContext(Dispatchers.IO) { - Log.i("$TAG Export file [$filePath] to Android's MediaStore") - val mediaStorePath = addContentToMediaStore(filePath) - if (mediaStorePath.isNotEmpty()) { - Log.i("$TAG File [$filePath] has been successfully exported to MediaStore") - val message = AppUtils.getString( - R.string.toast_file_successfully_exported_to_media_store - ) - showGreenToastEvent.postValue(Event(Pair(message, R.drawable.check))) - } else { - Log.e("$TAG Failed to export file [$filePath] to MediaStore!") - val message = AppUtils.getString( - R.string.toast_export_file_to_media_store_error - ) - showRedToastEvent.postValue(Event(Pair(message, R.drawable.x))) + if (isPdf.value == true) { + Log.i("$TAG Exporting PDF as document") + exportPdfEvent.postValue(Event(fileName.value.orEmpty())) + } else { + viewModelScope.launch { + withContext(Dispatchers.IO) { + Log.i("$TAG Export file [$filePath] to Android's MediaStore") + val mediaStorePath = addContentToMediaStore(filePath) + if (mediaStorePath.isNotEmpty()) { + Log.i( + "$TAG File [$filePath] has been successfully exported to MediaStore" + ) + val message = AppUtils.getString( + R.string.toast_file_successfully_exported_to_media_store + ) + showGreenToastEvent.postValue(Event(Pair(message, R.drawable.check))) + } else { + Log.e("$TAG Failed to export file [$filePath] to MediaStore!") + val message = AppUtils.getString( + R.string.toast_export_file_to_media_store_error + ) + showRedToastEvent.postValue(Event(Pair(message, R.drawable.x))) + } } } } @@ -204,6 +215,32 @@ class FileViewModel @UiThread constructor() : ViewModel() { } } + @UiThread + fun copyPdfToUri(dest: Uri) { + val source = Uri.parse(FileUtils.getProperFilePath(filePath)) + Log.i("$TAG Copying file URI [$source] to [$dest]") + viewModelScope.launch { + withContext(Dispatchers.IO) { + val result = FileUtils.copyFile(source, dest) + if (result) { + Log.i( + "$TAG File [$filePath] has been successfully exported to documents" + ) + val message = AppUtils.getString( + R.string.toast_file_successfully_exported_to_documents + ) + showGreenToastEvent.postValue(Event(Pair(message, R.drawable.check))) + } else { + Log.e("$TAG Failed to export file [$filePath] to documents!") + val message = AppUtils.getString( + R.string.toast_export_file_to_documents_error + ) + showRedToastEvent.postValue(Event(Pair(message, R.drawable.x))) + } + } + } + } + @UiThread private suspend fun addContentToMediaStore( path: String diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt index 557b35ba8..130aa60d4 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ b/app/src/main/java/org/linphone/utils/FileUtils.kt @@ -33,6 +33,7 @@ import androidx.annotation.AnyThread import androidx.core.content.FileProvider import java.io.File import java.io.FileInputStream +import java.io.FileNotFoundException import java.io.FileOutputStream import java.io.IOException import java.io.OutputStream @@ -259,6 +260,33 @@ class FileUtils { return false } + @AnyThread + fun copyFile(from: Uri, to: Uri): Boolean { + try { + coreContext.context.contentResolver.openFileDescriptor(to, "w")?.use { fd -> + FileOutputStream(fd.fileDescriptor).use { outputStream -> + val fileDescriptor = coreContext.context.contentResolver.openFileDescriptor( + from, + "r" + ) + val inputStream = FileInputStream(fileDescriptor?.fileDescriptor) + val buffer = ByteArray(4096) + var bytesRead: Int + while (inputStream.read(buffer).also { bytesRead = it } >= 0) { + outputStream.write(buffer, 0, bytesRead) + } + fileDescriptor?.close() + } + } + return true + } catch (e: FileNotFoundException) { + Log.e("$TAG Failed to find dest file: $e") + } catch (e: IOException) { + Log.e("$TAG Error copying file: $e") + } + return false + } + suspend fun copyFileTo(filePath: String, outputStream: OutputStream?): Boolean { if (outputStream == null) { Log.e("$TAG Can't copy file $filePath to given null output stream") diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dc1ab2661..196519e70 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -136,6 +136,8 @@ Connection error! File has been exported to native gallery Error trying to export file to native gallery + File has been exported to documents + Error trying to export file to documents Someone joined the conversation Someone left the conversation Someone is now admin