Compare commits

...

11 Commits

Author SHA1 Message Date
solokot 967d4551c5
Update Russian translation
Signed-off-by: Hardcore Sushi <hardcore.sushi@disroot.org>
2024-02-12 16:51:55 +01:00
Ali Beyaz b747d2822a
Add Turkish translation
Signed-off-by: Hardcore Sushi <hardcore.sushi@disroot.org>
2024-02-12 16:47:30 +01:00
CyanWolf e5652666d8
Update Spanish
Signed-off-by: Hardcore Sushi <hardcore.sushi@disroot.org>
2024-02-11 18:12:17 +01:00
Muhmmad14333653 cda0e90b96
Update Arabic translations
Signed-off-by: Hardcore Sushi <hardcore.sushi@disroot.org>
2024-02-11 18:08:54 +01:00
Matéo Duparc 6f43bc7417
Avoid being killed by SELinux when retrieving volume path 2024-02-11 17:55:24 +01:00
Matéo Duparc c26ab661c2
Logcat activity 2024-01-30 18:29:49 +01:00
Matéo Duparc 1c15f9fac8
Allow choosing export method 2024-01-28 15:44:53 +01:00
Matéo Duparc b4635dc2e0
Directory loading indicator 2024-01-13 23:19:22 +01:00
Matéo Duparc f4e47c1827
Allow directory creation on exposed volumes 2024-01-13 21:41:58 +01:00
Matéo Duparc 5474d6eea5
Add .opus & Update build config 2024-01-13 21:25:31 +01:00
Matéo Duparc 719faa31ee
Fix README 2023-10-15 17:09:48 +02:00
33 changed files with 751 additions and 123 deletions

View File

@ -11,7 +11,7 @@ For mortals: Encrypted storage compatible with already existing softwares.
</p>
# Support
The creator of DroidFS works as a freelance developer and privacy consultant. I am currently looking for new clients! If you are interested, take a look at the [website](https://arkensys.fr.to). Alternatively, you can directly support DroidFS by making a [donation](https://forge.chapril.org/hardcoresushi/DroidFS/src/branch/master/DONATE.txt).
The creator of DroidFS works as a freelance developer and privacy consultant. I am currently looking for new clients! If you are interested, take a look at the [website](https://arkensys.dedyn.io). Alternatively, you can directly support DroidFS by making a [donation](https://forge.chapril.org/hardcoresushi/DroidFS/src/branch/master/DONATE.txt).
Thank you so much ❤️.
@ -55,13 +55,14 @@ Some available features are considered risky and are therefore disabled by defau
Decrypt and open file using external apps. These apps could save and send the files thus opened.
</li>
<li><h4>Expose open volumes*:</h4>
Allow open volumes to be browsed in the system file explorer (<a href="https://developer.android.com/guide/topics/providers/document-provider">DocumentProvider</a> API). Encrypted files can then be selected from other applications, potentially with permanent access. This feature requires "*Keep volume open when the app goes in background*" to be enabled.
Allow open volumes to be browsed in the system file explorer (<a href="https://developer.android.com/guide/topics/providers/document-provider">DocumentProvider</a> API). Encrypted files can then be selected from other applications, potentially with permanent access. This feature requires <i>"Keep volume open when the app goes in background"</i> to be enabled.
</li>
<li><h4>Grant write access:</h4>
Files opened with another applications can be modified by them. This applies to both previous unsafe features.
</li>
</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
<a href="https://f-droid.org/packages/sushi.hardcore.droidfs">

View File

@ -21,7 +21,7 @@ if (hasProperty("nosplits")) {
android {
compileSdk 34
ndkVersion "25.2.9519653"
ndkVersion "26.1.10909125"
namespace "sushi.hardcore.droidfs"
compileOptions {
@ -58,6 +58,7 @@ android {
splits {
abi {
enable true
reset() // fix unknown bug (https://ru.stackoverflow.com/questions/1557805/abis-armeabi-mips-mips64-riscv64-are-not-supported-for-platform)
universalApk true
}
}

View File

@ -53,6 +53,7 @@
<activity android:name=".file_viewers.AudioPlayer" android:configChanges="screenSize|orientation" />
<activity android:name=".file_viewers.TextEditor" android:configChanges="screenSize|orientation" />
<activity android:name=".CameraActivity" android:screenOrientation="nosensor" />
<activity android:name=".LogcatActivity"/>
<service android:name=".WiperService" android:exported="false" android:stopWithTask="false"/>
<service android:name=".file_operations.FileOperationService" android:exported="false"/>

View File

@ -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
}

View File

@ -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
}
}
}
}

View File

@ -6,7 +6,7 @@ object FileTypes {
private val FILE_EXTENSIONS = mapOf(
Pair("image", listOf("png", "jpg", "jpeg", "gif", "webp", "bmp", "heic")),
Pair("video", listOf("mp4", "webm", "mkv", "mov")),
Pair("audio", listOf("mp3", "ogg", "m4a", "wav", "flac")),
Pair("audio", listOf("mp3", "ogg", "m4a", "wav", "flac", "opus")),
Pair("pdf", listOf("pdf")),
Pair("text", listOf(
"asc",

View File

@ -0,0 +1,88 @@
package sushi.hardcore.droidfs
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import sushi.hardcore.droidfs.databinding.ActivityLogcatBinding
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.InputStreamReader
import java.io.InterruptedIOException
import java.io.OutputStreamWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class LogcatActivity: BaseActivity() {
private lateinit var binding: ActivityLogcatBinding
private var process: Process? = null
private val dateFormat by lazy {
SimpleDateFormat("yyyy-MM-dd_HH:mm:ss", Locale.getDefault())
}
private val saveAs = registerForActivityResult(ActivityResultContracts.CreateDocument("text/*")) { uri ->
uri?.let {
saveTo(it)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLogcatBinding.inflate(layoutInflater)
setContentView(binding.root)
title = getString(R.string.logcat_title)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
lifecycleScope.launch(Dispatchers.IO) {
try {
BufferedReader(InputStreamReader(Runtime.getRuntime().exec("logcat").also {
process = it
}.inputStream)).forEachLine {
binding.content.post {
binding.content.append("$it\n")
}
}
} catch (_: InterruptedIOException) {}
}
}
override fun onDestroy() {
super.onDestroy()
process?.destroy()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.logcat, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
finish()
true
}
R.id.save -> {
saveAs.launch("DroidFS_${dateFormat.format(Date())}.log")
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun saveTo(uri: Uri) {
lifecycleScope.launch(Dispatchers.IO) {
BufferedWriter(OutputStreamWriter(contentResolver.openOutputStream(uri))).use {
it.write(binding.content.text.toString())
}
launch(Dispatchers.Main) {
Toast.makeText(this@LogcatActivity, R.string.logcat_saved, Toast.LENGTH_SHORT).show()
}
}
}
}

View File

@ -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 {

View File

@ -120,6 +120,10 @@ class SettingsActivity : BaseActivity() {
false
}
}
findPreference<Preference>("logcat")?.setOnPreferenceClickListener { _ ->
startActivity(Intent(requireContext(), LogcatActivity::class.java))
true
}
}
}
@ -179,17 +183,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 +193,19 @@ class SettingsActivity : BaseActivity() {
TemporaryFileProvider.usfSafWrite = checked
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
}
}
}
}

View File

@ -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

View File

@ -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)

View File

@ -236,14 +236,21 @@ class VolumeProvider: DocumentsProvider() {
): String? {
if (!usfExpose || !usfSafWrite) return null
val document = parseDocumentId(parentDocumentId) ?: return null
val newFile = PathUtils.pathJoin(document.path, displayName)
val f = document.encryptedVolume.openFileWriteMode(newFile)
return if (f == -1L) {
Log.e(TAG, "Failed to create file: $document")
null
val path = PathUtils.pathJoin(document.path, displayName)
var success = false
if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
success = document.encryptedVolume.mkdir(path)
} else {
document.encryptedVolume.closeFile(f)
document.rootId+newFile
val f = document.encryptedVolume.openFileWriteMode(path)
if (f != -1L) {
document.encryptedVolume.closeFile(f)
success = true
}
}
return if (success) {
document.rootId+path
} else {
null
}
}

View File

@ -11,10 +11,12 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageButton
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.activity.addCallback
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@ -23,10 +25,13 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import sushi.hardcore.droidfs.BaseActivity
import sushi.hardcore.droidfs.Constants
import sushi.hardcore.droidfs.EncryptedFileProvider
@ -49,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
@ -69,6 +75,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
}
protected lateinit var fileOperationService: FileOperationService
protected val activityScope = MainScope()
private var directoryLoadingTask: Job? = null
protected lateinit var explorerElements: MutableList<ExplorerElement>
protected lateinit var explorerAdapter: ExplorerElementAdapter
protected lateinit var app: VolumeManagerApp
@ -79,6 +86,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
private lateinit var titleText: TextView
private lateinit var recycler_view_explorer: RecyclerView
private lateinit var refresher: SwipeRefreshLayout
private lateinit var loader: ProgressBar
private lateinit var textDirEmpty: TextView
private lateinit var currentPathText: TextView
private lateinit var numberOfFilesText: TextView
@ -101,6 +109,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
init()
recycler_view_explorer = findViewById(R.id.recycler_view_explorer)
refresher = findViewById(R.id.refresher)
loader = findViewById(R.id.loader)
textDirEmpty = findViewById(R.id.text_dir_empty)
currentPathText = findViewById(R.id.current_path_text)
numberOfFilesText = findViewById(R.id.number_of_files_text)
@ -312,17 +321,15 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
}
private fun displayExplorerElements() {
synchronized(this) {
ExplorerElement.sortBy(sortOrderValues[currentSortOrderIndex], foldersFirst, explorerElements)
}
ExplorerElement.sortBy(sortOrderValues[currentSortOrderIndex], foldersFirst, explorerElements)
unselectAll(false)
loader.isVisible = false
recycler_view_explorer.isVisible = true
explorerAdapter.explorerElements = explorerElements
val sharedPrefsEditor = sharedPrefs.edit()
sharedPrefsEditor.putString(Constants.SORT_ORDER_KEY, sortOrderValues[currentSortOrderIndex])
sharedPrefsEditor.apply()
}
private fun recursiveSetSize(directory: ExplorerElement) {
private suspend fun recursiveSetSize(directory: ExplorerElement) {
yield()
for (child in encryptedVolume.readDir(directory.fullPath) ?: return) {
if (child.isDirectory) {
recursiveSetSize(child)
@ -346,15 +353,16 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
}
}
protected fun setCurrentPath(path: String, onDisplayed: (() -> Unit)? = null) {
synchronized(this) {
explorerElements = encryptedVolume.readDir(path) ?: return
if (path != "/") {
explorerElements.add(
0,
ExplorerElement("..", Stat.parentFolderStat(), parentPath = currentDirectoryPath)
)
}
protected fun setCurrentPath(path: String, onDisplayed: (() -> Unit)? = null) = lifecycleScope.launch {
directoryLoadingTask?.cancelAndJoin()
recycler_view_explorer.isVisible = false
loader.isVisible = true
explorerElements = encryptedVolume.readDir(path) ?: return@launch
if (path != "/") {
explorerElements.add(
0,
ExplorerElement("..", Stat.parentFolderStat(), parentPath = currentDirectoryPath)
)
}
textDirEmpty.visibility = if (explorerElements.size == 0) View.VISIBLE else View.GONE
currentDirectoryPath = path
@ -362,22 +370,19 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
displayNumberOfElements(numberOfFilesText, R.string.one_file, R.string.multiple_files, explorerElements.count { it.isRegularFile })
displayNumberOfElements(numberOfFoldersText, R.string.one_folder, R.string.multiple_folders, explorerElements.count { it.isDirectory })
if (mapFolders) {
lifecycleScope.launch {
var totalSize: Long = 0
withContext(Dispatchers.IO) {
synchronized(this@BaseExplorerActivity) {
for (element in explorerElements) {
if (element.isDirectory) {
recursiveSetSize(element)
}
totalSize += element.stat.size
}
var totalSize: Long = 0
directoryLoadingTask = launch(Dispatchers.IO) {
for (element in explorerElements) {
if (element.isDirectory) {
recursiveSetSize(element)
}
totalSize += element.stat.size
}
displayExplorerElements()
totalSizeText.text = getString(R.string.total_size, PathUtils.formatSize(totalSize))
onDisplayed?.invoke()
}
directoryLoadingTask!!.join()
displayExplorerElements()
totalSizeText.text = getString(R.string.total_size, PathUtils.formatSize(totalSize))
onDisplayed?.invoke()
} else {
displayExplorerElements()
totalSizeText.text = getString(
@ -560,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
@ -575,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
@ -607,7 +605,13 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
.setTitle(R.string.sort_order)
.setSingleChoiceItems(sortOrderEntries, currentSortOrderIndex) { dialog, which ->
currentSortOrderIndex = which
displayExplorerElements()
// displayExplorerElements must not be called if directoryLoadingTask is active
if (directoryLoadingTask?.isActive != true) {
displayExplorerElements()
}
val sharedPrefsEditor = sharedPrefs.edit()
sharedPrefsEditor.putString(Constants.SORT_ORDER_KEY, sortOrderValues[currentSortOrderIndex])
sharedPrefsEditor.apply()
dialog.dismiss()
}
.setNegativeButton(R.string.cancel, null)

View File

@ -3,6 +3,7 @@ package sushi.hardcore.droidfs.util
import android.content.ActivityNotFoundException
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.storage.StorageManager
import android.provider.DocumentsContract
@ -111,24 +112,27 @@ object PathUtils {
}
}
Log.d(PATH_RESOLVER_TAG, "getExternalFilesDirs failed")
try {
val process = ProcessBuilder("mount").redirectErrorStream(true).start().apply { waitFor() }
process.inputStream.readBytes().decodeToString().split("\n").forEach { line ->
if (line.startsWith("/dev/block/vold")) {
Log.d(PATH_RESOLVER_TAG, "mount: $line")
val fields = line.split(" ")
if (fields.size >= 3) {
val path = fields[2]
if (File(path).name == name) {
return path
// Don't risk to be killed by SELinux on newer Android versions
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
try {
val process = ProcessBuilder("mount").redirectErrorStream(true).start().apply { waitFor() }
process.inputStream.readBytes().decodeToString().split("\n").forEach { line ->
if (line.startsWith("/dev/block/vold")) {
Log.d(PATH_RESOLVER_TAG, "mount: $line")
val fields = line.split(" ")
if (fields.size >= 3) {
val path = fields[2]
if (File(path).name == name) {
return path
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
} catch (e: Exception) {
e.printStackTrace()
Log.d(PATH_RESOLVER_TAG, "mount processing failed")
}
Log.d(PATH_RESOLVER_TAG, "mount processing failed")
return null
}

View 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),
)
}

View File

@ -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
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/colorAccent" android:pathData="M5,16c0,3.87 3.13,7 7,7s7,-3.13 7,-7v-4L5,12v4zM16.12,4.37l2.1,-2.1 -0.82,-0.83 -2.3,2.31C14.16,3.28 13.12,3 12,3s-2.16,0.28 -3.09,0.75L6.6,1.44l-0.82,0.83 2.1,2.1C6.14,5.64 5,7.68 5,10v1h14v-1c0,-2.32 -1.14,-4.36 -2.88,-5.63zM9,9c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM15,9c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
</vector>

View File

@ -1,5 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
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>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:typeface="monospace" />
</HorizontalScrollView>
</ScrollView>

View File

@ -10,6 +10,12 @@
android:text="@string/dir_empty"
android:visibility="gone"/>
<ProgressBar
android:id="@+id/loader"
android:layout_centerInParent="true"
android:layout_width="40dp"
android:layout_height="40dp"/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/refresher"
android:layout_width="match_parent"

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/save"
app:showAsAction="ifRoom"
android:icon="@drawable/icon_save"
android:title="@string/save" />
</menu>

View File

@ -252,4 +252,20 @@
<string name="black_theme">لون أسود داكن</string>
<string name="password_fallback">العودة إلى كلمة المرور</string>
<string name="password_fallback_summary">طلب كلمة المرور في حال فشل المصادقة ببصمة الأصبع</string>
<string name="unknown_error_code">خطأ غير معروف: %d</string>
<string name="config_load_error">لا يمكن تحميل ملف الإعدادات. تأكد من صحّة مسار المجلد الآمن.</string>
<string name="wrong_password">لقد تعذر فك تشفر ملف الإعدادات. رجاءً قم بالتحقق من كلمة المرور.</string>
<string name="filesystem_id_changed">لقد إختلف معرف نظام الملفات الموجود في ملف الإعدادات عن آخر مرة فتحنا فيها هذا المجلد. قد يعني هذا أن أحد ما قد قام باستبدال ملفاتك بملفات أخرى بهدف إختراقك.</string>
<string name="inaccessible_base_dir">إن المجلد المشفر غير موجود أو لا يمكن الوصول إليه.</string>
<string name="task_failed">لقد فشلت عملية: %s</string>
<string name="usf_expose">كشف المجلدات المفتوحة</string>
<string name="usf_expose_summary">السماح للتطبيقات الأخرى بالوصول إلى المجلدات المشفرة عن طريق نظام "توفير المستندات" الخاص بنظام الأندرويد</string>
<string name="usf_saf_write">منح صلاحيات الكتابة</string>
<string name="usf_saf_write_summary">منح صلاحيات الكتابة عند فتح الملفات مع التطبيقات الأخرى</string>
<string name="saf">"إطار الوصول للقرص"</string>
<string name="tmp_export_failed">لقد فشل تصدير: %s</string>
<string name="export_failed_create">تعذر إنشاء الملف المستخرج</string>
<string name="export_failed_export">فشل إستخراج الملف</string>
<string name="export_mem">يتم الإستخراج إلى الذاكرة المؤقتة…</string>
<string name="export_disk">يتم الإستخراج إلى القرص…</string>
</resources>

View File

@ -242,6 +242,7 @@
<string name="file_op_delete_msg">Eliminando archivos…</string>
<string name="volume_type">(%s)</string>
<string name="volume_type_read_only">(%s, Sólo lectura)</string>
<string name="volume_type_inaccessible">(%s, inaccesible)</string>
<string name="io_error">I/O Error.</string>
<string name="use_fingerprint">Utilizar huella dactilar en lugar de la contraseña actual</string>
<string name="remember_volume">Recordar volumen</string>

View File

@ -17,4 +17,10 @@
<item>Фиолетовый</item>
<item>Розовый</item>
</string-array>
<string-array name="export_methods">
<item>Автовыбор (зависит от доступной памяти)</item>
<item>Временный файл в хранилище (надёжно, но могут остаться следы)</item>
<item>Файл в памяти (безопаснее, но не всегда возможно)</item>
</string-array>
</resources>

View File

@ -262,4 +262,10 @@
<string name="export_failed_export">невозможно экспортировать файл</string>
<string name="export_mem">Экспорт в память…</string>
<string name="export_disk">Экспорт в хранилище…</string>
<string name="memfd_create_unsupported">Текущее ядро не поддерживает memfd_create(). Для работы данной функции требуется версия ядра не ниже %s.</string>
<string name="export_method">Метод экспорта</string>
<string name="export_method_summary">Метод экспорта файлов. Используется для обмена, открытия во внешнем приложении и доступа к открытым файлам.</string>
<string name="debug">Отладка</string>
<string name="logcat_title">Журнал logcat DroidFS</string>
<string name="logcat_saved">Журнал logcat сохранён</string>
</resources>

View File

@ -0,0 +1,72 @@
<resources>
<string-array name="gocryptfs_encryption_ciphers">
<item>AES-GCM</item>
<item>XChaCha20-Poly1305</item>
<item>@string/auto</item>
</string-array>
<string-array name="cryfs_encryption_ciphers">
<item>xchacha20-poly1305</item>
<item>aes-256-gcm</item>
<item>aes-128-gcm</item>
<item>twofish-256-gcm</item>
<item>twofish-128-gcm</item>
<item>serpent-256-gcm</item>
<item>serpent-128-gcm</item>
<item>cast-256-gcm</item>
<item>mars-448-gcm</item>
<item>mars-256-gcm</item>
<item>mars-128-gcm</item>
</string-array>
<string-array name="sort_orders_entries">
<item>Ad</item>
<item>Boyut</item>
<item>Tarih</item>
<item>Ad (azalan)</item>
<item>Boyut (azalan)</item>
<item>Tarih (azalan)</item>
</string-array>
<string-array name="color_names">
<item>Yeşil</item>
<item>Kırmızı</item>
<item>Mavi</item>
<item>Sarı</item>
<item>Turuncu</item>
<item>Mor</item>
<item>Pembe</item>
</string-array>
<string-array name="export_methods">
<item>Otomatik (kullanılabilir belleğe bağlı olarak)</item>
<item>Diske geçici dışa aktarma (güvenilir ancak iz bırakabilir)</item>
<item>Bellek dosyası (daha güvenlidir ancak her zaman işe yaramaz)</item>
</string-array>
<!-- don't translate the following otherwise the app will crash -->
<string-array name="sort_orders_values">
<item>name</item>
<item>size</item>
<item>date</item>
<item>name_desc</item>
<item>size_desc</item>
<item>date_desc</item>
</string-array>
<string-array name="color_values">
<item>green</item>
<item>red</item>
<item>blue</item>
<item>yellow</item>
<item>orange</item>
<item>purple</item>
<item>pink</item>
</string-array>
<string-array name="export_methods_values">
<item>auto</item>
<item>disk</item>
<item>memory</item>
</string-array>
</resources>

View File

@ -0,0 +1,282 @@
<resources>
<string name="app_name">DroidFS</string>
<string name="create_volume">Birim oluştur</string>
<string name="open"></string>
<string name="create">Oluştur</string>
<string name="change_password">Şifreyi değiştir</string>
<string name="password">Şifre</string>
<string name="import_files">Dosyaları içe aktar/Şifrele</string>
<string name="import_folder">Klasörü İçe Aktar/Şifrele</string>
<string name="discovering_files">Dosyalar keşfediliyor…</string>
<string name="mkdir">Klasör oluştur</string>
<string name="dir_empty">Klasör boş</string>
<string name="warning">Dikkat !</string>
<string name="ask_lock_volume">Bu birimi kilitlemek istediğinizden emin misiniz?</string>
<string name="ok">Tamam</string>
<string name="cancel">İptal</string>
<string name="enter_folder_name">Klasör adı:</string>
<string name="error">Hata</string>
<string name="error_filename_empty">Lütfen bir isim girin</string>
<string name="error_mkdir">Klasör oluşturulamadı.</string>
<string name="success_import">Başarılı bir şekilde içe aktarıldı !</string>
<string name="success_import_msg">Seçili dosyalar başarılı bir şekilde içe aktarıldı.</string>
<string name="import_failed">İçe aktarılamadı: %s</string>
<string name="export_failed">Dışa aktarılamadı: %s</string>
<string name="success_export">Başarılı bir şekilde dışa aktarıldı !</string>
<string name="remove_failed">Silinemedi: %s</string>
<string name="passwords_mismatch">Şifreler uyuşmuyor/string>
<string name="dir_not_empty">Seçili klasör boş değil</string>
<string name="create_volume_failed">Birim oluşturualamadı.</string>
<string name="open_volume_failed">ılamadı</string>
<string name="share_chooser">Dosyayı paylaş</string>
<string name="storage_perm_denied">Depolama izni reddedildi</string>
<string name="storage_perm_denied_msg">DroidFS, depolama izinleri olmadan çalışamaz.</string>
<string name="get_size_failed">Dosya boyutu alınamadı.</string>
<string name="parent_folder">Ana klasör</string>
<string name="enter_volume_path">Lütfen birim yolunu girin</string>
<string name="enter_volume_name">Lütfen birim adını girin</string>
<string name="external_open">Harici uygulamayla aç</string>
<string name="single_delete_confirm">Silmek istediğimizden emin misiniz: %s</string>
<string name="multiple_delete_confirm">Bunları silmek istediğinizden emin misiniz: %s</string>
<string name="location">Konum: %s</string>
<string name="total_size">Toplam boyut: %s</string>
<string name="import_from_other_volume">Başka bir birimden içe aktar</string>
<string name="read_file_failed">Bu dosya açılamadı.</string>
<string name="volume">Birim: %s</string>
<string name="yes">Evet</string>
<string name="no">Hayır</string>
<string name="ask_for_wipe">Orijinal dosyaları silmek istiyor musunuz ?</string>
<string name="wipe_failed">Silinemedi: %s</string>
<string name="wipe_successful">Dosyalar başarılı bir şekilde silindi !</string>
<string name="rename">Yeniden adlandır</string>
<string name="rename_title">Yeni ad:</string>
<string name="rename_failed">Yeniden adlandırılamadı: %s</string>
<string name="sort_order">Sıralama biçimi:</string>
<string name="change_password_failed">İşlem başarısız oldu. Lütfen eski şifrenizi kontrol edin.</string>
<string name="share_menu_label">DroidFS ile şifrele</string>
<string name="share_intent_parsing_failed">Paylaşım isteği işlenemedi.</string>
<string name="listdir_null_error_msg">Bu dizine erişilemiyor</string>
<string name="fingerprint_save_checkbox_text">Parmak izini kullanarak şifre hash değerini kaydedin</string>
<string name="fingerprint_instruction">Lütfen parmak izi sensörüne dokunun</string>
<string name="illegal_block_size_exception">IllegalBlockSizeException</string>
<string name="illegal_block_size_exception_msg">Yeni bir parmak izi eklediyseniz bu durum meydana gelebilir. Hash depolamasının sıfırlanması bu sorunu çözebilir.</string>
<string name="reset_hash_storage">Hash depolamasını sıfırla</string>
<string name="MAC_verification_failed">İmza/MAC doğrulaması başarısız oldu. Android KeyStore veya kaydedilen hash değeri değiştirildi. Hash depolamasını sıfırlamak bu sorunu çözebilir.</string>
<string name="hash_storage_reset">Hash depolaması başarıyla sıfırlandı</string>
<string name="encrypt_action_description">Şifre hash değerini şifreleme ve kaydetme.</string>
<string name="decrypt_action_description">Şifre hash değeri şifreleniyor.</string>
<string name="title_activity_settings">DroidFS ayarları</string>
<string name="explorer">Tarayıcı</string>
<string name="settings_title_sort_order">Varsayılan sıralama düzeni</string>
<string name="usf_decrypt">Dosyaların dışa aktarılmasına/şifresinin çözülmesine izin ver</string>
<string name="usf_share">Android paylaşım menüsü aracılığıyla dosya paylaşımına izin ver</string>
<string name="usf_open">Dosyaların diğer uygulamalarla açılmasına izin ver</string>
<string name="usf_screenshot">Ekran görüntüsü almaya izin ver</string>
<string name="usf_fingerprint">Parmak izi kullanılarak şifre hash değerinin kaydedilmesine izin ver</string>
<string name="usf_volume_management">Birim yönetimi</string>
<string name="usf_keep_open">Uygulama arka plana geçtiğinde birimi açık tutun</string>
<string name="unsafe_features">Güvenli olmayan özellikler</string>
<string name="manage_unsafe_features">Güvenli olmayan özellikleri yönetin</string>
<string name="manage_unsafe_features_summary">Güvenli olmayan özellikleri etkinleştirme/devre dışı bırakma</string>
<string name="usf_home_warning_msg">DroidFS mümkün olduğunca güvenli olmaya çalışır. Ancak güvenlik çoğu zaman konfor eksikliğini de beraberinde getirir. Bu nedenle DroidFS, ihtiyaçlarınıza göre etkinleştirebileceğiniz/devre dışı bırakabileceğiniz güvenli olmayan ek özellikler sunar.\n\nDikkat: bu özellikler GÜVENLİ OLMAYABİLİR. Ne yaptığınızı tam olarak bilmiyorsanız bunları kullanmayın. Bunları etkinleştirmeden önce belgeleri okumanız önemle tavsiye edilir.</string>
<string name="see_unsafe_features">Güvenli olmayan özellikleri görün</string>
<string name="open_as">Farklı</string>
<string name="image">Resim</string>
<string name="video">Video</string>
<string name="audio">Ses</string>
<string name="playing_failed">Bu dosya oynatılamadı: %s</string>
<string name="text">Metin</string>
<string name="save_failed">Kaydedilemedi</string>
<string name="file_saved">Dosya kaydedildi !</string>
<string name="ask_save">Dosya kaydedilmemiş değişiklikler içeriyor. Çıkmadan önce bunları kaydetmek istiyor musunuz ?</string>
<string name="save">Kaydet</string>
<string name="discard">Gözardı et</string>
<string name="word_wrap">Kelime kaydırma</string>
<string name="outofmemoryerror_msg">OutOfMemoryError: Bu dosya belleğe yüklenemeyecek kadar büyük.</string>
<string name="new_file">Yeni dosya oluştur</string>
<string name="enter_file_name">Dosya adı:</string>
<string name="file_creation_failed">Dosya oluşturulamadı.</string>
<string name="loading">Yükleniyor…</string>
<string name="loading_msg_create">Birim oluşturuluyor…</string>
<string name="loading_msg_change_password">Şifre değiştiriliyor…</string>
<string name="loading_msg_open">Birim açılıyor…</string>
<string name="loading_msg_export">Dosyalar dışa aktarılıyor…</string>
<string name="query_cursor_null_error_msg">Bu dosyaya erişilemiyor</string>
<string name="about">Hakkında</string>
<string name="github">GitHub</string>
<string name="github_summary">DroidFS Github deposu. Kaynak kodu, dokümantasyon, hata izleyici…</string>
<string name="gitea">Gitea</string>
<string name="gitea_summary">Chapril Gitea bulut sunucusundaki DroidFS deposu GitHub\'tan farklı olarak Gitea tamamen ücretsiz bir yazılımdır ve kendi kendine barındırılır. Kaynak kodu, belgeler, hata izleyici…</string>
<string name="share">Paylaş</string>
<string name="decrypt_files">Dışa aktar/Şifre çöz</string>
<string name="copy_failed">Kopyalanamadı: %s</string>
<string name="copy_success">Başarıyla kopyalandı !</string>
<string name="add">Ekle</string>
<string name="camera">Kamera</string>
<string name="picture_save_success">Resim şuraya kaydedildi: %s</string>
<string name="picture_save_failed">Bu resim kaydedilemedi.</string>
<string name="video_save_success">Resim şuraya kaydedildi: %s</string>
<string name="file_overwrite_question">%s zaten mevcut, üzerine yazmak istiyor musunuz ?</string>
<string name="dir_overwrite_question">%s zaten mevcut, içeriğini birleştirmek istiyor musunuz ?</string>
<string name="enter_new_name">Yeni ad girin</string>
<string name="copy_menu_title">Kopyala</string>
<string name="move_failed">Taşınamadı: %s</string>
<string name="move_success">Başarıyla taşındı !</string>
<string name="enter_timer_duration">Zamanlayıcı süresini girin (saniye olarak)</string>
<string name="path_error">Seçilen yol alınamadı.</string>
<string name="create_cant_write_error_msg">DroidFS\'nin bu yola yazma erişimi yok. Lütfen başka bir konum deneyin.</string>
<string name="add_cant_write_warning">DroidFS\'nin bu yola yazma erişimi yok. Salt okunur erişimle birim ekleniyor.</string>
<string name="sdcard_error_header">DroidFS yalnızca aşağıdaki durumlarda çıkarılabilir SD kartlara yazabilir:</string>
<string name="sdcard_error_add_footer">Salt okunur erişimle birim ekleme.</string>
<string name="sdcard_error_create_footer">Lütfen bu yolun bir alt dizinini veya dahili depolamayı kullanın.</string>
<string name="slideshow_stopped">Slayt gösterisi durduruldu</string>
<string name="slideshow_started">Slayt gösterisi başlatıldı</string>
<string name="ask_save_img_rotated">Resim döndürüldü. Bu değişiklikleri kaydedip orijinal resmin üzerine yazmak istiyor musunuz ?</string>
<string name="image_saved_successfully">Resim değişiklikleri başarıyla kaydedildi.</string>
<string name="bitmap_compress_failed">Bitmap sıkıştırılamadı.</string>
<string name="file_write_failed">Dosya yazılamadı.</string>
<string name="error_not_a_volume">Şifrelenmiş birim tanınmadı. Lütfen seçilen yolu kontrol edin.</string>
<string name="version">Versiyon</string>
<string name="error_cipher_null">Hata şifre boş</string>
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
<string name="key_permanently_invalidated_exception_msg">Yeni bir parmak izi eklemişsiniz gibi görünüyor. Kaydedilen şifrelerin hash değeri kullanılamaz hale geldi.</string>
<string name="usf_read_doc">Bu seçeneklerden herhangi birini etkinleştirmeden önce dikkatlice okumalısınız.</string>
<string name="usf_doc">Güvenli olmayan özellikler dokümantasyonu</string>
<string name="error_retrieving_filename">Şu URI için dosya adı alınamıyor: %s</string>
<string name="hidden_volume">Gizli birim</string>
<string name="error_slash_in_name">Birim adı eğik çizgi sembolü içeremez</string>
<string name="hidden_volume_warning">Gizli birimler uygulamanın dahili deposunda saklanır. Diğer uygulamalar bu birimleri root erişimi olmadan göremez. Ancak DroidFS\'yi kaldırırsanız veya uygulamanın verilerini temizlerseniz tüm gizli birimleriniz KAYBOLACAKTIR. Yedekleme yaptığınızdan emin olun !</string>
<string name="camera_perm_needed">Fotoğraf çekebilmek için kamera izni gerekiyor.</string>
<string name="choose_resolution">Bir çözünürlük seçin</string>
<string name="file_operations">Dosya işlemleri</string>
<string name="file_op_copy_msg">Dosyalar kopyalanıyor…</string>
<string name="file_op_import_msg">Dosyalar içe aktarılıyor...</string>
<string name="file_op_export_msg">Dosyalar dışa aktarılıyor...</string>
<string name="file_op_move_msg">Dosyalar taşınıyor…</string>
<string name="file_op_wiping_msg">Dosyalar siliniyor…</string>
<string name="folders_first">Önce klasörler</string>
<string name="folders_first_summary">Klasörleri listenin başında göster</string>
<string name="auto_fit_title">Video oynatıcı ekranı otomatik döndürme</string>
<string name="auto_fit_summary">Video boyutlarına uyacak şekilde ekranı otomatik olarak döndürün</string>
<string name="open_tree_failed">Dosya tarayıcısı bulunamadı. Lütfen bir tane yükleyin ve tekrar deneyin.</string>
<string name="close_volume">Birimi kapat</string>
<string name="sort_by">Sırala</string>
<string name="cut">Kes</string>
<string name="map_folders">Klasörleri eşleme</string>
<string name="map_folders_summary">Boyutlarını hesaplamak için klasörleri yinelemeli olarak eşleyin (büyük birimleri açarken bunu devre dışı bırakmalısınız)</string>
<string name="camera_optimization">Kamera optimizasyonu</string>
<string name="maximize_quality">Kaliteyi maksimuma çıkarın</string>
<string name="minimize_latency">Gecikmeyi minimuma indirin</string>
<string name="auto">Otomatik</string>
<string name="encryption_cipher_label">Şifreleme şifresi:</string>
<string name="theme">Tema</string>
<string name="thumbnails">Küçük resimler</string>
<string name="thumbnails_summary">Resimlerin ve videoların küçük resimlerini göster</string>
<string name="seek_seconds_forward">+%d saniye</string>
<string name="seek_seconds_backward">-%d saniye</string>
<string name="add_volume">Birim ekle</string>
<string name="pick_directory">Klasör seç</string>
<string name="volume_alread_saved">Birim zaten kayıtlı</string>
<string name="open_dialog_title">ılıyor %s:</string>
<string name="remove">Kaldır</string>
<string name="settings">Ayarlar</string>
<string name="select_all">Tümünü seç</string>
<string name="remove_fingerprint">Parmak izini kaldır</string>
<string name="unrecoverable_key_exception_msg">%s. Şifreleme anahtarı yüklenemedi.</string>
<string name="unrecoverable_key_exception">UnrecoverableKeyException</string>
<string name="delete_hidden_volume_question">%s gizli, sadece birimin yolunu unutmak mı yoksa tüm İÇERİĞİNİ SİLMEK mi istiyorsunuz?</string>
<string name="forget_only">Sadece unut</string>
<string name="delete_volume">Birimi sil</string>
<string name="hidden_volume_description">Birimi DroidFS dahili deposunda saklayın</string>
<string name="error_is_file">Hata: dosya zaten mevcut</string>
<string name="volume_path_label">Birimin yolunu seçin:</string>
<string name="volume_name_label">Birimin adını girin:</string>
<string name="volume_path_hint">Birim yolu</string>
<string name="volume_name_hint">Birim adı</string>
<string name="password_label">Birim şifresini girin:</string>
<string name="password_confirmation_label">Şifreyi tekrarlayın:</string>
<string name="password_confirmation_hint">Şifre (doğrulama)</string>
<string name="password_hash_saved">şifre hash değeri kaydedildi</string>
<string name="no_volumes_text">Kaydedilmiş birim yok, + düğmesini tıklayarak biraz ekleyin</string>
<string name="fingerprint_error_msg">Parmak izi kimlik doğrulaması kullanılamaz: %s.</string>
<string name="keyguard_not_secure">tuş kilidi güvenli değil</string>
<string name="no_hardware">uygun donanım bulunamadı</string>
<string name="hardware_unavailable">donanım mevcut değil</string>
<string name="no_fingerprint">kayıtlı parmak izi yok</string>
<string name="unknown_error">bilinmeyen hata</string>
<string name="biometric_error">Biyometri hatası: %s</string>
<string name="apply_to_all">Bu seçimi tüm gizli birimlere uygula</string>
<string name="select_volume">Birimi seç</string>
<string name="current_password_label">Mevcut birim parolasını girin:</string>
<string name="current_password_hint">Mevcut şifre</string>
<string name="new_password_label">Yeni birim şifresini girin:</string>
<string name="new_password_hint">Yeni şifre</string>
<string name="new_password_confirmation_label">Yeni şifreyi tekrarlayın:</string>
<string name="error_marshmallow_required">Bu özellik yalnızca Android 6.0 (Marshmallow) veya üzeri sürümlerde mevcuttur.</string>
<string name="copy_hidden_volume">Paylaşılan depolamaya kopyala</string>
<string name="copy_external_volume">Gizli bir kopya oluştur</string>
<string name="copy_volume_notification">Birim kopyalanıyor…</string>
<string name="hidden_volume_already_exists">Aynı ada sahip bir gizli birim zaten mevcut.</string>
<string name="pdf_document">PDF dökümanı</string>
<string name="thumbnail_max_size">Küçük resimler için maksimum boyut</string>
<string name="thumbnail_max_size_summary">Küçük resmin yüklenebileceği maksimum dosya boyutu. Mevcut değer: %s</string>
<string name="size_hint">Boyut (KB olarak)</string>
<string name="invalid_number">Geçersiz numara</string>
<string name="new_volume_name">Yeni birim adı:</string>
<string name="volume_rename_failed">Birim yeniden adlandırılamadı</string>
<string name="switch_display_layout">Ekran düzenini değiştir</string>
<string name="one_file">1 dosya</string>
<string name="multiple_files">%d dosya</string>