2020-07-17 16:35:39 +02:00
|
|
|
package sushi.hardcore.droidfs
|
|
|
|
|
2022-03-05 12:51:02 +01:00
|
|
|
import android.annotation.SuppressLint
|
2022-03-06 14:45:52 +01:00
|
|
|
import android.content.ComponentName
|
|
|
|
import android.content.Context
|
2020-07-17 16:35:39 +02:00
|
|
|
import android.content.Intent
|
2022-03-06 14:45:52 +01:00
|
|
|
import android.content.ServiceConnection
|
|
|
|
import android.net.Uri
|
2022-03-05 12:51:02 +01:00
|
|
|
import android.os.Build
|
2020-07-17 16:35:39 +02:00
|
|
|
import android.os.Bundle
|
2022-03-06 14:45:52 +01:00
|
|
|
import android.os.IBinder
|
2022-04-17 17:38:49 +02:00
|
|
|
import android.text.InputType
|
2021-06-07 14:55:01 +02:00
|
|
|
import android.view.Menu
|
|
|
|
import android.view.MenuItem
|
2022-03-05 12:51:02 +01:00
|
|
|
import android.view.View
|
|
|
|
import android.view.WindowManager
|
2022-03-06 14:45:52 +01:00
|
|
|
import android.widget.Toast
|
2022-03-05 12:51:02 +01:00
|
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
2022-03-06 14:45:52 +01:00
|
|
|
import androidx.documentfile.provider.DocumentFile
|
2022-04-20 15:17:33 +02:00
|
|
|
import androidx.lifecycle.lifecycleScope
|
2022-03-05 12:51:02 +01:00
|
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
2022-04-20 15:17:33 +02:00
|
|
|
import kotlinx.coroutines.launch
|
2022-03-05 12:51:02 +01:00
|
|
|
import sushi.hardcore.droidfs.adapters.VolumeAdapter
|
|
|
|
import sushi.hardcore.droidfs.add_volume.AddVolumeActivity
|
|
|
|
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
2021-06-11 20:23:54 +02:00
|
|
|
import sushi.hardcore.droidfs.databinding.ActivityMainBinding
|
2022-03-05 12:51:02 +01:00
|
|
|
import sushi.hardcore.droidfs.databinding.DialogDeleteVolumeBinding
|
|
|
|
import sushi.hardcore.droidfs.databinding.DialogOpenVolumeBinding
|
|
|
|
import sushi.hardcore.droidfs.explorers.ExplorerActivity
|
|
|
|
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
|
|
|
|
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
|
2022-03-06 14:45:52 +01:00
|
|
|
import sushi.hardcore.droidfs.file_operations.FileOperationService
|
2022-06-18 21:13:16 +02:00
|
|
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
2022-06-29 13:43:56 +02:00
|
|
|
import sushi.hardcore.droidfs.util.ObjRef
|
2022-03-05 12:51:02 +01:00
|
|
|
import sushi.hardcore.droidfs.util.PathUtils
|
2022-06-18 21:13:16 +02:00
|
|
|
import sushi.hardcore.droidfs.util.WidgetUtil
|
2021-11-09 11:12:09 +01:00
|
|
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
2022-03-24 20:08:23 +01:00
|
|
|
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
2022-03-05 12:51:02 +01:00
|
|
|
import java.io.File
|
|
|
|
import java.util.*
|
2020-07-17 16:35:39 +02:00
|
|
|
|
2022-04-17 15:52:34 +02:00
|
|
|
class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
2021-06-11 20:23:54 +02:00
|
|
|
|
2022-04-11 15:14:15 +02:00
|
|
|
companion object {
|
|
|
|
const val DEFAULT_VOLUME_KEY = "default_volume"
|
|
|
|
}
|
|
|
|
|
2022-03-05 12:51:02 +01:00
|
|
|
private lateinit var binding: ActivityMainBinding
|
|
|
|
private lateinit var volumeDatabase: VolumeDatabase
|
|
|
|
private lateinit var volumeAdapter: VolumeAdapter
|
|
|
|
private var fingerprintProtector: FingerprintProtector? = null
|
|
|
|
private var usfFingerprint: Boolean = false
|
|
|
|
private var usfKeepOpen: Boolean = false
|
2022-04-11 15:14:15 +02:00
|
|
|
private var defaultVolumeName: String? = null
|
2022-03-05 12:51:02 +01:00
|
|
|
private var addVolume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
|
|
|
when (result.resultCode) {
|
2022-03-06 14:45:52 +01:00
|
|
|
AddVolumeActivity.RESULT_VOLUME_ADDED -> onVolumeAdded()
|
2022-03-05 12:51:02 +01:00
|
|
|
AddVolumeActivity.RESULT_HASH_STORAGE_RESET -> {
|
|
|
|
volumeAdapter.refresh()
|
|
|
|
binding.textNoVolumes.visibility = View.GONE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
private var changePasswordPosition: Int? = null
|
|
|
|
private var changePassword = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
2022-03-06 14:45:52 +01:00
|
|
|
changePasswordPosition?.let { unselect(it) }
|
|
|
|
}
|
|
|
|
private val pickDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
|
|
|
|
if (uri != null)
|
|
|
|
onDirectoryPicked(uri)
|
2022-03-05 12:51:02 +01:00
|
|
|
}
|
2022-03-06 14:45:52 +01:00
|
|
|
private lateinit var fileOperationService: FileOperationService
|
2022-03-05 12:51:02 +01:00
|
|
|
private var pickMode = false
|
|
|
|
private var dropMode = false
|
|
|
|
private var shouldCloseVolume = true // used when launched to pick file from another volume
|
|
|
|
|
2020-07-17 16:35:39 +02:00
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
super.onCreate(savedInstanceState)
|
2022-03-05 12:51:02 +01:00
|
|
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
2021-06-11 20:23:54 +02:00
|
|
|
setContentView(binding.root)
|
2021-11-09 11:12:09 +01:00
|
|
|
if (sharedPrefs.getBoolean("applicationFirstOpening", true)) {
|
|
|
|
CustomAlertDialogBuilder(this, themeValue)
|
|
|
|
.setTitle(R.string.warning)
|
|
|
|
.setMessage(R.string.usf_home_warning_msg)
|
|
|
|
.setCancelable(false)
|
|
|
|
.setPositiveButton(R.string.see_unsafe_features) { _, _ ->
|
|
|
|
val intent = Intent(this, SettingsActivity::class.java)
|
|
|
|
intent.putExtra("screen", "UnsafeFeaturesSettingsFragment")
|
|
|
|
startActivity(intent)
|
|
|
|
}
|
|
|
|
.setNegativeButton(R.string.ok, null)
|
2022-03-24 20:08:23 +01:00
|
|
|
.setOnDismissListener {
|
|
|
|
with (sharedPrefs.edit()) {
|
|
|
|
putBoolean("applicationFirstOpening", false)
|
|
|
|
apply()
|
|
|
|
}
|
|
|
|
}
|
2021-11-09 11:12:09 +01:00
|
|
|
.show()
|
2020-07-17 16:35:39 +02:00
|
|
|
}
|
2022-03-05 12:51:02 +01:00
|
|
|
pickMode = intent.action == "pick"
|
|
|
|
dropMode = (intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_SEND_MULTIPLE) && intent.extras != null
|
|
|
|
volumeDatabase = VolumeDatabase(this)
|
|
|
|
volumeAdapter = VolumeAdapter(
|
|
|
|
this,
|
|
|
|
volumeDatabase,
|
|
|
|
!pickMode && !dropMode,
|
|
|
|
!dropMode,
|
2022-04-17 15:52:34 +02:00
|
|
|
this,
|
2022-03-05 12:51:02 +01:00
|
|
|
)
|
|
|
|
binding.recyclerViewVolumes.adapter = volumeAdapter
|
|
|
|
binding.recyclerViewVolumes.layoutManager = LinearLayoutManager(this)
|
|
|
|
if (volumeAdapter.volumes.isEmpty()) {
|
|
|
|
binding.textNoVolumes.visibility = View.VISIBLE
|
2021-06-07 16:34:50 +02:00
|
|
|
}
|
2022-03-05 12:51:02 +01:00
|
|
|
if (pickMode) {
|
|
|
|
title = getString(R.string.select_volume)
|
|
|
|
binding.fab.visibility = View.GONE
|
|
|
|
} else {
|
|
|
|
binding.fab.setOnClickListener {
|
|
|
|
addVolume.launch(Intent(this, AddVolumeActivity::class.java))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
usfKeepOpen = sharedPrefs.getBoolean("usf_keep_open", false)
|
|
|
|
usfFingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
fingerprintProtector = FingerprintProtector.new(this, themeValue, volumeDatabase)
|
2021-06-07 16:34:50 +02:00
|
|
|
}
|
2022-04-11 15:14:15 +02:00
|
|
|
defaultVolumeName = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null)
|
|
|
|
defaultVolumeName?.let { name ->
|
|
|
|
try {
|
|
|
|
val (position, volume) = volumeAdapter.volumes.withIndex().first { it.value.name == name }
|
|
|
|
openVolume(volume, position)
|
|
|
|
} catch (e: NoSuchElementException) {
|
|
|
|
unsetDefaultVolume()
|
|
|
|
}
|
|
|
|
}
|
2022-03-06 14:45:52 +01:00
|
|
|
Intent(this, FileOperationService::class.java).also {
|
|
|
|
bindService(it, object : ServiceConnection {
|
|
|
|
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
|
|
|
fileOperationService = (service as FileOperationService.LocalBinder).getService()
|
|
|
|
}
|
|
|
|
override fun onServiceDisconnected(arg0: ComponentName) {}
|
|
|
|
}, Context.BIND_AUTO_CREATE)
|
|
|
|
}
|
2022-03-05 12:51:02 +01:00
|
|
|
}
|
|
|
|
|
2022-04-11 15:14:15 +02:00
|
|
|
override fun onStart() {
|
|
|
|
super.onStart()
|
|
|
|
// refresh this in case another instance of MainActivity changes its value
|
|
|
|
defaultVolumeName = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null)
|
|
|
|
}
|
|
|
|
|
2022-04-17 15:52:34 +02:00
|
|
|
override fun onSelectionChanged(size: Int) {
|
|
|
|
title = if (size == 0) {
|
|
|
|
getString(R.string.app_name)
|
|
|
|
} else {
|
|
|
|
getString(R.string.elements_selected, size, volumeAdapter.volumes.size)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-18 21:13:16 +02:00
|
|
|
override fun onVolumeItemClick(volume: SavedVolume, position: Int) {
|
2022-03-05 12:51:02 +01:00
|
|
|
if (volumeAdapter.selectedItems.isEmpty())
|
|
|
|
openVolume(volume, position)
|
|
|
|
else
|
|
|
|
invalidateOptionsMenu()
|
|
|
|
}
|
|
|
|
|
2022-04-17 15:52:34 +02:00
|
|
|
override fun onVolumeItemLongClick() {
|
2022-03-05 12:51:02 +01:00
|
|
|
invalidateOptionsMenu()
|
|
|
|
}
|
|
|
|
|
2022-03-06 14:45:52 +01:00
|
|
|
private fun onVolumeAdded() {
|
|
|
|
volumeAdapter.apply {
|
|
|
|
volumes = volumeDatabase.getVolumes()
|
|
|
|
notifyItemInserted(volumes.size)
|
|
|
|
}
|
|
|
|
binding.textNoVolumes.visibility = View.GONE
|
|
|
|
}
|
|
|
|
|
2022-04-09 19:20:20 +02:00
|
|
|
private fun unselectAll(notifyChange: Boolean = true) {
|
|
|
|
volumeAdapter.unSelectAll(notifyChange)
|
2022-03-05 12:51:02 +01:00
|
|
|
invalidateOptionsMenu()
|
|
|
|
}
|
|
|
|
|
2022-03-06 14:45:52 +01:00
|
|
|
private fun unselect(position: Int) {
|
|
|
|
volumeAdapter.selectedItems.remove(position)
|
|
|
|
volumeAdapter.onVolumeChanged(position)
|
2022-04-17 15:52:34 +02:00
|
|
|
onSelectionChanged(0) // unselect() is always called when only one element is selected
|
2022-03-06 14:45:52 +01:00
|
|
|
invalidateOptionsMenu()
|
|
|
|
}
|
|
|
|
|
2022-06-18 21:13:16 +02:00
|
|
|
private fun removeVolumes(volumes: List<SavedVolume>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) {
|
2022-03-05 12:51:02 +01:00
|
|
|
if (i < volumes.size) {
|
|
|
|
if (volumes[i].isHidden) {
|
|
|
|
if (doDeleteVolumeContent == null) {
|
|
|
|
val dialogBinding = DialogDeleteVolumeBinding.inflate(layoutInflater)
|
|
|
|
dialogBinding.textContent.text = getString(R.string.delete_hidden_volume_question, volumes[i].name)
|
|
|
|
// show checkbox only if there is at least one other hidden volume
|
|
|
|
for (j in (i+1 until volumes.size)) {
|
|
|
|
if (volumes[j].isHidden) {
|
|
|
|
dialogBinding.checkboxApplyToAll.visibility = View.VISIBLE
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CustomAlertDialogBuilder(this, themeValue)
|
|
|
|
.setTitle(R.string.warning)
|
|
|
|
.setView(dialogBinding.root)
|
|
|
|
.setPositiveButton(R.string.forget_only) { _, _ ->
|
|
|
|
volumeDatabase.removeVolume(volumes[i].name)
|
|
|
|
removeVolumes(volumes, i + 1, if (dialogBinding.checkboxApplyToAll.isChecked) false else null)
|
|
|
|
}
|
|
|
|
.setNegativeButton(R.string.delete_volume) { _, _ ->
|
|
|
|
PathUtils.recursiveRemoveDirectory(File(volumes[i].getFullPath(filesDir.path)))
|
|
|
|
volumeDatabase.removeVolume(volumes[i].name)
|
|
|
|
removeVolumes(volumes, i + 1, if (dialogBinding.checkboxApplyToAll.isChecked) true else null)
|
|
|
|
}
|
|
|
|
.setOnCancelListener {
|
|
|
|
volumeAdapter.refresh()
|
|
|
|
invalidateOptionsMenu()
|
|
|
|
}
|
|
|
|
.show()
|
|
|
|
} else {
|
|
|
|
if (doDeleteVolumeContent) {
|
|
|
|
PathUtils.recursiveRemoveDirectory(File(volumes[i].getFullPath(filesDir.path)))
|
|
|
|
}
|
|
|
|
volumeDatabase.removeVolume(volumes[i].name)
|
|
|
|
removeVolumes(volumes, i + 1, doDeleteVolumeContent)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
volumeDatabase.removeVolume(volumes[i].name)
|
|
|
|
removeVolumes(volumes, i + 1, doDeleteVolumeContent)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
volumeAdapter.refresh()
|
|
|
|
invalidateOptionsMenu()
|
|
|
|
if (volumeAdapter.volumes.isEmpty()) {
|
|
|
|
binding.textNoVolumes.visibility = View.VISIBLE
|
|
|
|
}
|
2021-06-07 16:34:50 +02:00
|
|
|
}
|
2020-07-17 16:35:39 +02:00
|
|
|
}
|
|
|
|
|
2022-04-11 15:14:15 +02:00
|
|
|
private fun unsetDefaultVolume() {
|
|
|
|
with (sharedPrefs.edit()) {
|
|
|
|
remove(DEFAULT_VOLUME_KEY)
|
|
|
|
apply()
|
|
|
|
}
|
|
|
|
defaultVolumeName = null
|
|
|
|
}
|
|
|
|
|
2020-07-17 16:35:39 +02:00
|
|
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
|
|
return when (item.itemId) {
|
2022-03-05 12:51:02 +01:00
|
|
|
android.R.id.home -> {
|
|
|
|
if (pickMode || dropMode) {
|
|
|
|
if (pickMode)
|
|
|
|
shouldCloseVolume = false
|
|
|
|
finish()
|
|
|
|
} else {
|
|
|
|
unselectAll()
|
|
|
|
}
|
|
|
|
true
|
|
|
|
}
|
|
|
|
R.id.select_all -> {
|
|
|
|
volumeAdapter.selectAll()
|
|
|
|
invalidateOptionsMenu()
|
|
|
|
true
|
|
|
|
}
|
|
|
|
R.id.remove -> {
|
|
|
|
val selectedVolumes = volumeAdapter.selectedItems.map { i -> volumeAdapter.volumes[i] }
|
|
|
|
removeVolumes(selectedVolumes)
|
|
|
|
true
|
|
|
|
}
|
|
|
|
R.id.forget_password -> {
|
|
|
|
for (i in volumeAdapter.selectedItems) {
|
|
|
|
if (volumeDatabase.removeHash(volumeAdapter.volumes[i]))
|
|
|
|
volumeAdapter.onVolumeChanged(i)
|
|
|
|
}
|
2022-04-09 19:20:20 +02:00
|
|
|
unselectAll(false)
|
2022-03-05 12:51:02 +01:00
|
|
|
true
|
|
|
|
}
|
|
|
|
R.id.change_password -> {
|
|
|
|
changePasswordPosition = volumeAdapter.selectedItems.elementAt(0)
|
|
|
|
changePassword.launch(Intent(this, ChangePasswordActivity::class.java).apply {
|
|
|
|
putExtra("volume", volumeAdapter.volumes[changePasswordPosition!!])
|
|
|
|
})
|
|
|
|
true
|
|
|
|
}
|
2022-04-11 15:14:15 +02:00
|
|
|
R.id.remove_default_open -> {
|
|
|
|
unsetDefaultVolume()
|
|
|
|
unselect(volumeAdapter.selectedItems.first())
|
|
|
|
true
|
|
|
|
}
|
2022-03-06 14:45:52 +01:00
|
|
|
R.id.copy -> {
|
|
|
|
val position = volumeAdapter.selectedItems.elementAt(0)
|
|
|
|
val volume = volumeAdapter.volumes[position]
|
|
|
|
when {
|
|
|
|
volume.isHidden -> {
|
|
|
|
PathUtils.safePickDirectory(pickDirectory, this, themeValue)
|
|
|
|
}
|
|
|
|
File(filesDir, volume.shortName).exists() -> {
|
|
|
|
CustomAlertDialogBuilder(this, themeValue)
|
|
|
|
.setTitle(R.string.error)
|
|
|
|
.setMessage(R.string.hidden_volume_already_exists)
|
|
|
|
.setPositiveButton(R.string.ok, null)
|
|
|
|
.show()
|
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
unselect(position)
|
|
|
|
copyVolume(
|
|
|
|
DocumentFile.fromFile(File(volume.name)),
|
|
|
|
DocumentFile.fromFile(filesDir),
|
|
|
|
) {
|
2022-06-18 21:13:16 +02:00
|
|
|
SavedVolume(volume.shortName, true, volume.type, volume.encryptedHash, volume.iv)
|
2022-03-06 14:45:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
true
|
|
|
|
}
|
2022-03-24 20:08:23 +01:00
|
|
|
R.id.rename -> {
|
|
|
|
val position = volumeAdapter.selectedItems.elementAt(0)
|
|
|
|
renameVolume(volumeAdapter.volumes[position], position)
|
|
|
|
true
|
|
|
|
}
|
2022-03-05 12:51:02 +01:00
|
|
|
R.id.settings -> {
|
2020-07-17 16:35:39 +02:00
|
|
|
val intent = Intent(this, SettingsActivity::class.java)
|
|
|
|
startActivity(intent)
|
|
|
|
true
|
|
|
|
}
|
|
|
|
else -> super.onOptionsItemSelected(item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
|
|
|
menuInflater.inflate(R.menu.main_activity, menu)
|
2022-03-05 12:51:02 +01:00
|
|
|
menu.findItem(R.id.settings).isVisible = !pickMode && !dropMode
|
|
|
|
val isSelecting = volumeAdapter.selectedItems.isNotEmpty()
|
2022-04-11 15:14:15 +02:00
|
|
|
menu.findItem(R.id.select_all).isVisible = isSelecting
|
|
|
|
menu.findItem(R.id.remove).isVisible = isSelecting
|
|
|
|
menu.findItem(R.id.forget_password).isVisible =
|
|
|
|
isSelecting &&
|
|
|
|
!volumeAdapter.selectedItems.any { i -> volumeAdapter.volumes[i].encryptedHash == null }
|
|
|
|
val onlyOneSelected = volumeAdapter.selectedItems.size == 1
|
|
|
|
val onlyOneAndWriteable =
|
|
|
|
onlyOneSelected &&
|
|
|
|
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].canWrite(filesDir.path)
|
2022-06-30 21:43:40 +02:00
|
|
|
menu.findItem(R.id.change_password).isVisible = onlyOneAndWriteable
|
2022-04-11 15:14:15 +02:00
|
|
|
menu.findItem(R.id.remove_default_open).isVisible =
|
|
|
|
onlyOneSelected &&
|
|
|
|
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].name == defaultVolumeName
|
2022-03-06 14:45:52 +01:00
|
|
|
with(menu.findItem(R.id.copy)) {
|
2022-04-11 15:14:15 +02:00
|
|
|
isVisible = onlyOneSelected
|
2022-03-06 14:45:52 +01:00
|
|
|
if (isVisible) {
|
|
|
|
setTitle(if (volumeAdapter.volumes[volumeAdapter.selectedItems.elementAt(0)].isHidden)
|
|
|
|
R.string.copy_hidden_volume
|
|
|
|
else
|
|
|
|
R.string.copy_external_volume
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2022-03-24 20:08:23 +01:00
|
|
|
menu.findItem(R.id.rename).isVisible = onlyOneAndWriteable
|
2022-03-05 12:51:02 +01:00
|
|
|
supportActionBar?.setDisplayHomeAsUpEnabled(isSelecting || pickMode || dropMode)
|
2020-07-17 16:35:39 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-03-06 14:45:52 +01:00
|
|
|
private fun onDirectoryPicked(uri: Uri) {
|
|
|
|
val position = volumeAdapter.selectedItems.elementAt(0)
|
|
|
|
val volume = volumeAdapter.volumes[position]
|
|
|
|
unselect(position)
|
|
|
|
val dstDocumentFile = DocumentFile.fromTreeUri(this, uri)
|
|
|
|
if (dstDocumentFile == null) {
|
|
|
|
CustomAlertDialogBuilder(this, themeValue)
|
|
|
|
.setTitle(R.string.error)
|
|
|
|
.setMessage(R.string.path_error)
|
|
|
|
.setPositiveButton(R.string.ok, null)
|
|
|
|
.show()
|
|
|
|
} else {
|
|
|
|
copyVolume(
|
|
|
|
DocumentFile.fromFile(File(volume.getFullPath(filesDir.path))),
|
|
|
|
dstDocumentFile,
|
|
|
|
) { dstRootDirectory ->
|
|
|
|
dstRootDirectory.name?.let { name ->
|
|
|
|
val path = PathUtils.getFullPathFromTreeUri(dstRootDirectory.uri, this)
|
|
|
|
if (path == null) null
|
2022-06-18 21:13:16 +02:00
|
|
|
else SavedVolume(
|
2022-03-06 14:45:52 +01:00
|
|
|
PathUtils.pathJoin(path, name),
|
|
|
|
false,
|
2022-06-18 21:13:16 +02:00
|
|
|
volume.type,
|
2022-03-06 14:45:52 +01:00
|
|
|
volume.encryptedHash,
|
|
|
|
volume.iv
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-18 21:13:16 +02:00
|
|
|
private fun copyVolume(srcDocumentFile: DocumentFile, dstDocumentFile: DocumentFile, getResultVolume: (DocumentFile) -> SavedVolume?) {
|
2022-04-20 15:17:33 +02:00
|
|
|
lifecycleScope.launch {
|
|
|
|
val result = fileOperationService.copyVolume(srcDocumentFile, dstDocumentFile)
|
|
|
|
when {
|
|
|
|
result.taskResult.cancelled -> {
|
|
|
|
result.dstRootDirectory?.delete()
|
|
|
|
}
|
|
|
|
result.taskResult.failedItem == null -> {
|
|
|
|
result.dstRootDirectory?.let {
|
2022-03-06 14:45:52 +01:00
|
|
|
getResultVolume(it)?.let { volume ->
|
|
|
|
volumeDatabase.saveVolume(volume)
|
|
|
|
onVolumeAdded()
|
2022-04-20 15:17:33 +02:00
|
|
|
Toast.makeText(this@MainActivity, R.string.copy_success, Toast.LENGTH_SHORT).show()
|
2022-03-06 14:45:52 +01:00
|
|
|
}
|
|
|
|
}
|
2022-04-20 15:17:33 +02:00
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
CustomAlertDialogBuilder(this@MainActivity, themeValue)
|
2022-03-06 14:45:52 +01:00
|
|
|
.setTitle(R.string.error)
|
2022-04-20 15:17:33 +02:00
|
|
|
.setMessage(getString(R.string.copy_failed, result.taskResult.failedItem.name))
|
2022-03-06 14:45:52 +01:00
|
|
|
.setPositiveButton(R.string.ok, null)
|
|
|
|
.show()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-18 21:13:16 +02:00
|
|
|
private fun renameVolume(volume: SavedVolume, position: Int) {
|
2022-04-18 14:49:35 +02:00
|
|
|
with (EditTextDialog(this, R.string.new_volume_name) { newName ->
|
2022-03-24 20:08:23 +01:00
|
|
|
val srcPath = File(volume.getFullPath(filesDir.path))
|
|
|
|
val dstPath = File(srcPath.parent, newName).canonicalFile
|
|
|
|
val newDBName: String
|
|
|
|
val success = if (volume.isHidden) {
|
2022-06-18 21:13:16 +02:00
|
|
|
if (newName.contains(PathUtils.SEPARATOR)) {
|
2022-03-24 20:08:23 +01:00
|
|
|
Toast.makeText(this, R.string.error_slash_in_name, Toast.LENGTH_SHORT).show()
|
|
|
|
renameVolume(volume, position)
|
|
|
|
return@EditTextDialog
|
|
|
|
}
|
|
|
|
newDBName = newName
|
|
|
|
srcPath.renameTo(dstPath)
|
|
|
|
} else {
|
|
|
|
newDBName = dstPath.path
|
|
|
|
DocumentFile.fromFile(srcPath).renameTo(newName)
|
|
|
|
}
|
|
|
|
if (success) {
|
|
|
|
volumeDatabase.renameVolume(volume.name, newDBName)
|
|
|
|
unselect(position)
|
2022-04-11 15:14:15 +02:00
|
|
|
if (volume.name == defaultVolumeName) {
|
|
|
|
with (sharedPrefs.edit()) {
|
|
|
|
putString(DEFAULT_VOLUME_KEY, newDBName)
|
|
|
|
apply()
|
|
|
|
}
|
|
|
|
defaultVolumeName = newDBName
|
|
|
|
}
|
2022-03-24 20:08:23 +01:00
|
|
|
} else {
|
|
|
|
Toast.makeText(this, R.string.volume_rename_failed, Toast.LENGTH_SHORT).show()
|
|
|
|
}
|
2022-04-18 14:49:35 +02:00
|
|
|
}) {
|
|
|
|
setSelectedText(volume.shortName)
|
|
|
|
show()
|
|
|
|
}
|
2022-03-24 20:08:23 +01:00
|
|
|
}
|
|
|
|
|
2022-03-05 12:51:02 +01:00
|
|
|
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
|
2022-06-18 21:13:16 +02:00
|
|
|
private fun openVolume(volume: SavedVolume, position: Int) {
|
|
|
|
if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE && BuildConfig.GOCRYPTFS_DISABLED) {
|
|
|
|
Toast.makeText(this, R.string.gocryptfs_disabled, Toast.LENGTH_SHORT).show()
|
|
|
|
return
|
|
|
|
} else if (volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE && BuildConfig.CRYFS_DISABLED) {
|
|
|
|
Toast.makeText(this, R.string.cryfs_disabled, Toast.LENGTH_SHORT).show()
|
|
|
|
return
|
|
|
|
}
|
2022-03-05 12:51:02 +01:00
|
|
|
var askForPassword = true
|
|
|
|
fingerprintProtector?.let { fingerprintProtector ->
|
|
|
|
volume.encryptedHash?.let { encryptedHash ->
|
|
|
|
volume.iv?.let { iv ->
|
|
|
|
askForPassword = false
|
|
|
|
fingerprintProtector.listener = object : FingerprintProtector.Listener {
|
|
|
|
override fun onHashStorageReset() {
|
|
|
|
volumeAdapter.refresh()
|
|
|
|
}
|
|
|
|
override fun onPasswordHashDecrypted(hash: ByteArray) {
|
2022-06-18 21:13:16 +02:00
|
|
|
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)
|
2022-03-05 12:51:02 +01:00
|
|
|
Arrays.fill(hash, 0)
|
2022-06-18 21:13:16 +02:00
|
|
|
return encryptedVolume
|
2022-04-20 15:17:33 +02:00
|
|
|
}
|
2022-06-18 21:13:16 +02:00
|
|
|
}.startTask(lifecycleScope) { encryptedVolume ->
|
|
|
|
if (encryptedVolume == null) {
|
2022-04-20 15:17:33 +02:00
|
|
|
CustomAlertDialogBuilder(this@MainActivity, themeValue)
|
|
|
|
.setTitle(R.string.open_volume_failed)
|
|
|
|
.setMessage(R.string.open_failed_hash_msg)
|
|
|
|
.setPositiveButton(R.string.ok, null)
|
|
|
|
.show()
|
2022-06-18 21:13:16 +02:00
|
|
|
} else {
|
|
|
|
startExplorer(encryptedVolume, volume.shortName)
|
2022-03-05 12:51:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
override fun onPasswordHashSaved() {}
|
2022-04-02 11:58:25 +02:00
|
|
|
override fun onFailed(pending: Boolean) {
|
|
|
|
if (!pending) {
|
|
|
|
askForPassword(volume, position)
|
|
|
|
}
|
|
|
|
}
|
2022-03-05 12:51:02 +01:00
|
|
|
}
|
|
|
|
fingerprintProtector.loadPasswordHash(volume.shortName, encryptedHash, iv)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (askForPassword)
|
|
|
|
askForPassword(volume, position)
|
|
|
|
}
|
|
|
|
|
2022-06-18 21:13:16 +02:00
|
|
|
private fun onPasswordSubmitted(volume: SavedVolume, position: Int, dialogBinding: DialogOpenVolumeBinding) {
|
2022-04-11 15:14:15 +02:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
2022-03-05 12:51:02 +01:00
|
|
|
// openVolumeWithPassword is responsible for wiping the password
|
|
|
|
openVolumeWithPassword(
|
|
|
|
volume,
|
|
|
|
position,
|
2022-06-18 21:13:16 +02:00
|
|
|
WidgetUtil.encodeEditTextContent(dialogBinding.editPassword),
|
2022-03-05 12:51:02 +01:00
|
|
|
dialogBinding.checkboxSavePassword.isChecked,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-06-18 21:13:16 +02:00
|
|
|
private fun askForPassword(volume: SavedVolume, position: Int, savePasswordHash: Boolean = false) {
|
2022-03-05 12:51:02 +01:00
|
|
|
val dialogBinding = DialogOpenVolumeBinding.inflate(layoutInflater)
|
2022-06-29 13:43:56 +02:00
|
|
|
if (!usfFingerprint || fingerprintProtector == null || volume.encryptedHash != null) {
|
2022-03-05 12:51:02 +01:00
|
|
|
dialogBinding.checkboxSavePassword.visibility = View.GONE
|
2022-04-21 10:46:31 +02:00
|
|
|
} else {
|
|
|
|
dialogBinding.checkboxSavePassword.isChecked = savePasswordHash
|
2022-03-05 12:51:02 +01:00
|
|
|
}
|
2022-04-11 15:14:15 +02:00
|
|
|
dialogBinding.checkboxDefaultOpen.isChecked = defaultVolumeName == volume.name
|
2022-03-05 12:51:02 +01:00
|
|
|
val dialog = CustomAlertDialogBuilder(this, themeValue)
|
2022-04-11 15:14:15 +02:00
|
|
|
.setTitle(getString(R.string.open_dialog_title, volume.shortName))
|
2022-03-05 12:51:02 +01:00
|
|
|
.setView(dialogBinding.root)
|
|
|
|
.setNegativeButton(R.string.cancel, null)
|
|
|
|
.setPositiveButton(R.string.open) { _, _ ->
|
|
|
|
onPasswordSubmitted(volume, position, dialogBinding)
|
|
|
|
}
|
|
|
|
.create()
|
2022-04-17 17:38:49 +02:00
|
|
|
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
|
|
|
|
}
|
2022-03-05 12:51:02 +01:00
|
|
|
}
|
|
|
|
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
|
|
|
dialog.show()
|
|
|
|
}
|
|
|
|
|
2022-06-18 21:13:16 +02:00
|
|
|
private fun openVolumeWithPassword(volume: SavedVolume, position: Int, password: ByteArray, savePasswordHash: Boolean) {
|
2022-06-29 13:43:56 +02:00
|
|
|
val returnedHash: ObjRef<ByteArray?>? = if (savePasswordHash) {
|
|
|
|
ObjRef(null)
|
|
|
|
} else {
|
|
|
|
null
|
2022-04-20 15:17:33 +02:00
|
|
|
}
|
2022-06-18 21:13:16 +02:00
|
|
|
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
|
2022-04-20 15:17:33 +02:00
|
|
|
}
|
2022-06-18 21:13:16 +02:00
|
|
|
}.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 {
|
2022-04-20 15:17:33 +02:00
|
|
|
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()
|
2022-03-05 12:51:02 +01:00
|
|
|
}
|
2022-04-20 15:17:33 +02:00
|
|
|
override fun onPasswordHashDecrypted(hash: ByteArray) {}
|
|
|
|
override fun onPasswordHashSaved() {
|
2022-06-29 13:43:56 +02:00
|
|
|
Arrays.fill(returnedHash.value!!, 0)
|
2022-04-20 15:17:33 +02:00
|
|
|
volumeAdapter.onVolumeChanged(position)
|
2022-06-18 21:13:16 +02:00
|
|
|
startExplorer(encryptedVolume, volume.shortName)
|
2022-04-20 15:17:33 +02:00
|
|
|
}
|
|
|
|
private var isClosed = false
|
|
|
|
override fun onFailed(pending: Boolean) {
|
|
|
|
if (!isClosed) {
|
2022-06-18 21:13:16 +02:00
|
|
|
encryptedVolume.close()
|
2022-04-20 15:17:33 +02:00
|
|
|
isClosed = true
|
2022-03-05 12:51:02 +01:00
|
|
|
}
|
2022-06-29 13:43:56 +02:00
|
|
|
Arrays.fill(returnedHash.value!!, 0)
|
2022-04-20 15:17:33 +02:00
|
|
|
}
|
|
|
|
}
|
2022-06-29 13:43:56 +02:00
|
|
|
fingerprintProtector.savePasswordHash(volume, returnedHash.value!!)
|
2022-04-20 15:17:33 +02:00
|
|
|
} else {
|
2022-06-18 21:13:16 +02:00
|
|
|
startExplorer(encryptedVolume, volume.shortName)
|
2022-04-20 15:17:33 +02:00
|
|
|
}
|
2022-03-05 12:51:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-18 21:13:16 +02:00
|
|
|
private fun startExplorer(encryptedVolume: EncryptedVolume, volumeShortName: String) {
|
2022-03-05 12:51:02 +01:00
|
|
|
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)
|
2022-06-18 21:13:16 +02:00
|
|
|
explorerIntent.putExtra("destinationVolume", intent.getParcelableExtra<EncryptedVolume>("volume")!!)
|
2022-03-05 12:51:02 +01:00
|
|
|
explorerIntent.flags = Intent.FLAG_ACTIVITY_FORWARD_RESULT
|
|
|
|
}
|
|
|
|
if (explorerIntent == null) {
|
|
|
|
explorerIntent = Intent(this, ExplorerActivity::class.java) //default opening
|
|
|
|
}
|
2022-06-18 21:13:16 +02:00
|
|
|
explorerIntent.putExtra("volume", encryptedVolume)
|
2022-03-05 12:51:02 +01:00
|
|
|
explorerIntent.putExtra("volume_name", volumeShortName)
|
|
|
|
startActivity(explorerIntent)
|
|
|
|
if (pickMode)
|
|
|
|
shouldCloseVolume = false
|
|
|
|
if (dropMode || pickMode)
|
|
|
|
finish()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onBackPressed() {
|
|
|
|
if (volumeAdapter.selectedItems.isNotEmpty()) {
|
|
|
|
unselectAll()
|
|
|
|
} else {
|
|
|
|
if (pickMode)
|
|
|
|
shouldCloseVolume = false
|
|
|
|
super.onBackPressed()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onStop() {
|
|
|
|
super.onStop()
|
|
|
|
if (pickMode && !usfKeepOpen) {
|
|
|
|
finish()
|
|
|
|
if (shouldCloseVolume) {
|
2022-06-18 21:13:16 +02:00
|
|
|
intent.getParcelableExtra<EncryptedVolume>("volume")?.close()
|
|
|
|
RestrictedFileProvider.wipeAll(this)
|
2022-03-05 12:51:02 +01:00
|
|
|
}
|
|
|
|
}
|
2020-07-17 16:35:39 +02:00
|
|
|
}
|
|
|
|
}
|