From 5474d6eea511ae08c75a94ce1ceaf31c0fbd2ef8 Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Sat, 13 Jan 2024 21:25:31 +0100 Subject: [PATCH 01/17] Add .opus & Update build config --- app/build.gradle | 3 ++- app/src/main/java/sushi/hardcore/droidfs/FileTypes.kt | 2 +- build.gradle | 4 ++-- gradle.properties | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2ac7e3d..7d557a0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/FileTypes.kt b/app/src/main/java/sushi/hardcore/droidfs/FileTypes.kt index 986574b..2020c24 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/FileTypes.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/FileTypes.kt @@ -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", diff --git a/build.gradle b/build.gradle index f37707f..18e59f1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ buildscript { - ext.kotlin_version = '1.9.0' + ext.kotlin_version = '1.9.22' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.1.1' + classpath 'com.android.tools.build:gradle:8.2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/gradle.properties b/gradle.properties index 52ed220..2131885 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,5 @@ android.useAndroidX=true kotlin.code.style=official android.native.buildOutput=verbose -android.nonTransitiveRClass=false \ No newline at end of file +android.nonTransitiveRClass=false +org.gradle.configuration-cache=true \ No newline at end of file From f4e47c1827e92b60c938de62ed2f49875d152567 Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Sat, 13 Jan 2024 21:41:58 +0100 Subject: [PATCH 02/17] Allow directory creation on exposed volumes --- .../content_providers/VolumeProvider.kt | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/sushi/hardcore/droidfs/content_providers/VolumeProvider.kt b/app/src/main/java/sushi/hardcore/droidfs/content_providers/VolumeProvider.kt index 9752050..60a2d35 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/content_providers/VolumeProvider.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/content_providers/VolumeProvider.kt @@ -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 } } From b4635dc2e0a17be2dca6039e9a123436b6394970 Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Sat, 13 Jan 2024 23:19:22 +0100 Subject: [PATCH 03/17] Directory loading indicator --- .../droidfs/explorers/BaseExplorerActivity.kt | 72 +++++++++++-------- app/src/main/res/layout/explorer_content.xml | 6 ++ 2 files changed, 47 insertions(+), 31 deletions(-) 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 f07c792..e774c91 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt @@ -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 @@ -69,6 +74,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 protected lateinit var explorerAdapter: ExplorerElementAdapter protected lateinit var app: VolumeManagerApp @@ -79,6 +85,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 +108,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 +320,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 +352,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 +369,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( @@ -607,7 +611,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) diff --git a/app/src/main/res/layout/explorer_content.xml b/app/src/main/res/layout/explorer_content.xml index babfb5e..6aa7c95 100644 --- a/app/src/main/res/layout/explorer_content.xml +++ b/app/src/main/res/layout/explorer_content.xml @@ -10,6 +10,12 @@ android:text="@string/dir_empty" android:visibility="gone"/> + + Date: Sun, 28 Jan 2024 15:44:53 +0100 Subject: [PATCH 04/17] 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 From c26ab661c253de3ddd466503c284247677f27fb6 Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Tue, 30 Jan 2024 18:28:05 +0100 Subject: [PATCH 05/17] Logcat activity --- app/src/main/AndroidManifest.xml | 1 + .../sushi/hardcore/droidfs/LogcatActivity.kt | 88 +++++++++++++++++++ .../hardcore/droidfs/SettingsActivity.kt | 4 + app/src/main/res/drawable/icon_debug.xml | 5 ++ app/src/main/res/layout/activity_logcat.xml | 20 +++++ app/src/main/res/menu/logcat.xml | 11 +++ app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/root_preferences.xml | 11 ++- build.gradle | 2 +- 9 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/sushi/hardcore/droidfs/LogcatActivity.kt create mode 100644 app/src/main/res/drawable/icon_debug.xml create mode 100644 app/src/main/res/layout/activity_logcat.xml create mode 100644 app/src/main/res/menu/logcat.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 46b3699..f045d33 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -53,6 +53,7 @@ + diff --git a/app/src/main/java/sushi/hardcore/droidfs/LogcatActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/LogcatActivity.kt new file mode 100644 index 0000000..3eac7e2 --- /dev/null +++ b/app/src/main/java/sushi/hardcore/droidfs/LogcatActivity.kt @@ -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() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt index 4cc633d..2e9bf8a 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt @@ -120,6 +120,10 @@ class SettingsActivity : BaseActivity() { false } } + findPreference("logcat")?.setOnPreferenceClickListener { _ -> + startActivity(Intent(requireContext(), LogcatActivity::class.java)) + true + } } } diff --git a/app/src/main/res/drawable/icon_debug.xml b/app/src/main/res/drawable/icon_debug.xml new file mode 100644 index 0000000..1cc8a97 --- /dev/null +++ b/app/src/main/res/drawable/icon_debug.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_logcat.xml b/app/src/main/res/layout/activity_logcat.xml new file mode 100644 index 0000000..c2174b8 --- /dev/null +++ b/app/src/main/res/layout/activity_logcat.xml @@ -0,0 +1,20 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/logcat.xml b/app/src/main/res/menu/logcat.xml new file mode 100644 index 0000000..2ffc835 --- /dev/null +++ b/app/src/main/res/menu/logcat.xml @@ -0,0 +1,11 @@ + + + + + + \ 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 57860b9..fd68b14 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -276,4 +276,7 @@ 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. + Debug + DroidFS Logcat + Logcat saved diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index a28bee5..01d46b2 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -93,6 +93,16 @@ + + + + + + diff --git a/build.gradle b/build.gradle index 18e59f1..a8173a6 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.2.1' + classpath 'com.android.tools.build:gradle:8.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } From 6f43bc7417893a441aba73b1319018e7efc23e3a Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Sun, 11 Feb 2024 17:55:24 +0100 Subject: [PATCH 06/17] Avoid being killed by SELinux when retrieving volume path --- .../sushi/hardcore/droidfs/util/PathUtils.kt | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt b/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt index 173b826..562cb62 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt @@ -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 } From cda0e90b968533330e6f0ce0f35d62ba31cd2da4 Mon Sep 17 00:00:00 2001 From: Muhmmad14333653 <104266674+Muhmmad14333653@users.noreply.github.com> Date: Thu, 5 Oct 2023 17:25:13 +0300 Subject: [PATCH 07/17] Update Arabic translations Signed-off-by: Hardcore Sushi --- app/src/main/res/values-ar/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 5def232..209ce08 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -252,4 +252,20 @@ لون أسود داكن العودة إلى كلمة المرور طلب كلمة المرور في حال فشل المصادقة ببصمة الأصبع + خطأ غير معروف: %d + لا يمكن تحميل ملف الإعدادات. تأكد من صحّة مسار المجلد الآمن. + لقد تعذر فك تشفر ملف الإعدادات. رجاءً قم بالتحقق من كلمة المرور. + لقد إختلف معرف نظام الملفات الموجود في ملف الإعدادات عن آخر مرة فتحنا فيها هذا المجلد. قد يعني هذا أن أحد ما قد قام باستبدال ملفاتك بملفات أخرى بهدف إختراقك. + إن المجلد المشفر غير موجود أو لا يمكن الوصول إليه. + لقد فشلت عملية: %s + كشف المجلدات المفتوحة + السماح للتطبيقات الأخرى بالوصول إلى المجلدات المشفرة عن طريق نظام "توفير المستندات" الخاص بنظام الأندرويد + منح صلاحيات الكتابة + منح صلاحيات الكتابة عند فتح الملفات مع التطبيقات الأخرى + "إطار الوصول للقرص" + لقد فشل تصدير: %s + تعذر إنشاء الملف المستخرج + فشل إستخراج الملف + يتم الإستخراج إلى الذاكرة المؤقتة… + يتم الإستخراج إلى القرص… From e5652666d85028db4ce440e0233730b7a2886074 Mon Sep 17 00:00:00 2001 From: CyanWolf <101049163+cyanwolfg@users.noreply.github.com> Date: Wed, 8 Nov 2023 15:04:38 +0000 Subject: [PATCH 08/17] Update Spanish Signed-off-by: Hardcore Sushi --- app/src/main/res/values-es/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 43ecaa4..751e7fb 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -242,6 +242,7 @@ Eliminando archivos… (%s) (%s, Sólo lectura) + (%s, inaccesible) I/O Error. Utilizar huella dactilar en lugar de la contraseña actual Recordar volumen From b747d2822a4a437c7e5d03f40e9e01a9fc9eee7b Mon Sep 17 00:00:00 2001 From: Ali Beyaz Date: Mon, 12 Feb 2024 16:47:30 +0100 Subject: [PATCH 09/17] Add Turkish translation Signed-off-by: Hardcore Sushi --- app/src/main/res/values-tr/arrays.xml | 72 +++++++ app/src/main/res/values-tr/strings.xml | 282 +++++++++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 app/src/main/res/values-tr/arrays.xml create mode 100644 app/src/main/res/values-tr/strings.xml diff --git a/app/src/main/res/values-tr/arrays.xml b/app/src/main/res/values-tr/arrays.xml new file mode 100644 index 0000000..a5bbc76 --- /dev/null +++ b/app/src/main/res/values-tr/arrays.xml @@ -0,0 +1,72 @@ + + + AES-GCM + XChaCha20-Poly1305 + @string/auto + + + + xchacha20-poly1305 + aes-256-gcm + aes-128-gcm + twofish-256-gcm + twofish-128-gcm + serpent-256-gcm + serpent-128-gcm + cast-256-gcm + mars-448-gcm + mars-256-gcm + mars-128-gcm + + + + Ad + Boyut + Tarih + Ad (azalan) + Boyut (azalan) + Tarih (azalan) + + + + Yeşil + Kırmızı + Mavi + Sarı + Turuncu + Mor + Pembe + + + + Otomatik (kullanılabilir belleğe bağlı olarak) + Diske geçici dışa aktarma (güvenilir ancak iz bırakabilir) + Bellek dosyası (daha güvenlidir ancak her zaman işe yaramaz) + + + + + name + size + date + name_desc + size_desc + date_desc + + + + green + red + blue + yellow + orange + purple + pink + + + + auto + disk + memory + + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml new file mode 100644 index 0000000..0740738 --- /dev/null +++ b/app/src/main/res/values-tr/strings.xml @@ -0,0 +1,282 @@ + + DroidFS + Birim oluştur + + Oluştur + Şifreyi değiştir + Şifre + Dosyaları içe aktar/Şifrele + Klasörü İçe Aktar/Şifrele + Dosyalar keşfediliyor… + Klasör oluştur + Klasör boş + Dikkat ! + Bu birimi kilitlemek istediğinizden emin misiniz? + Tamam + İptal + Klasör adı: + Hata + Lütfen bir isim girin + Klasör oluşturulamadı. + Başarılı bir şekilde içe aktarıldı ! + Seçili dosyalar başarılı bir şekilde içe aktarıldı. + İçe aktarılamadı: %s + Dışa aktarılamadı: %s + Başarılı bir şekilde dışa aktarıldı ! + Silinemedi: %s + Şifreler uyuşmuyor/string> + Seçili klasör boş değil + Birim oluşturualamadı. + Açılamadı + Dosyayı paylaş + Depolama izni reddedildi + DroidFS, depolama izinleri olmadan çalışamaz. + Dosya boyutu alınamadı. + Ana klasör + Lütfen birim yolunu girin + Lütfen birim adını girin + Harici uygulamayla aç + Silmek istediğimizden emin misiniz: %s + Bunları silmek istediğinizden emin misiniz: %s + Konum: %s + Toplam boyut: %s + Başka bir birimden içe aktar + Bu dosya açılamadı. + Birim: %s + Evet + Hayır + Orijinal dosyaları silmek istiyor musunuz ? + Silinemedi: %s + Dosyalar başarılı bir şekilde silindi ! + Yeniden adlandır + Yeni ad: + Yeniden adlandırılamadı: %s + Sıralama biçimi: + İşlem başarısız oldu. Lütfen eski şifrenizi kontrol edin. + DroidFS ile şifrele + Paylaşım isteği işlenemedi. + Bu dizine erişilemiyor + Parmak izini kullanarak şifre hash değerini kaydedin + Lütfen parmak izi sensörüne dokunun + IllegalBlockSizeException + Yeni bir parmak izi eklediyseniz bu durum meydana gelebilir. Hash depolamasının sıfırlanması bu sorunu çözebilir. + Hash depolamasını sıfırla + İ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. + Hash depolaması başarıyla sıfırlandı + Şifre hash değerini şifreleme ve kaydetme. + Şifre hash değeri şifreleniyor. + DroidFS ayarları + Tarayıcı + Varsayılan sıralama düzeni + Dosyaların dışa aktarılmasına/şifresinin çözülmesine izin ver + Android paylaşım menüsü aracılığıyla dosya paylaşımına izin ver + Dosyaların diğer uygulamalarla açılmasına izin ver + Ekran görüntüsü almaya izin ver + Parmak izi kullanılarak şifre hash değerinin kaydedilmesine izin ver + Birim yönetimi + Uygulama arka plana geçtiğinde birimi açık tutun + Güvenli olmayan özellikler + Güvenli olmayan özellikleri yönetin + Güvenli olmayan özellikleri etkinleştirme/devre dışı bırakma + 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. + Güvenli olmayan özellikleri görün + Farklı aç + Resim + Video + Ses + Bu dosya oynatılamadı: %s + Metin + Kaydedilemedi + Dosya kaydedildi ! + Dosya kaydedilmemiş değişiklikler içeriyor. Çıkmadan önce bunları kaydetmek istiyor musunuz ? + Kaydet + Gözardı et + Kelime kaydırma + OutOfMemoryError: Bu dosya belleğe yüklenemeyecek kadar büyük. + Yeni dosya oluştur + Dosya adı: + Dosya oluşturulamadı. + Yükleniyor… + Birim oluşturuluyor… + Şifre değiştiriliyor… + Birim açılıyor… + Dosyalar dışa aktarılıyor… + Bu dosyaya erişilemiyor + Hakkında + GitHub + DroidFS Github deposu. Kaynak kodu, dokümantasyon, hata izleyici… + Gitea + 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… + Paylaş + Dışa aktar/Şifre çöz + Kopyalanamadı: %s + Başarıyla kopyalandı ! + Ekle + Kamera + Resim şuraya kaydedildi: %s + Bu resim kaydedilemedi. + Resim şuraya kaydedildi: %s + %s zaten mevcut, üzerine yazmak istiyor musunuz ? + %s zaten mevcut, içeriğini birleştirmek istiyor musunuz ? + Yeni ad girin + Kopyala + Taşınamadı: %s + Başarıyla taşındı ! + Zamanlayıcı süresini girin (saniye olarak) + Seçilen yol alınamadı. + DroidFS\'nin bu yola yazma erişimi yok. Lütfen başka bir konum deneyin. + DroidFS\'nin bu yola yazma erişimi yok. Salt okunur erişimle birim ekleniyor. + DroidFS yalnızca aşağıdaki durumlarda çıkarılabilir SD kartlara yazabilir: + Salt okunur erişimle birim ekleme. + Lütfen bu yolun bir alt dizinini veya dahili depolamayı kullanın. + Slayt gösterisi durduruldu + Slayt gösterisi başlatıldı + Resim döndürüldü. Bu değişiklikleri kaydedip orijinal resmin üzerine yazmak istiyor musunuz ? + Resim değişiklikleri başarıyla kaydedildi. + Bitmap sıkıştırılamadı. + Dosya yazılamadı. + Şifrelenmiş birim tanınmadı. Lütfen seçilen yolu kontrol edin. + Versiyon + Hata şifre boş + KeyPermanentlyInvalidatedException + Yeni bir parmak izi eklemişsiniz gibi görünüyor. Kaydedilen şifrelerin hash değeri kullanılamaz hale geldi. + Bu seçeneklerden herhangi birini etkinleştirmeden önce dikkatlice okumalısınız. + Güvenli olmayan özellikler dokümantasyonu + Şu URI için dosya adı alınamıyor: %s + Gizli birim + Birim adı eğik çizgi sembolü içeremez + 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 ! + Fotoğraf çekebilmek için kamera izni gerekiyor. + Bir çözünürlük seçin + Dosya işlemleri + Dosyalar kopyalanıyor… + Dosyalar içe aktarılıyor... + Dosyalar dışa aktarılıyor... + Dosyalar taşınıyor… + Dosyalar siliniyor… + Önce klasörler + Klasörleri listenin başında göster + Video oynatıcı ekranı otomatik döndürme + Video boyutlarına uyacak şekilde ekranı otomatik olarak döndürün + Dosya tarayıcısı bulunamadı. Lütfen bir tane yükleyin ve tekrar deneyin. + Birimi kapat + Sırala + Kes + Klasörleri eşleme + 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) + Kamera optimizasyonu + Kaliteyi maksimuma çıkarın + Gecikmeyi minimuma indirin + Otomatik + Şifreleme şifresi: + Tema + Küçük resimler + Resimlerin ve videoların küçük resimlerini göster + +%d saniye + -%d saniye + Birim ekle + Klasör seç + Birim zaten kayıtlı + Açılıyor %s: + Kaldır + Ayarlar + Tümünü seç + Parmak izini kaldır + %s. Şifreleme anahtarı yüklenemedi. + UnrecoverableKeyException + %s gizli, sadece birimin yolunu unutmak mı yoksa tüm İÇERİĞİNİ SİLMEK mi istiyorsunuz? + Sadece unut + Birimi sil + Birimi DroidFS dahili deposunda saklayın + Hata: dosya zaten mevcut + Birimin yolunu seçin: + Birimin adını girin: + Birim yolu + Birim adı + Birim şifresini girin: + Şifreyi tekrarlayın: + Şifre (doğrulama) + şifre hash değeri kaydedildi + Kaydedilmiş birim yok, + düğmesini tıklayarak biraz ekleyin + Parmak izi kimlik doğrulaması kullanılamaz: %s. + tuş kilidi güvenli değil + uygun donanım bulunamadı + donanım mevcut değil + kayıtlı parmak izi yok + bilinmeyen hata + Biyometri hatası: %s + Bu seçimi tüm gizli birimlere uygula + Birimi seç + Mevcut birim parolasını girin: + Mevcut şifre + Yeni birim şifresini girin: + Yeni şifre + Yeni şifreyi tekrarlayın: + Bu özellik yalnızca Android 6.0 (Marshmallow) veya üzeri sürümlerde mevcuttur. + Paylaşılan depolamaya kopyala + Gizli bir kopya oluştur + Birim kopyalanıyor… + Aynı ada sahip bir gizli birim zaten mevcut. + PDF dökümanı + Küçük resimler için maksimum boyut + Küçük resmin yüklenebileceği maksimum dosya boyutu. Mevcut değer: %s + Boyut (KB olarak) + Geçersiz numara + Yeni birim adı: + Birim yeniden adlandırılamadı + Ekran düzenini değiştir + 1 dosya + %d dosya + 1 klasör + %d klasör + Uygulamayı başlattığınızda bu birimi açın + Varsayılan olarak açma + %d/%d seçildi + Sayısal tuş takımı düzeni + Birim parolalarını girerken sayısal tuş takımı düzeni kullanın + Birim türü: + Gocryptfs + CryFS + Gocryptfs desteği devre dışı bırakıldı + CryFS desteği devre dışı bırakıldı + Dosyalar silindi… + (%s) + (%s, salt-okunur) + (%s, erişilemez) + I/O hatası. + Mevcut şifre yerine parmak izini kullan + Birimi hatırla + Birimi aç + Lütfen mevcut bir birimi seçin + Birimin kilidi açıldı + Birimi kilitle + Kilitle + UX + Tema rengi + Uygulama teması rengini değiştirme + Siyah tema + Şifreye geri dönme + Parmak iziyle kimlik doğrulama iptal edildiğinde şifre sor + Bilinmeyen hata kodu: %d + Yapılandırma dosyası yüklenemiyor. Birimin erişilebilir olduğundan emin olun. + Yapılandırma dosyasının şifresi çözülemiyor. Lütfen şifrenizi kontrol edin. + Yapılandırma dosyasındaki dosya sistemi kimliği, bu birimi son açtığımız zamandan farklı. Bu, saldırganın dosya sistemini farklı bir sistemle değiştirdiği anlamına gelebilir. + Birim mevcut değil veya erişilemiyor. + Görev başarısız oldu: %s + Açık birimleri ortaya çıkarın + Diğer uygulamaların belge sağlayıcıları olarak açık birimlere göz atmasına izin ver + Yazma erişimi ver + Dosyaları diğer uygulamalarla açarken yazma erişimi verin + Depolama Erişim Sistemi + Dışa aktarma başarısız oldu: %s + dışa aktarılan dosya oluşturulamıyor + dosya dışa aktarılamadı + Belleğe aktarılıyor… + Diske dışarı aktarılıyor… + Mevcut çekirdeğiniz memfd_create() özelliğini desteklemiyor. Bu özellik minimum %s çekirdek sürümünü gerektirir. + Dışa aktarma yöntemi + Dosya dışa aktarma yöntemi. Açıkta kalan dosyaları paylaşmak, harici olarak açmak ve bunlara erişmek için kullanılır. + Debug + DroidFS Logcat + Logcat kaydedildi + From 967d4551c57abbc9febd5f23f6a7fd1af4118b89 Mon Sep 17 00:00:00 2001 From: solokot Date: Sun, 11 Feb 2024 21:01:50 +0300 Subject: [PATCH 10/17] Update Russian translation Signed-off-by: Hardcore Sushi --- app/src/main/res/values-ru/arrays.xml | 6 ++++++ app/src/main/res/values-ru/strings.xml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/app/src/main/res/values-ru/arrays.xml b/app/src/main/res/values-ru/arrays.xml index 92295f6..f9994dc 100644 --- a/app/src/main/res/values-ru/arrays.xml +++ b/app/src/main/res/values-ru/arrays.xml @@ -17,4 +17,10 @@ Фиолетовый Розовый + + + Автовыбор (зависит от доступной памяти) + Временный файл в хранилище (надёжно, но могут остаться следы) + Файл в памяти (безопаснее, но не всегда возможно) + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5350753..e64c7a7 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -262,4 +262,10 @@ невозможно экспортировать файл Экспорт в память… Экспорт в хранилище… + Текущее ядро не поддерживает memfd_create(). Для работы данной функции требуется версия ядра не ниже %s. + Метод экспорта + Метод экспорта файлов. Используется для обмена, открытия во внешнем приложении и доступа к открытым файлам. + Отладка + Журнал logcat DroidFS + Журнал logcat сохранён From 36e6ad99b301493d34b0a5ef678d126842a5bcbe Mon Sep 17 00:00:00 2001 From: intergalacticmonkey <141456227+intergalacticmonkey@users.noreply.github.com> Date: Thu, 23 May 2024 13:48:46 +0200 Subject: [PATCH 11/17] Fix typo in Turkish strings.xml Signed-off-by: Hardcore Sushi --- app/src/main/res/values-tr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 0740738..5476de0 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -24,7 +24,7 @@ Dışa aktarılamadı: %s Başarılı bir şekilde dışa aktarıldı ! Silinemedi: %s - Şifreler uyuşmuyor/string> + Şifreler uyuşmuyor Seçili klasör boş değil Birim oluşturualamadı. Açılamadı From 0805ebda35756795458e353c70faa6c184a0b41e Mon Sep 17 00:00:00 2001 From: sjceel <159622205+sjceel@users.noreply.github.com> Date: Wed, 29 May 2024 19:55:09 +0800 Subject: [PATCH 12/17] Add Chinese-Simplified translation Signed-off-by: Hardcore Sushi --- app/src/main/res/values-zh-rCN/arrays.xml | 20 ++ app/src/main/res/values-zh-rCN/strings.xml | 277 +++++++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 app/src/main/res/values-zh-rCN/arrays.xml create mode 100644 app/src/main/res/values-zh-rCN/strings.xml diff --git a/app/src/main/res/values-zh-rCN/arrays.xml b/app/src/main/res/values-zh-rCN/arrays.xml new file mode 100644 index 0000000..d3b5781 --- /dev/null +++ b/app/src/main/res/values-zh-rCN/arrays.xml @@ -0,0 +1,20 @@ + + + 名称 + 大小 + 日期 + 名称 (降序) + 大小 (降序) + 日期 (降序) + + + + 绿 + + + + + + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000..b3edc6f --- /dev/null +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,277 @@ + + + DroidFS + 创建加密卷 + 打开 + 创建 + 修改密码 + 密码 + 导入文件 + 导入文件夹 + 正在清点文件… + 新建文件夹 + 文件夹空 + 警告! + 你想要锁定该卷吗? + 锁了 + 取消 + 文件夹名称 + 出问题了 + 输入名称 + 文件夹创建失败 + 导入成功 + 选中文件成功导入 + 导入 %s 时失败 + 导出 %s 时失败 + 导出成功 + 删除 %s 失败 + 密码不匹配 + 选中文件夹非空 + 创建卷失败 + 打开失败 + 分享文件 + 存储空间权限被拒绝 + 没有存储权限时DroidFS无法工作 + 无法获取文件的大小 + 上一级文件夹 + 输入卷的路径 + 输入卷的名称 + 使用外部软件打开 + 确认删除%s? + 确认删除多个文件:%s + 位置: %s + 总大小: %s + 从别的卷导入 + 打开文件失败 + 卷: %s + 确认 + 取消 + 确认擦除原始文件? + 擦除失败: %s + 擦除成功! + 重命名 + 新名称: + 无法重命名: %s + 排序方法: + 操作失败,请检查你的旧密码 + 使用DroidFS加密 + 处理分享请求失败 + 无法访问这个文件夹 + 使用指纹存储 + 请使用指纹传感器 + 块大小非法(illegalBlockSizeException) + 在添加指纹后会出现此问题,重置保存的哈希值可解决此问题,随后你需要重新关联哈希与指纹 + 重置保存的哈希 + 签名/MAC验证失败。安卓密钥存储或者哈希存储已经受到修改。重置保存的哈希值可解决此问题,随后你需要重新关联哈希与指纹 + 哈希存储已经成功重置 + 正在加密并保存密码哈希 + 正在解密密码哈希 + DroidFS设置 + 浏览 + 默认排序方法 + 允许导出文件 + 允许通过系统分享菜单分享文件 + 允许其他应用打开文件 + 允许截屏 + 允许通过指纹保存密码哈希 + 加密卷管理 + 当应用切入后台时已打开的卷不再自动锁上 + 以下功能会降低安全性 + 管理非安全功能 + 打开/关闭非安全功能 + DroidFS会尽可能保证安全。但高度安全往往伴随着不便。这也是DroidFS允许你按照习惯打开/关闭非安全功能的原因。\n\n警告:这些功能会 降 低 安 全 性。在不清楚风险的情况下尽量不要使用。高度建议在启用这些功能之前阅读相关文档 + 查看非安全功能 + 打开为 + 图像 + 视频 + 音频 + 无法播放这个文件: %s + 文本 + 保存失败 + 文件已保存! + 有些更改未保存,在关闭文件之前要看看吗 + 保存 + 丢弃 + 自动换行 + 内存耗尽: 文件过大而无法读入内存 + 新建文件 + 文件名 + 无法创建文件 + 加载中… + 正在创建卷… + 正在更改密码… + 正在开启卷… + 导出文件… + 无法访问文件 + 关于 + Github + DroidFS在Github上的仓库。存有源码,文档以及BUG追踪等 + Gitea + DroidFS在Chapril Gitea站上的仓库。与Github不同, Gitea是完全的自主搭建的自由软件.存有源码,文档以及BUG追踪等 + 分享 + 导出 + 复制%s失败 + 复制成功 + 添加 + 相机 + 图片已经保存至%s + 保存图片失败 + 视频已经保存至%s + %s已经存在,覆盖吗? + %s已经存在,要合并吗? + 输入新名称 + 复制 + 移动%s失败 + 移动成功 + 输入持续时间(单位: 秒) + 检索所选路径失败 + DroidFS对于该路径并无写入权限,换一个吧 + DroidFS对于该路径并无写入权限,将会以只读方式添加卷 + DroidFS仅对可移动存储的如下路径有写入权限: + 以只读方式添加卷 + 请在该路径下使用或者使用内置存储 + 停止幻灯片放映 + 开始幻灯片放映 + 图片已经被旋转。要存储并覆盖原图吗 + 图片改动已经成功保存 + 位图压缩失败 + 写入文件失败 + 加密卷未能被识别,请检查路径 + 版本 + 错误: 密文为空 + KeyPermanentlyInvalidatedException + 看起来你添加了新的指纹。原有的密码哈希将失效 + 在启用这些功能之前请仔细阅读 + 非安全功能的说明文档 + 通过URI检索文件失败: %s + 隐藏卷 + 卷名不应当包含斜杠 + 隐藏卷存放在DroidFS的私有文件夹内,其他应用在没有Root权限的情况下无法读取隐藏卷。如果你卸载DroidFS或者在应用管理器中清除了DroidFS的数据,隐藏卷内的文件会全部消失。请提前做好备份! + 照相需要授予相机权限 + 选择分辨率 + 文件操作 + 正在复制文件… + 正在导入… + 正在导出… + 正在移动文件… + 正在擦除文件… + 文件夹优先 + 在列表顶部显示文件夹 + 视频播放器屏幕自动旋转 + 自动旋转屏幕以适应屏幕尺寸 + 未发现文件浏览器。请安装后重试 + 关闭卷 + 分类方式 + 剪切 + 显示文件夹大小 + 通过递归映射来计算文件夹的大小(如果文件夹很大,启用该功能将导致APP卡顿) + 相机优化 + 最大质量 + 最低延迟 + 自动 + 加密密文 + 主题 + 缩略图 + 展示图片与视频的缩略图 + +%d 秒 + -%d 秒 + 添加卷 + 选择文件夹 + 卷已经保存 + 正在打开%s: + 移除 + 设置 + 全选 + 移除指纹验证 + %s. 无法加载加密密钥 + UnrecoverableKeyException + %s 是隐藏的,你希望DroidFS暂时不在UI上显示该卷的入口还是希望DroidFS删除卷内的文件? + 仅删除UI上的入口 + 删除卷 + 在DroidFS的私有文件夹中存放该卷 + 出错: 文件已存在 + 选择加密卷的路径: + 输入卷的名称: + 卷的路径 + 卷的名称 + 输入卷的密码: + 再次输入密码: + 密码(确认) + 密码的哈希值已保存 + 没有保存的卷,通过点击\"+\"添加 + 指纹验证无法使用: %s + Keyguard不安全 + 未发现适用硬件 + 硬件不可用 + 无已经录入的指纹 + 未知错误 + 生物识别错误: %s + 将该设置应用到所有隐藏卷 + 选择卷 + 输入该卷的密码 + 当前密码 + 输入新密码: + 新密码 + 确认新密码: + 该功能仅在安卓6.0及以上可用 + 复制到共享存储 + 创建隐藏副本 + 正在复制卷… + 一个有相同名字的隐藏卷已经存在 + PDF文档 + 缩略图最大体积 + 允许的缩略图文件最大体积. 当前值: + 大小(KB): + 无效数字 + 新卷名: + 卷重命名失败 + 切换显示排版 + 单文件 + %d个文件 + 文件夹 + %d个文件夹 + 启动DroidFS时自动打开卷 + 不要默认打开 + 已选 %d/%d + 数字键盘布局 + 输入卷的密码时使用纯数字键盘 + 卷的类型: + Gocryptfs + CryFS + Gocrytfs支持已关闭 + CryFS支持已关闭 + 正在删除文件… + (%s) + (%s, 只读) + (%s, 不可访问) + I/O出错 + 使用指纹替代当前密码 + 记住卷 + 打开卷 + 请选择一个已存在的卷 + 卷已锁定 + 锁定卷 + 锁定 + 用户体验 + 主题色 + 更改DroidFS用户界面主题色 + 黑色主题 + 返回密码验证 + 当指纹验证取消时自动弹出密码验证 + 未知错误: %d + 无法读取配置. 请确保卷可访问 + 无法解密配置文件. 请检查你的密码 + 文件系统的ID与上一次打开时的ID不一样. 这可能意味着攻击者已经将文件系统替换 + 卷不存在或者不可访问 + 任务失败: %s + 暴露已打开的卷 + 允许其他应用软件通过DocumentsProviders接口读取卷内的文件 + 允许写入 + 允许其他应用读取和删改卷内的文件 + 存储访问框架(SAF) + 导出失败: %s + 无法创建导出文件 + 导出文件失败 + 导出至内存 + 导出至磁盘 + From d1e042c347d989cbb4624ee8cdc4e6267c070151 Mon Sep 17 00:00:00 2001 From: CyanWolf <101049163+cyanwolfg@users.noreply.github.com> Date: Wed, 29 May 2024 07:30:13 -0600 Subject: [PATCH 13/17] Update Spanish translation Signed-off-by: Hardcore Sushi --- app/src/main/res/values-es/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 751e7fb..0cb131f 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -273,4 +273,10 @@ Error al exportar el archivo Exportando a memoria… Exportar a disco… + El kernel actual no soporta memfd_create(). Esta característica requiere una versión mínima del kernel de %s. + Método de exportación + Método de exportación de archivos. Se utiliza para compartir, abrir externamente y acceder a archivos expuestos. + Depurar + Logcat de DroidFS + Logcat guardado From bd60e626350c9ff7b2ceb091af284df524891581 Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Mon, 3 Jun 2024 16:11:27 +0200 Subject: [PATCH 14/17] Allow importing from ClipData --- app/build.gradle | 10 +++--- .../droidfs/explorers/BaseExplorerActivity.kt | 21 ++++++++++++ .../droidfs/explorers/ExplorerActivity.kt | 28 +++------------ .../droidfs/explorers/ExplorerActivityDrop.kt | 34 ++++++++++++++----- 4 files changed, 56 insertions(+), 37 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7d557a0..f9e85e9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -104,17 +104,17 @@ android { dependencies { implementation project(":libpdfviewer:app") - implementation 'androidx.core:core-ktx:1.12.0' - implementation "androidx.appcompat:appcompat:1.6.1" + implementation 'androidx.core:core-ktx:1.13.1' + implementation "androidx.appcompat:appcompat:1.7.0" implementation "androidx.constraintlayout:constraintlayout:2.1.4" - def lifecycle_version = "2.6.2" + def lifecycle_version = "2.8.1" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version" - implementation "androidx.sqlite:sqlite-ktx:2.3.1" + implementation "androidx.sqlite:sqlite-ktx:2.4.0" implementation "androidx.preference:preference-ktx:1.2.1" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" - implementation 'com.google.android.material:material:1.9.0' + implementation 'com.google.android.material:material:1.12.0' implementation 'com.github.bumptech.glide:glide:4.16.0' implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05" 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 7c2c2e2..1955915 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt @@ -268,6 +268,27 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene .show() } + protected fun createNewFile(callback: (Long) -> Unit) { + EditTextDialog(this, R.string.enter_file_name) { + if (it.isEmpty()) { + Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show() + createNewFile(callback) + } else { + val filePath = PathUtils.pathJoin(currentDirectoryPath, it) + val handleID = encryptedVolume.openFileWriteMode(filePath) + if (handleID == -1L) { + CustomAlertDialogBuilder(this, theme) + .setTitle(R.string.error) + .setMessage(R.string.file_creation_failed) + .setPositiveButton(R.string.ok, null) + .show() + } else { + callback(handleID) + } + } + }.show() + } + private fun setVolumeNameTitle() { titleText.text = getString(R.string.volume, volumeName) } diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt index 6f0e619..abdca09 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt @@ -189,9 +189,11 @@ class ExplorerActivity : BaseExplorerActivity() { pickImportDirectory.launch(null) } "createFile" -> { - EditTextDialog(this, R.string.enter_file_name) { - createNewFile(it) - }.show() + createNewFile { + encryptedVolume.closeFile(it) + setCurrentPath(currentDirectoryPath) + invalidateOptionsMenu() + } } "createFolder" -> { openDialogCreateFolder() @@ -219,26 +221,6 @@ class ExplorerActivity : BaseExplorerActivity() { cancelItemAction() } - private fun createNewFile(fileName: String){ - if (fileName.isEmpty()) { - Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show() - } else { - val filePath = PathUtils.pathJoin(currentDirectoryPath, fileName) - val handleID = encryptedVolume.openFileWriteMode(filePath) - if (handleID == -1L) { - CustomAlertDialogBuilder(this, theme) - .setTitle(R.string.error) - .setMessage(R.string.file_creation_failed) - .setPositiveButton(R.string.ok, null) - .show() - } else { - encryptedVolume.closeFile(handleID) - setCurrentPath(currentDirectoryPath) - invalidateOptionsMenu() - } - } - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.explorer, menu) val result = super.onCreateOptionsMenu(menu) diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt index c2d9080..5c5f4cc 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt @@ -9,6 +9,8 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder +import java.nio.CharBuffer +import java.nio.charset.StandardCharsets class ExplorerActivityDrop : BaseExplorerActivity() { @@ -30,15 +32,15 @@ class ExplorerActivityDrop : BaseExplorerActivity() { return when (item.itemId) { R.id.validate -> { val extras = intent.extras - val errorMsg: String? = if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)) { + val success = if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)) { when (intent.action) { Intent.ACTION_SEND -> { val uri = IntentUtils.getParcelableExtra(intent, Intent.EXTRA_STREAM) if (uri == null) { - getString(R.string.share_intent_parsing_failed) + false } else { importFilesFromUris(listOf(uri), ::onImported) - null + true } } Intent.ACTION_SEND_MULTIPLE -> { @@ -50,20 +52,34 @@ class ExplorerActivityDrop : BaseExplorerActivity() { } if (uris != null) { importFilesFromUris(uris, ::onImported) - null + true } else { - getString(R.string.share_intent_parsing_failed) + false } } - else -> getString(R.string.share_intent_parsing_failed) + else -> false } + } else if ((intent.clipData?.itemCount ?: 0) > 0) { + val byteBuffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(intent.clipData!!.getItemAt(0).text)) + val byteArray = ByteArray(byteBuffer.remaining()) + byteBuffer.get(byteArray) + val size = byteArray.size.toLong() + createNewFile { + var offset = 0L + while (offset < size) { + offset += encryptedVolume.write(it, offset, byteArray, offset, size-offset) + } + encryptedVolume.closeFile(it) + onImported() + } + true } else { - getString(R.string.share_intent_parsing_failed) + false } - errorMsg?.let { + if (!success) { CustomAlertDialogBuilder(this, theme) .setTitle(R.string.error) - .setMessage(it) + .setMessage(R.string.share_intent_parsing_failed) .setPositiveButton(R.string.ok, null) .show() } From 7c72c4e8294a8c25dc799a940f8142c12a759c8f Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Thu, 6 Jun 2024 21:08:11 +0200 Subject: [PATCH 15/17] Update dependencies & Fix build --- BUILD.md | 33 ++++++++++-------------- app/build.gradle | 2 +- app/ffmpeg/build.sh | 2 +- app/src/main/native/libmux.c | 6 ++--- build.gradle | 13 +++------- gradle/wrapper/gradle-wrapper.properties | 5 ++-- libpdfviewer | 2 +- settings.gradle | 6 +++++ 8 files changed, 32 insertions(+), 37 deletions(-) diff --git a/BUILD.md b/BUILD.md index ddf9961..3a28ed1 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,18 +1,21 @@ # Introduction DroidFS relies on modified versions of the original encrypted filesystems programs to open volumes. [CryFS](https://github.com/cryfs/cryfs) is written in C++ while [gocryptfs](https://github.com/rfjakob/gocryptfs) is written in [Go](https://golang.org). Thus, building DroidFS requires the compilation of native code. However, for the sake of simplicity, the application has been designed in a modular way: you can build a version of DroidFS that supports both Gocryptfs and CryFS, or only one of the two. -Moreover, DroidFS aims to be accessible to as many people as possible. If you encounter any problems or need help with the build, feel free to open an issue, a discussion, or contact me by [email](mailto:hardcore.sushi@disroot.org) or on [Matrix](https://matrix.org): @hardcoresushi:matrix.underworld.fr +Moreover, DroidFS aims to be accessible to as many people as possible. If you encounter any problems or need help with the build, feel free to open an issue, a discussion, or contact me (currently the main developer) by [email](mailto:gh@arkensys.dedyn.io) or on [Matrix](https://matrix.org): @hardcoresushi:matrix.underworld.fr # Setup + +The following two steps assume you're using a Debian-based Linux distribution. Package names might be similar for other distributions. Don't hesitate to ask if you're having trouble with this. + Install required packages: ``` -$ sudo apt-get install openjdk-11-jdk-headless build-essential pkg-config git gnupg2 wget apksigner +$ sudo apt-get install openjdk-17-jdk-headless build-essential pkg-config git gnupg2 wget apksigner npm ``` -You also need to manually install the [Android SDK](https://developer.android.com/studio/index.html#command-tools) and the [Android Native Development Kit (NDK)](https://developer.android.com/ndk/downloads) (r23 versions are recommended). +You also need to manually install the [Android SDK](https://developer.android.com/studio/index.html#command-tools) and the [Android Native Development Kit (NDK)](https://github.com/android/ndk/wiki/Unsupported-Downloads#r25c) version `25.2.9519653` (r25c). libcryfs cannot be built with newer NDK versions at the moment due to compatibility issues with [boost](https://www.boost.org). If you succeed in building it with a more recent version of NDK, please report it. -If you want a support for Gocryptfs volumes, you must install [Go](https://golang.org/doc/install) and libssl: +If you want a support for Gocryptfs volumes, you need to install [Go](https://golang.org/doc/install): ``` -$ sudo apt-get install golang-go libssl-dev +$ sudo apt-get install golang-go ``` The code should be authenticated before being built. To verify the signatures, you will need my PGP key: ``` @@ -45,16 +48,16 @@ $ git clone --depth=1 https://git.ffmpeg.org/ffmpeg.git If you want Gocryptfs support, you need to download OpenSSL: ``` $ cd ../libgocryptfs -$ wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz +$ wget https://openssl.org/source/openssl-3.3.1.tar.gz ``` Verify OpenSSL signature: ``` -$ wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz.asc -$ gpg --verify openssl-1.1.1w.tar.gz.asc openssl-1.1.1w.tar.gz +$ https://openssl.org/source/openssl-3.3.1.tar.gz.asc +$ gpg --verify openssl-3.3.1.tar.gz.asc openssl-3.3.1.tar.gz ``` Continue **ONLY** if the signature is **VALID**. ``` -$ tar -xzf openssl-1.1.1w.tar.gz +$ tar -xzf openssl-3.3.1.tar.gz ``` If you want CryFS support, initialize libcryfs: ``` @@ -62,14 +65,6 @@ $ cd app/libcryfs $ git submodule update --depth=1 --init ``` -To be able to open PDF files internally, [pdf.js](https://github.com/mozilla/pdf.js) must be downloaded: -``` -$ mkdir libpdfviewer/app/pdfjs-dist && cd libpdfviewer/app/pdfjs-dist -$ wget https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.8.162.tgz -$ tar xf pdfjs-dist-3.8.162.tgz package/build/pdf.min.js package/build/pdf.worker.min.js -$ mv package/build . && rm pdfjs-dist-3.8.162.tgz -``` - # Build Retrieve your Android NDK installation path, usually something like `/home/\/Android/SDK/ndk/\`. Then, make it available in your shell: ``` @@ -84,8 +79,8 @@ $ ./build.sh ffmpeg This step is only required if you want Gocryptfs support. ``` $ cd app/libgocryptfs -$ OPENSSL_PATH="./openssl-1.1.1w" ./build.sh - ``` +$ ANDROID_NDK_ROOT="$ANDROID_NDK_HOME" OPENSSL_PATH="./openssl-3.3.1" ./build.sh +``` ## Compile APKs Gradle build libgocryptfs and libcryfs by default. diff --git a/app/build.gradle b/app/build.gradle index f9e85e9..f21c47f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,7 +21,7 @@ if (hasProperty("nosplits")) { android { compileSdk 34 - ndkVersion "26.1.10909125" + ndkVersion '25.2.9519653' namespace "sushi.hardcore.droidfs" compileOptions { diff --git a/app/ffmpeg/build.sh b/app/ffmpeg/build.sh index c798e6a..e63ea62 100755 --- a/app/ffmpeg/build.sh +++ b/app/ffmpeg/build.sh @@ -74,7 +74,7 @@ else --disable-appkit \ --disable-alsa \ --disable-debug \ - >/dev/null && + && make -j $(nproc --all) >/dev/null) && mkdir -p build/$1/libavformat build/$1/libavcodec build/$1/libavutil && cp $FFMPEG_DIR/libavformat/*.h $FFMPEG_DIR/libavformat/libavformat.so build/$1/libavformat && diff --git a/app/src/main/native/libmux.c b/app/src/main/native/libmux.c index eb5c0d5..fb9ba75 100644 --- a/app/src/main/native/libmux.c +++ b/app/src/main/native/libmux.c @@ -40,7 +40,7 @@ struct Muxer { jmethodID seek_method_id; }; -int write_packet(void* opaque, uint8_t* buff, int buff_size) { +int write_packet(void* opaque, const uint8_t* buff, int buff_size) { struct Muxer* muxer = opaque; JNIEnv *env; (*muxer->jvm)->GetEnv(muxer->jvm, (void **) &env, JNI_VERSION_1_6); @@ -108,8 +108,8 @@ Java_sushi_hardcore_droidfs_video_1recording_FFmpegMuxer_addVideoTrack(JNIEnv *e stream->codecpar->height = height; stream->codecpar->format = AV_PIX_FMT_YUVJ420P; stream->time_base = (AVRational) {1, frame_rate}; - uint8_t* matrix = av_stream_new_side_data(stream, AV_PKT_DATA_DISPLAYMATRIX, sizeof(int32_t) * 9); - av_display_rotation_set((int32_t *) matrix, orientation_hint); + AVPacketSideData *side_data_packet = av_packet_side_data_new(&stream->codecpar->coded_side_data, &stream->codecpar->nb_coded_side_data, AV_PKT_DATA_DISPLAYMATRIX, sizeof(int32_t) * 9, 0); + av_display_rotation_set((int32_t *) side_data_packet->data, orientation_hint); return stream->index; } diff --git a/build.gradle b/build.gradle index a8173a6..eff89ba 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,6 @@ -buildscript { - ext.kotlin_version = '1.9.22' - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:8.2.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } +plugins { + id("com.android.application") version '8.4.0' apply false + id("org.jetbrains.kotlin.android") version "1.9.24" apply false } allprojects { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7c2c8c2..98c5b0f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,8 @@ +#Sun Jun 02 15:50:33 UTC 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=591855b517fc635b9e04de1d05d5e76ada3f89f5fc76f87978d1b245b4f69225 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/libpdfviewer b/libpdfviewer index 2296593..4863138 160000 --- a/libpdfviewer +++ b/libpdfviewer @@ -1 +1 @@ -Subproject commit 22965932759f232328810eadf3f02671b5c6ff99 +Subproject commit 48631380a7a8b5f0932078e2b643e06c3f433890 diff --git a/settings.gradle b/settings.gradle index ee71f79..348790a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,8 @@ +pluginManagement { + repositories { + google() + mavenCentral() + } +} include ':app', ':libpdfviewer:app' rootProject.name = "DroidFS" \ No newline at end of file From 4b002c7b242a11ef2f76230d3f535eb52bbd33fe Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Fri, 7 Jun 2024 16:07:20 +0200 Subject: [PATCH 16/17] Fix SecurityException when importing from exposed volume --- .../sushi/hardcore/droidfs/explorers/ExplorerActivity.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt index abdca09..a552c52 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt @@ -68,7 +68,11 @@ class ExplorerActivity : BaseExplorerActivity() { private val pickFiles = registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { uris -> if (uris != null) { for (uri in uris) { - contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + try { + contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + } catch (e: SecurityException) { + e.printStackTrace() + } } importFilesFromUris(uris) { onImportComplete(uris) From d44601f69fb9b5472a105491fa6fae95d12d0516 Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Mon, 10 Jun 2024 23:39:52 +0200 Subject: [PATCH 17/17] Restore upstream video player controls & Update dependencies --- app/build.gradle | 6 +- app/libcryfs | 2 +- .../androidx/camera/video/originals/README.md | 2 +- .../hardcore/droidfs/SettingsActivity.kt | 11 +- .../droidfs/widgets/DoubleTapPlayerView.kt | 58 +------ .../main/res/layout/exo_center_controls.xml | 30 ---- .../res/layout/exo_player_control_view.xml | 148 ------------------ 7 files changed, 15 insertions(+), 242 deletions(-) delete mode 100644 app/src/main/res/layout/exo_center_controls.xml delete mode 100644 app/src/main/res/layout/exo_player_control_view.xml diff --git a/app/build.gradle b/app/build.gradle index f21c47f..557056c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -118,14 +118,14 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.16.0' implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05" - def media3_version = "1.1.1" + def media3_version = "1.3.1" implementation "androidx.media3:media3-exoplayer:$media3_version" - implementation 'androidx.media3:media3-ui:1.1.1' + implementation "androidx.media3:media3-ui:$media3_version" implementation "androidx.media3:media3-datasource:$media3_version" implementation "androidx.concurrent:concurrent-futures:1.1.0" - def camerax_version = "1.3.0-rc02" + def camerax_version = "1.3.3" implementation "androidx.camera:camera-camera2:$camerax_version" implementation "androidx.camera:camera-lifecycle:$camerax_version" implementation "androidx.camera:camera-view:$camerax_version" diff --git a/app/libcryfs b/app/libcryfs index 6388eaf..0398d48 160000 --- a/app/libcryfs +++ b/app/libcryfs @@ -1 +1 @@ -Subproject commit 6388eaf433a4196f10389921d5e346c90ee3d793 +Subproject commit 0398d48b0963c01092976c5c7012b02327e564f0 diff --git a/app/src/main/java/androidx/camera/video/originals/README.md b/app/src/main/java/androidx/camera/video/originals/README.md index 9e619dd..d7b0df5 100644 --- a/app/src/main/java/androidx/camera/video/originals/README.md +++ b/app/src/main/java/androidx/camera/video/originals/README.md @@ -5,7 +5,7 @@ Create the `new` folder if needed: mkdir -p new ``` -Put new CameraX files from upstream in the `new` folder. +Put the new CameraX files from upstream (`androidx.camera.video.Recorder`, `androidx.camera.video.Recording`, `androidx.camera.video.PendingRecording` and `androidx.camera.video.internal.encoder.EncoderImpl`) in the `new` folder. Perform the 3 way merge: ``` diff --git a/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt index 2e9bf8a..35cfa6b 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt @@ -1,5 +1,6 @@ package sushi.hardcore.droidfs +import android.app.ActivityOptions import android.content.Intent import android.content.SharedPreferences import android.os.Build @@ -90,9 +91,15 @@ class SettingsActivity : BaseActivity() { private fun refreshTheme() { with(requireActivity()) { - startActivity(Intent(this, SettingsActivity::class.java)) + startActivity( + Intent(this, SettingsActivity::class.java), + ActivityOptions.makeCustomAnimation( + this, + android.R.anim.fade_in, + android.R.anim.fade_out + ).toBundle() + ) finish() - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/widgets/DoubleTapPlayerView.kt b/app/src/main/java/sushi/hardcore/droidfs/widgets/DoubleTapPlayerView.kt index 7816702..13d1b9e 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/widgets/DoubleTapPlayerView.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/widgets/DoubleTapPlayerView.kt @@ -2,23 +2,13 @@ package sushi.hardcore.droidfs.widgets import android.annotation.SuppressLint import android.content.Context -import android.content.res.Configuration import android.media.session.PlaybackState import android.os.Handler import android.os.Looper import android.util.AttributeSet import android.view.GestureDetector import android.view.MotionEvent -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.FrameLayout -import android.widget.LinearLayout -import androidx.core.view.GestureDetectorCompat -import androidx.core.view.updateLayoutParams -import androidx.core.view.updatePadding import androidx.media3.ui.PlayerView -import sushi.hardcore.droidfs.R class DoubleTapPlayerView @JvmOverloads constructor( context: Context, @@ -75,22 +65,7 @@ class DoubleTapPlayerView @JvmOverloads constructor( handler.postDelayed(stopDoubleTap, 700) } } - private val gestureDetector = GestureDetectorCompat(context, gestureListener) - private val density by lazy { - context.resources.displayMetrics.density - } - private val originalExoIconPaddingBottom by lazy { - resources.getDimension(R.dimen.exo_icon_padding_bottom) - } - private val originalExoIconSize by lazy { - resources.getDimension(R.dimen.exo_icon_size) - } - - init { - if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { - handleOrientationChange(Configuration.ORIENTATION_LANDSCAPE) - } - } + private val gestureDetector = GestureDetector(context, gestureListener) @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { @@ -135,35 +110,4 @@ class DoubleTapPlayerView @JvmOverloads constructor( } } } - - private fun updateButtonSize(orientation: Int) { - val size = (if (orientation == Configuration.ORIENTATION_LANDSCAPE) 45*density else originalExoIconSize).toInt() - listOf(R.id.exo_prev, R.id.exo_rew_with_amount, R.id.exo_play_pause, R.id.exo_ffwd_with_amount, R.id.exo_next).forEach { - findViewById(it).updateLayoutParams { - width = size - height = size - } - } - // fix text vertical alignment inside icons - val paddingBottom = (if (orientation == Configuration.ORIENTATION_LANDSCAPE) 15*density else originalExoIconPaddingBottom).toInt() - listOf(R.id.exo_rew_with_amount, R.id.exo_ffwd_with_amount).forEach { - findViewById