Added setting to enable VFS + changes to properly display files
This commit is contained in:
parent
3f36e4cc74
commit
f1ad823364
22 changed files with 178 additions and 80 deletions
|
@ -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"
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,8 @@ class ChatMessageContentViewModel(
|
|||
|
||||
val fileName = MutableLiveData<String>()
|
||||
|
||||
val filePath = MutableLiveData<String>()
|
||||
|
||||
val fileSize = MutableLiveData<String>()
|
||||
|
||||
val downloadable = MutableLiveData<Boolean>()
|
||||
|
@ -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())) {
|
||||
|
|
|
@ -52,15 +52,13 @@ class AudioViewerFragment : SecureFragment<FileAudioViewerFragmentBinding>() {
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -47,15 +47,13 @@ class ImageViewerFragment : SecureFragment<FileImageViewerFragmentBinding>() {
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -47,15 +47,13 @@ class PdfViewerFragment : SecureFragment<FilePdfViewerFragmentBinding>() {
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -45,15 +45,13 @@ class TextViewerFragment : SecureFragment<FileTextViewerFragmentBinding>() {
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ class TopBarFragment : GenericFragment<FileViewerTopBarFragmentBinding>() {
|
|||
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)
|
||||
|
|
|
@ -51,15 +51,13 @@ class VideoViewerFragment : SecureFragment<FileVideoViewerFragmentBinding>() {
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -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 <T : ViewModel?> create(modelClass: Class<T>): 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 {
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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 <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return ImageFileViewModel(filePath) as T
|
||||
return ImageFileViewModel(content) as T
|
||||
}
|
||||
}
|
||||
|
||||
class ImageFileViewModel(val filePath: String) : ViewModel()
|
||||
class ImageFileViewModel(content: Content) : FileViewerViewModel(content)
|
||||
|
|
|
@ -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 <T : ViewModel?> create(modelClass: Class<T>): 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<Boolean>()
|
||||
|
||||
private val pdfRenderer: PdfRenderer
|
||||
|
|
|
@ -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 <T : ViewModel?> create(modelClass: Class<T>): 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<Boolean>()
|
||||
|
||||
val text = MutableLiveData<String>()
|
||||
|
@ -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')
|
||||
|
|
|
@ -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 <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return VideoFileViewModel(filePath) as T
|
||||
return VideoFileViewModel(content) as T
|
||||
}
|
||||
}
|
||||
|
||||
class VideoFileViewModel(val filePath: String) : ViewModel()
|
||||
class VideoFileViewModel(content: Content) : FileViewerViewModel(content)
|
||||
|
|
|
@ -38,6 +38,13 @@ class AdvancedSettingsViewModel : GenericSettingsViewModel() {
|
|||
}
|
||||
val debugMode = MutableLiveData<Boolean>()
|
||||
|
||||
val logsServerUrlListener = object : SettingListenerStub() {
|
||||
override fun onTextValueChanged(newValue: String) {
|
||||
core.logCollectionUploadServerUrl = newValue
|
||||
}
|
||||
}
|
||||
val logsServerUrl = MutableLiveData<String>()
|
||||
|
||||
val backgroundModeListener = object : SettingListenerStub() {
|
||||
override fun onBoolValueChanged(newValue: Boolean) {
|
||||
prefs.keepServiceAlive = newValue
|
||||
|
@ -97,12 +104,13 @@ class AdvancedSettingsViewModel : GenericSettingsViewModel() {
|
|||
}
|
||||
val remoteProvisioningUrl = MutableLiveData<String>()
|
||||
|
||||
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<String>()
|
||||
val vfs = MutableLiveData<Boolean>()
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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<String?, String?>? {
|
||||
fun encryptToken(string_to_encrypt: String): Pair<String?, String?> {
|
||||
val encryptedData = encryptData(string_to_encrypt)
|
||||
return Pair<String?, String?>(
|
||||
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()
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
||||
|
|
|
@ -126,6 +126,14 @@
|
|||
linphone:defaultValue="@{viewModel.remoteProvisioningUrl}"
|
||||
linphone:inputType="@{InputType.TYPE_TEXT_VARIATION_URI}"/>
|
||||
|
||||
<include
|
||||
layout="@layout/settings_widget_switch"
|
||||
linphone:title="@{@string/advanced_settings_vfs_title}"
|
||||
linphone:subtitle="@{@string/advanced_settings_vfs_summary}"
|
||||
linphone:listener="@{viewModel.vfsListener}"
|
||||
linphone:checked="@={viewModel.vfs}"
|
||||
linphone:enabled="@{!viewModel.vfs}"/>
|
||||
|
||||
<TextView
|
||||
style="@style/settings_category_font"
|
||||
android:id="@+id/pref_video_codecs_header"
|
||||
|
|
|
@ -485,6 +485,8 @@
|
|||
<string name="advanced_settings_go_to_battery_optimization_settings">Battery optimization settings</string>
|
||||
<string name="advanced_settings_go_to_power_manager_settings">Power manager settings</string>
|
||||
<string name="advanced_settings_go_to_android_app_settings">Android app settings</string>
|
||||
<string name="advanced_settings_vfs_title">Encrypt everything</string>
|
||||
<string name="advanced_settings_vfs_summary">Once enabled it can\'t be disabled!</string>
|
||||
|
||||
<!-- Tunnel settings -->
|
||||
<string name="tunnel_settings_hostname_url_title">Hostname</string>
|
||||
|
|
Loading…
Reference in a new issue