forked from hardcoresushi/DroidFS
Allow choosing export method
This commit is contained in:
parent
b4635dc2e0
commit
1c15f9fac8
@ -62,7 +62,7 @@ Some available features are considered risky and are therefore disabled by defau
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
\* 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
|
# Download
|
||||||
<a href="https://f-droid.org/packages/sushi.hardcore.droidfs">
|
<a href="https://f-droid.org/packages/sushi.hardcore.droidfs">
|
||||||
|
@ -16,7 +16,7 @@ import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
|||||||
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume
|
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume
|
||||||
import sushi.hardcore.droidfs.util.IntentUtils
|
import sushi.hardcore.droidfs.util.IntentUtils
|
||||||
import sushi.hardcore.droidfs.util.ObjRef
|
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 sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -89,8 +89,8 @@ class ChangePasswordActivity: BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun changeVolumePassword() {
|
private fun changeVolumePassword() {
|
||||||
val newPassword = WidgetUtil.encodeEditTextContent(binding.editNewPassword)
|
val newPassword = UIUtils.encodeEditTextContent(binding.editNewPassword)
|
||||||
val newPasswordConfirm = WidgetUtil.encodeEditTextContent(binding.editPasswordConfirm)
|
val newPasswordConfirm = UIUtils.encodeEditTextContent(binding.editPasswordConfirm)
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
if (!newPassword.contentEquals(newPasswordConfirm)) {
|
if (!newPassword.contentEquals(newPasswordConfirm)) {
|
||||||
Toast.makeText(this, R.string.passwords_mismatch, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.passwords_mismatch, Toast.LENGTH_SHORT).show()
|
||||||
@ -135,7 +135,7 @@ class ChangePasswordActivity: BaseActivity() {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
val currentPassword = if (givenHash == null) {
|
val currentPassword = if (givenHash == null) {
|
||||||
WidgetUtil.encodeEditTextContent(binding.editCurrentPassword)
|
UIUtils.encodeEditTextContent(binding.editCurrentPassword)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import android.os.Handler
|
|||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.system.Os
|
import android.system.Os
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
@ -22,6 +23,23 @@ class EncryptedFileProvider(context: Context) {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "EncryptedFileProvider"
|
private const val TAG = "EncryptedFileProvider"
|
||||||
fun getTmpFilesDir(context: Context) = File(context.cacheDir, "tmp")
|
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()
|
private val memoryInfo = ActivityManager.MemoryInfo()
|
||||||
@ -33,6 +51,11 @@ class EncryptedFileProvider(context: Context) {
|
|||||||
(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).getMemoryInfo(
|
(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).getMemoryInfo(
|
||||||
memoryInfo
|
memoryInfo
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getString("export_method", null)?.let {
|
||||||
|
exportMethod = ExportMethod.parse(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExportedDiskFile private constructor(
|
class ExportedDiskFile private constructor(
|
||||||
@ -118,16 +141,18 @@ class EncryptedFileProvider(context: Context) {
|
|||||||
path: String,
|
path: String,
|
||||||
size: Long,
|
size: Long,
|
||||||
): ExportedFile? {
|
): ExportedFile? {
|
||||||
return if (size > memoryInfo.availMem * 0.8) {
|
val diskFile by lazy { ExportedDiskFile.create(path, tmpFilesDir, handler) }
|
||||||
ExportedDiskFile.create(
|
val memFile by lazy { ExportedMemFile.create(path, size) }
|
||||||
path,
|
return when (exportMethod) {
|
||||||
tmpFilesDir,
|
ExportMethod.MEMORY -> memFile
|
||||||
handler,
|
ExportMethod.DISK -> diskFile
|
||||||
)
|
ExportMethod.AUTO -> {
|
||||||
} else if (isMemFileSupported) {
|
if (isMemFileSupported && size < memoryInfo.availMem * 0.8) {
|
||||||
ExportedMemFile.create(path, size) as ExportedFile
|
memFile
|
||||||
} else {
|
} else {
|
||||||
null
|
diskFile
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ import sushi.hardcore.droidfs.file_operations.FileOperationService
|
|||||||
import sushi.hardcore.droidfs.file_operations.TaskResult
|
import sushi.hardcore.droidfs.file_operations.TaskResult
|
||||||
import sushi.hardcore.droidfs.util.IntentUtils
|
import sushi.hardcore.droidfs.util.IntentUtils
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
|
import sushi.hardcore.droidfs.util.UIUtils
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -354,7 +355,11 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.main_activity, menu)
|
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()
|
val isSelecting = volumeAdapter.selectedItems.isNotEmpty()
|
||||||
menu.findItem(R.id.select_all).isVisible = isSelecting
|
menu.findItem(R.id.select_all).isVisible = isSelecting
|
||||||
menu.findItem(R.id.lock).isVisible = isSelecting && volumeAdapter.selectedItems.any {
|
menu.findItem(R.id.lock).isVisible = isSelecting && volumeAdapter.selectedItems.any {
|
||||||
|
@ -179,17 +179,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
switchExpose.setOnPreferenceChangeListener { _, checked ->
|
switchExpose.setOnPreferenceChangeListener { _, checked ->
|
||||||
if (checked as Boolean) {
|
VolumeProvider.usfExpose = 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
|
|
||||||
updateView(usfExpose = checked)
|
updateView(usfExpose = checked)
|
||||||
VolumeProvider.notifyRootsChanged(requireContext())
|
VolumeProvider.notifyRootsChanged(requireContext())
|
||||||
true
|
true
|
||||||
@ -199,6 +189,19 @@ class SettingsActivity : BaseActivity() {
|
|||||||
TemporaryFileProvider.usfSafWrite = checked
|
TemporaryFileProvider.usfSafWrite = checked
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findPreference<ListPreference>("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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,7 +13,7 @@ import sushi.hardcore.droidfs.Constants.DEFAULT_VOLUME_KEY
|
|||||||
import sushi.hardcore.droidfs.databinding.DialogOpenVolumeBinding
|
import sushi.hardcore.droidfs.databinding.DialogOpenVolumeBinding
|
||||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
import sushi.hardcore.droidfs.util.ObjRef
|
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 sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ class VolumeOpener(
|
|||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val password = WidgetUtil.encodeEditTextContent(dialogBinding!!.editPassword)
|
val password = UIUtils.encodeEditTextContent(dialogBinding!!.editPassword)
|
||||||
val savePasswordHash = dialogBinding!!.checkboxSavePassword.isChecked
|
val savePasswordHash = dialogBinding!!.checkboxSavePassword.isChecked
|
||||||
dialogBinding = null
|
dialogBinding = null
|
||||||
// openVolumeWithPassword is responsible for wiping the password
|
// openVolumeWithPassword is responsible for wiping the password
|
||||||
|
@ -20,7 +20,7 @@ import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
|||||||
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume
|
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume
|
||||||
import sushi.hardcore.droidfs.util.Compat
|
import sushi.hardcore.droidfs.util.Compat
|
||||||
import sushi.hardcore.droidfs.util.ObjRef
|
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 sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -146,8 +146,8 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createVolume() {
|
private fun createVolume() {
|
||||||
val password = WidgetUtil.encodeEditTextContent(binding.editPassword)
|
val password = UIUtils.encodeEditTextContent(binding.editPassword)
|
||||||
val passwordConfirm = WidgetUtil.encodeEditTextContent(binding.editPasswordConfirm)
|
val passwordConfirm = UIUtils.encodeEditTextContent(binding.editPasswordConfirm)
|
||||||
if (!password.contentEquals(passwordConfirm)) {
|
if (!password.contentEquals(passwordConfirm)) {
|
||||||
Toast.makeText(requireContext(), R.string.passwords_mismatch, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), R.string.passwords_mismatch, Toast.LENGTH_SHORT).show()
|
||||||
Arrays.fill(password, 0)
|
Arrays.fill(password, 0)
|
||||||
|
@ -54,6 +54,7 @@ import sushi.hardcore.droidfs.file_viewers.VideoPlayer
|
|||||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
import sushi.hardcore.droidfs.filesystems.Stat
|
import sushi.hardcore.droidfs.filesystems.Stat
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
|
import sushi.hardcore.droidfs.util.UIUtils
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
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 {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menu.findItem(R.id.rename).isVisible = false
|
menu.findItem(R.id.rename).isVisible = false
|
||||||
menu.findItem(R.id.open_as)?.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
|
menu.findItem(R.id.external_open)?.isVisible = false
|
||||||
}
|
}
|
||||||
val noItemSelected = explorerAdapter.selectedItems.isEmpty()
|
val noItemSelected = explorerAdapter.selectedItems.isEmpty()
|
||||||
val iconColor = ContextCompat.getColor(this, R.color.neutralIconTint)
|
with(UIUtils.getMenuIconNeutralTint(this, menu)) {
|
||||||
setMenuIconTint(menu, iconColor, R.id.sort, R.drawable.icon_sort)
|
applyTo(R.id.sort, R.drawable.icon_sort)
|
||||||
setMenuIconTint(menu, iconColor, R.id.share, R.drawable.icon_share)
|
applyTo(R.id.share, R.drawable.icon_share)
|
||||||
|
}
|
||||||
menu.findItem(R.id.sort).isVisible = noItemSelected
|
menu.findItem(R.id.sort).isVisible = noItemSelected
|
||||||
menu.findItem(R.id.lock).isVisible = noItemSelected
|
menu.findItem(R.id.lock).isVisible = noItemSelected
|
||||||
menu.findItem(R.id.close).isVisible = noItemSelected
|
menu.findItem(R.id.close).isVisible = noItemSelected
|
||||||
|
42
app/src/main/java/sushi/hardcore/droidfs/util/UIUtils.kt
Normal file
42
app/src/main/java/sushi/hardcore/droidfs/util/UIUtils.kt
Normal file
@ -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),
|
||||||
|
)
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
<vector android:height="24dp"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
|
<path android:fillColor="?attr/colorAccent" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
@ -38,6 +38,12 @@
|
|||||||
<item>Pink</item>
|
<item>Pink</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="export_methods">
|
||||||
|
<item>Auto (depending on available memory)</item>
|
||||||
|
<item>Temporary export on disk (reliable but may leave traces)</item>
|
||||||
|
<item>Memory file (safer but doesn\'t always work)</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<!-- don't translate the following otherwise the app will crash -->
|
<!-- don't translate the following otherwise the app will crash -->
|
||||||
<string-array name="sort_orders_values">
|
<string-array name="sort_orders_values">
|
||||||
<item>name</item>
|
<item>name</item>
|
||||||
@ -57,4 +63,10 @@
|
|||||||
<item>purple</item>
|
<item>purple</item>
|
||||||
<item>pink</item>
|
<item>pink</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="export_methods_values">
|
||||||
|
<item>auto</item>
|
||||||
|
<item>disk</item>
|
||||||
|
<item>memory</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
@ -273,4 +273,7 @@
|
|||||||
<string name="export_failed_export">failed to export file</string>
|
<string name="export_failed_export">failed to export file</string>
|
||||||
<string name="export_mem">Exporting to memory…</string>
|
<string name="export_mem">Exporting to memory…</string>
|
||||||
<string name="export_disk">Exporting to disk…</string>
|
<string name="export_disk">Exporting to disk…</string>
|
||||||
|
<string name="memfd_create_unsupported">Your current kernel does not support memfd_create(). This feature requires a minimum kernel version of %s.</string>
|
||||||
|
<string name="export_method">Export method</string>
|
||||||
|
<string name="export_method_summary">File export method. Used for sharing, external opening and accessing exposed files.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -78,6 +78,15 @@
|
|||||||
android:title="@string/usf_saf_write"
|
android:title="@string/usf_saf_write"
|
||||||
android:summary="@string/usf_saf_write_summary" />
|
android:summary="@string/usf_saf_write_summary" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:key="export_method"
|
||||||
|
android:entries="@array/export_methods"
|
||||||
|
android:entryValues="@array/export_methods_values"
|
||||||
|
android:defaultValue="auto"
|
||||||
|
android:title="@string/export_method"
|
||||||
|
android:summary="@string/export_method_summary"
|
||||||
|
android:icon="@drawable/icon_settings"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
Loading…
Reference in New Issue
Block a user