From 1c15f9fac80dcf3473c0341ed8f9d363d4d8bf37 Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Sun, 28 Jan 2024 15:44:53 +0100 Subject: [PATCH] Allow choosing export method --- README.md | 2 +- .../droidfs/ChangePasswordActivity.kt | 8 ++-- .../hardcore/droidfs/EncryptedFileProvider.kt | 45 ++++++++++++++----- .../sushi/hardcore/droidfs/MainActivity.kt | 7 ++- .../hardcore/droidfs/SettingsActivity.kt | 25 ++++++----- .../sushi/hardcore/droidfs/VolumeOpener.kt | 4 +- .../add_volume/CreateVolumeFragment.kt | 6 +-- .../droidfs/explorers/BaseExplorerActivity.kt | 16 +++---- .../sushi/hardcore/droidfs/util/UIUtils.kt | 42 +++++++++++++++++ .../sushi/hardcore/droidfs/util/WidgetUtil.kt | 19 -------- app/src/main/res/drawable/icon_settings.xml | 4 +- app/src/main/res/values/arrays.xml | 12 +++++ app/src/main/res/values/strings.xml | 3 ++ .../res/xml/unsafe_features_preferences.xml | 9 ++++ 14 files changed, 138 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/sushi/hardcore/droidfs/util/UIUtils.kt delete mode 100644 app/src/main/java/sushi/hardcore/droidfs/util/WidgetUtil.kt diff --git a/README.md b/README.md index 38d21a4..37aab3c 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Some available features are considered risky and are therefore disabled by defau -\* These features may require temporarily writing the plain file to disk (DroidFS internal storage). This file can be read by applications with root access or by physical access if your device is not encrypted. For files small enough and on a 3.17+ kernel, DroidFS will try to use memory-only storage using `memfd_create(2)` (can break some apps). +\* These features can work in two ways: temporarily writing the plain file to disk (DroidFS internal storage) or sharing it via memory. By default, DroidFS will choose to keep the file only in memory as it's more secure, but will fallback to disk export if the file is too large to be held in memory. This behavior can be changed with the *"Export method"* parameter in the settings. Please note that some applications require the file to be stored on disk, and therefore do not work with memory-exported files. # Download diff --git a/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt index 5d9ec04..4b43864 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt @@ -16,7 +16,7 @@ import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.GocryptfsVolume import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.util.ObjRef -import sushi.hardcore.droidfs.util.WidgetUtil +import sushi.hardcore.droidfs.util.UIUtils import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import java.util.* @@ -89,8 +89,8 @@ class ChangePasswordActivity: BaseActivity() { } private fun changeVolumePassword() { - val newPassword = WidgetUtil.encodeEditTextContent(binding.editNewPassword) - val newPasswordConfirm = WidgetUtil.encodeEditTextContent(binding.editPasswordConfirm) + val newPassword = UIUtils.encodeEditTextContent(binding.editNewPassword) + val newPasswordConfirm = UIUtils.encodeEditTextContent(binding.editPasswordConfirm) @SuppressLint("NewApi") if (!newPassword.contentEquals(newPasswordConfirm)) { Toast.makeText(this, R.string.passwords_mismatch, Toast.LENGTH_SHORT).show() @@ -135,7 +135,7 @@ class ChangePasswordActivity: BaseActivity() { null } val currentPassword = if (givenHash == null) { - WidgetUtil.encodeEditTextContent(binding.editCurrentPassword) + UIUtils.encodeEditTextContent(binding.editCurrentPassword) } else { null } diff --git a/app/src/main/java/sushi/hardcore/droidfs/EncryptedFileProvider.kt b/app/src/main/java/sushi/hardcore/droidfs/EncryptedFileProvider.kt index 5a667b6..f4837fa 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/EncryptedFileProvider.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/EncryptedFileProvider.kt @@ -7,6 +7,7 @@ import android.os.Handler import android.os.ParcelFileDescriptor import android.system.Os import android.util.Log +import androidx.preference.PreferenceManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import sushi.hardcore.droidfs.filesystems.EncryptedVolume @@ -22,6 +23,23 @@ class EncryptedFileProvider(context: Context) { companion object { private const val TAG = "EncryptedFileProvider" fun getTmpFilesDir(context: Context) = File(context.cacheDir, "tmp") + + var exportMethod = ExportMethod.AUTO + } + + enum class ExportMethod { + AUTO, + DISK, + MEMORY; + + companion object { + fun parse(value: String) = when (value) { + "auto" -> EncryptedFileProvider.ExportMethod.AUTO + "disk" -> EncryptedFileProvider.ExportMethod.DISK + "memory" -> EncryptedFileProvider.ExportMethod.MEMORY + else -> throw IllegalArgumentException("Invalid export method: $value") + } + } } private val memoryInfo = ActivityManager.MemoryInfo() @@ -33,6 +51,11 @@ class EncryptedFileProvider(context: Context) { (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).getMemoryInfo( memoryInfo ) + + PreferenceManager.getDefaultSharedPreferences(context) + .getString("export_method", null)?.let { + exportMethod = ExportMethod.parse(it) + } } class ExportedDiskFile private constructor( @@ -118,16 +141,18 @@ class EncryptedFileProvider(context: Context) { path: String, size: Long, ): ExportedFile? { - return if (size > memoryInfo.availMem * 0.8) { - ExportedDiskFile.create( - path, - tmpFilesDir, - handler, - ) - } else if (isMemFileSupported) { - ExportedMemFile.create(path, size) as ExportedFile - } else { - null + val diskFile by lazy { ExportedDiskFile.create(path, tmpFilesDir, handler) } + val memFile by lazy { ExportedMemFile.create(path, size) } + return when (exportMethod) { + ExportMethod.MEMORY -> memFile + ExportMethod.DISK -> diskFile + ExportMethod.AUTO -> { + if (isMemFileSupported && size < memoryInfo.availMem * 0.8) { + memFile + } else { + diskFile + } + } } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt index 67faa69..ec681b7 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt @@ -28,6 +28,7 @@ import sushi.hardcore.droidfs.file_operations.FileOperationService import sushi.hardcore.droidfs.file_operations.TaskResult import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.util.PathUtils +import sushi.hardcore.droidfs.util.UIUtils import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.EditTextDialog import java.io.File @@ -354,7 +355,11 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.main_activity, menu) - menu.findItem(R.id.settings).isVisible = !explorerRouter.pickMode && !explorerRouter.dropMode + val settingsVisible = !explorerRouter.pickMode && !explorerRouter.dropMode + menu.findItem(R.id.settings).isVisible = settingsVisible + if (settingsVisible) { + UIUtils.getMenuIconNeutralTint(this, menu).applyTo(R.id.settings, R.drawable.icon_settings) + } val isSelecting = volumeAdapter.selectedItems.isNotEmpty() menu.findItem(R.id.select_all).isVisible = isSelecting menu.findItem(R.id.lock).isVisible = isSelecting && volumeAdapter.selectedItems.any { diff --git a/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt index f455eff..4cc633d 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt @@ -179,17 +179,7 @@ class SettingsActivity : BaseActivity() { true } switchExpose.setOnPreferenceChangeListener { _, checked -> - if (checked as Boolean) { - if (!Compat.isMemFileSupported()) { - CustomAlertDialogBuilder(requireContext(), (requireActivity() as BaseActivity).theme) - .setTitle(R.string.error) - .setMessage("Your current kernel does not support memfd_create(). This feature requires a minimum kernel version of ${Compat.MEMFD_CREATE_MINIMUM_KERNEL_VERSION}.") - .setPositiveButton(R.string.ok, null) - .show() - return@setOnPreferenceChangeListener false - } - } - VolumeProvider.usfExpose = checked + VolumeProvider.usfExpose = checked as Boolean updateView(usfExpose = checked) VolumeProvider.notifyRootsChanged(requireContext()) true @@ -199,6 +189,19 @@ class SettingsActivity : BaseActivity() { TemporaryFileProvider.usfSafWrite = checked true } + + findPreference("export_method")!!.setOnPreferenceChangeListener { _, newValue -> + if (newValue as String == "memory" && !Compat.isMemFileSupported()) { + CustomAlertDialogBuilder(requireContext(), (requireActivity() as BaseActivity).theme) + .setTitle(R.string.error) + .setMessage(getString(R.string.memfd_create_unsupported, Compat.MEMFD_CREATE_MINIMUM_KERNEL_VERSION)) + .setPositiveButton(R.string.ok, null) + .show() + return@setOnPreferenceChangeListener false + } + EncryptedFileProvider.exportMethod = EncryptedFileProvider.ExportMethod.parse(newValue) + true + } } } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/VolumeOpener.kt b/app/src/main/java/sushi/hardcore/droidfs/VolumeOpener.kt index cfbf18d..6dba779 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/VolumeOpener.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/VolumeOpener.kt @@ -13,7 +13,7 @@ import sushi.hardcore.droidfs.Constants.DEFAULT_VOLUME_KEY import sushi.hardcore.droidfs.databinding.DialogOpenVolumeBinding import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.util.ObjRef -import sushi.hardcore.droidfs.util.WidgetUtil +import sushi.hardcore.droidfs.util.UIUtils import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import java.util.* @@ -123,7 +123,7 @@ class VolumeOpener( apply() } } - val password = WidgetUtil.encodeEditTextContent(dialogBinding!!.editPassword) + val password = UIUtils.encodeEditTextContent(dialogBinding!!.editPassword) val savePasswordHash = dialogBinding!!.checkboxSavePassword.isChecked dialogBinding = null // openVolumeWithPassword is responsible for wiping the password diff --git a/app/src/main/java/sushi/hardcore/droidfs/add_volume/CreateVolumeFragment.kt b/app/src/main/java/sushi/hardcore/droidfs/add_volume/CreateVolumeFragment.kt index c472a91..a0c796c 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/add_volume/CreateVolumeFragment.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/add_volume/CreateVolumeFragment.kt @@ -20,7 +20,7 @@ import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.GocryptfsVolume import sushi.hardcore.droidfs.util.Compat import sushi.hardcore.droidfs.util.ObjRef -import sushi.hardcore.droidfs.util.WidgetUtil +import sushi.hardcore.droidfs.util.UIUtils import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import java.io.File import java.util.* @@ -146,8 +146,8 @@ class CreateVolumeFragment: Fragment() { } private fun createVolume() { - val password = WidgetUtil.encodeEditTextContent(binding.editPassword) - val passwordConfirm = WidgetUtil.encodeEditTextContent(binding.editPasswordConfirm) + val password = UIUtils.encodeEditTextContent(binding.editPassword) + val passwordConfirm = UIUtils.encodeEditTextContent(binding.editPasswordConfirm) if (!password.contentEquals(passwordConfirm)) { Toast.makeText(requireContext(), R.string.passwords_mismatch, Toast.LENGTH_SHORT).show() Arrays.fill(password, 0) diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt index e774c91..7c2c2e2 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt @@ -54,6 +54,7 @@ import sushi.hardcore.droidfs.file_viewers.VideoPlayer import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.Stat import sushi.hardcore.droidfs.util.PathUtils +import sushi.hardcore.droidfs.util.UIUtils import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.EditTextDialog @@ -564,14 +565,6 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene } } - private fun setMenuIconTint(menu: Menu, iconColor: Int, menuItemId: Int, drawableId: Int) { - menu.findItem(menuItemId)?.let { - it.icon = ContextCompat.getDrawable(this, drawableId)?.apply { - setTint(iconColor) - } - } - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { menu.findItem(R.id.rename).isVisible = false menu.findItem(R.id.open_as)?.isVisible = false @@ -579,9 +572,10 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene menu.findItem(R.id.external_open)?.isVisible = false } val noItemSelected = explorerAdapter.selectedItems.isEmpty() - val iconColor = ContextCompat.getColor(this, R.color.neutralIconTint) - setMenuIconTint(menu, iconColor, R.id.sort, R.drawable.icon_sort) - setMenuIconTint(menu, iconColor, R.id.share, R.drawable.icon_share) + with(UIUtils.getMenuIconNeutralTint(this, menu)) { + applyTo(R.id.sort, R.drawable.icon_sort) + applyTo(R.id.share, R.drawable.icon_share) + } menu.findItem(R.id.sort).isVisible = noItemSelected menu.findItem(R.id.lock).isVisible = noItemSelected menu.findItem(R.id.close).isVisible = noItemSelected diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/UIUtils.kt b/app/src/main/java/sushi/hardcore/droidfs/util/UIUtils.kt new file mode 100644 index 0000000..75a28a5 --- /dev/null +++ b/app/src/main/java/sushi/hardcore/droidfs/util/UIUtils.kt @@ -0,0 +1,42 @@ +package sushi.hardcore.droidfs.util + +import android.content.Context +import android.view.Menu +import android.widget.EditText +import androidx.core.content.ContextCompat +import sushi.hardcore.droidfs.R +import java.nio.CharBuffer +import java.nio.charset.StandardCharsets +import java.util.* + +object UIUtils { + fun encodeEditTextContent(editText: EditText): ByteArray { + val charArray = CharArray(editText.text.length) + editText.text.getChars(0, editText.text.length, charArray, 0) + val byteBuffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(charArray)) + Arrays.fill(charArray, Char.MIN_VALUE) + val byteArray = ByteArray(byteBuffer.remaining()) + byteBuffer.get(byteArray) + Wiper.wipe(byteBuffer) + return byteArray + } + + class MenuIconColor( + private val context: Context, + private val menu: Menu, + private val color: Int + ) { + fun applyTo(menuItemId: Int, drawableId: Int) { + menu.findItem(menuItemId)?.let { + it.icon = ContextCompat.getDrawable(context, drawableId)?.apply { + setTint(color) + } + } + } + } + + fun getMenuIconNeutralTint(context: Context, menu: Menu) = MenuIconColor( + context, menu, + ContextCompat.getColor(context, R.color.neutralIconTint), + ) +} diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/WidgetUtil.kt b/app/src/main/java/sushi/hardcore/droidfs/util/WidgetUtil.kt deleted file mode 100644 index 8003c72..0000000 --- a/app/src/main/java/sushi/hardcore/droidfs/util/WidgetUtil.kt +++ /dev/null @@ -1,19 +0,0 @@ -package sushi.hardcore.droidfs.util - -import android.widget.EditText -import java.nio.CharBuffer -import java.nio.charset.StandardCharsets -import java.util.* - -object WidgetUtil { - fun encodeEditTextContent(editText: EditText): ByteArray { - val charArray = CharArray(editText.text.length) - editText.text.getChars(0, editText.text.length, charArray, 0) - val byteBuffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(charArray)) - Arrays.fill(charArray, Char.MIN_VALUE) - val byteArray = ByteArray(byteBuffer.remaining()) - byteBuffer.get(byteArray) - Wiper.wipe(byteBuffer) - return byteArray - } -} \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_settings.xml b/app/src/main/res/drawable/icon_settings.xml index b240b83..0010407 100644 --- a/app/src/main/res/drawable/icon_settings.xml +++ b/app/src/main/res/drawable/icon_settings.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index f20d0cc..45c669a 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -38,6 +38,12 @@ Pink + + Auto (depending on available memory) + Temporary export on disk (reliable but may leave traces) + Memory file (safer but doesn\'t always work) + + name @@ -57,4 +63,10 @@ purple pink + + + auto + disk + memory + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 216cc06..57860b9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -273,4 +273,7 @@ failed to export file Exporting to memory… Exporting to disk… + Your current kernel does not support memfd_create(). This feature requires a minimum kernel version of %s. + Export method + File export method. Used for sharing, external opening and accessing exposed files. diff --git a/app/src/main/res/xml/unsafe_features_preferences.xml b/app/src/main/res/xml/unsafe_features_preferences.xml index 13f02b4..a336cb8 100644 --- a/app/src/main/res/xml/unsafe_features_preferences.xml +++ b/app/src/main/res/xml/unsafe_features_preferences.xml @@ -78,6 +78,15 @@ android:title="@string/usf_saf_write" android:summary="@string/usf_saf_write_summary" /> + + \ No newline at end of file