forked from hardcoresushi/DroidFS
Allow to open & create volumes without remembering
This commit is contained in:
parent
4df1086734
commit
7c2f87109a
@ -18,7 +18,7 @@ open class BaseActivity: AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
|
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
if (shouldCheckTheme && applyCustomTheme) {
|
if (shouldCheckTheme && applyCustomTheme) {
|
||||||
themeValue = sharedPrefs.getString("theme", ConstValues.DEFAULT_THEME_VALUE)!!
|
themeValue = sharedPrefs.getString(ConstValues.THEME_VALUE_KEY, ConstValues.DEFAULT_THEME_VALUE)!!
|
||||||
when (themeValue) {
|
when (themeValue) {
|
||||||
"black_green" -> setTheme(R.style.BlackGreen)
|
"black_green" -> setTheme(R.style.BlackGreen)
|
||||||
"dark_red" -> setTheme(R.style.DarkRed)
|
"dark_red" -> setTheme(R.style.DarkRed)
|
||||||
@ -47,13 +47,4 @@ open class BaseActivity: AppCompatActivity() {
|
|||||||
recreate()
|
recreate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T: Parcelable> 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -32,6 +32,7 @@ import kotlinx.coroutines.launch
|
|||||||
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
||||||
import sushi.hardcore.droidfs.databinding.ActivityCameraBinding
|
import sushi.hardcore.droidfs.databinding.ActivityCameraBinding
|
||||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
|
import sushi.hardcore.droidfs.util.IntentUtils
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.video_recording.SeekableWriter
|
import sushi.hardcore.droidfs.video_recording.SeekableWriter
|
||||||
import sushi.hardcore.droidfs.video_recording.VideoCapture
|
import sushi.hardcore.droidfs.video_recording.VideoCapture
|
||||||
@ -95,7 +96,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|||||||
binding = ActivityCameraBinding.inflate(layoutInflater)
|
binding = ActivityCameraBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
supportActionBar?.hide()
|
supportActionBar?.hide()
|
||||||
encryptedVolume = getParcelableExtra(intent, "volume")!!
|
encryptedVolume = IntentUtils.getParcelableExtra(intent, "volume")!!
|
||||||
outputDirectory = intent.getStringExtra("path")!!
|
outputDirectory = intent.getStringExtra("path")!!
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
@ -14,6 +14,7 @@ import sushi.hardcore.droidfs.databinding.ActivityChangePasswordBinding
|
|||||||
import sushi.hardcore.droidfs.filesystems.CryfsVolume
|
import sushi.hardcore.droidfs.filesystems.CryfsVolume
|
||||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume
|
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume
|
||||||
|
import sushi.hardcore.droidfs.util.IntentUtils
|
||||||
import sushi.hardcore.droidfs.util.ObjRef
|
import sushi.hardcore.droidfs.util.ObjRef
|
||||||
import sushi.hardcore.droidfs.util.WidgetUtil
|
import sushi.hardcore.droidfs.util.WidgetUtil
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
@ -22,7 +23,7 @@ import java.util.*
|
|||||||
class ChangePasswordActivity: BaseActivity() {
|
class ChangePasswordActivity: BaseActivity() {
|
||||||
|
|
||||||
private lateinit var binding: ActivityChangePasswordBinding
|
private lateinit var binding: ActivityChangePasswordBinding
|
||||||
private lateinit var volume: SavedVolume
|
private lateinit var volume: VolumeData
|
||||||
private lateinit var volumeDatabase: VolumeDatabase
|
private lateinit var volumeDatabase: VolumeDatabase
|
||||||
private var fingerprintProtector: FingerprintProtector? = null
|
private var fingerprintProtector: FingerprintProtector? = null
|
||||||
private var usfFingerprint: Boolean = false
|
private var usfFingerprint: Boolean = false
|
||||||
@ -32,7 +33,7 @@ class ChangePasswordActivity: BaseActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
volume = getParcelableExtra(intent, "volume")!!
|
volume = IntentUtils.getParcelableExtra(intent, "volume")!!
|
||||||
binding = ActivityChangePasswordBinding.inflate(layoutInflater)
|
binding = ActivityChangePasswordBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
title = getString(R.string.change_password)
|
title = getString(R.string.change_password)
|
||||||
|
@ -4,7 +4,6 @@ import android.net.Uri
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object ConstValues {
|
object ConstValues {
|
||||||
const val CREATOR = "DroidFS"
|
|
||||||
const val VOLUME_DATABASE_NAME = "SavedVolumes"
|
const val VOLUME_DATABASE_NAME = "SavedVolumes"
|
||||||
const val CRYFS_LOCAL_STATE_DIR = "cryfsLocalState"
|
const val CRYFS_LOCAL_STATE_DIR = "cryfsLocalState"
|
||||||
const val SORT_ORDER_KEY = "sort_order"
|
const val SORT_ORDER_KEY = "sort_order"
|
||||||
@ -13,6 +12,9 @@ object ConstValues {
|
|||||||
const val IO_BUFF_SIZE = 16384
|
const val IO_BUFF_SIZE = 16384
|
||||||
const val SLIDESHOW_DELAY: Long = 4000
|
const val SLIDESHOW_DELAY: Long = 4000
|
||||||
const val DEFAULT_THEME_VALUE = "dark_green"
|
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 THUMBNAIL_MAX_SIZE_KEY = "thumbnail_max_size"
|
||||||
const val DEFAULT_THUMBNAIL_MAX_SIZE = 10_000L
|
const val DEFAULT_THUMBNAIL_MAX_SIZE = 10_000L
|
||||||
const val PIN_PASSWORDS_KEY = "pin_passwords"
|
const val PIN_PASSWORDS_KEY = "pin_passwords"
|
||||||
|
@ -133,7 +133,7 @@ class FingerprintProtector private constructor(
|
|||||||
private lateinit var cipher: Cipher
|
private lateinit var cipher: Cipher
|
||||||
private var isCipherReady = false
|
private var isCipherReady = false
|
||||||
private var cipherActionMode: Int? = null
|
private var cipherActionMode: Int? = null
|
||||||
private lateinit var volume: SavedVolume
|
private lateinit var volume: VolumeData
|
||||||
private lateinit var dataToProcess: ByteArray
|
private lateinit var dataToProcess: ByteArray
|
||||||
|
|
||||||
private fun resetHashStorage() {
|
private fun resetHashStorage() {
|
||||||
@ -207,7 +207,7 @@ class FingerprintProtector private constructor(
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun savePasswordHash(volume: SavedVolume, plainText: ByteArray) {
|
fun savePasswordHash(volume: VolumeData, plainText: ByteArray) {
|
||||||
this.volume = volume
|
this.volume = volume
|
||||||
val biometricPromptInfo = BiometricPrompt.PromptInfo.Builder()
|
val biometricPromptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||||
.setTitle(activity.getString(R.string.encrypt_action_description))
|
.setTitle(activity.getString(R.string.encrypt_action_description))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package sushi.hardcore.droidfs
|
package sushi.hardcore.droidfs
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -8,7 +8,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import sushi.hardcore.droidfs.databinding.DialogLoadingBinding
|
import sushi.hardcore.droidfs.databinding.DialogLoadingBinding
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
|
|
||||||
abstract class LoadingTask<T>(val activity: AppCompatActivity, themeValue: String, loadingMessageResId: Int) {
|
abstract class LoadingTask<T>(val activity: FragmentActivity, themeValue: String, loadingMessageResId: Int) {
|
||||||
private val dialogLoading = CustomAlertDialogBuilder(activity, themeValue)
|
private val dialogLoading = CustomAlertDialogBuilder(activity, themeValue)
|
||||||
.setView(
|
.setView(
|
||||||
DialogLoadingBinding.inflate(activity.layoutInflater).apply {
|
DialogLoadingBinding.inflate(activity.layoutInflater).apply {
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
package sushi.hardcore.droidfs
|
package sushi.hardcore.droidfs
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.text.InputType
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
@ -21,45 +17,31 @@ import androidx.documentfile.provider.DocumentFile
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import sushi.hardcore.droidfs.ConstValues.DEFAULT_VOLUME_KEY
|
||||||
import sushi.hardcore.droidfs.adapters.VolumeAdapter
|
import sushi.hardcore.droidfs.adapters.VolumeAdapter
|
||||||
import sushi.hardcore.droidfs.add_volume.AddVolumeActivity
|
import sushi.hardcore.droidfs.add_volume.AddVolumeActivity
|
||||||
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
||||||
import sushi.hardcore.droidfs.databinding.ActivityMainBinding
|
import sushi.hardcore.droidfs.databinding.ActivityMainBinding
|
||||||
import sushi.hardcore.droidfs.databinding.DialogDeleteVolumeBinding
|
import sushi.hardcore.droidfs.databinding.DialogDeleteVolumeBinding
|
||||||
import sushi.hardcore.droidfs.databinding.DialogOpenVolumeBinding
|
import sushi.hardcore.droidfs.explorers.ExplorerRouter
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerActivity
|
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
|
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
|
|
||||||
import sushi.hardcore.droidfs.file_operations.FileOperationService
|
import sushi.hardcore.droidfs.file_operations.FileOperationService
|
||||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
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.PathUtils
|
||||||
import sushi.hardcore.droidfs.util.WidgetUtil
|
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val DEFAULT_VOLUME_KEY = "default_volume"
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private lateinit var volumeDatabase: VolumeDatabase
|
private lateinit var volumeDatabase: VolumeDatabase
|
||||||
private lateinit var volumeAdapter: VolumeAdapter
|
private lateinit var volumeAdapter: VolumeAdapter
|
||||||
private var fingerprintProtector: FingerprintProtector? = null
|
private lateinit var volumeOpener: VolumeOpener
|
||||||
private var usfFingerprint: Boolean = false
|
|
||||||
private var usfKeepOpen: Boolean = false
|
private var usfKeepOpen: Boolean = false
|
||||||
private var defaultVolumeName: String? = null
|
|
||||||
private var addVolume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private var addVolume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
when (result.resultCode) {
|
if ((explorerRouter.pickMode || explorerRouter.dropMode) && result.resultCode != AddVolumeActivity.RESULT_USER_BACK) {
|
||||||
AddVolumeActivity.RESULT_VOLUME_ADDED -> onVolumeAdded()
|
setResult(result.resultCode, result.data) // forward result
|
||||||
AddVolumeActivity.RESULT_HASH_STORAGE_RESET -> {
|
finish()
|
||||||
volumeAdapter.refresh()
|
|
||||||
binding.textNoVolumes.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private var changePasswordPosition: Int? = null
|
private var changePasswordPosition: Int? = null
|
||||||
@ -71,8 +53,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
onDirectoryPicked(uri)
|
onDirectoryPicked(uri)
|
||||||
}
|
}
|
||||||
private lateinit var fileOperationService: FileOperationService
|
private lateinit var fileOperationService: FileOperationService
|
||||||
private var pickMode = false
|
private lateinit var explorerRouter: ExplorerRouter
|
||||||
private var dropMode = false
|
|
||||||
private var shouldCloseVolume = true // used when launched to pick file from another volume
|
private var shouldCloseVolume = true // used when launched to pick file from another volume
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -98,14 +79,13 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
pickMode = intent.action == "pick"
|
explorerRouter = ExplorerRouter(this, intent)
|
||||||
dropMode = (intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_SEND_MULTIPLE) && intent.extras != null
|
|
||||||
volumeDatabase = VolumeDatabase(this)
|
volumeDatabase = VolumeDatabase(this)
|
||||||
volumeAdapter = VolumeAdapter(
|
volumeAdapter = VolumeAdapter(
|
||||||
this,
|
this,
|
||||||
volumeDatabase,
|
volumeDatabase,
|
||||||
!pickMode && !dropMode,
|
!explorerRouter.pickMode && !explorerRouter.dropMode,
|
||||||
!dropMode,
|
!explorerRouter.dropMode,
|
||||||
this,
|
this,
|
||||||
)
|
)
|
||||||
binding.recyclerViewVolumes.adapter = volumeAdapter
|
binding.recyclerViewVolumes.adapter = volumeAdapter
|
||||||
@ -113,24 +93,22 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
if (volumeAdapter.volumes.isEmpty()) {
|
if (volumeAdapter.volumes.isEmpty()) {
|
||||||
binding.textNoVolumes.visibility = View.VISIBLE
|
binding.textNoVolumes.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
if (pickMode) {
|
if (explorerRouter.pickMode) {
|
||||||
title = getString(R.string.select_volume)
|
title = getString(R.string.select_volume)
|
||||||
binding.fab.visibility = View.GONE
|
}
|
||||||
} else {
|
binding.fab.setOnClickListener {
|
||||||
binding.fab.setOnClickListener {
|
addVolume.launch(Intent(this, AddVolumeActivity::class.java).also {
|
||||||
addVolume.launch(Intent(this, AddVolumeActivity::class.java))
|
if (explorerRouter.dropMode || explorerRouter.pickMode) {
|
||||||
}
|
IntentUtils.forwardIntent(intent, it)
|
||||||
|
shouldCloseVolume = false
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
usfKeepOpen = sharedPrefs.getBoolean("usf_keep_open", false)
|
usfKeepOpen = sharedPrefs.getBoolean("usf_keep_open", false)
|
||||||
usfFingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
|
volumeOpener = VolumeOpener(this)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
volumeOpener.defaultVolumeName?.let { name ->
|
||||||
fingerprintProtector = FingerprintProtector.new(this, themeValue, volumeDatabase)
|
|
||||||
}
|
|
||||||
defaultVolumeName = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null)
|
|
||||||
defaultVolumeName?.let { name ->
|
|
||||||
try {
|
try {
|
||||||
val (position, volume) = volumeAdapter.volumes.withIndex().first { it.value.name == name }
|
openVolume(volumeAdapter.volumes.first { it.name == name })
|
||||||
openVolume(volume, position)
|
|
||||||
} catch (e: NoSuchElementException) {
|
} catch (e: NoSuchElementException) {
|
||||||
unsetDefaultVolume()
|
unsetDefaultVolume()
|
||||||
}
|
}
|
||||||
@ -139,8 +117,9 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
if (volumeAdapter.selectedItems.isNotEmpty()) {
|
if (volumeAdapter.selectedItems.isNotEmpty()) {
|
||||||
unselectAll()
|
unselectAll()
|
||||||
} else {
|
} else {
|
||||||
if (pickMode)
|
if (explorerRouter.pickMode) {
|
||||||
shouldCloseVolume = false
|
shouldCloseVolume = false
|
||||||
|
}
|
||||||
isEnabled = false
|
isEnabled = false
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
@ -158,10 +137,15 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
// refresh theme if changed in SettingsActivity
|
// 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)
|
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
|
// 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) {
|
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())
|
if (volumeAdapter.selectedItems.isEmpty())
|
||||||
openVolume(volume, position)
|
openVolume(volume)
|
||||||
else
|
else
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
@ -183,14 +167,6 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onVolumeAdded() {
|
|
||||||
volumeAdapter.apply {
|
|
||||||
volumes = volumeDatabase.getVolumes()
|
|
||||||
notifyItemInserted(volumes.size)
|
|
||||||
}
|
|
||||||
binding.textNoVolumes.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unselectAll(notifyChange: Boolean = true) {
|
private fun unselectAll(notifyChange: Boolean = true) {
|
||||||
volumeAdapter.unSelectAll(notifyChange)
|
volumeAdapter.unSelectAll(notifyChange)
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
@ -203,7 +179,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeVolumes(volumes: List<SavedVolume>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) {
|
private fun removeVolumes(volumes: List<VolumeData>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) {
|
||||||
if (i < volumes.size) {
|
if (i < volumes.size) {
|
||||||
if (volumes[i].isHidden) {
|
if (volumes[i].isHidden) {
|
||||||
if (doDeleteVolumeContent == null) {
|
if (doDeleteVolumeContent == null) {
|
||||||
@ -258,15 +234,16 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
remove(DEFAULT_VOLUME_KEY)
|
remove(DEFAULT_VOLUME_KEY)
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
defaultVolumeName = null
|
volumeOpener.defaultVolumeName = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
android.R.id.home -> {
|
android.R.id.home -> {
|
||||||
if (pickMode || dropMode) {
|
if (explorerRouter.pickMode || explorerRouter.dropMode) {
|
||||||
if (pickMode)
|
if (explorerRouter.pickMode) {
|
||||||
shouldCloseVolume = false
|
shouldCloseVolume = false
|
||||||
|
}
|
||||||
finish()
|
finish()
|
||||||
} else {
|
} else {
|
||||||
unselectAll()
|
unselectAll()
|
||||||
@ -323,7 +300,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
DocumentFile.fromFile(File(volume.name)),
|
DocumentFile.fromFile(File(volume.name)),
|
||||||
DocumentFile.fromFile(filesDir),
|
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 {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.main_activity, menu)
|
menuInflater.inflate(R.menu.main_activity, menu)
|
||||||
menu.findItem(R.id.settings).isVisible = !pickMode && !dropMode
|
menu.findItem(R.id.settings).isVisible = !explorerRouter.pickMode && !explorerRouter.dropMode
|
||||||
val isSelecting = volumeAdapter.selectedItems.isNotEmpty()
|
val isSelecting = volumeAdapter.selectedItems.isNotEmpty()
|
||||||
menu.findItem(R.id.select_all).isVisible = isSelecting
|
menu.findItem(R.id.select_all).isVisible = isSelecting
|
||||||
menu.findItem(R.id.remove).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.change_password).isVisible = onlyOneAndWriteable
|
||||||
menu.findItem(R.id.remove_default_open).isVisible =
|
menu.findItem(R.id.remove_default_open).isVisible =
|
||||||
onlyOneSelected &&
|
onlyOneSelected &&
|
||||||
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].name == defaultVolumeName
|
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].name == volumeOpener.defaultVolumeName
|
||||||
with(menu.findItem(R.id.copy)) {
|
with(menu.findItem(R.id.copy)) {
|
||||||
isVisible = onlyOneSelected
|
isVisible = onlyOneSelected
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
@ -371,7 +348,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
menu.findItem(R.id.rename).isVisible = onlyOneAndWriteable
|
menu.findItem(R.id.rename).isVisible = onlyOneAndWriteable
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(isSelecting || pickMode || dropMode)
|
supportActionBar?.setDisplayHomeAsUpEnabled(isSelecting || explorerRouter.pickMode || explorerRouter.dropMode)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,7 +371,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
dstRootDirectory.name?.let { name ->
|
dstRootDirectory.name?.let { name ->
|
||||||
val path = PathUtils.getFullPathFromTreeUri(dstRootDirectory.uri, this)
|
val path = PathUtils.getFullPathFromTreeUri(dstRootDirectory.uri, this)
|
||||||
if (path == null) null
|
if (path == null) null
|
||||||
else SavedVolume(
|
else VolumeData(
|
||||||
PathUtils.pathJoin(path, name),
|
PathUtils.pathJoin(path, name),
|
||||||
false,
|
false,
|
||||||
volume.type,
|
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 {
|
lifecycleScope.launch {
|
||||||
val result = fileOperationService.copyVolume(srcDocumentFile, dstDocumentFile)
|
val result = fileOperationService.copyVolume(srcDocumentFile, dstDocumentFile)
|
||||||
when {
|
when {
|
||||||
@ -417,7 +394,11 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
result.dstRootDirectory?.let {
|
result.dstRootDirectory?.let {
|
||||||
getResultVolume(it)?.let { volume ->
|
getResultVolume(it)?.let { volume ->
|
||||||
volumeDatabase.saveVolume(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()
|
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 ->
|
with (EditTextDialog(this, R.string.new_volume_name) { newName ->
|
||||||
val srcPath = File(volume.getFullPath(filesDir.path))
|
val srcPath = File(volume.getFullPath(filesDir.path))
|
||||||
val dstPath = File(srcPath.parent, newName).canonicalFile
|
val dstPath = File(srcPath.parent, newName).canonicalFile
|
||||||
@ -453,12 +434,12 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
if (success) {
|
if (success) {
|
||||||
volumeDatabase.renameVolume(volume.name, newDBName)
|
volumeDatabase.renameVolume(volume.name, newDBName)
|
||||||
unselect(position)
|
unselect(position)
|
||||||
if (volume.name == defaultVolumeName) {
|
if (volume.name == volumeOpener.defaultVolumeName) {
|
||||||
with (sharedPrefs.edit()) {
|
with (sharedPrefs.edit()) {
|
||||||
putString(DEFAULT_VOLUME_KEY, newDBName)
|
putString(DEFAULT_VOLUME_KEY, newDBName)
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
defaultVolumeName = newDBName
|
volumeOpener.defaultVolumeName = newDBName
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, R.string.volume_rename_failed, Toast.LENGTH_SHORT).show()
|
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: VolumeData) {
|
||||||
private fun openVolume(volume: SavedVolume, position: Int) {
|
volumeOpener.openVolume(volume, true, object : VolumeOpener.VolumeOpenerCallbacks {
|
||||||
if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE && BuildConfig.GOCRYPTFS_DISABLED) {
|
override fun onHashStorageReset() {
|
||||||
Toast.makeText(this, R.string.gocryptfs_disabled, Toast.LENGTH_SHORT).show()
|
volumeAdapter.refresh()
|
||||||
return
|
}
|
||||||
} else if (volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE && BuildConfig.CRYFS_DISABLED) {
|
|
||||||
Toast.makeText(this, R.string.cryfs_disabled, Toast.LENGTH_SHORT).show()
|
override fun onVolumeOpened(encryptedVolume: EncryptedVolume, volumeShortName: String) {
|
||||||
return
|
startActivity(explorerRouter.getExplorerIntent(encryptedVolume, volumeShortName))
|
||||||
}
|
if (explorerRouter.pickMode) {
|
||||||
var askForPassword = true
|
shouldCloseVolume = false
|
||||||
fingerprintProtector?.let { fingerprintProtector ->
|
}
|
||||||
volume.encryptedHash?.let { encryptedHash ->
|
if (explorerRouter.dropMode || explorerRouter.pickMode) {
|
||||||
volume.iv?.let { iv ->
|
finish()
|
||||||
askForPassword = false
|
|
||||||
fingerprintProtector.listener = object : FingerprintProtector.Listener {
|
|
||||||
override fun onHashStorageReset() {
|
|
||||||
volumeAdapter.refresh()
|
|
||||||
}
|
|
||||||
override fun onPasswordHashDecrypted(hash: ByteArray) {
|
|
||||||
object : LoadingTask<EncryptedVolume?>(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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
if (askForPassword)
|
|
||||||
askForPassword(volume, position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onPasswordSubmitted(volume: SavedVolume, position: Int, dialogBinding: DialogOpenVolumeBinding) {
|
override fun onResume() {
|
||||||
if (dialogBinding.checkboxDefaultOpen.isChecked xor (defaultVolumeName == volume.name)) {
|
super.onResume()
|
||||||
with (sharedPrefs.edit()) {
|
shouldCloseVolume = true
|
||||||
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<ByteArray?>? = if (savePasswordHash) {
|
|
||||||
ObjRef(null)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
object : LoadingTask<EncryptedVolume?>(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<EncryptedVolume>(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 onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
if (pickMode && !usfKeepOpen) {
|
if (explorerRouter.pickMode && !usfKeepOpen && shouldCloseVolume) {
|
||||||
|
IntentUtils.getParcelableExtra<EncryptedVolume>(intent, "volume")?.close()
|
||||||
|
RestrictedFileProvider.wipeAll(this)
|
||||||
finish()
|
finish()
|
||||||
if (shouldCloseVolume) {
|
|
||||||
getParcelableExtra<EncryptedVolume>(intent, "volume")?.close()
|
|
||||||
RestrictedFileProvider.wipeAll(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
findPreference<ListPreference>("theme")?.setOnPreferenceChangeListener { _, newValue ->
|
findPreference<ListPreference>(ConstValues.THEME_VALUE_KEY)?.setOnPreferenceChangeListener { _, newValue ->
|
||||||
(activity as BaseActivity).onThemeChanged(newValue as String)
|
(activity as BaseActivity).onThemeChanged(newValue as String)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import android.os.Parcelable
|
|||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import java.io.File
|
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(
|
constructor(parcel: Parcel) : this(
|
||||||
parcel.readString()!!,
|
parcel.readString()!!,
|
||||||
@ -48,9 +48,9 @@ class SavedVolume(val name: String, val isHidden: Boolean = false, val type: Byt
|
|||||||
const val VOLUMES_DIRECTORY = "volumes"
|
const val VOLUMES_DIRECTORY = "volumes"
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val CREATOR = object : Parcelable.Creator<SavedVolume> {
|
val CREATOR = object : Parcelable.Creator<VolumeData> {
|
||||||
override fun createFromParcel(parcel: Parcel) = SavedVolume(parcel)
|
override fun createFromParcel(parcel: Parcel) = VolumeData(parcel)
|
||||||
override fun newArray(size: Int) = arrayOfNulls<SavedVolume>(size)
|
override fun newArray(size: Int) = arrayOfNulls<VolumeData>(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getHiddenVolumeFullPath(filesDir: String, name: String): String {
|
fun getHiddenVolumeFullPath(filesDir: String, name: String): String {
|
@ -2,6 +2,7 @@ package sushi.hardcore.droidfs
|
|||||||
|
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.database.Cursor
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
import android.util.Log
|
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_HASH = "hash"
|
||||||
const val COLUMN_IV = "iv"
|
const val COLUMN_IV = "iv"
|
||||||
|
|
||||||
private fun contentValuesFromVolume(volume: SavedVolume): ContentValues {
|
private fun contentValuesFromVolume(volume: VolumeData): ContentValues {
|
||||||
val contentValues = ContentValues()
|
val contentValues = ContentValues()
|
||||||
contentValues.put(COLUMN_NAME, volume.name)
|
contentValues.put(COLUMN_NAME, volume.name)
|
||||||
contentValues.put(COLUMN_HIDDEN, volume.isHidden)
|
contentValues.put(COLUMN_HIDDEN, volume.isHidden)
|
||||||
@ -38,7 +39,7 @@ class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, Co
|
|||||||
"$COLUMN_IV BLOB" +
|
"$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) {
|
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
@ -49,7 +50,7 @@ class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, Co
|
|||||||
}, null, null)
|
}, null, null)
|
||||||
|
|
||||||
// Moving hidden volumes to the "volumes" directory
|
// 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(
|
val cursor = db.query(
|
||||||
TABLE_NAME,
|
TABLE_NAME,
|
||||||
arrayOf(COLUMN_NAME),
|
arrayOf(COLUMN_NAME),
|
||||||
@ -68,7 +69,7 @@ class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, Co
|
|||||||
)
|
)
|
||||||
).renameTo(
|
).renameTo(
|
||||||
File(
|
File(
|
||||||
SavedVolume(
|
VolumeData(
|
||||||
volumeName,
|
volumeName,
|
||||||
true,
|
true,
|
||||||
EncryptedVolume.GOCRYPTFS_VOLUME_TYPE
|
EncryptedVolume.GOCRYPTFS_VOLUME_TYPE
|
||||||
@ -82,37 +83,55 @@ class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isVolumeSaved(volumeName: String, isHidden: Boolean): Boolean {
|
private fun extractVolumeData(cursor: Cursor): VolumeData {
|
||||||
val cursor = readableDatabase.query(TABLE_NAME,
|
return VolumeData(
|
||||||
arrayOf(COLUMN_NAME), "$COLUMN_NAME=? AND $COLUMN_HIDDEN=?",
|
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()),
|
arrayOf(volumeName, (if (isHidden) 1 else 0).toString()),
|
||||||
null, null, null
|
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
|
val result = cursor.count > 0
|
||||||
cursor.close()
|
cursor.close()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveVolume(volume: SavedVolume): Boolean {
|
fun saveVolume(volume: VolumeData): Boolean {
|
||||||
if (!isVolumeSaved(volume.name, volume.isHidden)) {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getVolumes(): List<SavedVolume> {
|
fun getVolumes(): List<VolumeData> {
|
||||||
val list: MutableList<SavedVolume> = ArrayList()
|
val list: MutableList<VolumeData> = ArrayList()
|
||||||
val cursor = readableDatabase.rawQuery("SELECT * FROM $TABLE_NAME", null)
|
val cursor = readableDatabase.rawQuery("SELECT * FROM $TABLE_NAME", null)
|
||||||
while (cursor.moveToNext()){
|
while (cursor.moveToNext()){
|
||||||
list.add(
|
list.add(extractVolumeData(cursor))
|
||||||
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))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
cursor.close()
|
cursor.close()
|
||||||
return list
|
return list
|
||||||
@ -130,14 +149,14 @@ class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, Co
|
|||||||
return isHashSaved
|
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
|
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(
|
return writableDatabase.update(
|
||||||
TABLE_NAME, contentValuesFromVolume(
|
TABLE_NAME, contentValuesFromVolume(
|
||||||
SavedVolume(
|
VolumeData(
|
||||||
volume.name,
|
volume.name,
|
||||||
volume.isHidden,
|
volume.isHidden,
|
||||||
volume.type,
|
volume.type,
|
||||||
|
201
app/src/main/java/sushi/hardcore/droidfs/VolumeOpener.kt
Normal file
201
app/src/main/java/sushi/hardcore/droidfs/VolumeOpener.kt
Normal file
@ -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<EncryptedVolume?>(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<ByteArray?>? = if (savePasswordHash) {
|
||||||
|
ObjRef(null)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
object : LoadingTask<EncryptedVolume?>(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ import android.widget.LinearLayout
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.SavedVolume
|
import sushi.hardcore.droidfs.VolumeData
|
||||||
import sushi.hardcore.droidfs.VolumeDatabase
|
import sushi.hardcore.droidfs.VolumeDatabase
|
||||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
|
|
||||||
@ -20,9 +20,9 @@ class VolumeAdapter(
|
|||||||
private val allowSelection: Boolean,
|
private val allowSelection: Boolean,
|
||||||
private val showReadOnly: Boolean,
|
private val showReadOnly: Boolean,
|
||||||
private val listener: Listener,
|
private val listener: Listener,
|
||||||
) : SelectableAdapter<SavedVolume>(listener::onSelectionChanged) {
|
) : SelectableAdapter<VolumeData>(listener::onSelectionChanged) {
|
||||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
lateinit var volumes: List<SavedVolume>
|
lateinit var volumes: List<VolumeData>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
reloadVolumes()
|
reloadVolumes()
|
||||||
@ -30,11 +30,11 @@ class VolumeAdapter(
|
|||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
fun onSelectionChanged(size: Int)
|
fun onSelectionChanged(size: Int)
|
||||||
fun onVolumeItemClick(volume: SavedVolume, position: Int)
|
fun onVolumeItemClick(volume: VolumeData, position: Int)
|
||||||
fun onVolumeItemLongClick()
|
fun onVolumeItemLongClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItems(): List<SavedVolume> {
|
override fun getItems(): List<VolumeData> {
|
||||||
return volumes
|
return volumes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,31 +2,39 @@ package sushi.hardcore.droidfs.add_volume
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import sushi.hardcore.droidfs.BaseActivity
|
import sushi.hardcore.droidfs.*
|
||||||
import sushi.hardcore.droidfs.ConstValues
|
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
||||||
import sushi.hardcore.droidfs.R
|
|
||||||
import sushi.hardcore.droidfs.databinding.ActivityAddVolumeBinding
|
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() {
|
class AddVolumeActivity: BaseActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val RESULT_VOLUME_ADDED = 1
|
const val RESULT_USER_BACK = 10
|
||||||
const val RESULT_HASH_STORAGE_RESET = 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var binding: ActivityAddVolumeBinding
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityAddVolumeBinding.inflate(layoutInflater)
|
binding = ActivityAddVolumeBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
usfKeepOpen = sharedPrefs.getBoolean("usf_keep_open", false)
|
||||||
|
explorerRouter = ExplorerRouter(this, intent)
|
||||||
|
volumeOpener = VolumeOpener(this)
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
supportFragmentManager
|
supportFragmentManager
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
.add(
|
.add(
|
||||||
R.id.fragment_container,
|
R.id.fragment_container,
|
||||||
SelectPathFragment.newInstance(themeValue),
|
SelectPathFragment.newInstance(themeValue, explorerRouter.pickMode),
|
||||||
)
|
)
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
@ -36,12 +44,21 @@ class AddVolumeActivity: BaseActivity() {
|
|||||||
if (item.itemId == android.R.id.home) {
|
if (item.itemId == android.R.id.home) {
|
||||||
if (supportFragmentManager.backStackEntryCount > 0)
|
if (supportFragmentManager.backStackEntryCount > 0)
|
||||||
supportFragmentManager.popBackStack()
|
supportFragmentManager.popBackStack()
|
||||||
else
|
else {
|
||||||
|
setResult(RESULT_USER_BACK)
|
||||||
|
shouldCloseVolume = false
|
||||||
finish()
|
finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item)
|
return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
setResult(RESULT_USER_BACK)
|
||||||
|
shouldCloseVolume = false
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
fun onFragmentLoaded(selectPathFragment: Boolean) {
|
fun onFragmentLoaded(selectPathFragment: Boolean) {
|
||||||
title = getString(
|
title = getString(
|
||||||
if (selectPathFragment) {
|
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()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onVolumeAdded(hashStorageReset: Boolean) {
|
fun onVolumeSelected(volume: VolumeData, rememberVolume: Boolean) {
|
||||||
setResult(if (hashStorageReset) RESULT_HASH_STORAGE_RESET else RESULT_VOLUME_ADDED)
|
if (rememberVolume) {
|
||||||
finish()
|
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
|
supportFragmentManager
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
.replace(
|
.replace(
|
||||||
@ -69,6 +97,7 @@ class AddVolumeActivity: BaseActivity() {
|
|||||||
themeValue,
|
themeValue,
|
||||||
volumePath,
|
volumePath,
|
||||||
isHidden,
|
isHidden,
|
||||||
|
rememberVolume,
|
||||||
sharedPrefs.getBoolean(ConstValues.PIN_PASSWORDS_KEY, false),
|
sharedPrefs.getBoolean(ConstValues.PIN_PASSWORDS_KEY, false),
|
||||||
sharedPrefs.getBoolean("usf_fingerprint", false),
|
sharedPrefs.getBoolean("usf_fingerprint", false),
|
||||||
)
|
)
|
||||||
@ -76,4 +105,18 @@ class AddVolumeActivity: BaseActivity() {
|
|||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
shouldCloseVolume = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
if (explorerRouter.pickMode && !usfKeepOpen && shouldCloseVolume) {
|
||||||
|
IntentUtils.getParcelableExtra<EncryptedVolume>(intent, "volume")?.close()
|
||||||
|
RestrictedFileProvider.wipeAll(this)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -23,13 +23,13 @@ import sushi.hardcore.droidfs.util.WidgetUtil
|
|||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
class CreateVolumeFragment: Fragment() {
|
class CreateVolumeFragment: Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val KEY_THEME_VALUE = "theme"
|
private const val KEY_THEME_VALUE = "theme"
|
||||||
private const val KEY_VOLUME_PATH = "path"
|
private const val KEY_VOLUME_PATH = "path"
|
||||||
private const val KEY_IS_HIDDEN = "hidden"
|
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_PIN_PASSWORDS = ConstValues.PIN_PASSWORDS_KEY
|
||||||
private const val KEY_USF_FINGERPRINT = "fingerprint"
|
private const val KEY_USF_FINGERPRINT = "fingerprint"
|
||||||
|
|
||||||
@ -37,6 +37,7 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
themeValue: String,
|
themeValue: String,
|
||||||
volumePath: String,
|
volumePath: String,
|
||||||
isHidden: Boolean,
|
isHidden: Boolean,
|
||||||
|
rememberVolume: Boolean,
|
||||||
pinPasswords: Boolean,
|
pinPasswords: Boolean,
|
||||||
usfFingerprint: Boolean,
|
usfFingerprint: Boolean,
|
||||||
): CreateVolumeFragment {
|
): CreateVolumeFragment {
|
||||||
@ -45,6 +46,7 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
putString(KEY_THEME_VALUE, themeValue)
|
putString(KEY_THEME_VALUE, themeValue)
|
||||||
putString(KEY_VOLUME_PATH, volumePath)
|
putString(KEY_VOLUME_PATH, volumePath)
|
||||||
putBoolean(KEY_IS_HIDDEN, isHidden)
|
putBoolean(KEY_IS_HIDDEN, isHidden)
|
||||||
|
putBoolean(KEY_REMEMBER_VOLUME, rememberVolume)
|
||||||
putBoolean(KEY_PIN_PASSWORDS, pinPasswords)
|
putBoolean(KEY_PIN_PASSWORDS, pinPasswords)
|
||||||
putBoolean(KEY_USF_FINGERPRINT, usfFingerprint)
|
putBoolean(KEY_USF_FINGERPRINT, usfFingerprint)
|
||||||
}
|
}
|
||||||
@ -57,6 +59,7 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
private val volumeTypes = ArrayList<String>(2)
|
private val volumeTypes = ArrayList<String>(2)
|
||||||
private lateinit var volumePath: String
|
private lateinit var volumePath: String
|
||||||
private var isHiddenVolume: Boolean = false
|
private var isHiddenVolume: Boolean = false
|
||||||
|
private var rememberVolume: Boolean = false
|
||||||
private var usfFingerprint: Boolean = false
|
private var usfFingerprint: Boolean = false
|
||||||
private lateinit var volumeDatabase: VolumeDatabase
|
private lateinit var volumeDatabase: VolumeDatabase
|
||||||
private var fingerprintProtector: FingerprintProtector? = null
|
private var fingerprintProtector: FingerprintProtector? = null
|
||||||
@ -76,6 +79,7 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
arguments.getString(KEY_THEME_VALUE)?.let { themeValue = it }
|
arguments.getString(KEY_THEME_VALUE)?.let { themeValue = it }
|
||||||
volumePath = arguments.getString(KEY_VOLUME_PATH)!!
|
volumePath = arguments.getString(KEY_VOLUME_PATH)!!
|
||||||
isHiddenVolume = arguments.getBoolean(KEY_IS_HIDDEN)
|
isHiddenVolume = arguments.getBoolean(KEY_IS_HIDDEN)
|
||||||
|
rememberVolume = arguments.getBoolean(KEY_REMEMBER_VOLUME)
|
||||||
usfFingerprint = arguments.getBoolean(KEY_USF_FINGERPRINT)
|
usfFingerprint = arguments.getBoolean(KEY_USF_FINGERPRINT)
|
||||||
arguments.getBoolean(KEY_PIN_PASSWORDS)
|
arguments.getBoolean(KEY_PIN_PASSWORDS)
|
||||||
}
|
}
|
||||||
@ -83,7 +87,7 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
fingerprintProtector = FingerprintProtector.new(requireActivity(), themeValue, volumeDatabase)
|
fingerprintProtector = FingerprintProtector.new(requireActivity(), themeValue, volumeDatabase)
|
||||||
}
|
}
|
||||||
if (!usfFingerprint || fingerprintProtector == null) {
|
if (!rememberVolume || !usfFingerprint || fingerprintProtector == null) {
|
||||||
binding.checkboxSavePassword.visibility = View.GONE
|
binding.checkboxSavePassword.visibility = View.GONE
|
||||||
}
|
}
|
||||||
if (!BuildConfig.GOCRYPTFS_DISABLED) {
|
if (!BuildConfig.GOCRYPTFS_DISABLED) {
|
||||||
@ -140,21 +144,6 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
(activity as AddVolumeActivity).onFragmentLoaded(false)
|
(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() {
|
private fun createVolume() {
|
||||||
val password = WidgetUtil.encodeEditTextContent(binding.editPassword)
|
val password = WidgetUtil.encodeEditTextContent(binding.editPassword)
|
||||||
val passwordConfirm = WidgetUtil.encodeEditTextContent(binding.editPasswordConfirm)
|
val passwordConfirm = WidgetUtil.encodeEditTextContent(binding.editPasswordConfirm)
|
||||||
@ -169,50 +158,73 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
object: LoadingTask<SavedVolume?>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) {
|
val encryptedVolume = if (rememberVolume) {
|
||||||
override suspend fun doTask(): SavedVolume? {
|
null
|
||||||
|
} else {
|
||||||
|
ObjRef<EncryptedVolume?>(null)
|
||||||
|
}
|
||||||
|
object: LoadingTask<Byte>(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)
|
val volumeFile = File(volumePath)
|
||||||
if (!volumeFile.exists())
|
if (!volumeFile.exists())
|
||||||
volumeFile.mkdirs()
|
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) {
|
val xchacha = when (binding.spinnerCipher.selectedItemPosition) {
|
||||||
0 -> 0
|
0 -> 0
|
||||||
1 -> 1
|
1 -> 1
|
||||||
else -> -1
|
else -> -1
|
||||||
}
|
}
|
||||||
saveVolume(GocryptfsVolume.createVolume(
|
generateResult(GocryptfsVolume.createAndOpenVolume(
|
||||||
volumePath,
|
volumePath,
|
||||||
password,
|
password,
|
||||||
false,
|
false,
|
||||||
xchacha,
|
xchacha,
|
||||||
GocryptfsVolume.ScryptDefaultLogN,
|
|
||||||
ConstValues.CREATOR,
|
|
||||||
returnedHash?.apply {
|
returnedHash?.apply {
|
||||||
value = ByteArray(GocryptfsVolume.KeyLen)
|
value = ByteArray(GocryptfsVolume.KeyLen)
|
||||||
}?.value,
|
}?.value,
|
||||||
|
encryptedVolume,
|
||||||
), EncryptedVolume.GOCRYPTFS_VOLUME_TYPE)
|
), EncryptedVolume.GOCRYPTFS_VOLUME_TYPE)
|
||||||
} else {
|
} else {
|
||||||
saveVolume(CryfsVolume.create(
|
generateResult(CryfsVolume.create(
|
||||||
volumePath,
|
volumePath,
|
||||||
CryfsVolume.getLocalStateDir(activity.filesDir.path),
|
CryfsVolume.getLocalStateDir(activity.filesDir.path),
|
||||||
password,
|
password,
|
||||||
returnedHash,
|
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)
|
), EncryptedVolume.CRYFS_VOLUME_TYPE)
|
||||||
}
|
}
|
||||||
Arrays.fill(password, 0)
|
Arrays.fill(password, 0)
|
||||||
return volume
|
return result
|
||||||
}
|
}
|
||||||
}.startTask(lifecycleScope) { volume ->
|
}.startTask(lifecycleScope) { result ->
|
||||||
if (volume == null) {
|
if (result.compareTo(-1) == 0) {
|
||||||
CustomAlertDialogBuilder(requireContext(), themeValue)
|
CustomAlertDialogBuilder(requireContext(), themeValue)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(R.string.create_volume_failed)
|
.setMessage(R.string.create_volume_failed)
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
} else {
|
} 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
|
@SuppressLint("NewApi") // if fingerprintProtector is null checkboxSavePassword is hidden
|
||||||
if (binding.checkboxSavePassword.isChecked && returnedHash != null) {
|
if (isVolumeSaved && binding.checkboxSavePassword.isChecked && returnedHash != null) {
|
||||||
fingerprintProtector!!.let {
|
fingerprintProtector!!.let {
|
||||||
it.listener = object : FingerprintProtector.Listener {
|
it.listener = object : FingerprintProtector.Listener {
|
||||||
override fun onHashStorageReset() {
|
override fun onHashStorageReset() {
|
||||||
@ -223,24 +235,32 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
override fun onPasswordHashDecrypted(hash: ByteArray) {} // shouldn't happen here
|
override fun onPasswordHashDecrypted(hash: ByteArray) {} // shouldn't happen here
|
||||||
override fun onPasswordHashSaved() {
|
override fun onPasswordHashSaved() {
|
||||||
Arrays.fill(returnedHash.value!!, 0)
|
Arrays.fill(returnedHash.value!!, 0)
|
||||||
onVolumeCreated()
|
onVolumeCreated(encryptedVolume?.value, volume.shortName)
|
||||||
}
|
}
|
||||||
override fun onFailed(pending: Boolean) {
|
override fun onFailed(pending: Boolean) {
|
||||||
if (!pending) {
|
if (!pending) {
|
||||||
Arrays.fill(returnedHash.value!!, 0)
|
Arrays.fill(returnedHash.value!!, 0)
|
||||||
onVolumeCreated()
|
onVolumeCreated(encryptedVolume?.value, volume.shortName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
it.savePasswordHash(volume, returnedHash.value!!)
|
it.savePasswordHash(volume, returnedHash.value!!)
|
||||||
}
|
}
|
||||||
} else onVolumeCreated()
|
} else {
|
||||||
|
onVolumeCreated(encryptedVolume?.value, volume.shortName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onVolumeCreated() {
|
private fun onVolumeCreated(encryptedVolume: EncryptedVolume?, volumeShortName: String) {
|
||||||
(activity as AddVolumeActivity).onVolumeAdded(hashStorageReset)
|
(activity as AddVolumeActivity).apply {
|
||||||
|
if (rememberVolume || encryptedVolume == null) {
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
startExplorer(encryptedVolume, volumeShortName)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ package sushi.hardcore.droidfs.add_volume
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@ -15,7 +16,11 @@ import android.widget.Toast
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
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.DialogSdcardErrorBinding
|
||||||
import sushi.hardcore.droidfs.databinding.FragmentSelectPathBinding
|
import sushi.hardcore.droidfs.databinding.FragmentSelectPathBinding
|
||||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
@ -26,11 +31,13 @@ import java.io.File
|
|||||||
class SelectPathFragment: Fragment() {
|
class SelectPathFragment: Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val KEY_THEME_VALUE = "theme"
|
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 {
|
return SelectPathFragment().apply {
|
||||||
arguments = Bundle().apply {
|
arguments = Bundle().apply {
|
||||||
putString(KEY_THEME_VALUE, themeValue)
|
putString(KEY_THEME_VALUE, themeValue)
|
||||||
|
putBoolean(KEY_PICK_MODE, pickMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,7 +46,7 @@ class SelectPathFragment: Fragment() {
|
|||||||
private lateinit var binding: FragmentSelectPathBinding
|
private lateinit var binding: FragmentSelectPathBinding
|
||||||
private val askStoragePermissions = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
private val askStoragePermissions = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
||||||
if (result[Manifest.permission.READ_EXTERNAL_STORAGE] == true && result[Manifest.permission.WRITE_EXTERNAL_STORAGE] == true)
|
if (result[Manifest.permission.READ_EXTERNAL_STORAGE] == true && result[Manifest.permission.WRITE_EXTERNAL_STORAGE] == true)
|
||||||
PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue)
|
launchPickDirectory()
|
||||||
else
|
else
|
||||||
CustomAlertDialogBuilder(requireContext(), themeValue)
|
CustomAlertDialogBuilder(requireContext(), themeValue)
|
||||||
.setTitle(R.string.storage_perm_denied)
|
.setTitle(R.string.storage_perm_denied)
|
||||||
@ -54,6 +61,12 @@ class SelectPathFragment: Fragment() {
|
|||||||
}
|
}
|
||||||
private var themeValue = ConstValues.DEFAULT_THEME_VALUE
|
private var themeValue = ConstValues.DEFAULT_THEME_VALUE
|
||||||
private lateinit var volumeDatabase: VolumeDatabase
|
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(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@ -65,15 +78,24 @@ class SelectPathFragment: Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
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?.let { arguments ->
|
||||||
arguments.getString(KEY_THEME_VALUE)?.let { themeValue = it }
|
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())
|
volumeDatabase = VolumeDatabase(requireContext())
|
||||||
|
filesDir = requireContext().filesDir.path
|
||||||
binding.containerHiddenVolume.setOnClickListener {
|
binding.containerHiddenVolume.setOnClickListener {
|
||||||
binding.switchHiddenVolume.performClick()
|
binding.switchHiddenVolume.performClick()
|
||||||
}
|
}
|
||||||
binding.switchHiddenVolume.setOnClickListener {
|
binding.switchHiddenVolume.setOnClickListener {
|
||||||
showRightSection()
|
showRightSection()
|
||||||
|
refreshStatus(binding.editVolumeName.text)
|
||||||
}
|
}
|
||||||
binding.buttonPickDirectory.setOnClickListener {
|
binding.buttonPickDirectory.setOnClickListener {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
@ -86,7 +108,7 @@ class SelectPathFragment: Fragment() {
|
|||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
)
|
)
|
||||||
PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue)
|
launchPickDirectory()
|
||||||
else
|
else
|
||||||
askStoragePermissions.launch(
|
askStoragePermissions.launch(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
@ -95,35 +117,18 @@ class SelectPathFragment: Fragment() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else
|
} else
|
||||||
PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue)
|
launchPickDirectory()
|
||||||
}
|
}
|
||||||
var isVolumeAlreadySaved = false
|
|
||||||
var volumeAction: Action? = null
|
|
||||||
binding.editVolumeName.addTextChangedListener(object: TextWatcher {
|
binding.editVolumeName.addTextChangedListener(object: TextWatcher {
|
||||||
override fun afterTextChanged(s: Editable?) {}
|
override fun afterTextChanged(s: Editable?) {}
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
isVolumeAlreadySaved = volumeDatabase.isVolumeSaved(s.toString(), binding.switchHiddenVolume.isChecked)
|
refreshStatus(s)
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
binding.editVolumeName.setOnEditorActionListener { _, _, _ -> onPathSelected(isVolumeAlreadySaved, volumeAction); true }
|
binding.switchRemember.setOnCheckedChangeListener { _, _ -> refreshButtonText() }
|
||||||
binding.buttonAction.setOnClickListener { onPathSelected(isVolumeAlreadySaved, volumeAction) }
|
binding.editVolumeName.setOnEditorActionListener { _, _, _ -> onPathSelected(); true }
|
||||||
|
binding.buttonAction.setOnClickListener { onPathSelected() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||||
@ -132,6 +137,11 @@ class SelectPathFragment: Fragment() {
|
|||||||
showRightSection()
|
showRightSection()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun launchPickDirectory() {
|
||||||
|
(activity as AddVolumeActivity).shouldCloseVolume = false
|
||||||
|
PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue)
|
||||||
|
}
|
||||||
|
|
||||||
private fun showRightSection() {
|
private fun showRightSection() {
|
||||||
if (binding.switchHiddenVolume.isChecked) {
|
if (binding.switchHiddenVolume.isChecked) {
|
||||||
binding.textLabel.text = requireContext().getString(R.string.volume_name_label)
|
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) {
|
private fun onDirectoryPicked(uri: Uri) {
|
||||||
val path = PathUtils.getFullPathFromTreeUri(uri, requireContext())
|
val path = PathUtils.getFullPathFromTreeUri(uri, requireContext())
|
||||||
if (path != null)
|
if (path != null)
|
||||||
@ -158,95 +210,105 @@ class SelectPathFragment: Fragment() {
|
|||||||
|
|
||||||
private fun getCurrentVolumePath(): String {
|
private fun getCurrentVolumePath(): String {
|
||||||
return if (binding.switchHiddenVolume.isChecked)
|
return if (binding.switchHiddenVolume.isChecked)
|
||||||
SavedVolume.getHiddenVolumeFullPath(requireContext().filesDir.path, binding.editVolumeName.text.toString())
|
VolumeData.getHiddenVolumeFullPath(filesDir, binding.editVolumeName.text.toString())
|
||||||
else
|
else
|
||||||
binding.editVolumeName.text.toString()
|
binding.editVolumeName.text.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onPathSelected(isVolumeAlreadySaved: Boolean, volumeAction: Action?) {
|
private fun onPathSelected() {
|
||||||
if (isVolumeAlreadySaved) {
|
if (binding.switchRemember.isChecked != originalRememberVolume) {
|
||||||
(activity as AddVolumeActivity).onSelectedAlreadySavedVolume()
|
with(sharedPrefs.edit()) {
|
||||||
} else {
|
putBoolean(ConstValues.REMEMBER_VOLUME_KEY, binding.switchRemember.isChecked)
|
||||||
if (binding.switchHiddenVolume.isChecked && volumeAction == Action.CREATE) {
|
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)
|
CustomAlertDialogBuilder(requireContext(), themeValue)
|
||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setMessage(R.string.hidden_volume_warning)
|
.setMessage(R.string.hidden_volume_warning)
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
addVolume(volumeAction)
|
onNewVolumeSelected(currentVolumeValue, isHidden)
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
} else {
|
} else {
|
||||||
addVolume(volumeAction)
|
onNewVolumeSelected(currentVolumeValue, isHidden)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
(activity as AddVolumeActivity).onVolumeSelected(currentVolumeData!!, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addVolume(volumeAction: Action?) {
|
private fun onNewVolumeSelected(currentVolumeValue: String, isHidden: Boolean) {
|
||||||
val currentVolumeValue = binding.editVolumeName.text.toString()
|
val volumePath = getCurrentVolumePath()
|
||||||
val isHidden = binding.switchHiddenVolume.isChecked
|
when (volumeAction!!) {
|
||||||
if (currentVolumeValue.isEmpty()) {
|
Action.CREATE -> {
|
||||||
Toast.makeText(
|
val volumeFile = File(volumePath)
|
||||||
requireContext(),
|
var goodDirectory = false
|
||||||
if (isHidden) R.string.enter_volume_name else R.string.enter_volume_path,
|
if (volumeFile.isFile) {
|
||||||
Toast.LENGTH_SHORT
|
Toast.makeText(requireContext(), R.string.error_is_file, Toast.LENGTH_SHORT).show()
|
||||||
).show()
|
} else if (volumeFile.isDirectory) {
|
||||||
} else if (isHidden && currentVolumeValue.contains(PathUtils.SEPARATOR)) {
|
val dirContent = volumeFile.list()
|
||||||
Toast.makeText(requireContext(), R.string.error_slash_in_name, Toast.LENGTH_SHORT).show()
|
if (dirContent != null) {
|
||||||
} else {
|
if (dirContent.isEmpty()) {
|
||||||
val volumePath = getCurrentVolumePath()
|
if (volumeFile.canWrite()) {
|
||||||
when (volumeAction!!) {
|
goodDirectory = true
|
||||||
Action.CREATE -> {
|
} else {
|
||||||
val volumeFile = File(volumePath)
|
errorDirectoryNotWritable(volumePath)
|
||||||
var goodDirectory = false
|
}
|
||||||
if (volumeFile.isFile) {
|
} else {
|
||||||
Toast.makeText(requireContext(), R.string.error_is_file, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), R.string.dir_not_empty, 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()
|
|
||||||
} else {
|
} else {
|
||||||
if (File(PathUtils.getParentPath(volumePath)).canWrite())
|
Toast.makeText(requireContext(), R.string.listdir_null_error_msg, Toast.LENGTH_SHORT).show()
|
||||||
goodDirectory = true
|
}
|
||||||
else
|
} else {
|
||||||
errorDirectoryNotWritable(volumePath)
|
if (File(PathUtils.getParentPath(volumePath)).canWrite()) {
|
||||||
|
goodDirectory = true
|
||||||
|
} else {
|
||||||
|
errorDirectoryNotWritable(volumePath)
|
||||||
}
|
}
|
||||||
if (goodDirectory)
|
|
||||||
(activity as AddVolumeActivity).createVolume(volumePath, isHidden)
|
|
||||||
}
|
}
|
||||||
Action.ADD -> {
|
if (goodDirectory) {
|
||||||
val volumeType = EncryptedVolume.getVolumeType(volumePath)
|
(activity as AddVolumeActivity).createVolume(volumePath, isHidden, binding.switchRemember.isChecked)
|
||||||
if (volumeType < 0) {
|
}
|
||||||
CustomAlertDialogBuilder(requireContext(), themeValue)
|
}
|
||||||
.setTitle(R.string.error)
|
Action.ADD -> {
|
||||||
.setMessage(R.string.error_not_a_volume)
|
val volumeType = EncryptedVolume.getVolumeType(volumePath)
|
||||||
.setPositiveButton(R.string.ok, null)
|
if (volumeType < 0) {
|
||||||
.show()
|
CustomAlertDialogBuilder(requireContext(), themeValue)
|
||||||
} else if (!File(volumePath).canWrite()) {
|
.setTitle(R.string.error)
|
||||||
val dialog = CustomAlertDialogBuilder(requireContext(), themeValue)
|
.setMessage(R.string.error_not_a_volume)
|
||||||
.setTitle(R.string.warning)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.setCancelable(false)
|
.show()
|
||||||
.setPositiveButton(R.string.ok) { _, _ -> addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden, volumeType) }
|
} else if (!File(volumePath).canWrite()) {
|
||||||
if (PathUtils.isPathOnExternalStorage(volumePath, requireContext()))
|
val dialog = CustomAlertDialogBuilder(requireContext(), themeValue)
|
||||||
dialog.setView(
|
.setTitle(R.string.warning)
|
||||||
DialogSdcardErrorBinding.inflate(layoutInflater).apply {
|
.setCancelable(false)
|
||||||
path.text = PathUtils.getPackageDataFolder(requireContext())
|
.setPositiveButton(R.string.ok) { _, _ -> addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden, volumeType) }
|
||||||
footer.text = getString(R.string.sdcard_error_add_footer)
|
if (PathUtils.isPathOnExternalStorage(volumePath, requireContext())) {
|
||||||
}.root
|
dialog.setView(
|
||||||
)
|
DialogSdcardErrorBinding.inflate(layoutInflater).apply {
|
||||||
else
|
path.text = PathUtils.getPackageDataFolder(requireContext())
|
||||||
dialog.setMessage(R.string.add_cant_write_warning)
|
footer.text = getString(R.string.sdcard_error_add_footer)
|
||||||
dialog.show()
|
}.root
|
||||||
|
)
|
||||||
} else {
|
} 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) {
|
private fun addVolume(volumeName: String, isHidden: Boolean, volumeType: Byte) {
|
||||||
volumeDatabase.saveVolume(SavedVolume(volumeName, isHidden, volumeType))
|
val volumeData = VolumeData(volumeName, isHidden, volumeType)
|
||||||
(activity as AddVolumeActivity).onVolumeAdded(false)
|
if (binding.switchRemember.isChecked) {
|
||||||
|
volumeDatabase.saveVolume(volumeData)
|
||||||
|
}
|
||||||
|
(activity as AddVolumeActivity).onVolumeSelected(volumeData, binding.switchRemember.isChecked)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -39,6 +39,7 @@ import sushi.hardcore.droidfs.file_operations.OperationFile
|
|||||||
import sushi.hardcore.droidfs.file_viewers.*
|
import sushi.hardcore.droidfs.file_viewers.*
|
||||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
import sushi.hardcore.droidfs.filesystems.Stat
|
import sushi.hardcore.droidfs.filesystems.Stat
|
||||||
|
import sushi.hardcore.droidfs.util.IntentUtils
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
||||||
@ -82,7 +83,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
usf_open = sharedPrefs.getBoolean("usf_open", false)
|
usf_open = sharedPrefs.getBoolean("usf_open", false)
|
||||||
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
|
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
|
||||||
volumeName = intent.getStringExtra("volume_name") ?: ""
|
volumeName = intent.getStringExtra("volume_name") ?: ""
|
||||||
encryptedVolume = getParcelableExtra(intent, "volume")!!
|
encryptedVolume = IntentUtils.getParcelableExtra(intent, "volume")!!
|
||||||
sortOrderEntries = resources.getStringArray(R.array.sort_orders_entries)
|
sortOrderEntries = resources.getStringArray(R.array.sort_orders_entries)
|
||||||
sortOrderValues = resources.getStringArray(R.array.sort_orders_values)
|
sortOrderValues = resources.getStringArray(R.array.sort_orders_values)
|
||||||
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
|
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
|
||||||
@ -156,7 +157,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun init() {
|
protected open fun init() {
|
||||||
setContentView(R.layout.activity_explorer_base)
|
setContentView(R.layout.activity_explorer)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun bindFileOperationService(){
|
protected open fun bindFileOperationService(){
|
||||||
|
@ -8,16 +8,17 @@ import android.view.MenuItem
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import sushi.hardcore.droidfs.CameraActivity
|
import sushi.hardcore.droidfs.CameraActivity
|
||||||
import sushi.hardcore.droidfs.MainActivity
|
import sushi.hardcore.droidfs.MainActivity
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.adapters.IconTextDialogAdapter
|
import sushi.hardcore.droidfs.adapters.IconTextDialogAdapter
|
||||||
import sushi.hardcore.droidfs.content_providers.ExternalProvider
|
import sushi.hardcore.droidfs.content_providers.ExternalProvider
|
||||||
import sushi.hardcore.droidfs.databinding.ActivityExplorerBinding
|
|
||||||
import sushi.hardcore.droidfs.file_operations.OperationFile
|
import sushi.hardcore.droidfs.file_operations.OperationFile
|
||||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
import sushi.hardcore.droidfs.filesystems.Stat
|
import sushi.hardcore.droidfs.filesystems.Stat
|
||||||
|
import sushi.hardcore.droidfs.util.IntentUtils
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
||||||
@ -31,11 +32,10 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
private var usf_share = false
|
private var usf_share = false
|
||||||
private var currentItemAction = ItemsActions.NONE
|
private var currentItemAction = ItemsActions.NONE
|
||||||
private val itemsToProcess = ArrayList<OperationFile>()
|
private val itemsToProcess = ArrayList<OperationFile>()
|
||||||
private lateinit var binding: ActivityExplorerBinding
|
|
||||||
private val pickFromOtherVolumes = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private val pickFromOtherVolumes = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
result.data?.let { resultIntent ->
|
result.data?.let { resultIntent ->
|
||||||
val remoteEncryptedVolume = getParcelableExtra<EncryptedVolume>(resultIntent, "volume")!!
|
val remoteEncryptedVolume = IntentUtils.getParcelableExtra<EncryptedVolume>(resultIntent, "volume")!!
|
||||||
val path = resultIntent.getStringExtra("path")
|
val path = resultIntent.getStringExtra("path")
|
||||||
val operationFiles = ArrayList<OperationFile>()
|
val operationFiles = ArrayList<OperationFile>()
|
||||||
if (path == null){ //multiples elements
|
if (path == null){ //multiples elements
|
||||||
@ -168,9 +168,8 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
binding = ActivityExplorerBinding.inflate(layoutInflater)
|
super.init()
|
||||||
setContentView(binding.root)
|
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener {
|
||||||
binding.fab.setOnClickListener {
|
|
||||||
if (currentItemAction != ItemsActions.NONE){
|
if (currentItemAction != ItemsActions.NONE){
|
||||||
openDialogCreateFolder()
|
openDialogCreateFolder()
|
||||||
} else {
|
} else {
|
||||||
|
@ -5,17 +5,16 @@ import android.net.Uri
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.databinding.ActivityExplorerDropBinding
|
import sushi.hardcore.droidfs.util.IntentUtils
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
|
|
||||||
class ExplorerActivityDrop : BaseExplorerActivity() {
|
class ExplorerActivityDrop : BaseExplorerActivity() {
|
||||||
private lateinit var binding: ActivityExplorerDropBinding
|
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
binding = ActivityExplorerDropBinding.inflate(layoutInflater)
|
super.init()
|
||||||
setContentView(binding.root)
|
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener {
|
||||||
binding.fab.setOnClickListener {
|
|
||||||
openDialogCreateFolder()
|
openDialogCreateFolder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,7 +33,7 @@ class ExplorerActivityDrop : BaseExplorerActivity() {
|
|||||||
val errorMsg: String? = if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)) {
|
val errorMsg: String? = if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)) {
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
Intent.ACTION_SEND -> {
|
Intent.ACTION_SEND -> {
|
||||||
val uri = getParcelableExtra<Uri>(intent, Intent.EXTRA_STREAM)
|
val uri = IntentUtils.getParcelableExtra<Uri>(intent, Intent.EXTRA_STREAM)
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
getString(R.string.share_intent_parsing_failed)
|
getString(R.string.share_intent_parsing_failed)
|
||||||
} else {
|
} else {
|
||||||
|
@ -6,13 +6,14 @@ import android.view.Menu
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
|
import sushi.hardcore.droidfs.util.IntentUtils
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
|
|
||||||
class ExplorerActivityPick : BaseExplorerActivity() {
|
class ExplorerActivityPick : BaseExplorerActivity() {
|
||||||
private var resultIntent = Intent()
|
private var resultIntent = Intent()
|
||||||
private var isFinishingIntentionally = false
|
private var isFinishingIntentionally = false
|
||||||
override fun init() {
|
override fun init() {
|
||||||
super.init()
|
setContentView(R.layout.activity_explorer_pick)
|
||||||
resultIntent.putExtra("volume", encryptedVolume)
|
resultIntent.putExtra("volume", encryptedVolume)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
|||||||
|
|
||||||
override fun closeVolumeOnDestroy() {
|
override fun closeVolumeOnDestroy() {
|
||||||
if (!isFinishingIntentionally && !usf_keep_open){
|
if (!isFinishingIntentionally && !usf_keep_open){
|
||||||
getParcelableExtra<EncryptedVolume>(intent, "destinationVolume")?.close()
|
IntentUtils.getParcelableExtra<EncryptedVolume>(intent, "destinationVolume")?.close()
|
||||||
super.closeVolumeOnDestroy()
|
super.closeVolumeOnDestroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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<EncryptedVolume>(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
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import sushi.hardcore.droidfs.R
|
|||||||
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||||
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
|
import sushi.hardcore.droidfs.util.IntentUtils
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ abstract class FileViewerActivity: BaseActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
filePath = intent.getStringExtra("path")!!
|
filePath = intent.getStringExtra("path")!!
|
||||||
originalParentPath = PathUtils.getParentPath(filePath)
|
originalParentPath = PathUtils.getParentPath(filePath)
|
||||||
encryptedVolume = getParcelableExtra(intent, "volume")!!
|
encryptedVolume = IntentUtils.getParcelableExtra(intent, "volume")!!
|
||||||
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
|
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
|
||||||
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
|
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
|
||||||
windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
|
windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
|
||||||
|
@ -67,8 +67,14 @@ class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(baseDir: String, localStateDir: String, password: ByteArray, returnedHash: ObjRef<ByteArray?>?, cipher: String?): Boolean {
|
fun create(baseDir: String, localStateDir: String, password: ByteArray, returnedHash: ObjRef<ByteArray?>?, cipher: String?, volume: ObjRef<EncryptedVolume?>?): Boolean {
|
||||||
return init(baseDir, localStateDir, password, null, returnedHash, true, cipher)?.also { it.close() } != null
|
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<ByteArray?>?): CryfsVolume? {
|
fun init(baseDir: String, localStateDir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ObjRef<ByteArray?>?): CryfsVolume? {
|
||||||
|
@ -5,7 +5,7 @@ import android.net.Uri
|
|||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import sushi.hardcore.droidfs.ConstValues
|
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.explorers.ExplorerElement
|
||||||
import sushi.hardcore.droidfs.util.ObjRef
|
import sushi.hardcore.droidfs.util.ObjRef
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
@ -42,7 +42,7 @@ abstract class EncryptedVolume: Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun init(
|
fun init(
|
||||||
volume: SavedVolume,
|
volume: VolumeData,
|
||||||
filesDir: String,
|
filesDir: String,
|
||||||
password: ByteArray?,
|
password: ByteArray?,
|
||||||
givenHash: ByteArray?,
|
givenHash: ByteArray?,
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package sushi.hardcore.droidfs.filesystems
|
package sushi.hardcore.droidfs.filesystems
|
||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
|
import android.util.Log
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||||
|
import sushi.hardcore.droidfs.util.ObjRef
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() {
|
class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() {
|
||||||
@ -22,10 +24,20 @@ class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KeyLen = 32
|
const val KeyLen = 32
|
||||||
const val ScryptDefaultLogN = 16
|
private const val ScryptDefaultLogN = 16
|
||||||
const val MAX_KERNEL_WRITE = 128*1024
|
private const val VOLUME_CREATOR = "DroidFS"
|
||||||
|
private const val MAX_KERNEL_WRITE = 128*1024
|
||||||
const val CONFIG_FILE_NAME = "gocryptfs.conf"
|
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
|
private external fun nativeInit(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int
|
||||||
external fun changePassword(
|
external fun changePassword(
|
||||||
root_cipher_dir: String,
|
root_cipher_dir: String,
|
||||||
@ -35,6 +47,29 @@ class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() {
|
|||||||
returnedHash: ByteArray?
|
returnedHash: ByteArray?
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
|
fun createAndOpenVolume(
|
||||||
|
root_cipher_dir: String,
|
||||||
|
password: ByteArray,
|
||||||
|
plainTextNames: Boolean,
|
||||||
|
xchacha: Int,
|
||||||
|
returnedHash: ByteArray?,
|
||||||
|
volume: ObjRef<EncryptedVolume?>?
|
||||||
|
): 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? {
|
fun init(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): GocryptfsVolume? {
|
||||||
val sessionId = nativeInit(root_cipher_dir, password, givenHash, returnedHash)
|
val sessionId = nativeInit(root_cipher_dir, password, givenHash, returnedHash)
|
||||||
return if (sessionId == -1) {
|
return if (sessionId == -1) {
|
||||||
@ -52,7 +87,7 @@ class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() {
|
|||||||
constructor(parcel: Parcel) : this(parcel.readInt())
|
constructor(parcel: Parcel) : this(parcel.readInt())
|
||||||
|
|
||||||
override fun openFile(path: String): Long {
|
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 {
|
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 {
|
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 {
|
override fun rmdir(path: String): Boolean {
|
||||||
|
21
app/src/main/java/sushi/hardcore/droidfs/util/IntentUtils.kt
Normal file
21
app/src/main/java/sushi/hardcore/droidfs/util/IntentUtils.kt
Normal file
@ -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 <reified T: Parcelable> 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) }
|
||||||
|
}
|
||||||
|
}
|
@ -5,15 +5,18 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include "libgocryptfs.h"
|
#include "libgocryptfs.h"
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
const int KeyLen = 32;
|
||||||
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_createVolume(JNIEnv *env, jclass clazz,
|
|
||||||
|
JNIEXPORT jint JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_nativeCreateVolume(JNIEnv *env, jclass clazz,
|
||||||
jstring jroot_cipher_dir,
|
jstring jroot_cipher_dir,
|
||||||
jbyteArray jpassword,
|
jbyteArray jpassword,
|
||||||
jboolean plainTextNames,
|
jboolean plainTextNames,
|
||||||
jint xchacha,
|
jint xchacha,
|
||||||
jint logN,
|
jint logN,
|
||||||
jstring jcreator,
|
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* root_cipher_dir = (*env)->GetStringUTFChars(env, jroot_cipher_dir, NULL);
|
||||||
const char* creator = (*env)->GetStringUTFChars(env, jcreator, NULL);
|
const char* creator = (*env)->GetStringUTFChars(env, jcreator, NULL);
|
||||||
GoString gofilename = {root_cipher_dir, strlen(root_cipher_dir)}, gocreator = {creator, strlen(creator)};
|
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};
|
GoSlice go_password = {password, password_len, password_len};
|
||||||
|
|
||||||
size_t returned_hash_len;
|
size_t returned_hash_len;
|
||||||
jbyte* returned_hash;
|
GoSlice go_returned_hash;
|
||||||
GoSlice go_returned_hash = {NULL, 0, 0};
|
|
||||||
if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) {
|
if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) {
|
||||||
returned_hash_len = (*env)->GetArrayLength(env, jreturned_hash);
|
returned_hash_len = (*env)->GetArrayLength(env, jreturned_hash);
|
||||||
returned_hash = (*env)->GetByteArrayElements(env, jreturned_hash, NULL);
|
go_returned_hash.data = (*env)->GetByteArrayElements(env, jreturned_hash, NULL);
|
||||||
go_returned_hash.data = returned_hash;
|
} else if (open_after_creation) {
|
||||||
go_returned_hash.len = returned_hash_len;
|
returned_hash_len = KeyLen;
|
||||||
go_returned_hash.cap = returned_hash_len;
|
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);
|
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)->ReleaseByteArrayElements(env, jpassword, password, 0);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, jcreator, creator);
|
||||||
|
|
||||||
if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) {
|
GoInt sessionID = -2;
|
||||||
(*env)->ReleaseByteArrayElements(env, jreturned_hash, returned_hash, 0);
|
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<returned_hash_len; ++i) {
|
||||||
|
((unsigned char*) go_returned_hash.data)[i] = 0;
|
||||||
|
}
|
||||||
|
free(go_returned_hash.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionID*open_after_creation+result*!open_after_creation;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<include layout="@layout/explorer_info_bar"/>
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<include layout="@layout/explorer_content"/>
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/fab"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_margin="15dp"
|
|
||||||
android:src="@drawable/icon_add"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
@ -101,6 +101,14 @@
|
|||||||
android:layout_marginHorizontal="@dimen/volume_operation_horizontal_gap"
|
android:layout_marginHorizontal="@dimen/volume_operation_horizontal_gap"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
|
android:id="@+id/switch_remember"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/remember_volume"
|
||||||
|
android:checked="true"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
android:id="@+id/button_action"
|
android:id="@+id/button_action"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -108,6 +116,6 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout_marginHorizontal="@dimen/volume_operation_button_horizontal_margin"
|
android:layout_marginHorizontal="@dimen/volume_operation_button_horizontal_margin"
|
||||||
android:layout_marginTop="@dimen/volume_operation_vertical_gap"
|
android:layout_marginTop="@dimen/volume_operation_vertical_gap"
|
||||||
android:text="@string/add_volume" />
|
android:text="@string/create_volume" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -250,4 +250,7 @@
|
|||||||
<string name="volume_type_read_only">(%s, read-only)</string>
|
<string name="volume_type_read_only">(%s, read-only)</string>
|
||||||
<string name="io_error">I/O Error.</string>
|
<string name="io_error">I/O Error.</string>
|
||||||
<string name="use_fingerprint">Use fingerprint instead of current password</string>
|
<string name="use_fingerprint">Use fingerprint instead of current password</string>
|
||||||
|
<string name="remember_volume">Remember volume</string>
|
||||||
|
<string name="open_volume">Open volume</string>
|
||||||
|
<string name="choose_existing_volume">Please choose an existing volume</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user