Changes to prepare for VFS

This commit is contained in:
Sylvain Berfini 2021-04-01 16:47:54 +02:00
parent 5411648e2e
commit d165b04307
14 changed files with 110 additions and 85 deletions

View file

@ -19,11 +19,9 @@
*/ */
package org.linphone.activities.chat_bubble package org.linphone.activities.chat_bubble
import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.webkit.MimeTypeMap import android.widget.Toast
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -31,7 +29,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R import org.linphone.R
import org.linphone.activities.GenericActivity import org.linphone.activities.GenericActivity
@ -113,8 +110,12 @@ class ChatBubbleActivity : GenericActivity() {
adapter.disableContextMenu() adapter.disableContextMenu()
adapter.openContentEvent.observe(this, { adapter.openContentEvent.observe(this, {
it.consume { path -> it.consume { content ->
openFile(path) if (content.isFileEncrypted) {
Toast.makeText(this, R.string.chat_bubble_cant_open_enrypted_file, Toast.LENGTH_LONG).show()
} else {
FileUtils.openFileInThirdPartyApp(this, content.filePath.orEmpty(), true)
}
} }
}) })
@ -174,41 +175,4 @@ class ChatBubbleActivity : GenericActivity() {
binding.chatMessagesList.scrollToPosition(adapter.itemCount - 1) binding.chatMessagesList.scrollToPosition(adapter.itemCount - 1)
} }
} }
private fun openFile(contentFilePath: String) {
val intent = Intent(Intent.ACTION_VIEW)
val contentUri: Uri = FileUtils.getPublicFilePath(this, contentFilePath)
val filePath: String = contentUri.toString()
Log.i("[Chat Bubble] Trying to open file: $filePath")
var type: String? = null
val extension = FileUtils.getExtensionFromFileName(filePath)
if (extension.isNotEmpty()) {
Log.i("[Chat Bubble] Found extension $extension")
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
} else {
Log.e("[Chat Bubble] Couldn't find extension")
}
if (type != null) {
Log.i("[Chat Bubble] Found matching MIME type $type")
} else {
type = "file/$extension"
Log.e("[Chat Bubble] Can't get MIME type from extension: $extension, will use $type")
}
intent.setDataAndType(contentUri, type)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
startActivity(intent)
if (LinphoneApplication.corePreferences.enableAnimations) {
overridePendingTransition(R.anim.enter_right, R.anim.exit_left)
}
} catch (anfe: ActivityNotFoundException) {
Log.e("[Chat Bubble] Couldn't find an activity to handle MIME type: $type")
}
}
} }

View file

@ -40,6 +40,7 @@ import org.linphone.activities.main.chat.viewmodels.OnContentClickedListener
import org.linphone.activities.main.viewmodels.ListTopBarViewModel import org.linphone.activities.main.viewmodels.ListTopBarViewModel
import org.linphone.core.ChatMessage import org.linphone.core.ChatMessage
import org.linphone.core.ChatRoomCapabilities import org.linphone.core.ChatRoomCapabilities
import org.linphone.core.Content
import org.linphone.core.EventLog import org.linphone.core.EventLog
import org.linphone.databinding.ChatEventListCellBinding import org.linphone.databinding.ChatEventListCellBinding
import org.linphone.databinding.ChatMessageListCellBinding import org.linphone.databinding.ChatMessageListCellBinding
@ -73,13 +74,13 @@ class ChatMessagesListAdapter(
MutableLiveData<Event<String>>() MutableLiveData<Event<String>>()
} }
val openContentEvent: MutableLiveData<Event<String>> by lazy { val openContentEvent: MutableLiveData<Event<Content>> by lazy {
MutableLiveData<Event<String>>() MutableLiveData<Event<Content>>()
} }
private val contentClickedListener = object : OnContentClickedListener { private val contentClickedListener = object : OnContentClickedListener {
override fun onContentClicked(path: String) { override fun onContentClicked(content: Content) {
openContentEvent.value = Event(path) openContentEvent.value = Event(content)
} }
} }

View file

@ -222,24 +222,47 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
}) })
adapter.openContentEvent.observe(viewLifecycleOwner, { adapter.openContentEvent.observe(viewLifecycleOwner, {
it.consume { path -> it.consume { content ->
val path = content.filePath.orEmpty()
if (!File(path).exists()) { if (!File(path).exists()) {
(requireActivity() as MainActivity).showSnackBar(R.string.chat_room_file_not_found) (requireActivity() as MainActivity).showSnackBar(R.string.chat_room_file_not_found)
} else { } else {
Log.i("[Chat Message] Opening file: $path") Log.i("[Chat Message] Opening file: $path")
sharedViewModel.fileToOpen.value = path sharedViewModel.contentToOpen.value = content
val preventScreenshots = viewModel.chatRoom.currentParams.encryptionEnabled()
when { if (corePreferences.useInAppFileViewerForNonEncryptedFiles || content.isFileEncrypted) {
FileUtils.isExtensionImage(path) -> navigateToImageFileViewer(preventScreenshots) val preventScreenshots =
FileUtils.isExtensionVideo(path) -> navigateToVideoFileViewer(preventScreenshots) viewModel.chatRoom.currentParams.encryptionEnabled()
FileUtils.isExtensionAudio(path) -> navigateToAudioFileViewer(preventScreenshots) when {
FileUtils.isExtensionPdf(path) -> navigateToPdfFileViewer(preventScreenshots) FileUtils.isExtensionImage(path) -> navigateToImageFileViewer(
FileUtils.isPlainTextFile(path) -> navigateToTextFileViewer(preventScreenshots) preventScreenshots
else -> { )
if (!FileUtils.openFileInThirdPartyApp(requireActivity(), path)) { FileUtils.isExtensionVideo(path) -> navigateToVideoFileViewer(
showDialogToSuggestOpeningFileAsText() preventScreenshots
)
FileUtils.isExtensionAudio(path) -> navigateToAudioFileViewer(
preventScreenshots
)
FileUtils.isExtensionPdf(path) -> navigateToPdfFileViewer(
preventScreenshots
)
FileUtils.isPlainTextFile(path) -> navigateToTextFileViewer(
preventScreenshots
)
else -> {
if (content.isFileEncrypted) {
Log.w("[Chat Message] File is encrypted and can't be opened in one of our viewers...")
// TODO: show dialog to ask user for consent before trying to export the file first
} else if (!FileUtils.openFileInThirdPartyApp(requireActivity(), path)) {
showDialogToSuggestOpeningFileAsText()
}
} }
} }
} else {
if (!FileUtils.openFileInThirdPartyApp(requireActivity(), path)) {
showDialogToSuggestOpeningFileAsText()
}
} }
} }
} }

View file

@ -146,10 +146,10 @@ class ChatMessageContentViewModel(
} }
fun openFile() { fun openFile() {
listener?.onContentClicked(content.filePath.orEmpty()) listener?.onContentClicked(content)
} }
} }
interface OnContentClickedListener { interface OnContentClickedListener {
fun onContentClicked(path: String) fun onContentClicked(content: Content)
} }

View file

@ -45,11 +45,13 @@ class ImageViewerFragment : SecureFragment<ImageViewerFragmentBinding>() {
ViewModelProvider(this).get(SharedMainViewModel::class.java) ViewModelProvider(this).get(SharedMainViewModel::class.java)
} }
val filePath = sharedViewModel.fileToOpen.value val content = sharedViewModel.contentToOpen.value
content ?: return
val filePath = content.filePath
filePath ?: return filePath ?: return
(childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment) (childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment)
?.setFilePath(filePath) ?.setContent(content)
viewModel = ViewModelProvider( viewModel = ViewModelProvider(
this, this,

View file

@ -45,11 +45,13 @@ class PdfViewerFragment : SecureFragment<PdfViewerFragmentBinding>() {
ViewModelProvider(this).get(SharedMainViewModel::class.java) ViewModelProvider(this).get(SharedMainViewModel::class.java)
} }
val filePath = sharedViewModel.fileToOpen.value val content = sharedViewModel.contentToOpen.value
content ?: return
val filePath = content.filePath
filePath ?: return filePath ?: return
(childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment) (childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment)
?.setFilePath(filePath) ?.setContent(content)
viewModel = ViewModelProvider( viewModel = ViewModelProvider(
this, this,

View file

@ -43,11 +43,13 @@ class TextViewerFragment : SecureFragment<TextViewerFragmentBinding>() {
ViewModelProvider(this).get(SharedMainViewModel::class.java) ViewModelProvider(this).get(SharedMainViewModel::class.java)
} }
val filePath = sharedViewModel.fileToOpen.value val content = sharedViewModel.contentToOpen.value
content ?: return
val filePath = content.filePath
filePath ?: return filePath ?: return
(childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment) (childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment)
?.setFilePath(filePath) ?.setContent(content)
viewModel = ViewModelProvider( viewModel = ViewModelProvider(
this, this,

View file

@ -24,12 +24,14 @@ import androidx.navigation.fragment.findNavController
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.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.utils.FileUtils import org.linphone.utils.FileUtils
class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() { class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
private var filePath: String = "" private var content: Content? = null
private var plainFilePath: String = ""
override fun getLayoutId(): Int = R.layout.file_viewer_top_bar_fragment override fun getLayoutId(): Int = R.layout.file_viewer_top_bar_fragment
@ -43,24 +45,36 @@ class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
} }
binding.setExportClickListener { binding.setExportClickListener {
if (!FileUtils.openFileInThirdPartyApp(requireActivity(), filePath)) { if (content != null) {
(requireActivity() as SnackBarActivity).showSnackBar(R.string.chat_message_no_app_found_to_handle_file_mime_type) val filePath = content?.plainFilePath.orEmpty()
plainFilePath = if (filePath.isEmpty()) content?.filePath.orEmpty() else filePath
Log.i("[File Viewer] Plain file path is: $filePath")
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 {
Log.e("[File Viewer] No Content set!")
} }
} }
} }
fun setFilePath(newFilePath: String) { override fun onDestroyView() {
Log.i("[File Viewer] File path is: $newFilePath") if (plainFilePath.isNotEmpty() && plainFilePath != content?.filePath.orEmpty()) {
filePath = newFilePath Log.i("[File Viewer] Destroying plain file path: $plainFilePath")
FileUtils.deleteFile(plainFilePath)
}
super.onDestroyView()
} }
override fun onSaveInstanceState(outState: Bundle) { fun setContent(c: Content) {
outState.putString("FilePath", filePath) Log.i("[File Viewer] Content file path is: ${c.filePath}")
super.onSaveInstanceState(outState) content = c
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
filePath = savedInstanceState?.getString("FilePath") ?: filePath
} }
} }

View file

@ -47,11 +47,13 @@ class VideoViewerFragment : SecureFragment<VideoViewerFragmentBinding>() {
ViewModelProvider(this).get(SharedMainViewModel::class.java) ViewModelProvider(this).get(SharedMainViewModel::class.java)
} }
val filePath = sharedViewModel.fileToOpen.value val content = sharedViewModel.contentToOpen.value
content ?: return
val filePath = content.filePath
filePath ?: return filePath ?: return
(childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment) (childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment)
?.setFilePath(filePath) ?.setContent(content)
viewModel = ViewModelProvider( viewModel = ViewModelProvider(
this, this,

View file

@ -48,7 +48,7 @@ class SharedMainViewModel : ViewModel() {
MutableLiveData<Event<ChatMessage>>() MutableLiveData<Event<ChatMessage>>()
} }
val fileToOpen = MutableLiveData<String>() val contentToOpen = MutableLiveData<Content>()
var createEncryptedChatRoom: Boolean = false var createEncryptedChatRoom: Boolean = false

View file

@ -128,6 +128,12 @@ class CorePreferences constructor(private val context: Context) {
config.setBool("app", "make_downloaded_images_public_in_gallery", value) config.setBool("app", "make_downloaded_images_public_in_gallery", value)
} }
var useInAppFileViewerForNonEncryptedFiles: Boolean
get() = config.getBool("app", "use_in_app_file_viewer_for_non_encrypted_files", true)
set(value) {
config.setBool("app", "use_in_app_file_viewer_for_non_encrypted_files", value)
}
var hideChatMessageContentInNotification: Boolean var hideChatMessageContentInNotification: Boolean
get() = config.getBool("app", "hide_chat_message_content_in_notification", false) get() = config.getBool("app", "hide_chat_message_content_in_notification", false)
set(value) { set(value) {

View file

@ -297,7 +297,11 @@ class FileUtils {
return contentUri return contentUri
} }
fun openFileInThirdPartyApp(activity: Activity, contentFilePath: String): Boolean { fun openFileInThirdPartyApp(
activity: Activity,
contentFilePath: String,
newTask: Boolean = false
): Boolean {
val intent = Intent(Intent.ACTION_VIEW) val intent = Intent(Intent.ACTION_VIEW)
val contentUri: Uri = getPublicFilePath(activity, contentFilePath) val contentUri: Uri = getPublicFilePath(activity, contentFilePath)
val filePath: String = contentUri.toString() val filePath: String = contentUri.toString()
@ -321,6 +325,9 @@ class FileUtils {
intent.setDataAndType(contentUri, type) intent.setDataAndType(contentUri, type)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
if (newTask) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
try { try {
activity.startActivity(intent) activity.startActivity(intent)

View file

@ -568,4 +568,5 @@
<string name="content_description_open_app">Ouvrir les conversations dans l\'application au lieu de la bulle</string> <string name="content_description_open_app">Ouvrir les conversations dans l\'application au lieu de la bulle</string>
<string name="content_description_export">Ouvrir le fichier dans une application tierce</string> <string name="content_description_export">Ouvrir le fichier dans une application tierce</string>
<string name="chat_message_no_app_found_to_handle_file_mime_type">Aucune application n\'est disponible pour ouvrir ce type de fichier</string> <string name="chat_message_no_app_found_to_handle_file_mime_type">Aucune application n\'est disponible pour ouvrir ce type de fichier</string>
<string name="chat_bubble_cant_open_enrypted_file">Impossible d\'ouvrir un fichier chiffré dans la bulle de conversation</string>
</resources> </resources>

View file

@ -201,6 +201,7 @@
</plurals> </plurals>
<string name="chat_message_notification_hidden_content">&lt;Redacted&gt;</string> <string name="chat_message_notification_hidden_content">&lt;Redacted&gt;</string>
<string name="chat_message_no_app_found_to_handle_file_mime_type">No app available for this kind of file</string> <string name="chat_message_no_app_found_to_handle_file_mime_type">No app available for this kind of file</string>
<string name="chat_bubble_cant_open_enrypted_file">Can\'t open encrypted file in chat bubble</string>
<!-- Recordings --> <!-- Recordings -->
<string name="recordings_empty_list">No recordings</string> <string name="recordings_empty_list">No recordings</string>