From 7c2f87109a8db7579587958ba0e3e3bdc21eabc2 Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Fri, 30 Sep 2022 21:22:37 +0200 Subject: [PATCH] Allow to open & create volumes without remembering --- .../sushi/hardcore/droidfs/BaseActivity.kt | 11 +- .../sushi/hardcore/droidfs/CameraActivity.kt | 3 +- .../droidfs/ChangePasswordActivity.kt | 5 +- .../sushi/hardcore/droidfs/ConstValues.kt | 4 +- .../hardcore/droidfs/FingerprintProtector.kt | 4 +- .../sushi/hardcore/droidfs/LoadingTask.kt | 4 +- .../sushi/hardcore/droidfs/MainActivity.kt | 323 ++++-------------- .../hardcore/droidfs/SettingsActivity.kt | 2 +- .../droidfs/{SavedVolume.kt => VolumeData.kt} | 8 +- .../sushi/hardcore/droidfs/VolumeDatabase.kt | 65 ++-- .../sushi/hardcore/droidfs/VolumeOpener.kt | 201 +++++++++++ .../droidfs/adapters/VolumeAdapter.kt | 10 +- .../droidfs/add_volume/AddVolumeActivity.kt | 67 +++- .../add_volume/CreateVolumeFragment.kt | 88 +++-- .../droidfs/add_volume/SelectPathFragment.kt | 261 ++++++++------ .../droidfs/explorers/BaseExplorerActivity.kt | 5 +- .../droidfs/explorers/ExplorerActivity.kt | 11 +- .../droidfs/explorers/ExplorerActivityDrop.kt | 11 +- .../droidfs/explorers/ExplorerActivityPick.kt | 5 +- .../droidfs/explorers/ExplorerRouter.kt | 29 ++ .../file_viewers/FileViewerActivity.kt | 3 +- .../droidfs/filesystems/CryfsVolume.kt | 10 +- .../droidfs/filesystems/EncryptedVolume.kt | 4 +- .../droidfs/filesystems/GocryptfsVolume.kt | 45 ++- .../hardcore/droidfs/util/IntentUtils.kt | 21 ++ app/src/main/native/gocryptfs_jni.c | 47 ++- .../res/layout/activity_explorer_drop.xml | 26 -- ...er_base.xml => activity_explorer_pick.xml} | 0 .../main/res/layout/fragment_select_path.xml | 10 +- app/src/main/res/values/strings.xml | 3 + 30 files changed, 774 insertions(+), 512 deletions(-) rename app/src/main/java/sushi/hardcore/droidfs/{SavedVolume.kt => VolumeData.kt} (77%) create mode 100644 app/src/main/java/sushi/hardcore/droidfs/VolumeOpener.kt create mode 100644 app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerRouter.kt create mode 100644 app/src/main/java/sushi/hardcore/droidfs/util/IntentUtils.kt delete mode 100644 app/src/main/res/layout/activity_explorer_drop.xml rename app/src/main/res/layout/{activity_explorer_base.xml => activity_explorer_pick.xml} (100%) diff --git a/app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt index 39bd0ad..1398e41 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt @@ -18,7 +18,7 @@ open class BaseActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this) if (shouldCheckTheme && applyCustomTheme) { - themeValue = sharedPrefs.getString("theme", ConstValues.DEFAULT_THEME_VALUE)!! + themeValue = sharedPrefs.getString(ConstValues.THEME_VALUE_KEY, ConstValues.DEFAULT_THEME_VALUE)!! when (themeValue) { "black_green" -> setTheme(R.style.BlackGreen) "dark_red" -> setTheme(R.style.DarkRed) @@ -47,13 +47,4 @@ open class BaseActivity: AppCompatActivity() { recreate() } } - - inline fun getParcelableExtra(intent: Intent, name: String): T? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent.getParcelableExtra(name, T::class.java) - } else { - @Suppress("Deprecation") - intent.getParcelableExtra(name) - } - } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt index 5c8d34c..fe8b4c3 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.launch import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider import sushi.hardcore.droidfs.databinding.ActivityCameraBinding import sushi.hardcore.droidfs.filesystems.EncryptedVolume +import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.video_recording.SeekableWriter import sushi.hardcore.droidfs.video_recording.VideoCapture @@ -95,7 +96,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener { binding = ActivityCameraBinding.inflate(layoutInflater) setContentView(binding.root) supportActionBar?.hide() - encryptedVolume = getParcelableExtra(intent, "volume")!! + encryptedVolume = IntentUtils.getParcelableExtra(intent, "volume")!! outputDirectory = intent.getStringExtra("path")!! if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { diff --git a/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt index ab4131e..5acb8ed 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt @@ -14,6 +14,7 @@ import sushi.hardcore.droidfs.databinding.ActivityChangePasswordBinding import sushi.hardcore.droidfs.filesystems.CryfsVolume 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.widgets.CustomAlertDialogBuilder @@ -22,7 +23,7 @@ import java.util.* class ChangePasswordActivity: BaseActivity() { private lateinit var binding: ActivityChangePasswordBinding - private lateinit var volume: SavedVolume + private lateinit var volume: VolumeData private lateinit var volumeDatabase: VolumeDatabase private var fingerprintProtector: FingerprintProtector? = null private var usfFingerprint: Boolean = false @@ -32,7 +33,7 @@ class ChangePasswordActivity: BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - volume = getParcelableExtra(intent, "volume")!! + volume = IntentUtils.getParcelableExtra(intent, "volume")!! binding = ActivityChangePasswordBinding.inflate(layoutInflater) setContentView(binding.root) title = getString(R.string.change_password) diff --git a/app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt b/app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt index 8924e01..cbe96bb 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt @@ -4,7 +4,6 @@ import android.net.Uri import java.io.File object ConstValues { - const val CREATOR = "DroidFS" const val VOLUME_DATABASE_NAME = "SavedVolumes" const val CRYFS_LOCAL_STATE_DIR = "cryfsLocalState" const val SORT_ORDER_KEY = "sort_order" @@ -13,6 +12,9 @@ object ConstValues { const val IO_BUFF_SIZE = 16384 const val SLIDESHOW_DELAY: Long = 4000 const val DEFAULT_THEME_VALUE = "dark_green" + const val THEME_VALUE_KEY = "theme" + const val DEFAULT_VOLUME_KEY = "default_volume" + const val REMEMBER_VOLUME_KEY = "remember_volume" const val THUMBNAIL_MAX_SIZE_KEY = "thumbnail_max_size" const val DEFAULT_THUMBNAIL_MAX_SIZE = 10_000L const val PIN_PASSWORDS_KEY = "pin_passwords" diff --git a/app/src/main/java/sushi/hardcore/droidfs/FingerprintProtector.kt b/app/src/main/java/sushi/hardcore/droidfs/FingerprintProtector.kt index 05bb457..9461886 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/FingerprintProtector.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/FingerprintProtector.kt @@ -133,7 +133,7 @@ class FingerprintProtector private constructor( private lateinit var cipher: Cipher private var isCipherReady = false private var cipherActionMode: Int? = null - private lateinit var volume: SavedVolume + private lateinit var volume: VolumeData private lateinit var dataToProcess: ByteArray private fun resetHashStorage() { @@ -207,7 +207,7 @@ class FingerprintProtector private constructor( .show() } - fun savePasswordHash(volume: SavedVolume, plainText: ByteArray) { + fun savePasswordHash(volume: VolumeData, plainText: ByteArray) { this.volume = volume val biometricPromptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle(activity.getString(R.string.encrypt_action_description)) diff --git a/app/src/main/java/sushi/hardcore/droidfs/LoadingTask.kt b/app/src/main/java/sushi/hardcore/droidfs/LoadingTask.kt index 4454740..74294d7 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/LoadingTask.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/LoadingTask.kt @@ -1,6 +1,6 @@ package sushi.hardcore.droidfs -import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.FragmentActivity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -8,7 +8,7 @@ import kotlinx.coroutines.withContext import sushi.hardcore.droidfs.databinding.DialogLoadingBinding import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder -abstract class LoadingTask(val activity: AppCompatActivity, themeValue: String, loadingMessageResId: Int) { +abstract class LoadingTask(val activity: FragmentActivity, themeValue: String, loadingMessageResId: Int) { private val dialogLoading = CustomAlertDialogBuilder(activity, themeValue) .setView( DialogLoadingBinding.inflate(activity.layoutInflater).apply { diff --git a/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt index 03ae61e..dc7667e 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt @@ -1,19 +1,15 @@ package sushi.hardcore.droidfs -import android.annotation.SuppressLint import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.net.Uri -import android.os.Build import android.os.Bundle import android.os.IBinder -import android.text.InputType import android.view.Menu import android.view.MenuItem import android.view.View -import android.view.WindowManager import android.widget.Toast import androidx.activity.addCallback import androidx.activity.result.contract.ActivityResultContracts @@ -21,45 +17,31 @@ import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.coroutines.launch +import sushi.hardcore.droidfs.ConstValues.DEFAULT_VOLUME_KEY import sushi.hardcore.droidfs.adapters.VolumeAdapter import sushi.hardcore.droidfs.add_volume.AddVolumeActivity import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider import sushi.hardcore.droidfs.databinding.ActivityMainBinding import sushi.hardcore.droidfs.databinding.DialogDeleteVolumeBinding -import sushi.hardcore.droidfs.databinding.DialogOpenVolumeBinding -import sushi.hardcore.droidfs.explorers.ExplorerActivity -import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop -import sushi.hardcore.droidfs.explorers.ExplorerActivityPick +import sushi.hardcore.droidfs.explorers.ExplorerRouter import sushi.hardcore.droidfs.file_operations.FileOperationService import sushi.hardcore.droidfs.filesystems.EncryptedVolume -import sushi.hardcore.droidfs.util.ObjRef +import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.util.PathUtils -import sushi.hardcore.droidfs.util.WidgetUtil import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.EditTextDialog import java.io.File -import java.util.* class MainActivity : BaseActivity(), VolumeAdapter.Listener { - - companion object { - const val DEFAULT_VOLUME_KEY = "default_volume" - } - private lateinit var binding: ActivityMainBinding private lateinit var volumeDatabase: VolumeDatabase private lateinit var volumeAdapter: VolumeAdapter - private var fingerprintProtector: FingerprintProtector? = null - private var usfFingerprint: Boolean = false + private lateinit var volumeOpener: VolumeOpener private var usfKeepOpen: Boolean = false - private var defaultVolumeName: String? = null private var addVolume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - when (result.resultCode) { - AddVolumeActivity.RESULT_VOLUME_ADDED -> onVolumeAdded() - AddVolumeActivity.RESULT_HASH_STORAGE_RESET -> { - volumeAdapter.refresh() - binding.textNoVolumes.visibility = View.GONE - } + if ((explorerRouter.pickMode || explorerRouter.dropMode) && result.resultCode != AddVolumeActivity.RESULT_USER_BACK) { + setResult(result.resultCode, result.data) // forward result + finish() } } private var changePasswordPosition: Int? = null @@ -71,8 +53,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { onDirectoryPicked(uri) } private lateinit var fileOperationService: FileOperationService - private var pickMode = false - private var dropMode = false + private lateinit var explorerRouter: ExplorerRouter private var shouldCloseVolume = true // used when launched to pick file from another volume override fun onCreate(savedInstanceState: Bundle?) { @@ -98,14 +79,13 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { } .show() } - pickMode = intent.action == "pick" - dropMode = (intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_SEND_MULTIPLE) && intent.extras != null + explorerRouter = ExplorerRouter(this, intent) volumeDatabase = VolumeDatabase(this) volumeAdapter = VolumeAdapter( this, volumeDatabase, - !pickMode && !dropMode, - !dropMode, + !explorerRouter.pickMode && !explorerRouter.dropMode, + !explorerRouter.dropMode, this, ) binding.recyclerViewVolumes.adapter = volumeAdapter @@ -113,24 +93,22 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { if (volumeAdapter.volumes.isEmpty()) { binding.textNoVolumes.visibility = View.VISIBLE } - if (pickMode) { + if (explorerRouter.pickMode) { title = getString(R.string.select_volume) - binding.fab.visibility = View.GONE - } else { - binding.fab.setOnClickListener { - addVolume.launch(Intent(this, AddVolumeActivity::class.java)) - } + } + binding.fab.setOnClickListener { + addVolume.launch(Intent(this, AddVolumeActivity::class.java).also { + if (explorerRouter.dropMode || explorerRouter.pickMode) { + IntentUtils.forwardIntent(intent, it) + shouldCloseVolume = false + } + }) } usfKeepOpen = sharedPrefs.getBoolean("usf_keep_open", false) - usfFingerprint = sharedPrefs.getBoolean("usf_fingerprint", false) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - fingerprintProtector = FingerprintProtector.new(this, themeValue, volumeDatabase) - } - defaultVolumeName = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null) - defaultVolumeName?.let { name -> + volumeOpener = VolumeOpener(this) + volumeOpener.defaultVolumeName?.let { name -> try { - val (position, volume) = volumeAdapter.volumes.withIndex().first { it.value.name == name } - openVolume(volume, position) + openVolume(volumeAdapter.volumes.first { it.name == name }) } catch (e: NoSuchElementException) { unsetDefaultVolume() } @@ -139,8 +117,9 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { if (volumeAdapter.selectedItems.isNotEmpty()) { unselectAll() } else { - if (pickMode) + if (explorerRouter.pickMode) { shouldCloseVolume = false + } isEnabled = false onBackPressedDispatcher.onBackPressed() } @@ -158,10 +137,15 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { override fun onStart() { super.onStart() // refresh theme if changed in SettingsActivity - val newThemeValue = sharedPrefs.getString("theme", ConstValues.DEFAULT_THEME_VALUE)!! + val newThemeValue = sharedPrefs.getString(ConstValues.THEME_VALUE_KEY, ConstValues.DEFAULT_THEME_VALUE)!! onThemeChanged(newThemeValue) + volumeOpener.themeValue = newThemeValue + volumeAdapter.refresh() + if (volumeAdapter.volumes.isNotEmpty()) { + binding.textNoVolumes.visibility = View.GONE + } // refresh this in case another instance of MainActivity changes its value - defaultVolumeName = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null) + volumeOpener.defaultVolumeName = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null) } override fun onSelectionChanged(size: Int) { @@ -172,9 +156,9 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { } } - override fun onVolumeItemClick(volume: SavedVolume, position: Int) { + override fun onVolumeItemClick(volume: VolumeData, position: Int) { if (volumeAdapter.selectedItems.isEmpty()) - openVolume(volume, position) + openVolume(volume) else invalidateOptionsMenu() } @@ -183,14 +167,6 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { invalidateOptionsMenu() } - private fun onVolumeAdded() { - volumeAdapter.apply { - volumes = volumeDatabase.getVolumes() - notifyItemInserted(volumes.size) - } - binding.textNoVolumes.visibility = View.GONE - } - private fun unselectAll(notifyChange: Boolean = true) { volumeAdapter.unSelectAll(notifyChange) invalidateOptionsMenu() @@ -203,7 +179,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { invalidateOptionsMenu() } - private fun removeVolumes(volumes: List, i: Int = 0, doDeleteVolumeContent: Boolean? = null) { + private fun removeVolumes(volumes: List, i: Int = 0, doDeleteVolumeContent: Boolean? = null) { if (i < volumes.size) { if (volumes[i].isHidden) { if (doDeleteVolumeContent == null) { @@ -258,15 +234,16 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { remove(DEFAULT_VOLUME_KEY) apply() } - defaultVolumeName = null + volumeOpener.defaultVolumeName = null } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { android.R.id.home -> { - if (pickMode || dropMode) { - if (pickMode) + if (explorerRouter.pickMode || explorerRouter.dropMode) { + if (explorerRouter.pickMode) { shouldCloseVolume = false + } finish() } else { unselectAll() @@ -323,7 +300,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { DocumentFile.fromFile(File(volume.name)), DocumentFile.fromFile(filesDir), ) { - SavedVolume(volume.shortName, true, volume.type, volume.encryptedHash, volume.iv) + VolumeData(volume.shortName, true, volume.type, volume.encryptedHash, volume.iv) } } } @@ -345,7 +322,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.main_activity, menu) - menu.findItem(R.id.settings).isVisible = !pickMode && !dropMode + menu.findItem(R.id.settings).isVisible = !explorerRouter.pickMode && !explorerRouter.dropMode val isSelecting = volumeAdapter.selectedItems.isNotEmpty() menu.findItem(R.id.select_all).isVisible = isSelecting menu.findItem(R.id.remove).isVisible = isSelecting @@ -359,7 +336,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { menu.findItem(R.id.change_password).isVisible = onlyOneAndWriteable menu.findItem(R.id.remove_default_open).isVisible = onlyOneSelected && - volumeAdapter.volumes[volumeAdapter.selectedItems.first()].name == defaultVolumeName + volumeAdapter.volumes[volumeAdapter.selectedItems.first()].name == volumeOpener.defaultVolumeName with(menu.findItem(R.id.copy)) { isVisible = onlyOneSelected if (isVisible) { @@ -371,7 +348,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { } } menu.findItem(R.id.rename).isVisible = onlyOneAndWriteable - supportActionBar?.setDisplayHomeAsUpEnabled(isSelecting || pickMode || dropMode) + supportActionBar?.setDisplayHomeAsUpEnabled(isSelecting || explorerRouter.pickMode || explorerRouter.dropMode) return true } @@ -394,7 +371,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { dstRootDirectory.name?.let { name -> val path = PathUtils.getFullPathFromTreeUri(dstRootDirectory.uri, this) if (path == null) null - else SavedVolume( + else VolumeData( PathUtils.pathJoin(path, name), false, volume.type, @@ -406,7 +383,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { } } - private fun copyVolume(srcDocumentFile: DocumentFile, dstDocumentFile: DocumentFile, getResultVolume: (DocumentFile) -> SavedVolume?) { + private fun copyVolume(srcDocumentFile: DocumentFile, dstDocumentFile: DocumentFile, getResultVolume: (DocumentFile) -> VolumeData?) { lifecycleScope.launch { val result = fileOperationService.copyVolume(srcDocumentFile, dstDocumentFile) when { @@ -417,7 +394,11 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { result.dstRootDirectory?.let { getResultVolume(it)?.let { volume -> volumeDatabase.saveVolume(volume) - onVolumeAdded() + volumeAdapter.apply { + volumes = volumeDatabase.getVolumes() + notifyItemInserted(volumes.size) + } + binding.textNoVolumes.visibility = View.GONE Toast.makeText(this@MainActivity, R.string.copy_success, Toast.LENGTH_SHORT).show() } } @@ -433,7 +414,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { } } - private fun renameVolume(volume: SavedVolume, position: Int) { + private fun renameVolume(volume: VolumeData, position: Int) { with (EditTextDialog(this, R.string.new_volume_name) { newName -> val srcPath = File(volume.getFullPath(filesDir.path)) val dstPath = File(srcPath.parent, newName).canonicalFile @@ -453,12 +434,12 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { if (success) { volumeDatabase.renameVolume(volume.name, newDBName) unselect(position) - if (volume.name == defaultVolumeName) { + if (volume.name == volumeOpener.defaultVolumeName) { with (sharedPrefs.edit()) { putString(DEFAULT_VOLUME_KEY, newDBName) apply() } - defaultVolumeName = newDBName + volumeOpener.defaultVolumeName = newDBName } } else { Toast.makeText(this, R.string.volume_rename_failed, Toast.LENGTH_SHORT).show() @@ -469,193 +450,35 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener { } } - @SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23 - private fun openVolume(volume: SavedVolume, position: Int) { - if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE && BuildConfig.GOCRYPTFS_DISABLED) { - Toast.makeText(this, R.string.gocryptfs_disabled, Toast.LENGTH_SHORT).show() - return - } else if (volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE && BuildConfig.CRYFS_DISABLED) { - Toast.makeText(this, R.string.cryfs_disabled, Toast.LENGTH_SHORT).show() - return - } - var askForPassword = true - fingerprintProtector?.let { fingerprintProtector -> - volume.encryptedHash?.let { encryptedHash -> - volume.iv?.let { iv -> - askForPassword = false - fingerprintProtector.listener = object : FingerprintProtector.Listener { - override fun onHashStorageReset() { - volumeAdapter.refresh() - } - override fun onPasswordHashDecrypted(hash: ByteArray) { - object : LoadingTask(this@MainActivity, themeValue, R.string.loading_msg_open) { - override suspend fun doTask(): EncryptedVolume? { - val encryptedVolume = EncryptedVolume.init(volume, filesDir.path, null, hash, null) - Arrays.fill(hash, 0) - return encryptedVolume - } - }.startTask(lifecycleScope) { encryptedVolume -> - if (encryptedVolume == null) { - CustomAlertDialogBuilder(this@MainActivity, themeValue) - .setTitle(R.string.open_volume_failed) - .setMessage(R.string.open_failed_hash_msg) - .setPositiveButton(R.string.ok, null) - .show() - } else { - startExplorer(encryptedVolume, volume.shortName) - } - } - } - override fun onPasswordHashSaved() {} - override fun onFailed(pending: Boolean) { - if (!pending) { - askForPassword(volume, position) - } - } - } - fingerprintProtector.loadPasswordHash(volume.shortName, encryptedHash, iv) + private fun openVolume(volume: VolumeData) { + volumeOpener.openVolume(volume, true, object : VolumeOpener.VolumeOpenerCallbacks { + override fun onHashStorageReset() { + volumeAdapter.refresh() + } + + override fun onVolumeOpened(encryptedVolume: EncryptedVolume, volumeShortName: String) { + startActivity(explorerRouter.getExplorerIntent(encryptedVolume, volumeShortName)) + if (explorerRouter.pickMode) { + shouldCloseVolume = false + } + if (explorerRouter.dropMode || explorerRouter.pickMode) { + finish() } } - } - if (askForPassword) - askForPassword(volume, position) + }) } - private fun onPasswordSubmitted(volume: SavedVolume, position: Int, dialogBinding: DialogOpenVolumeBinding) { - if (dialogBinding.checkboxDefaultOpen.isChecked xor (defaultVolumeName == volume.name)) { - with (sharedPrefs.edit()) { - defaultVolumeName = if (dialogBinding.checkboxDefaultOpen.isChecked) { - putString(DEFAULT_VOLUME_KEY, volume.name) - volume.name - } else { - remove(DEFAULT_VOLUME_KEY) - null - } - apply() - } - } - // openVolumeWithPassword is responsible for wiping the password - openVolumeWithPassword( - volume, - position, - WidgetUtil.encodeEditTextContent(dialogBinding.editPassword), - dialogBinding.checkboxSavePassword.isChecked, - ) - } - - private fun askForPassword(volume: SavedVolume, position: Int, savePasswordHash: Boolean = false) { - val dialogBinding = DialogOpenVolumeBinding.inflate(layoutInflater) - if (!usfFingerprint || fingerprintProtector == null || volume.encryptedHash != null) { - dialogBinding.checkboxSavePassword.visibility = View.GONE - } else { - dialogBinding.checkboxSavePassword.isChecked = savePasswordHash - } - dialogBinding.checkboxDefaultOpen.isChecked = defaultVolumeName == volume.name - val dialog = CustomAlertDialogBuilder(this, themeValue) - .setTitle(getString(R.string.open_dialog_title, volume.shortName)) - .setView(dialogBinding.root) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.open) { _, _ -> - onPasswordSubmitted(volume, position, dialogBinding) - } - .create() - dialogBinding.editPassword.apply { - setOnEditorActionListener { _, _, _ -> - dialog.dismiss() - onPasswordSubmitted(volume, position, dialogBinding) - true - } - if (sharedPrefs.getBoolean(ConstValues.PIN_PASSWORDS_KEY, false)) { - inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD - } - } - dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) - dialog.show() - } - - private fun openVolumeWithPassword(volume: SavedVolume, position: Int, password: ByteArray, savePasswordHash: Boolean) { - val returnedHash: ObjRef? = if (savePasswordHash) { - ObjRef(null) - } else { - null - } - object : LoadingTask(this, themeValue, R.string.loading_msg_open) { - override suspend fun doTask(): EncryptedVolume? { - val encryptedVolume = EncryptedVolume.init(volume, filesDir.path, password, null, returnedHash) - Arrays.fill(password, 0) - return encryptedVolume - } - }.startTask(lifecycleScope) { encryptedVolume -> - if (encryptedVolume == null) { - CustomAlertDialogBuilder(this, themeValue) - .setTitle(R.string.open_volume_failed) - .setMessage(R.string.open_volume_failed_msg) - .setPositiveButton(R.string.ok) { _, _ -> - askForPassword(volume, position, savePasswordHash) - } - .show() - } else { - val fingerprintProtector = fingerprintProtector - @SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23 - if (savePasswordHash && returnedHash != null && fingerprintProtector != null) { - fingerprintProtector.listener = object : FingerprintProtector.Listener { - override fun onHashStorageReset() { - volumeAdapter.refresh() - } - override fun onPasswordHashDecrypted(hash: ByteArray) {} - override fun onPasswordHashSaved() { - Arrays.fill(returnedHash.value!!, 0) - volumeAdapter.onVolumeChanged(position) - startExplorer(encryptedVolume, volume.shortName) - } - private var isClosed = false - override fun onFailed(pending: Boolean) { - if (!isClosed) { - encryptedVolume.close() - isClosed = true - } - Arrays.fill(returnedHash.value!!, 0) - } - } - fingerprintProtector.savePasswordHash(volume, returnedHash.value!!) - } else { - startExplorer(encryptedVolume, volume.shortName) - } - } - } - } - - private fun startExplorer(encryptedVolume: EncryptedVolume, volumeShortName: String) { - var explorerIntent: Intent? = null - if (dropMode) { //import via android share menu - explorerIntent = Intent(this, ExplorerActivityDrop::class.java) - explorerIntent.action = intent.action //forward action - explorerIntent.putExtras(intent.extras!!) //forward extras - } else if (pickMode) { - explorerIntent = Intent(this, ExplorerActivityPick::class.java) - explorerIntent.putExtra("destinationVolume", getParcelableExtra(intent, "volume")!!) - explorerIntent.flags = Intent.FLAG_ACTIVITY_FORWARD_RESULT - } - if (explorerIntent == null) { - explorerIntent = Intent(this, ExplorerActivity::class.java) //default opening - } - explorerIntent.putExtra("volume", encryptedVolume) - explorerIntent.putExtra("volume_name", volumeShortName) - startActivity(explorerIntent) - if (pickMode) - shouldCloseVolume = false - if (dropMode || pickMode) - finish() + override fun onResume() { + super.onResume() + shouldCloseVolume = true } override fun onStop() { super.onStop() - if (pickMode && !usfKeepOpen) { + if (explorerRouter.pickMode && !usfKeepOpen && shouldCloseVolume) { + IntentUtils.getParcelableExtra(intent, "volume")?.close() + RestrictedFileProvider.wipeAll(this) finish() - if (shouldCloseVolume) { - getParcelableExtra(intent, "volume")?.close() - RestrictedFileProvider.wipeAll(this) - } } } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt index 1892cbf..7995834 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt @@ -82,7 +82,7 @@ class SettingsActivity : BaseActivity() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.root_preferences, rootKey) sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext()) - findPreference("theme")?.setOnPreferenceChangeListener { _, newValue -> + findPreference(ConstValues.THEME_VALUE_KEY)?.setOnPreferenceChangeListener { _, newValue -> (activity as BaseActivity).onThemeChanged(newValue as String) true } diff --git a/app/src/main/java/sushi/hardcore/droidfs/SavedVolume.kt b/app/src/main/java/sushi/hardcore/droidfs/VolumeData.kt similarity index 77% rename from app/src/main/java/sushi/hardcore/droidfs/SavedVolume.kt rename to app/src/main/java/sushi/hardcore/droidfs/VolumeData.kt index a0c35d4..3f22476 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/SavedVolume.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/VolumeData.kt @@ -5,7 +5,7 @@ import android.os.Parcelable import sushi.hardcore.droidfs.util.PathUtils import java.io.File -class SavedVolume(val name: String, val isHidden: Boolean = false, val type: Byte, var encryptedHash: ByteArray? = null, var iv: ByteArray? = null): Parcelable { +class VolumeData(val name: String, val isHidden: Boolean = false, val type: Byte, var encryptedHash: ByteArray? = null, var iv: ByteArray? = null): Parcelable { constructor(parcel: Parcel) : this( parcel.readString()!!, @@ -48,9 +48,9 @@ class SavedVolume(val name: String, val isHidden: Boolean = false, val type: Byt const val VOLUMES_DIRECTORY = "volumes" @JvmField - val CREATOR = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel) = SavedVolume(parcel) - override fun newArray(size: Int) = arrayOfNulls(size) + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = VolumeData(parcel) + override fun newArray(size: Int) = arrayOfNulls(size) } fun getHiddenVolumeFullPath(filesDir: String, name: String): String { diff --git a/app/src/main/java/sushi/hardcore/droidfs/VolumeDatabase.kt b/app/src/main/java/sushi/hardcore/droidfs/VolumeDatabase.kt index 1c57388..6ad279a 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/VolumeDatabase.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/VolumeDatabase.kt @@ -2,6 +2,7 @@ package sushi.hardcore.droidfs import android.content.ContentValues import android.content.Context +import android.database.Cursor import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import android.util.Log @@ -18,7 +19,7 @@ class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, Co const val COLUMN_HASH = "hash" const val COLUMN_IV = "iv" - private fun contentValuesFromVolume(volume: SavedVolume): ContentValues { + private fun contentValuesFromVolume(volume: VolumeData): ContentValues { val contentValues = ContentValues() contentValues.put(COLUMN_NAME, volume.name) contentValues.put(COLUMN_HIDDEN, volume.isHidden) @@ -38,7 +39,7 @@ class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, Co "$COLUMN_IV BLOB" + ");" ) - File(context.filesDir, SavedVolume.VOLUMES_DIRECTORY).mkdir() + File(context.filesDir, VolumeData.VOLUMES_DIRECTORY).mkdir() } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { @@ -49,7 +50,7 @@ class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, Co }, null, null) // Moving hidden volumes to the "volumes" directory - if (File(context.filesDir, SavedVolume.VOLUMES_DIRECTORY).mkdir()) { + if (File(context.filesDir, VolumeData.VOLUMES_DIRECTORY).mkdir()) { val cursor = db.query( TABLE_NAME, arrayOf(COLUMN_NAME), @@ -68,7 +69,7 @@ class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, Co ) ).renameTo( File( - SavedVolume( + VolumeData( volumeName, true, EncryptedVolume.GOCRYPTFS_VOLUME_TYPE @@ -82,37 +83,55 @@ class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, Co } } - fun isVolumeSaved(volumeName: String, isHidden: Boolean): Boolean { - val cursor = readableDatabase.query(TABLE_NAME, - arrayOf(COLUMN_NAME), "$COLUMN_NAME=? AND $COLUMN_HIDDEN=?", + private fun extractVolumeData(cursor: Cursor): VolumeData { + return VolumeData( + cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME)), + cursor.getShort(cursor.getColumnIndexOrThrow(COLUMN_HIDDEN)) == 1.toShort(), + cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_TYPE))[0], + cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_HASH)), + cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_IV)) + ) + } + + private fun getVolumeCursor(volumeName: String, isHidden: Boolean): Cursor { + return readableDatabase.query( + TABLE_NAME, null, + "$COLUMN_NAME=? AND $COLUMN_HIDDEN=?", arrayOf(volumeName, (if (isHidden) 1 else 0).toString()), null, null, null ) + } + + fun getVolume(volumeName: String, isHidden: Boolean): VolumeData? { + val cursor = getVolumeCursor(volumeName, isHidden) + val volumeData = if (cursor.moveToNext()) { + extractVolumeData(cursor) + } else { + null + } + cursor.close() + return volumeData + } + + fun isVolumeSaved(volumeName: String, isHidden: Boolean): Boolean { + val cursor = getVolumeCursor(volumeName, isHidden) val result = cursor.count > 0 cursor.close() return result } - fun saveVolume(volume: SavedVolume): Boolean { + fun saveVolume(volume: VolumeData): Boolean { if (!isVolumeSaved(volume.name, volume.isHidden)) { - return (writableDatabase.insert(TABLE_NAME, null, contentValuesFromVolume(volume)) == 0.toLong()) + return (writableDatabase.insert(TABLE_NAME, null, contentValuesFromVolume(volume)) >= 0.toLong()) } return false } - fun getVolumes(): List { - val list: MutableList = ArrayList() + fun getVolumes(): List { + val list: MutableList = ArrayList() val cursor = readableDatabase.rawQuery("SELECT * FROM $TABLE_NAME", null) while (cursor.moveToNext()){ - list.add( - SavedVolume( - cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME)), - cursor.getShort(cursor.getColumnIndexOrThrow(COLUMN_HIDDEN)) == 1.toShort(), - cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_TYPE))[0], - cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_HASH)), - cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_IV)) - ) - ) + list.add(extractVolumeData(cursor)) } cursor.close() return list @@ -130,14 +149,14 @@ class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, Co return isHashSaved } - fun addHash(volume: SavedVolume): Boolean { + fun addHash(volume: VolumeData): Boolean { return writableDatabase.update(TABLE_NAME, contentValuesFromVolume(volume), "$COLUMN_NAME=?", arrayOf(volume.name)) > 0 } - fun removeHash(volume: SavedVolume): Boolean { + fun removeHash(volume: VolumeData): Boolean { return writableDatabase.update( TABLE_NAME, contentValuesFromVolume( - SavedVolume( + VolumeData( volume.name, volume.isHidden, volume.type, diff --git a/app/src/main/java/sushi/hardcore/droidfs/VolumeOpener.kt b/app/src/main/java/sushi/hardcore/droidfs/VolumeOpener.kt new file mode 100644 index 0000000..873e723 --- /dev/null +++ b/app/src/main/java/sushi/hardcore/droidfs/VolumeOpener.kt @@ -0,0 +1,201 @@ +package sushi.hardcore.droidfs + +import android.annotation.SuppressLint +import android.os.Build +import android.text.InputType +import android.view.View +import android.view.WindowManager +import android.widget.Toast +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.lifecycleScope +import androidx.preference.PreferenceManager +import sushi.hardcore.droidfs.ConstValues.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.widgets.CustomAlertDialogBuilder +import java.util.* + +class VolumeOpener( + private val activity: FragmentActivity, +) { + interface VolumeOpenerCallbacks { + fun onHashStorageReset() {} + fun onVolumeOpened(encryptedVolume: EncryptedVolume, volumeShortName: String) + } + + private val volumeDatabase = VolumeDatabase(activity) + private var fingerprintProtector: FingerprintProtector? = null + private val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(activity) + var themeValue = sharedPrefs.getString(ConstValues.THEME_VALUE_KEY, ConstValues.DEFAULT_THEME_VALUE)!! + var defaultVolumeName: String? = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null) + + init { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + fingerprintProtector = FingerprintProtector.new(activity, themeValue, volumeDatabase) + } + } + + @SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23 + fun openVolume(volume: VolumeData, isVolumeSaved: Boolean, callbacks: VolumeOpenerCallbacks) { + if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE && BuildConfig.GOCRYPTFS_DISABLED) { + Toast.makeText(activity, R.string.gocryptfs_disabled, Toast.LENGTH_SHORT).show() + return + } else if (volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE && BuildConfig.CRYFS_DISABLED) { + Toast.makeText(activity, R.string.cryfs_disabled, Toast.LENGTH_SHORT).show() + return + } + var askForPassword = true + fingerprintProtector?.let { fingerprintProtector -> + volume.encryptedHash?.let { encryptedHash -> + volume.iv?.let { iv -> + askForPassword = false + fingerprintProtector.listener = object : FingerprintProtector.Listener { + override fun onHashStorageReset() { + callbacks.onHashStorageReset() + } + override fun onPasswordHashDecrypted(hash: ByteArray) { + object : LoadingTask(activity, themeValue, R.string.loading_msg_open) { + override suspend fun doTask(): EncryptedVolume? { + val encryptedVolume = EncryptedVolume.init(volume, activity.filesDir.path, null, hash, null) + Arrays.fill(hash, 0) + return encryptedVolume + } + }.startTask(activity.lifecycleScope) { encryptedVolume -> + if (encryptedVolume == null) { + CustomAlertDialogBuilder(activity, themeValue) + .setTitle(R.string.open_volume_failed) + .setMessage(R.string.open_failed_hash_msg) + .setPositiveButton(R.string.ok, null) + .show() + } else { + callbacks.onVolumeOpened(encryptedVolume, volume.shortName) + } + } + } + override fun onPasswordHashSaved() {} + override fun onFailed(pending: Boolean) { + if (!pending) { + askForPassword(volume, isVolumeSaved, callbacks) + } + } + } + fingerprintProtector.loadPasswordHash(volume.shortName, encryptedHash, iv) + } + } + } + if (askForPassword) { + askForPassword(volume, isVolumeSaved, callbacks) + } + } + + private fun onPasswordSubmitted(volume: VolumeData, isVolumeSaved: Boolean, dialogBinding: DialogOpenVolumeBinding, callbacks: VolumeOpenerCallbacks) { + if (dialogBinding.checkboxDefaultOpen.isChecked xor (defaultVolumeName == volume.name)) { + with (sharedPrefs.edit()) { + defaultVolumeName = if (dialogBinding.checkboxDefaultOpen.isChecked) { + putString(DEFAULT_VOLUME_KEY, volume.name) + volume.name + } else { + remove(DEFAULT_VOLUME_KEY) + null + } + apply() + } + } + // openVolumeWithPassword is responsible for wiping the password + openVolumeWithPassword( + volume, + WidgetUtil.encodeEditTextContent(dialogBinding.editPassword), + isVolumeSaved, + dialogBinding.checkboxSavePassword.isChecked, + callbacks, + ) + } + + private fun askForPassword(volume: VolumeData, isVolumeSaved: Boolean, callbacks: VolumeOpenerCallbacks, savePasswordHash: Boolean = false) { + val dialogBinding = DialogOpenVolumeBinding.inflate(activity.layoutInflater) + if (isVolumeSaved) { + if (!sharedPrefs.getBoolean("usf_fingerprint", false) || fingerprintProtector == null || volume.encryptedHash != null) { + dialogBinding.checkboxSavePassword.visibility = View.GONE + } else { + dialogBinding.checkboxSavePassword.isChecked = savePasswordHash + } + dialogBinding.checkboxDefaultOpen.isChecked = defaultVolumeName == volume.name + } else { + dialogBinding.checkboxSavePassword.visibility = View.GONE + dialogBinding.checkboxDefaultOpen.visibility = View.GONE + } + val dialog = CustomAlertDialogBuilder(activity, themeValue) + .setTitle(activity.getString(R.string.open_dialog_title, volume.shortName)) + .setView(dialogBinding.root) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.open) { _, _ -> + onPasswordSubmitted(volume, isVolumeSaved, dialogBinding, callbacks) + } + .create() + dialogBinding.editPassword.apply { + setOnEditorActionListener { _, _, _ -> + dialog.dismiss() + onPasswordSubmitted(volume, isVolumeSaved, dialogBinding, callbacks) + true + } + if (sharedPrefs.getBoolean(ConstValues.PIN_PASSWORDS_KEY, false)) { + inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD + } + } + dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) + dialog.show() + } + + private fun openVolumeWithPassword(volume: VolumeData, password: ByteArray, isVolumeSaved: Boolean, savePasswordHash: Boolean, callbacks: VolumeOpenerCallbacks) { + val returnedHash: ObjRef? = if (savePasswordHash) { + ObjRef(null) + } else { + null + } + object : LoadingTask(activity, themeValue, R.string.loading_msg_open) { + override suspend fun doTask(): EncryptedVolume? { + val encryptedVolume = EncryptedVolume.init(volume, activity.filesDir.path, password, null, returnedHash) + Arrays.fill(password, 0) + return encryptedVolume + } + }.startTask(activity.lifecycleScope) { encryptedVolume -> + if (encryptedVolume == null) { + CustomAlertDialogBuilder(activity, themeValue) + .setTitle(R.string.open_volume_failed) + .setMessage(R.string.open_volume_failed_msg) + .setPositiveButton(R.string.ok) { _, _ -> + askForPassword(volume, isVolumeSaved, callbacks, savePasswordHash) + } + .show() + } else { + val fingerprintProtector = fingerprintProtector + @SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23 + if (savePasswordHash && returnedHash != null && fingerprintProtector != null) { + fingerprintProtector.listener = object : FingerprintProtector.Listener { + override fun onHashStorageReset() { + callbacks.onHashStorageReset() + } + override fun onPasswordHashDecrypted(hash: ByteArray) {} + override fun onPasswordHashSaved() { + Arrays.fill(returnedHash.value!!, 0) + callbacks.onVolumeOpened(encryptedVolume, volume.shortName) + } + private var isClosed = false + override fun onFailed(pending: Boolean) { + if (!isClosed) { + encryptedVolume.close() + isClosed = true + } + Arrays.fill(returnedHash.value!!, 0) + } + } + fingerprintProtector.savePasswordHash(volume, returnedHash.value!!) + } else { + callbacks.onVolumeOpened(encryptedVolume, volume.shortName) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt b/app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt index 953be7b..c8ce808 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt @@ -10,7 +10,7 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import sushi.hardcore.droidfs.R -import sushi.hardcore.droidfs.SavedVolume +import sushi.hardcore.droidfs.VolumeData import sushi.hardcore.droidfs.VolumeDatabase import sushi.hardcore.droidfs.filesystems.EncryptedVolume @@ -20,9 +20,9 @@ class VolumeAdapter( private val allowSelection: Boolean, private val showReadOnly: Boolean, private val listener: Listener, -) : SelectableAdapter(listener::onSelectionChanged) { +) : SelectableAdapter(listener::onSelectionChanged) { private val inflater: LayoutInflater = LayoutInflater.from(context) - lateinit var volumes: List + lateinit var volumes: List init { reloadVolumes() @@ -30,11 +30,11 @@ class VolumeAdapter( interface Listener { fun onSelectionChanged(size: Int) - fun onVolumeItemClick(volume: SavedVolume, position: Int) + fun onVolumeItemClick(volume: VolumeData, position: Int) fun onVolumeItemLongClick() } - override fun getItems(): List { + override fun getItems(): List { return volumes } diff --git a/app/src/main/java/sushi/hardcore/droidfs/add_volume/AddVolumeActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/add_volume/AddVolumeActivity.kt index 4765b28..06f9546 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/add_volume/AddVolumeActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/add_volume/AddVolumeActivity.kt @@ -2,31 +2,39 @@ package sushi.hardcore.droidfs.add_volume import android.os.Bundle import android.view.MenuItem -import sushi.hardcore.droidfs.BaseActivity -import sushi.hardcore.droidfs.ConstValues -import sushi.hardcore.droidfs.R +import sushi.hardcore.droidfs.* +import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider import sushi.hardcore.droidfs.databinding.ActivityAddVolumeBinding +import sushi.hardcore.droidfs.explorers.ExplorerRouter +import sushi.hardcore.droidfs.filesystems.EncryptedVolume +import sushi.hardcore.droidfs.util.IntentUtils class AddVolumeActivity: BaseActivity() { companion object { - const val RESULT_VOLUME_ADDED = 1 - const val RESULT_HASH_STORAGE_RESET = 2 + const val RESULT_USER_BACK = 10 } private lateinit var binding: ActivityAddVolumeBinding + private lateinit var explorerRouter: ExplorerRouter + private lateinit var volumeOpener: VolumeOpener + private var usfKeepOpen = false + var shouldCloseVolume = true // used when launched to pick file from another volume override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityAddVolumeBinding.inflate(layoutInflater) setContentView(binding.root) supportActionBar?.setDisplayHomeAsUpEnabled(true) + usfKeepOpen = sharedPrefs.getBoolean("usf_keep_open", false) + explorerRouter = ExplorerRouter(this, intent) + volumeOpener = VolumeOpener(this) if (savedInstanceState == null) { supportFragmentManager .beginTransaction() .add( R.id.fragment_container, - SelectPathFragment.newInstance(themeValue), + SelectPathFragment.newInstance(themeValue, explorerRouter.pickMode), ) .commit() } @@ -36,12 +44,21 @@ class AddVolumeActivity: BaseActivity() { if (item.itemId == android.R.id.home) { if (supportFragmentManager.backStackEntryCount > 0) supportFragmentManager.popBackStack() - else + else { + setResult(RESULT_USER_BACK) + shouldCloseVolume = false finish() + } } return super.onOptionsItemSelected(item) } + override fun onBackPressed() { + setResult(RESULT_USER_BACK) + shouldCloseVolume = false + super.onBackPressed() + } + fun onFragmentLoaded(selectPathFragment: Boolean) { title = getString( if (selectPathFragment) { @@ -52,16 +69,27 @@ class AddVolumeActivity: BaseActivity() { ) } - fun onSelectedAlreadySavedVolume() { + fun startExplorer(encryptedVolume: EncryptedVolume, volumeShortName: String) { + startActivity(explorerRouter.getExplorerIntent(encryptedVolume, volumeShortName)) + shouldCloseVolume = false finish() } - fun onVolumeAdded(hashStorageReset: Boolean) { - setResult(if (hashStorageReset) RESULT_HASH_STORAGE_RESET else RESULT_VOLUME_ADDED) - finish() + fun onVolumeSelected(volume: VolumeData, rememberVolume: Boolean) { + if (rememberVolume) { + setResult(RESULT_USER_BACK) + shouldCloseVolume = false + finish() + } else { + volumeOpener.openVolume(volume, false, object : VolumeOpener.VolumeOpenerCallbacks { + override fun onVolumeOpened(encryptedVolume: EncryptedVolume, volumeShortName: String) { + startExplorer(encryptedVolume, volumeShortName) + } + }) + } } - fun createVolume(volumePath: String, isHidden: Boolean) { + fun createVolume(volumePath: String, isHidden: Boolean, rememberVolume: Boolean) { supportFragmentManager .beginTransaction() .replace( @@ -69,6 +97,7 @@ class AddVolumeActivity: BaseActivity() { themeValue, volumePath, isHidden, + rememberVolume, sharedPrefs.getBoolean(ConstValues.PIN_PASSWORDS_KEY, false), sharedPrefs.getBoolean("usf_fingerprint", false), ) @@ -76,4 +105,18 @@ class AddVolumeActivity: BaseActivity() { .addToBackStack(null) .commit() } + + override fun onStart() { + super.onStart() + shouldCloseVolume = true + } + + override fun onStop() { + super.onStop() + if (explorerRouter.pickMode && !usfKeepOpen && shouldCloseVolume) { + IntentUtils.getParcelableExtra(intent, "volume")?.close() + RestrictedFileProvider.wipeAll(this) + finish() + } + } } \ No newline at end of file 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 8cc20b6..0cb5b55 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 @@ -23,13 +23,13 @@ import sushi.hardcore.droidfs.util.WidgetUtil import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import java.io.File import java.util.* -import kotlin.collections.ArrayList class CreateVolumeFragment: Fragment() { companion object { private const val KEY_THEME_VALUE = "theme" private const val KEY_VOLUME_PATH = "path" private const val KEY_IS_HIDDEN = "hidden" + private const val KEY_REMEMBER_VOLUME = "remember" private const val KEY_PIN_PASSWORDS = ConstValues.PIN_PASSWORDS_KEY private const val KEY_USF_FINGERPRINT = "fingerprint" @@ -37,6 +37,7 @@ class CreateVolumeFragment: Fragment() { themeValue: String, volumePath: String, isHidden: Boolean, + rememberVolume: Boolean, pinPasswords: Boolean, usfFingerprint: Boolean, ): CreateVolumeFragment { @@ -45,6 +46,7 @@ class CreateVolumeFragment: Fragment() { putString(KEY_THEME_VALUE, themeValue) putString(KEY_VOLUME_PATH, volumePath) putBoolean(KEY_IS_HIDDEN, isHidden) + putBoolean(KEY_REMEMBER_VOLUME, rememberVolume) putBoolean(KEY_PIN_PASSWORDS, pinPasswords) putBoolean(KEY_USF_FINGERPRINT, usfFingerprint) } @@ -57,6 +59,7 @@ class CreateVolumeFragment: Fragment() { private val volumeTypes = ArrayList(2) private lateinit var volumePath: String private var isHiddenVolume: Boolean = false + private var rememberVolume: Boolean = false private var usfFingerprint: Boolean = false private lateinit var volumeDatabase: VolumeDatabase private var fingerprintProtector: FingerprintProtector? = null @@ -76,6 +79,7 @@ class CreateVolumeFragment: Fragment() { arguments.getString(KEY_THEME_VALUE)?.let { themeValue = it } volumePath = arguments.getString(KEY_VOLUME_PATH)!! isHiddenVolume = arguments.getBoolean(KEY_IS_HIDDEN) + rememberVolume = arguments.getBoolean(KEY_REMEMBER_VOLUME) usfFingerprint = arguments.getBoolean(KEY_USF_FINGERPRINT) arguments.getBoolean(KEY_PIN_PASSWORDS) } @@ -83,7 +87,7 @@ class CreateVolumeFragment: Fragment() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { fingerprintProtector = FingerprintProtector.new(requireActivity(), themeValue, volumeDatabase) } - if (!usfFingerprint || fingerprintProtector == null) { + if (!rememberVolume || !usfFingerprint || fingerprintProtector == null) { binding.checkboxSavePassword.visibility = View.GONE } if (!BuildConfig.GOCRYPTFS_DISABLED) { @@ -140,21 +144,6 @@ class CreateVolumeFragment: Fragment() { (activity as AddVolumeActivity).onFragmentLoaded(false) } - private fun saveVolume(success: Boolean, volumeType: Byte): SavedVolume? { - return if (success) { - val volumeName = if (isHiddenVolume) File(volumePath).name else volumePath - val volume = SavedVolume(volumeName, isHiddenVolume, volumeType) - volumeDatabase.apply { - if (isVolumeSaved(volumeName, isHiddenVolume)) // cleaning old saved path - removeVolume(volumeName) - saveVolume(volume) - } - volume - } else { - null - } - } - private fun createVolume() { val password = WidgetUtil.encodeEditTextContent(binding.editPassword) val passwordConfirm = WidgetUtil.encodeEditTextContent(binding.editPasswordConfirm) @@ -169,50 +158,73 @@ class CreateVolumeFragment: Fragment() { } else { null } - object: LoadingTask(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) { - override suspend fun doTask(): SavedVolume? { + val encryptedVolume = if (rememberVolume) { + null + } else { + ObjRef(null) + } + object: LoadingTask(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) { + private fun generateResult(success: Boolean, volumeType: Byte): Byte { + return if (success) { + volumeType + } else { + -1 + } + } + + override suspend fun doTask(): Byte { val volumeFile = File(volumePath) if (!volumeFile.exists()) volumeFile.mkdirs() - val volume = if (volumeTypes[binding.spinnerVolumeType.selectedItemPosition] == resources.getString(R.string.gocryptfs)) { + val result = if (volumeTypes[binding.spinnerVolumeType.selectedItemPosition] == resources.getString(R.string.gocryptfs)) { val xchacha = when (binding.spinnerCipher.selectedItemPosition) { 0 -> 0 1 -> 1 else -> -1 } - saveVolume(GocryptfsVolume.createVolume( + generateResult(GocryptfsVolume.createAndOpenVolume( volumePath, password, false, xchacha, - GocryptfsVolume.ScryptDefaultLogN, - ConstValues.CREATOR, returnedHash?.apply { value = ByteArray(GocryptfsVolume.KeyLen) }?.value, + encryptedVolume, ), EncryptedVolume.GOCRYPTFS_VOLUME_TYPE) } else { - saveVolume(CryfsVolume.create( + generateResult(CryfsVolume.create( volumePath, CryfsVolume.getLocalStateDir(activity.filesDir.path), password, returnedHash, - resources.getStringArray(R.array.cryfs_encryption_ciphers)[binding.spinnerCipher.selectedItemPosition] + resources.getStringArray(R.array.cryfs_encryption_ciphers)[binding.spinnerCipher.selectedItemPosition], + encryptedVolume, ), EncryptedVolume.CRYFS_VOLUME_TYPE) } Arrays.fill(password, 0) - return volume + return result } - }.startTask(lifecycleScope) { volume -> - if (volume == null) { + }.startTask(lifecycleScope) { result -> + if (result.compareTo(-1) == 0) { CustomAlertDialogBuilder(requireContext(), themeValue) .setTitle(R.string.error) .setMessage(R.string.create_volume_failed) .setPositiveButton(R.string.ok, null) .show() } else { + val volumeName = if (isHiddenVolume) File(volumePath).name else volumePath + val volume = VolumeData(volumeName, isHiddenVolume, result) + var isVolumeSaved = false + volumeDatabase.apply { + if (isVolumeSaved(volumeName, isHiddenVolume)) // cleaning old saved path + removeVolume(volumeName) + if (rememberVolume) { + isVolumeSaved = saveVolume(volume) + } + } @SuppressLint("NewApi") // if fingerprintProtector is null checkboxSavePassword is hidden - if (binding.checkboxSavePassword.isChecked && returnedHash != null) { + if (isVolumeSaved && binding.checkboxSavePassword.isChecked && returnedHash != null) { fingerprintProtector!!.let { it.listener = object : FingerprintProtector.Listener { override fun onHashStorageReset() { @@ -223,24 +235,32 @@ class CreateVolumeFragment: Fragment() { override fun onPasswordHashDecrypted(hash: ByteArray) {} // shouldn't happen here override fun onPasswordHashSaved() { Arrays.fill(returnedHash.value!!, 0) - onVolumeCreated() + onVolumeCreated(encryptedVolume?.value, volume.shortName) } override fun onFailed(pending: Boolean) { if (!pending) { Arrays.fill(returnedHash.value!!, 0) - onVolumeCreated() + onVolumeCreated(encryptedVolume?.value, volume.shortName) } } } it.savePasswordHash(volume, returnedHash.value!!) } - } else onVolumeCreated() + } else { + onVolumeCreated(encryptedVolume?.value, volume.shortName) + } } } } } - private fun onVolumeCreated() { - (activity as AddVolumeActivity).onVolumeAdded(hashStorageReset) + private fun onVolumeCreated(encryptedVolume: EncryptedVolume?, volumeShortName: String) { + (activity as AddVolumeActivity).apply { + if (rememberVolume || encryptedVolume == null) { + finish() + } else { + startExplorer(encryptedVolume, volumeShortName) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt b/app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt index f27ef24..c9b8fdf 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt @@ -2,6 +2,7 @@ package sushi.hardcore.droidfs.add_volume import android.Manifest import android.annotation.SuppressLint +import android.content.SharedPreferences import android.content.pm.PackageManager import android.net.Uri import android.os.Build @@ -15,7 +16,11 @@ import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment -import sushi.hardcore.droidfs.* +import androidx.preference.PreferenceManager +import sushi.hardcore.droidfs.ConstValues +import sushi.hardcore.droidfs.R +import sushi.hardcore.droidfs.VolumeData +import sushi.hardcore.droidfs.VolumeDatabase import sushi.hardcore.droidfs.databinding.DialogSdcardErrorBinding import sushi.hardcore.droidfs.databinding.FragmentSelectPathBinding import sushi.hardcore.droidfs.filesystems.EncryptedVolume @@ -26,11 +31,13 @@ import java.io.File class SelectPathFragment: Fragment() { companion object { private const val KEY_THEME_VALUE = "theme" + private const val KEY_PICK_MODE = "pick" - fun newInstance(themeValue: String): SelectPathFragment { + fun newInstance(themeValue: String, pickMode: Boolean): SelectPathFragment { return SelectPathFragment().apply { arguments = Bundle().apply { putString(KEY_THEME_VALUE, themeValue) + putBoolean(KEY_PICK_MODE, pickMode) } } } @@ -39,7 +46,7 @@ class SelectPathFragment: Fragment() { private lateinit var binding: FragmentSelectPathBinding private val askStoragePermissions = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result -> if (result[Manifest.permission.READ_EXTERNAL_STORAGE] == true && result[Manifest.permission.WRITE_EXTERNAL_STORAGE] == true) - PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue) + launchPickDirectory() else CustomAlertDialogBuilder(requireContext(), themeValue) .setTitle(R.string.storage_perm_denied) @@ -54,6 +61,12 @@ class SelectPathFragment: Fragment() { } private var themeValue = ConstValues.DEFAULT_THEME_VALUE private lateinit var volumeDatabase: VolumeDatabase + private lateinit var filesDir: String + private lateinit var sharedPrefs: SharedPreferences + private var pickMode = false + private var originalRememberVolume = true + private var currentVolumeData: VolumeData? = null + private var volumeAction: Action? = null override fun onCreateView( inflater: LayoutInflater, @@ -65,15 +78,24 @@ class SelectPathFragment: Fragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext()) + originalRememberVolume = sharedPrefs.getBoolean(ConstValues.REMEMBER_VOLUME_KEY, true) + binding.switchRemember.isChecked = originalRememberVolume arguments?.let { arguments -> arguments.getString(KEY_THEME_VALUE)?.let { themeValue = it } + pickMode = arguments.getBoolean(KEY_PICK_MODE) + } + if (pickMode) { + binding.buttonAction.text = getString(R.string.add_volume) } volumeDatabase = VolumeDatabase(requireContext()) + filesDir = requireContext().filesDir.path binding.containerHiddenVolume.setOnClickListener { binding.switchHiddenVolume.performClick() } binding.switchHiddenVolume.setOnClickListener { showRightSection() + refreshStatus(binding.editVolumeName.text) } binding.buttonPickDirectory.setOnClickListener { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -86,7 +108,7 @@ class SelectPathFragment: Fragment() { Manifest.permission.WRITE_EXTERNAL_STORAGE ) == PackageManager.PERMISSION_GRANTED ) - PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue) + launchPickDirectory() else askStoragePermissions.launch( arrayOf( @@ -95,35 +117,18 @@ class SelectPathFragment: Fragment() { ) ) } else - PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue) + launchPickDirectory() } - var isVolumeAlreadySaved = false - var volumeAction: Action? = null binding.editVolumeName.addTextChangedListener(object: TextWatcher { override fun afterTextChanged(s: Editable?) {} override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - isVolumeAlreadySaved = volumeDatabase.isVolumeSaved(s.toString(), binding.switchHiddenVolume.isChecked) - if (isVolumeAlreadySaved) - binding.textWarning.apply { - text = getString(R.string.volume_alread_saved) - visibility = View.VISIBLE - } - else - binding.textWarning.visibility = View.GONE - val path = File(getCurrentVolumePath()) - volumeAction = if (path.isDirectory) - if (path.list()?.isEmpty() == true) Action.CREATE else Action.ADD - else - Action.CREATE - binding.buttonAction.text = getString(when (volumeAction) { - Action.CREATE -> R.string.create - else -> R.string.add_volume - }) + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + refreshStatus(s) } }) - binding.editVolumeName.setOnEditorActionListener { _, _, _ -> onPathSelected(isVolumeAlreadySaved, volumeAction); true } - binding.buttonAction.setOnClickListener { onPathSelected(isVolumeAlreadySaved, volumeAction) } + binding.switchRemember.setOnCheckedChangeListener { _, _ -> refreshButtonText() } + binding.editVolumeName.setOnEditorActionListener { _, _, _ -> onPathSelected(); true } + binding.buttonAction.setOnClickListener { onPathSelected() } } override fun onViewStateRestored(savedInstanceState: Bundle?) { @@ -132,6 +137,11 @@ class SelectPathFragment: Fragment() { showRightSection() } + private fun launchPickDirectory() { + (activity as AddVolumeActivity).shouldCloseVolume = false + PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue) + } + private fun showRightSection() { if (binding.switchHiddenVolume.isChecked) { binding.textLabel.text = requireContext().getString(R.string.volume_name_label) @@ -144,6 +154,48 @@ class SelectPathFragment: Fragment() { } } + private fun refreshButtonText() { + binding.buttonAction.text = getString( + if (pickMode || volumeAction == Action.ADD) { + if (binding.switchRemember.isChecked || currentVolumeData != null) { + R.string.add_volume + } else { + R.string.open_volume + } + } else { + R.string.create_volume + } + ) + } + + private fun refreshStatus(content: CharSequence) { + val path = File(getCurrentVolumePath()) + volumeAction = if (path.isDirectory) { + if (path.list()?.isEmpty() == true || content.isEmpty()) Action.CREATE else Action.ADD + } else { + Action.CREATE + } + currentVolumeData = if (volumeAction == Action.CREATE) { + null + } else { + volumeDatabase.getVolume(content.toString(), binding.switchHiddenVolume.isChecked) + } + binding.textWarning.visibility = if (volumeAction == Action.CREATE && pickMode) { + binding.textWarning.text = getString(R.string.choose_existing_volume) + binding.buttonAction.isEnabled = false + View.VISIBLE + } else { + refreshButtonText() + binding.buttonAction.isEnabled = true + if (currentVolumeData == null) { + View.GONE + } else { + binding.textWarning.text = getString(R.string.volume_alread_saved) + View.VISIBLE + } + } + } + private fun onDirectoryPicked(uri: Uri) { val path = PathUtils.getFullPathFromTreeUri(uri, requireContext()) if (path != null) @@ -158,95 +210,105 @@ class SelectPathFragment: Fragment() { private fun getCurrentVolumePath(): String { return if (binding.switchHiddenVolume.isChecked) - SavedVolume.getHiddenVolumeFullPath(requireContext().filesDir.path, binding.editVolumeName.text.toString()) + VolumeData.getHiddenVolumeFullPath(filesDir, binding.editVolumeName.text.toString()) else binding.editVolumeName.text.toString() } - private fun onPathSelected(isVolumeAlreadySaved: Boolean, volumeAction: Action?) { - if (isVolumeAlreadySaved) { - (activity as AddVolumeActivity).onSelectedAlreadySavedVolume() - } else { - if (binding.switchHiddenVolume.isChecked && volumeAction == Action.CREATE) { + private fun onPathSelected() { + if (binding.switchRemember.isChecked != originalRememberVolume) { + with(sharedPrefs.edit()) { + putBoolean(ConstValues.REMEMBER_VOLUME_KEY, binding.switchRemember.isChecked) + apply() + } + } + if (currentVolumeData == null) { // volume not known + val currentVolumeValue = binding.editVolumeName.text.toString() + val isHidden = binding.switchHiddenVolume.isChecked + if (currentVolumeValue.isEmpty()) { + Toast.makeText( + requireContext(), + if (isHidden) R.string.enter_volume_name else R.string.enter_volume_path, + Toast.LENGTH_SHORT + ).show() + } else if (isHidden && currentVolumeValue.contains(PathUtils.SEPARATOR)) { + Toast.makeText(requireContext(), R.string.error_slash_in_name, Toast.LENGTH_SHORT).show() + } else if (isHidden && volumeAction == Action.CREATE) { CustomAlertDialogBuilder(requireContext(), themeValue) .setTitle(R.string.warning) .setMessage(R.string.hidden_volume_warning) .setPositiveButton(R.string.ok) { _, _ -> - addVolume(volumeAction) + onNewVolumeSelected(currentVolumeValue, isHidden) } .show() } else { - addVolume(volumeAction) + onNewVolumeSelected(currentVolumeValue, isHidden) } + } else { + (activity as AddVolumeActivity).onVolumeSelected(currentVolumeData!!, true) } } - private fun addVolume(volumeAction: Action?) { - val currentVolumeValue = binding.editVolumeName.text.toString() - val isHidden = binding.switchHiddenVolume.isChecked - if (currentVolumeValue.isEmpty()) { - Toast.makeText( - requireContext(), - if (isHidden) R.string.enter_volume_name else R.string.enter_volume_path, - Toast.LENGTH_SHORT - ).show() - } else if (isHidden && currentVolumeValue.contains(PathUtils.SEPARATOR)) { - Toast.makeText(requireContext(), R.string.error_slash_in_name, Toast.LENGTH_SHORT).show() - } else { - val volumePath = getCurrentVolumePath() - when (volumeAction!!) { - Action.CREATE -> { - val volumeFile = File(volumePath) - var goodDirectory = false - if (volumeFile.isFile) { - Toast.makeText(requireContext(), R.string.error_is_file, Toast.LENGTH_SHORT).show() - } else if (volumeFile.isDirectory) { - val dirContent = volumeFile.list() - if (dirContent != null) { - if (dirContent.isEmpty()) { - if (volumeFile.canWrite()) - goodDirectory = true - else - errorDirectoryNotWritable(volumePath) - } else - Toast.makeText(requireContext(), R.string.dir_not_empty, Toast.LENGTH_SHORT).show() - } else - Toast.makeText(requireContext(), R.string.listdir_null_error_msg, Toast.LENGTH_SHORT).show() + private fun onNewVolumeSelected(currentVolumeValue: String, isHidden: Boolean) { + val volumePath = getCurrentVolumePath() + when (volumeAction!!) { + Action.CREATE -> { + val volumeFile = File(volumePath) + var goodDirectory = false + if (volumeFile.isFile) { + Toast.makeText(requireContext(), R.string.error_is_file, Toast.LENGTH_SHORT).show() + } else if (volumeFile.isDirectory) { + val dirContent = volumeFile.list() + if (dirContent != null) { + if (dirContent.isEmpty()) { + if (volumeFile.canWrite()) { + goodDirectory = true + } else { + errorDirectoryNotWritable(volumePath) + } + } else { + Toast.makeText(requireContext(), R.string.dir_not_empty, Toast.LENGTH_SHORT).show() + } } else { - if (File(PathUtils.getParentPath(volumePath)).canWrite()) - goodDirectory = true - else - errorDirectoryNotWritable(volumePath) + Toast.makeText(requireContext(), R.string.listdir_null_error_msg, Toast.LENGTH_SHORT).show() + } + } else { + if (File(PathUtils.getParentPath(volumePath)).canWrite()) { + goodDirectory = true + } else { + errorDirectoryNotWritable(volumePath) } - if (goodDirectory) - (activity as AddVolumeActivity).createVolume(volumePath, isHidden) } - Action.ADD -> { - val volumeType = EncryptedVolume.getVolumeType(volumePath) - if (volumeType < 0) { - CustomAlertDialogBuilder(requireContext(), themeValue) - .setTitle(R.string.error) - .setMessage(R.string.error_not_a_volume) - .setPositiveButton(R.string.ok, null) - .show() - } else if (!File(volumePath).canWrite()) { - val dialog = CustomAlertDialogBuilder(requireContext(), themeValue) - .setTitle(R.string.warning) - .setCancelable(false) - .setPositiveButton(R.string.ok) { _, _ -> addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden, volumeType) } - if (PathUtils.isPathOnExternalStorage(volumePath, requireContext())) - dialog.setView( - DialogSdcardErrorBinding.inflate(layoutInflater).apply { - path.text = PathUtils.getPackageDataFolder(requireContext()) - footer.text = getString(R.string.sdcard_error_add_footer) - }.root - ) - else - dialog.setMessage(R.string.add_cant_write_warning) - dialog.show() + if (goodDirectory) { + (activity as AddVolumeActivity).createVolume(volumePath, isHidden, binding.switchRemember.isChecked) + } + } + Action.ADD -> { + val volumeType = EncryptedVolume.getVolumeType(volumePath) + if (volumeType < 0) { + CustomAlertDialogBuilder(requireContext(), themeValue) + .setTitle(R.string.error) + .setMessage(R.string.error_not_a_volume) + .setPositiveButton(R.string.ok, null) + .show() + } else if (!File(volumePath).canWrite()) { + val dialog = CustomAlertDialogBuilder(requireContext(), themeValue) + .setTitle(R.string.warning) + .setCancelable(false) + .setPositiveButton(R.string.ok) { _, _ -> addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden, volumeType) } + if (PathUtils.isPathOnExternalStorage(volumePath, requireContext())) { + dialog.setView( + DialogSdcardErrorBinding.inflate(layoutInflater).apply { + path.text = PathUtils.getPackageDataFolder(requireContext()) + footer.text = getString(R.string.sdcard_error_add_footer) + }.root + ) } else { - addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden, volumeType) + dialog.setMessage(R.string.add_cant_write_warning) } + dialog.show() + } else { + addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden, volumeType) } } } @@ -270,7 +332,10 @@ class SelectPathFragment: Fragment() { } private fun addVolume(volumeName: String, isHidden: Boolean, volumeType: Byte) { - volumeDatabase.saveVolume(SavedVolume(volumeName, isHidden, volumeType)) - (activity as AddVolumeActivity).onVolumeAdded(false) + val volumeData = VolumeData(volumeName, isHidden, volumeType) + if (binding.switchRemember.isChecked) { + volumeDatabase.saveVolume(volumeData) + } + (activity as AddVolumeActivity).onVolumeSelected(volumeData, binding.switchRemember.isChecked) } } \ No newline at end of file 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 a0b021f..856ab0b 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt @@ -39,6 +39,7 @@ import sushi.hardcore.droidfs.file_operations.OperationFile import sushi.hardcore.droidfs.file_viewers.* import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.Stat +import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.EditTextDialog @@ -82,7 +83,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene usf_open = sharedPrefs.getBoolean("usf_open", false) usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false) volumeName = intent.getStringExtra("volume_name") ?: "" - encryptedVolume = getParcelableExtra(intent, "volume")!! + encryptedVolume = IntentUtils.getParcelableExtra(intent, "volume")!! sortOrderEntries = resources.getStringArray(R.array.sort_orders_entries) sortOrderValues = resources.getStringArray(R.array.sort_orders_values) foldersFirst = sharedPrefs.getBoolean("folders_first", true) @@ -156,7 +157,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene } protected open fun init() { - setContentView(R.layout.activity_explorer_base) + setContentView(R.layout.activity_explorer) } protected open fun bindFileOperationService(){ 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 db23a22..193ec4d 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt @@ -8,16 +8,17 @@ import android.view.MenuItem import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.documentfile.provider.DocumentFile +import com.google.android.material.floatingactionbutton.FloatingActionButton import kotlinx.coroutines.launch import sushi.hardcore.droidfs.CameraActivity import sushi.hardcore.droidfs.MainActivity import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.adapters.IconTextDialogAdapter import sushi.hardcore.droidfs.content_providers.ExternalProvider -import sushi.hardcore.droidfs.databinding.ActivityExplorerBinding import sushi.hardcore.droidfs.file_operations.OperationFile import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.Stat +import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.EditTextDialog @@ -31,11 +32,10 @@ class ExplorerActivity : BaseExplorerActivity() { private var usf_share = false private var currentItemAction = ItemsActions.NONE private val itemsToProcess = ArrayList() - private lateinit var binding: ActivityExplorerBinding private val pickFromOtherVolumes = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == Activity.RESULT_OK) { result.data?.let { resultIntent -> - val remoteEncryptedVolume = getParcelableExtra(resultIntent, "volume")!! + val remoteEncryptedVolume = IntentUtils.getParcelableExtra(resultIntent, "volume")!! val path = resultIntent.getStringExtra("path") val operationFiles = ArrayList() if (path == null){ //multiples elements @@ -168,9 +168,8 @@ class ExplorerActivity : BaseExplorerActivity() { } override fun init() { - binding = ActivityExplorerBinding.inflate(layoutInflater) - setContentView(binding.root) - binding.fab.setOnClickListener { + super.init() + findViewById(R.id.fab).setOnClickListener { if (currentItemAction != ItemsActions.NONE){ openDialogCreateFolder() } else { 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 fedb66e..1e802d9 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt @@ -5,17 +5,16 @@ import android.net.Uri import android.os.Build import android.view.Menu import android.view.MenuItem +import com.google.android.material.floatingactionbutton.FloatingActionButton import sushi.hardcore.droidfs.R -import sushi.hardcore.droidfs.databinding.ActivityExplorerDropBinding +import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder class ExplorerActivityDrop : BaseExplorerActivity() { - private lateinit var binding: ActivityExplorerDropBinding override fun init() { - binding = ActivityExplorerDropBinding.inflate(layoutInflater) - setContentView(binding.root) - binding.fab.setOnClickListener { + super.init() + findViewById(R.id.fab).setOnClickListener { openDialogCreateFolder() } } @@ -34,7 +33,7 @@ class ExplorerActivityDrop : BaseExplorerActivity() { val errorMsg: String? = if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)) { when (intent.action) { Intent.ACTION_SEND -> { - val uri = getParcelableExtra(intent, Intent.EXTRA_STREAM) + val uri = IntentUtils.getParcelableExtra(intent, Intent.EXTRA_STREAM) if (uri == null) { getString(R.string.share_intent_parsing_failed) } else { diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt index 1838c61..bdcb912 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt @@ -6,13 +6,14 @@ import android.view.Menu import android.view.MenuItem import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.filesystems.EncryptedVolume +import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.util.PathUtils class ExplorerActivityPick : BaseExplorerActivity() { private var resultIntent = Intent() private var isFinishingIntentionally = false override fun init() { - super.init() + setContentView(R.layout.activity_explorer_pick) resultIntent.putExtra("volume", encryptedVolume) } @@ -83,7 +84,7 @@ class ExplorerActivityPick : BaseExplorerActivity() { override fun closeVolumeOnDestroy() { if (!isFinishingIntentionally && !usf_keep_open){ - getParcelableExtra(intent, "destinationVolume")?.close() + IntentUtils.getParcelableExtra(intent, "destinationVolume")?.close() super.closeVolumeOnDestroy() } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerRouter.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerRouter.kt new file mode 100644 index 0000000..cafc0da --- /dev/null +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerRouter.kt @@ -0,0 +1,29 @@ +package sushi.hardcore.droidfs.explorers + +import android.content.Context +import android.content.Intent +import sushi.hardcore.droidfs.filesystems.EncryptedVolume +import sushi.hardcore.droidfs.util.IntentUtils + +class ExplorerRouter(private val context: Context, private val intent: Intent) { + var pickMode = intent.action == "pick" + var dropMode = (intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_SEND_MULTIPLE) && intent.extras != null + + fun getExplorerIntent(encryptedVolume: EncryptedVolume, volumeShortName: String): Intent { + var explorerIntent: Intent? = null + if (dropMode) { //import via android share menu + explorerIntent = Intent(context, ExplorerActivityDrop::class.java) + IntentUtils.forwardIntent(intent, explorerIntent) + } else if (pickMode) { + explorerIntent = Intent(context, ExplorerActivityPick::class.java) + explorerIntent.putExtra("destinationVolume", IntentUtils.getParcelableExtra(intent, "volume")!!) + explorerIntent.flags = Intent.FLAG_ACTIVITY_FORWARD_RESULT + } + if (explorerIntent == null) { + explorerIntent = Intent(context, ExplorerActivity::class.java) //default opening + } + explorerIntent.putExtra("volume", encryptedVolume) + explorerIntent.putExtra("volume_name", volumeShortName) + return explorerIntent + } +} \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt index 9e80533..65953e2 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt @@ -11,6 +11,7 @@ import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.filesystems.EncryptedVolume +import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder @@ -34,7 +35,7 @@ abstract class FileViewerActivity: BaseActivity() { super.onCreate(savedInstanceState) filePath = intent.getStringExtra("path")!! originalParentPath = PathUtils.getParentPath(filePath) - encryptedVolume = getParcelableExtra(intent, "volume")!! + encryptedVolume = IntentUtils.getParcelableExtra(intent, "volume")!! usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false) foldersFirst = sharedPrefs.getBoolean("folders_first", true) windowInsetsController = WindowInsetsControllerCompat(window, window.decorView) diff --git a/app/src/main/java/sushi/hardcore/droidfs/filesystems/CryfsVolume.kt b/app/src/main/java/sushi/hardcore/droidfs/filesystems/CryfsVolume.kt index 249cabc..c679810 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/filesystems/CryfsVolume.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/filesystems/CryfsVolume.kt @@ -67,8 +67,14 @@ class CryfsVolume(private val fusePtr: Long): EncryptedVolume() { } } - fun create(baseDir: String, localStateDir: String, password: ByteArray, returnedHash: ObjRef?, cipher: String?): Boolean { - return init(baseDir, localStateDir, password, null, returnedHash, true, cipher)?.also { it.close() } != null + fun create(baseDir: String, localStateDir: String, password: ByteArray, returnedHash: ObjRef?, cipher: String?, volume: ObjRef?): Boolean { + return init(baseDir, localStateDir, password, null, returnedHash, true, cipher)?.also { + if (volume == null) { + it.close() + } else { + volume.value = it + } + } != null } fun init(baseDir: String, localStateDir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ObjRef?): CryfsVolume? { diff --git a/app/src/main/java/sushi/hardcore/droidfs/filesystems/EncryptedVolume.kt b/app/src/main/java/sushi/hardcore/droidfs/filesystems/EncryptedVolume.kt index 19eb99d..db06975 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/filesystems/EncryptedVolume.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/filesystems/EncryptedVolume.kt @@ -5,7 +5,7 @@ import android.net.Uri import android.os.Parcel import android.os.Parcelable import sushi.hardcore.droidfs.ConstValues -import sushi.hardcore.droidfs.SavedVolume +import sushi.hardcore.droidfs.VolumeData import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.util.ObjRef import sushi.hardcore.droidfs.util.PathUtils @@ -42,7 +42,7 @@ abstract class EncryptedVolume: Parcelable { } fun init( - volume: SavedVolume, + volume: VolumeData, filesDir: String, password: ByteArray?, givenHash: ByteArray?, diff --git a/app/src/main/java/sushi/hardcore/droidfs/filesystems/GocryptfsVolume.kt b/app/src/main/java/sushi/hardcore/droidfs/filesystems/GocryptfsVolume.kt index c5c58f3..8fc7241 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/filesystems/GocryptfsVolume.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/filesystems/GocryptfsVolume.kt @@ -1,7 +1,9 @@ package sushi.hardcore.droidfs.filesystems import android.os.Parcel +import android.util.Log import sushi.hardcore.droidfs.explorers.ExplorerElement +import sushi.hardcore.droidfs.util.ObjRef import kotlin.math.min class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() { @@ -22,10 +24,20 @@ class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() { companion object { const val KeyLen = 32 - const val ScryptDefaultLogN = 16 - const val MAX_KERNEL_WRITE = 128*1024 + private const val ScryptDefaultLogN = 16 + private const val VOLUME_CREATOR = "DroidFS" + private const val MAX_KERNEL_WRITE = 128*1024 const val CONFIG_FILE_NAME = "gocryptfs.conf" - external fun createVolume(root_cipher_dir: String, password: ByteArray, plainTextNames: Boolean, xchacha: Int, logN: Int, creator: String, returnedHash: ByteArray?): Boolean + private external fun nativeCreateVolume( + root_cipher_dir: String, + password: ByteArray, + plainTextNames: Boolean, + xchacha: Int, + logN: Int, + creator: String, + returnedHash: ByteArray?, + openAfterCreation: Boolean, + ): Int private external fun nativeInit(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int external fun changePassword( root_cipher_dir: String, @@ -35,6 +47,29 @@ class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() { returnedHash: ByteArray? ): Boolean + fun createAndOpenVolume( + root_cipher_dir: String, + password: ByteArray, + plainTextNames: Boolean, + xchacha: Int, + returnedHash: ByteArray?, + volume: ObjRef? + ): Boolean { + val openAfterCreation = volume != null + val result = nativeCreateVolume(root_cipher_dir, password, plainTextNames, xchacha, ScryptDefaultLogN, VOLUME_CREATOR, returnedHash, openAfterCreation) + return if (!openAfterCreation) { + result == 1 + } else if (result == -1) { + Log.e("gocryptfs", "Failed to open volume after creation") + true + } else if (result == -2) { + false + } else { + volume!!.value = GocryptfsVolume(result) + true + } + } + fun init(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): GocryptfsVolume? { val sessionId = nativeInit(root_cipher_dir, password, givenHash, returnedHash) return if (sessionId == -1) { @@ -52,7 +87,7 @@ class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() { constructor(parcel: Parcel) : this(parcel.readInt()) override fun openFile(path: String): Long { - return native_open_write_mode(sessionID, path, 0).toLong() + return native_open_write_mode(sessionID, path, 384).toLong() // 0600 } override fun read(fileHandle: Long, fileOffset: Long, buffer: ByteArray, dstOffset: Long, length: Long): Int { @@ -81,7 +116,7 @@ class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() { } override fun mkdir(path: String): Boolean { - return native_mkdir(sessionID, path, 0) + return native_mkdir(sessionID, path, 448) // 0700 } override fun rmdir(path: String): Boolean { diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/IntentUtils.kt b/app/src/main/java/sushi/hardcore/droidfs/util/IntentUtils.kt new file mode 100644 index 0000000..12fde99 --- /dev/null +++ b/app/src/main/java/sushi/hardcore/droidfs/util/IntentUtils.kt @@ -0,0 +1,21 @@ +package sushi.hardcore.droidfs.util + +import android.content.Intent +import android.os.Build +import android.os.Parcelable + +object IntentUtils { + inline fun getParcelableExtra(intent: Intent, name: String): T? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra(name, T::class.java) + } else { + @Suppress("Deprecation") + intent.getParcelableExtra(name) + } + } + + fun forwardIntent(sourceIntent: Intent, targetIntent: Intent) { + targetIntent.action = sourceIntent.action + sourceIntent.extras?.let { targetIntent.putExtras(it) } + } +} \ No newline at end of file diff --git a/app/src/main/native/gocryptfs_jni.c b/app/src/main/native/gocryptfs_jni.c index dde718c..9fe35c9 100644 --- a/app/src/main/native/gocryptfs_jni.c +++ b/app/src/main/native/gocryptfs_jni.c @@ -5,15 +5,18 @@ #include #include "libgocryptfs.h" -JNIEXPORT jboolean JNICALL -Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_createVolume(JNIEnv *env, jclass clazz, +const int KeyLen = 32; + +JNIEXPORT jint JNICALL +Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_nativeCreateVolume(JNIEnv *env, jclass clazz, jstring jroot_cipher_dir, jbyteArray jpassword, jboolean plainTextNames, jint xchacha, jint logN, jstring jcreator, - jbyteArray jreturned_hash) { + jbyteArray jreturned_hash, + jboolean open_after_creation) { const char* root_cipher_dir = (*env)->GetStringUTFChars(env, jroot_cipher_dir, NULL); const char* creator = (*env)->GetStringUTFChars(env, jcreator, NULL); GoString gofilename = {root_cipher_dir, strlen(root_cipher_dir)}, gocreator = {creator, strlen(creator)}; @@ -23,27 +26,43 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_createVol GoSlice go_password = {password, password_len, password_len}; size_t returned_hash_len; - jbyte* returned_hash; - GoSlice go_returned_hash = {NULL, 0, 0}; + GoSlice go_returned_hash; if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) { returned_hash_len = (*env)->GetArrayLength(env, jreturned_hash); - returned_hash = (*env)->GetByteArrayElements(env, jreturned_hash, NULL); - go_returned_hash.data = returned_hash; - go_returned_hash.len = returned_hash_len; - go_returned_hash.cap = returned_hash_len; + go_returned_hash.data = (*env)->GetByteArrayElements(env, jreturned_hash, NULL); + } else if (open_after_creation) { + returned_hash_len = KeyLen; + go_returned_hash.data = malloc(KeyLen); + } else { + returned_hash_len = 0; + go_returned_hash.data = NULL; } + go_returned_hash.len = returned_hash_len; + go_returned_hash.cap = returned_hash_len; GoUint8 result = gcf_create_volume(gofilename, go_password, plainTextNames, xchacha, logN, gocreator, go_returned_hash); - (*env)->ReleaseStringUTFChars(env, jroot_cipher_dir, root_cipher_dir); - (*env)->ReleaseStringUTFChars(env, jcreator, creator); (*env)->ReleaseByteArrayElements(env, jpassword, password, 0); + (*env)->ReleaseStringUTFChars(env, jcreator, creator); - if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) { - (*env)->ReleaseByteArrayElements(env, jreturned_hash, returned_hash, 0); + GoInt sessionID = -2; + if (result && open_after_creation) { + GoSlice null_slice = {NULL, 0, 0}; + sessionID = gcf_init(gofilename, null_slice, go_returned_hash, null_slice); } - return result; + (*env)->ReleaseStringUTFChars(env, jroot_cipher_dir, root_cipher_dir); + + if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) { + (*env)->ReleaseByteArrayElements(env, jreturned_hash, go_returned_hash.data, 0); + } else if (open_after_creation) { + for (unsigned int i=0; i - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_explorer_base.xml b/app/src/main/res/layout/activity_explorer_pick.xml similarity index 100% rename from app/src/main/res/layout/activity_explorer_base.xml rename to app/src/main/res/layout/activity_explorer_pick.xml diff --git a/app/src/main/res/layout/fragment_select_path.xml b/app/src/main/res/layout/fragment_select_path.xml index ffef32d..d07b136 100644 --- a/app/src/main/res/layout/fragment_select_path.xml +++ b/app/src/main/res/layout/fragment_select_path.xml @@ -101,6 +101,14 @@ android:layout_marginHorizontal="@dimen/volume_operation_horizontal_gap" android:visibility="gone"/> + + + android:text="@string/create_volume" /> \ 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 1a569e1..91d5f79 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -250,4 +250,7 @@ (%s, read-only) I/O Error. Use fingerprint instead of current password + Remember volume + Open volume + Please choose an existing volume