diff --git a/app/build.gradle b/app/build.gradle index fd0002ad2..5e7251d80 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -227,6 +227,7 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation "androidx.security:security-crypto:1.1.0-alpha03" implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android:flexbox:2.0.0' @@ -253,7 +254,6 @@ dependencies { // Only enable leak canary prior to release //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4' - implementation "androidx.security:security-crypto:1.1.0-alpha03" } diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageContentViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageContentViewModel.kt index c237265f1..7d18a1180 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageContentViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageContentViewModel.kt @@ -46,6 +46,8 @@ class ChatMessageContentViewModel( val fileName = MutableLiveData() + val filePath = MutableLiveData() + val fileSize = MutableLiveData() val downloadable = MutableLiveData() @@ -88,6 +90,7 @@ class ChatMessageContentViewModel( } init { + filePath.value = "" fileName.value = if (content.name.isNullOrEmpty() && !content.filePath.isNullOrEmpty()) { FileUtils.getNameFromFilePath(content.filePath!!) } else { @@ -96,19 +99,20 @@ class ChatMessageContentViewModel( fileSize.value = AppUtils.bytesToDisplayableSize(content.fileSize.toLong()) if (content.isFile || (content.isFileTransfer && chatMessage.isOutgoing)) { - val filePath = content.filePath ?: "" - downloadable.value = filePath.isEmpty() + val path = if (content.isFileEncrypted) content.plainFilePath else content.filePath ?: "" + downloadable.value = content.filePath.orEmpty().isEmpty() - if (filePath.isNotEmpty()) { - Log.i("[Content] Found displayable content: $filePath") - isImage.value = FileUtils.isExtensionImage(filePath) - isVideo.value = FileUtils.isExtensionVideo(filePath) - isAudio.value = FileUtils.isExtensionAudio(filePath) + if (path.isNotEmpty()) { + Log.i("[Content] Found displayable content: $path") + filePath.value = path + isImage.value = FileUtils.isExtensionImage(path) + isVideo.value = FileUtils.isExtensionVideo(path) + isAudio.value = FileUtils.isExtensionAudio(path) if (isVideo.value == true) { viewModelScope.launch { withContext(Dispatchers.IO) { - videoPreview.postValue(ImageUtils.getVideoPreview(filePath)) + videoPreview.postValue(ImageUtils.getVideoPreview(path)) } } } @@ -130,6 +134,17 @@ class ChatMessageContentViewModel( chatMessage.addListener(chatMessageListener) } + override fun onCleared() { + val path = filePath.value.orEmpty() + if (content.isFileEncrypted && path.isNotEmpty()) { + Log.i("[Content] Deleting file used for preview: $path") + FileUtils.deleteFile(path) + filePath.value = "" + } + + super.onCleared() + } + fun download() { val filePath = content.filePath if (content.isFileTransfer && (filePath == null || filePath.isEmpty())) { diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/AudioViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/AudioViewerFragment.kt index 7ee45d360..4be04e48b 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/AudioViewerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/AudioViewerFragment.kt @@ -52,15 +52,13 @@ class AudioViewerFragment : SecureFragment() { val content = sharedViewModel.contentToOpen.value content ?: return - val filePath = content.filePath - filePath ?: return (childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment) ?.setContent(content) viewModel = ViewModelProvider( this, - AudioFileViewModelFactory(filePath) + AudioFileViewModelFactory(content) )[AudioFileViewModel::class.java] binding.viewModel = viewModel diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt index 0c0630765..d9aa83502 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt @@ -47,15 +47,13 @@ class ImageViewerFragment : SecureFragment() { val content = sharedViewModel.contentToOpen.value content ?: return - val filePath = content.filePath - filePath ?: return (childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment) ?.setContent(content) viewModel = ViewModelProvider( this, - ImageFileViewModelFactory(filePath) + ImageFileViewModelFactory(content) )[ImageFileViewModel::class.java] binding.viewModel = viewModel diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/PdfViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/PdfViewerFragment.kt index 85a9a0813..bae4a4608 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/PdfViewerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/PdfViewerFragment.kt @@ -47,15 +47,13 @@ class PdfViewerFragment : SecureFragment() { val content = sharedViewModel.contentToOpen.value content ?: return - val filePath = content.filePath - filePath ?: return (childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment) ?.setContent(content) viewModel = ViewModelProvider( this, - PdfFileViewModelFactory(filePath) + PdfFileViewModelFactory(content) )[PdfFileViewModel::class.java] binding.viewModel = viewModel diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt index ee710fba7..372823741 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt @@ -45,15 +45,13 @@ class TextViewerFragment : SecureFragment() { val content = sharedViewModel.contentToOpen.value content ?: return - val filePath = content.filePath - filePath ?: return (childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment) ?.setContent(content) viewModel = ViewModelProvider( this, - TextFileViewModelFactory(filePath) + TextFileViewModelFactory(content) )[TextFileViewModel::class.java] binding.viewModel = viewModel diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt index 3e993fb1d..cfbe103ac 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt @@ -48,7 +48,7 @@ class TopBarFragment : GenericFragment() { if (content != null) { val filePath = content?.plainFilePath.orEmpty() plainFilePath = if (filePath.isEmpty()) content?.filePath.orEmpty() else filePath - Log.i("[File Viewer] Plain file path is: $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) diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt index cf2540561..474132241 100644 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt @@ -51,15 +51,13 @@ class VideoViewerFragment : SecureFragment() { val content = sharedViewModel.contentToOpen.value content ?: return - val filePath = content.filePath - filePath ?: return (childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment) ?.setContent(content) viewModel = ViewModelProvider( this, - VideoFileViewModelFactory(filePath) + VideoFileViewModelFactory(content) )[VideoFileViewModel::class.java] binding.viewModel = viewModel diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/AudioFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/AudioFileViewModel.kt index e028a638c..237ed0d75 100644 --- a/app/src/main/java/org/linphone/activities/main/files/viewmodels/AudioFileViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/files/viewmodels/AudioFileViewModel.kt @@ -25,17 +25,18 @@ import android.widget.MediaController import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import java.lang.IllegalStateException +import org.linphone.core.Content -class AudioFileViewModelFactory(private val filePath: String) : +class AudioFileViewModelFactory(private val content: Content) : ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return AudioFileViewModel(filePath) as T + return AudioFileViewModel(content) as T } } -class AudioFileViewModel(val filePath: String) : ViewModel(), MediaController.MediaPlayerControl { +class AudioFileViewModel(content: Content) : FileViewerViewModel(content), MediaController.MediaPlayerControl { val mediaPlayer = MediaPlayer() init { diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/FileViewerViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/FileViewerViewModel.kt new file mode 100644 index 000000000..8de31a6ce --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/files/viewmodels/FileViewerViewModel.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.activities.main.files.viewmodels + +import androidx.lifecycle.ViewModel +import org.linphone.core.Content +import org.linphone.core.tools.Log +import org.linphone.utils.FileUtils + +open class FileViewerViewModel(val content: Content) : ViewModel() { + val filePath: String + private val deleteAfterUse: Boolean = content.isFileEncrypted + + init { + filePath = if (deleteAfterUse) content.plainFilePath else content.filePath.orEmpty() + } + + override fun onCleared() { + if (deleteAfterUse) { + Log.i("[File Viewer] Deleting temporary plain file: $filePath") + FileUtils.deleteFile(filePath) + } + + super.onCleared() + } +} diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/ImageFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/ImageFileViewModel.kt index 736bc3143..2279a94fa 100644 --- a/app/src/main/java/org/linphone/activities/main/files/viewmodels/ImageFileViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/files/viewmodels/ImageFileViewModel.kt @@ -21,14 +21,15 @@ package org.linphone.activities.main.files.viewmodels import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import org.linphone.core.Content -class ImageFileViewModelFactory(private val filePath: String) : +class ImageFileViewModelFactory(private val content: Content) : ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return ImageFileViewModel(filePath) as T + return ImageFileViewModel(content) as T } } -class ImageFileViewModel(val filePath: String) : ViewModel() +class ImageFileViewModel(content: Content) : FileViewerViewModel(content) diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/PdfFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/PdfFileViewModel.kt index 14908b0bb..de34b35c4 100644 --- a/app/src/main/java/org/linphone/activities/main/files/viewmodels/PdfFileViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/files/viewmodels/PdfFileViewModel.kt @@ -28,18 +28,19 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import java.io.File import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.Content import org.linphone.core.tools.Log -class PdfFileViewModelFactory(private val filePath: String) : +class PdfFileViewModelFactory(private val content: Content) : ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return PdfFileViewModel(filePath) as T + return PdfFileViewModel(content) as T } } -class PdfFileViewModel(filePath: String) : ViewModel() { +class PdfFileViewModel(content: Content) : FileViewerViewModel(content) { val operationInProgress = MutableLiveData() private val pdfRenderer: PdfRenderer diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/TextFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/TextFileViewModel.kt index 03f37ffde..b39413320 100644 --- a/app/src/main/java/org/linphone/activities/main/files/viewmodels/TextFileViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/files/viewmodels/TextFileViewModel.kt @@ -29,18 +29,19 @@ import java.lang.StringBuilder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.linphone.core.Content import org.linphone.core.tools.Log -class TextFileViewModelFactory(private val filePath: String) : +class TextFileViewModelFactory(private val content: Content) : ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return TextFileViewModel(filePath) as T + return TextFileViewModel(content) as T } } -class TextFileViewModel(filePath: String) : ViewModel() { +class TextFileViewModel(content: Content) : FileViewerViewModel(content) { val operationInProgress = MutableLiveData() val text = MutableLiveData() @@ -48,18 +49,18 @@ class TextFileViewModel(filePath: String) : ViewModel() { init { operationInProgress.value = false - openFile(filePath) + openFile() } - private fun openFile(filePath: String) { + private fun openFile() { + operationInProgress.value = true + viewModelScope.launch { withContext(Dispatchers.IO) { - operationInProgress.postValue(true) - try { val br = BufferedReader(FileReader(filePath)) var line: String? - var textBuilder = StringBuilder() + val textBuilder = StringBuilder() while (br.readLine().also { line = it } != null) { textBuilder.append(line) textBuilder.append('\n') diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/VideoFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/VideoFileViewModel.kt index cdea1b695..7602c185a 100644 --- a/app/src/main/java/org/linphone/activities/main/files/viewmodels/VideoFileViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/files/viewmodels/VideoFileViewModel.kt @@ -21,14 +21,15 @@ package org.linphone.activities.main.files.viewmodels import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import org.linphone.core.Content -class VideoFileViewModelFactory(private val filePath: String) : +class VideoFileViewModelFactory(private val content: Content) : ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return VideoFileViewModel(filePath) as T + return VideoFileViewModel(content) as T } } -class VideoFileViewModel(val filePath: String) : ViewModel() +class VideoFileViewModel(content: Content) : FileViewerViewModel(content) diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AdvancedSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AdvancedSettingsViewModel.kt index fb38e8799..4cdba85fd 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AdvancedSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AdvancedSettingsViewModel.kt @@ -38,6 +38,13 @@ class AdvancedSettingsViewModel : GenericSettingsViewModel() { } val debugMode = MutableLiveData() + val logsServerUrlListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + core.logCollectionUploadServerUrl = newValue + } + } + val logsServerUrl = MutableLiveData() + val backgroundModeListener = object : SettingListenerStub() { override fun onBoolValueChanged(newValue: Boolean) { prefs.keepServiceAlive = newValue @@ -97,12 +104,13 @@ class AdvancedSettingsViewModel : GenericSettingsViewModel() { } val remoteProvisioningUrl = MutableLiveData() - val logsServerUrlListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - core.logCollectionUploadServerUrl = newValue + val vfsListener = object : SettingListenerStub() { + override fun onBoolValueChanged(newValue: Boolean) { + prefs.vfsEnabled = newValue + if (newValue) coreContext.setupVFS() } } - val logsServerUrl = MutableLiveData() + val vfs = MutableLiveData() val goToBatterySettingsListener = object : SettingListenerStub() { override fun onClicked() { @@ -129,6 +137,7 @@ class AdvancedSettingsViewModel : GenericSettingsViewModel() { init { debugMode.value = prefs.debugLogs + logsServerUrl.value = core.logCollectionUploadServerUrl backgroundMode.value = prefs.keepServiceAlive autoStart.value = prefs.autoStart @@ -142,7 +151,7 @@ class AdvancedSettingsViewModel : GenericSettingsViewModel() { animations.value = prefs.enableAnimations deviceName.value = prefs.deviceName remoteProvisioningUrl.value = core.provisioningUri - logsServerUrl.value = core.logCollectionUploadServerUrl + vfs.value = prefs.vfsEnabled batterySettingsVisibility.value = Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60) } diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 7c267cc35..e0b5f782e 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -292,7 +292,7 @@ class CoreContext(val context: Context, coreConfig: Config) { } if (corePreferences.vfsEnabled) { - setupVFS(context) + setupVFS() } core = Factory.instance().createCoreWithConfig(coreConfig, context) @@ -645,16 +645,19 @@ class CoreContext(val context: Context, coreConfig: Config) { /* VFS */ companion object { - private val TRANSFORMATION = "AES/GCM/NoPadding" - private val ANDROID_KEY_STORE = "AndroidKeyStore" - private val ALIAS = "vfs" - private val LINPHONE_VFS_ENCRYPTION_AES256GCM128_SHA256 = 2 + private const val TRANSFORMATION = "AES/GCM/NoPadding" + private const val ANDROID_KEY_STORE = "AndroidKeyStore" + private const val ALIAS = "vfs" + private const val LINPHONE_VFS_ENCRYPTION_AES256GCM128_SHA256 = 2 + private const val VFS_FILE = "vfs.prefs" + private const val VFS_IV = "vfsiv" + private const val VFS_KEY = "vfskey" } @Throws(java.lang.Exception::class) private fun generateSecretKey() { - val keyGenerator: KeyGenerator - keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE) + val keyGenerator = + KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE) keyGenerator.init( KeyGenParameterSpec.Builder( ALIAS, @@ -701,7 +704,7 @@ class CoreContext(val context: Context, coreConfig: Config) { } @Throws(java.lang.Exception::class) - fun encryptToken(string_to_encrypt: String): Pair? { + fun encryptToken(string_to_encrypt: String): Pair { val encryptedData = encryptData(string_to_encrypt) return Pair( Base64.encodeToString(encryptedData.first, Base64.DEFAULT), @@ -710,7 +713,7 @@ class CoreContext(val context: Context, coreConfig: Config) { } @Throws(java.lang.Exception::class) - fun sha512(input: String): String? { + fun sha512(input: String): String { val md = MessageDigest.getInstance("SHA-512") val messageDigest = md.digest(input.toByteArray()) val no = BigInteger(1, messageDigest) @@ -722,41 +725,48 @@ class CoreContext(val context: Context, coreConfig: Config) { } @Throws(java.lang.Exception::class) - fun getVfsKey(sharedPreferences: SharedPreferences): String? { + fun getVfsKey(sharedPreferences: SharedPreferences): String { val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE) keyStore.load(null) return decryptData( - sharedPreferences.getString("vfskey", null), - Base64.decode(sharedPreferences.getString("vfsiv", null), Base64.DEFAULT) + sharedPreferences.getString(VFS_KEY, null), + Base64.decode(sharedPreferences.getString(VFS_IV, null), Base64.DEFAULT) ) } - private fun setupVFS(c: Context) { + fun setupVFS() { try { + Log.i("[Context] Enabling VFS") + val masterKey: MasterKey = Builder( - c, + context, MasterKey.DEFAULT_MASTER_KEY_ALIAS ).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build() val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create( - c, "vfs.prefs", masterKey, + context, VFS_FILE, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) - if (sharedPreferences.getString("vfsiv", null) == null) { + + if (sharedPreferences.getString(VFS_IV, null) == null) { generateSecretKey() - generateToken()?.let { encryptToken(it) }?.let { data -> - sharedPreferences.edit().putString("vfsiv", data.first) - .putString("vfskey", data.second).commit() + generateToken()?.let { encryptToken(it) }?.let { data -> + sharedPreferences + .edit() + .putString(VFS_IV, data.first) + .putString(VFS_KEY, data.second) + .commit() } } Factory.instance().setVfsEncryption( LINPHONE_VFS_ENCRYPTION_AES256GCM128_SHA256, - Arrays.copyOfRange(getVfsKey(sharedPreferences)?.toByteArray(), 0, 32), + getVfsKey(sharedPreferences).toByteArray().copyOfRange(0, 32), 32 ) + + Log.i("[Context] VFS enabled") } catch (e: Exception) { - e.printStackTrace() - throw RuntimeException("Unable to setup VFS encryption") + Log.f("[Context] Unable to setup VFS encryption: $e") } } } diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 4a88d66f9..cc2a3c893 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -41,7 +41,7 @@ class CorePreferences constructor(private val context: Context) { get() = config.getBool("app", "vfs", false) set(value) { if (!value && config.getBool("app", "vfs", false)) { - Log.w("[VFS] It is not possible to deactivate VFS after it has been activated") + Log.w("[VFS] It is not possible to disable VFS once it has been enabled") return } config.setBool("app", "vfs", value) @@ -141,7 +141,7 @@ class CorePreferences constructor(private val context: Context) { } var useInAppFileViewerForNonEncryptedFiles: Boolean - get() = config.getBool("app", "use_in_app_file_viewer_for_non_encrypted_files", true) + get() = config.getBool("app", "use_in_app_file_viewer_for_non_encrypted_files", false) set(value) { config.setBool("app", "use_in_app_file_viewer_for_non_encrypted_files", value) } diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt index 6be800485..5d12c054a 100644 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt @@ -36,6 +36,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.Guideline import androidx.databinding.* import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestOptions @@ -320,7 +321,15 @@ fun loadAvatarWithGlideFallback(imageView: ImageView, path: String?) { @BindingAdapter("glidePath") fun loadImageWithGlide(imageView: ImageView, path: String) { if (path.isNotEmpty() && FileUtils.isExtensionImage(path)) { - GlideApp.with(imageView).load(path).into(imageView) + if (corePreferences.vfsEnabled) { + GlideApp.with(imageView) + .load(path) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + .into(imageView) + } else { + GlideApp.with(imageView).load(path).into(imageView) + } } else { Log.w("[Data Binding] [Glide] Can't load $path") } diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt index 50438a2fc..f3a034345 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ b/app/src/main/java/org/linphone/utils/FileUtils.kt @@ -43,6 +43,8 @@ import org.linphone.core.tools.Log class FileUtils { companion object { + private const val VFS_PLAIN_FILE_EXTENSION = ".bctbx_evfs_plain" + fun getNameFromFilePath(filePath: String): String { var name = filePath val i = filePath.lastIndexOf('/') @@ -53,13 +55,18 @@ class FileUtils { } fun getExtensionFromFileName(fileName: String): String { - var extension = MimeTypeMap.getFileExtensionFromUrl(fileName) - if (extension == null || extension.isEmpty()) { - val i = fileName.lastIndexOf('.') + val realFileName = if (fileName.endsWith(VFS_PLAIN_FILE_EXTENSION)) { + fileName.substring(0, fileName.length - VFS_PLAIN_FILE_EXTENSION.length) + } else fileName + + var extension = MimeTypeMap.getFileExtensionFromUrl(realFileName) + if (extension.isNullOrEmpty()) { + val i = realFileName.lastIndexOf('.') if (i > 0) { - extension = fileName.substring(i + 1) + extension = realFileName.substring(i + 1) } } + return extension } diff --git a/app/src/main/res/layout/chat_message_content_cell.xml b/app/src/main/res/layout/chat_message_content_cell.xml index 1fd06173c..fde48602b 100644 --- a/app/src/main/res/layout/chat_message_content_cell.xml +++ b/app/src/main/res/layout/chat_message_content_cell.xml @@ -24,7 +24,7 @@ android:layout_height="wrap_content" android:maxHeight="@{data.alone ? @dimen/chat_message_bubble_image_height_big : @dimen/chat_message_bubble_image_height_small}" android:layout_margin="5dp" - app:glidePath="@{data.content.filePath}" + app:glidePath="@{data.filePath}" android:visibility="@{data.image ? View.VISIBLE : View.GONE}" android:adjustViewBounds="true" /> diff --git a/app/src/main/res/layout/settings_advanced_fragment.xml b/app/src/main/res/layout/settings_advanced_fragment.xml index e0714b2b7..753ec0536 100644 --- a/app/src/main/res/layout/settings_advanced_fragment.xml +++ b/app/src/main/res/layout/settings_advanced_fragment.xml @@ -126,6 +126,14 @@ linphone:defaultValue="@{viewModel.remoteProvisioningUrl}" linphone:inputType="@{InputType.TYPE_TEXT_VARIATION_URI}"/> + + Battery optimization settings Power manager settings Android app settings + Encrypt everything + Once enabled it can\'t be disabled! Hostname