Encrypted overlay filesystems implementation for Android.
Also available on GitHub: https://github.com/hardcore-sushi/DroidFS
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
651 lines
28 KiB
651 lines
28 KiB
package sushi.hardcore.droidfs |
|
|
|
import android.annotation.SuppressLint |
|
import android.content.ComponentName |
|
import android.content.Context |
|
import android.content.Intent |
|
import android.content.ServiceConnection |
|
import android.net.Uri |
|
import android.os.Build |
|
import android.os.Bundle |
|
import android.os.IBinder |
|
import android.text.InputType |
|
import android.view.Menu |
|
import android.view.MenuItem |
|
import android.view.View |
|
import android.view.WindowManager |
|
import android.widget.Toast |
|
import androidx.activity.result.contract.ActivityResultContracts |
|
import androidx.documentfile.provider.DocumentFile |
|
import androidx.lifecycle.lifecycleScope |
|
import androidx.recyclerview.widget.LinearLayoutManager |
|
import kotlinx.coroutines.launch |
|
import sushi.hardcore.droidfs.adapters.VolumeAdapter |
|
import sushi.hardcore.droidfs.add_volume.AddVolumeActivity |
|
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider |
|
import sushi.hardcore.droidfs.databinding.ActivityMainBinding |
|
import sushi.hardcore.droidfs.databinding.DialogDeleteVolumeBinding |
|
import sushi.hardcore.droidfs.databinding.DialogOpenVolumeBinding |
|
import sushi.hardcore.droidfs.explorers.ExplorerActivity |
|
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop |
|
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick |
|
import sushi.hardcore.droidfs.file_operations.FileOperationService |
|
import sushi.hardcore.droidfs.util.PathUtils |
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder |
|
import sushi.hardcore.droidfs.widgets.EditTextDialog |
|
import java.io.File |
|
import java.util.* |
|
|
|
class MainActivity : BaseActivity(), VolumeAdapter.Listener { |
|
|
|
companion object { |
|
const val DEFAULT_VOLUME_KEY = "default_volume" |
|
} |
|
|
|
private lateinit var binding: ActivityMainBinding |
|
private lateinit var volumeDatabase: VolumeDatabase |
|
private lateinit var volumeAdapter: VolumeAdapter |
|
private var fingerprintProtector: FingerprintProtector? = null |
|
private var usfFingerprint: Boolean = false |
|
private var usfKeepOpen: Boolean = false |
|
private var defaultVolumeName: String? = null |
|
private var addVolume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> |
|
when (result.resultCode) { |
|
AddVolumeActivity.RESULT_VOLUME_ADDED -> onVolumeAdded() |
|
AddVolumeActivity.RESULT_HASH_STORAGE_RESET -> { |
|
volumeAdapter.refresh() |
|
binding.textNoVolumes.visibility = View.GONE |
|
} |
|
} |
|
} |
|
private var changePasswordPosition: Int? = null |
|
private var changePassword = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { |
|
changePasswordPosition?.let { unselect(it) } |
|
} |
|
private val pickDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri -> |
|
if (uri != null) |
|
onDirectoryPicked(uri) |
|
} |
|
private lateinit var fileOperationService: FileOperationService |
|
private var pickMode = false |
|
private var dropMode = false |
|
private var shouldCloseVolume = true // used when launched to pick file from another volume |
|
|
|
override fun onCreate(savedInstanceState: Bundle?) { |
|
super.onCreate(savedInstanceState) |
|
binding = ActivityMainBinding.inflate(layoutInflater) |
|
setContentView(binding.root) |
|
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) |
|
.setOnDismissListener { |
|
with (sharedPrefs.edit()) { |
|
putBoolean("applicationFirstOpening", false) |
|
apply() |
|
} |
|
} |
|
.show() |
|
} |
|
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, |
|
this, |
|
) |
|
binding.recyclerViewVolumes.adapter = volumeAdapter |
|
binding.recyclerViewVolumes.layoutManager = LinearLayoutManager(this) |
|
if (volumeAdapter.volumes.isEmpty()) { |
|
binding.textNoVolumes.visibility = View.VISIBLE |
|
} |
|
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) |
|
} |
|
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() |
|
} |
|
} |
|
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) |
|
} |
|
} |
|
|
|
override fun onStart() { |
|
super.onStart() |
|
// refresh this in case another instance of MainActivity changes its value |
|
defaultVolumeName = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null) |
|
} |
|
|
|
override fun onSelectionChanged(size: Int) { |
|
title = if (size == 0) { |
|
getString(R.string.app_name) |
|
} else { |
|
getString(R.string.elements_selected, size, volumeAdapter.volumes.size) |
|
} |
|
} |
|
|
|
override fun onVolumeItemClick(volume: Volume, position: Int) { |
|
if (volumeAdapter.selectedItems.isEmpty()) |
|
openVolume(volume, position) |
|
else |
|
invalidateOptionsMenu() |
|
} |
|
|
|
override fun onVolumeItemLongClick() { |
|
invalidateOptionsMenu() |
|
} |
|
|
|
private fun onVolumeAdded() { |
|
volumeAdapter.apply { |
|
volumes = volumeDatabase.getVolumes() |
|
notifyItemInserted(volumes.size) |
|
} |
|
binding.textNoVolumes.visibility = View.GONE |
|
} |
|
|
|
private fun unselectAll(notifyChange: Boolean = true) { |
|
volumeAdapter.unSelectAll(notifyChange) |
|
invalidateOptionsMenu() |
|
} |
|
|
|
private fun unselect(position: Int) { |
|
volumeAdapter.selectedItems.remove(position) |
|
volumeAdapter.onVolumeChanged(position) |
|
onSelectionChanged(0) // unselect() is always called when only one element is selected |
|
invalidateOptionsMenu() |
|
} |
|
|
|
private fun removeVolumes(volumes: List<Volume>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) { |
|
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 |
|
} |
|
} |
|
} |
|
|
|
private fun unsetDefaultVolume() { |
|
with (sharedPrefs.edit()) { |
|
remove(DEFAULT_VOLUME_KEY) |
|
apply() |
|
} |
|
defaultVolumeName = null |
|
} |
|
|
|
override fun onOptionsItemSelected(item: MenuItem): Boolean { |
|
return when (item.itemId) { |
|
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) |
|
} |
|
unselectAll(false) |
|
true |
|
} |
|
R.id.change_password -> { |
|
changePasswordPosition = volumeAdapter.selectedItems.elementAt(0) |
|
changePassword.launch(Intent(this, ChangePasswordActivity::class.java).apply { |
|
putExtra("volume", volumeAdapter.volumes[changePasswordPosition!!]) |
|
}) |
|
true |
|
} |
|
R.id.remove_default_open -> { |
|
unsetDefaultVolume() |
|
unselect(volumeAdapter.selectedItems.first()) |
|
true |
|
} |
|
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), |
|
) { |
|
Volume(volume.shortName, true, volume.encryptedHash, volume.iv) |
|
} |
|
} |
|
} |
|
true |
|
} |
|
R.id.rename -> { |
|
val position = volumeAdapter.selectedItems.elementAt(0) |
|
renameVolume(volumeAdapter.volumes[position], position) |
|
true |
|
} |
|
R.id.settings -> { |
|
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) |
|
menu.findItem(R.id.settings).isVisible = !pickMode && !dropMode |
|
val isSelecting = volumeAdapter.selectedItems.isNotEmpty() |
|
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) |
|
menu.findItem(R.id.change_password).isVisible = onlyOneAndWriteable |
|
menu.findItem(R.id.remove_default_open).isVisible = |
|
onlyOneSelected && |
|
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].name == defaultVolumeName |
|
with(menu.findItem(R.id.copy)) { |
|
isVisible = onlyOneSelected |
|
if (isVisible) { |
|
setTitle(if (volumeAdapter.volumes[volumeAdapter.selectedItems.elementAt(0)].isHidden) |
|
R.string.copy_hidden_volume |
|
else |
|
R.string.copy_external_volume |
|
) |
|
} |
|
} |
|
menu.findItem(R.id.rename).isVisible = onlyOneAndWriteable |
|
supportActionBar?.setDisplayHomeAsUpEnabled(isSelecting || pickMode || dropMode) |
|
return true |
|
} |
|
|
|
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 |
|
else Volume( |
|
PathUtils.pathJoin(path, name), |
|
false, |
|
volume.encryptedHash, |
|
volume.iv |
|
) |
|
} |
|
} |
|
} |
|
} |
|
|
|
private fun copyVolume(srcDocumentFile: DocumentFile, dstDocumentFile: DocumentFile, getResultVolume: (DocumentFile) -> Volume?) { |
|
lifecycleScope.launch { |
|
val result = fileOperationService.copyVolume(srcDocumentFile, dstDocumentFile) |
|
when { |
|
result.taskResult.cancelled -> { |
|
result.dstRootDirectory?.delete() |
|
} |
|
result.taskResult.failedItem == null -> { |
|
result.dstRootDirectory?.let { |
|
getResultVolume(it)?.let { volume -> |
|
volumeDatabase.saveVolume(volume) |
|
onVolumeAdded() |
|
Toast.makeText(this@MainActivity, R.string.copy_success, Toast.LENGTH_SHORT).show() |
|
} |
|
} |
|
} |
|
else -> { |
|
CustomAlertDialogBuilder(this@MainActivity, themeValue) |
|
.setTitle(R.string.error) |
|
.setMessage(getString(R.string.copy_failed, result.taskResult.failedItem.name)) |
|
.setPositiveButton(R.string.ok, null) |
|
.show() |
|
} |
|
} |
|
} |
|
} |
|
|
|
private fun renameVolume(volume: Volume, position: Int) { |
|
with (EditTextDialog(this, R.string.new_volume_name) { newName -> |
|
val srcPath = File(volume.getFullPath(filesDir.path)) |
|
val dstPath = File(srcPath.parent, newName).canonicalFile |
|
val newDBName: String |
|
val success = if (volume.isHidden) { |
|
if (newName.contains("/")) { |
|
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) |
|
if (volume.name == defaultVolumeName) { |
|
with (sharedPrefs.edit()) { |
|
putString(DEFAULT_VOLUME_KEY, newDBName) |
|
apply() |
|
} |
|
defaultVolumeName = newDBName |
|
} |
|
} else { |
|
Toast.makeText(this, R.string.volume_rename_failed, Toast.LENGTH_SHORT).show() |
|
} |
|
}) { |
|
setSelectedText(volume.shortName) |
|
show() |
|
} |
|
} |
|
|
|
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23 |
|
private fun openVolume(volume: Volume, position: Int) { |
|
var askForPassword = true |
|
fingerprintProtector?.let { fingerprintProtector -> |
|
volume.encryptedHash?.let { encryptedHash -> |
|
volume.iv?.let { iv -> |
|
askForPassword = false |
|
fingerprintProtector.listener = object : FingerprintProtector.Listener { |
|
override fun onHashStorageReset() { |
|
volumeAdapter.refresh() |
|
} |
|
override fun onPasswordHashDecrypted(hash: ByteArray) { |
|
object : LoadingTask<Int>(this@MainActivity, themeValue, R.string.loading_msg_open) { |
|
override suspend fun doTask(): Int { |
|
val sessionId = GocryptfsVolume.init(volume.getFullPath(filesDir.path), null, hash, null) |
|
Arrays.fill(hash, 0) |
|
return sessionId |
|
} |
|
}.startTask(lifecycleScope) { sessionId -> |
|
if (sessionId != -1) { |
|
startExplorer(sessionId, volume.shortName) |
|
} else { |
|
CustomAlertDialogBuilder(this@MainActivity, themeValue) |
|
.setTitle(R.string.open_volume_failed) |
|
.setMessage(R.string.open_failed_hash_msg) |
|
.setPositiveButton(R.string.ok, null) |
|
.show() |
|
} |
|
} |
|
} |
|
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: Volume, position: Int, dialogBinding: DialogOpenVolumeBinding) { |
|
if (dialogBinding.checkboxDefaultOpen.isChecked xor (defaultVolumeName == volume.name)) { |
|
with (sharedPrefs.edit()) { |
|
defaultVolumeName = if (dialogBinding.checkboxDefaultOpen.isChecked) { |
|
putString(DEFAULT_VOLUME_KEY, volume.name) |
|
volume.name |
|
} else { |
|
remove(DEFAULT_VOLUME_KEY) |
|
null |
|
} |
|
apply() |
|
} |
|
} |
|
val password = CharArray(dialogBinding.editPassword.text.length) |
|
dialogBinding.editPassword.text.getChars(0, password.size, password, 0) |
|
// openVolumeWithPassword is responsible for wiping the password |
|
openVolumeWithPassword( |
|
volume, |
|
position, |
|
password, |
|
dialogBinding.checkboxSavePassword.isChecked, |
|
) |
|
} |
|
|
|
private fun askForPassword(volume: Volume, 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: Volume, position: Int, password: CharArray, savePasswordHash: Boolean) { |
|
val usfFingerprint = sharedPrefs.getBoolean("usf_fingerprint", false) |
|
var returnedHash: ByteArray? = null |
|
if (savePasswordHash && usfFingerprint) { |
|
returnedHash = ByteArray(GocryptfsVolume.KeyLen) |
|
} |
|
object : LoadingTask<Int>(this, themeValue, R.string.loading_msg_open) { |
|
override suspend fun doTask(): Int { |
|
val sessionId = GocryptfsVolume.init(volume.getFullPath(filesDir.path), password, null, returnedHash) |
|
Arrays.fill(password, 0.toChar()) |
|
return sessionId |
|
} |
|
}.startTask(lifecycleScope) { sessionId -> |
|
if (sessionId != -1) { |
|
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, 0) |
|
volumeAdapter.onVolumeChanged(position) |
|
startExplorer(sessionId, volume.shortName) |
|
} |
|
private var isClosed = false |
|
override fun onFailed(pending: Boolean) { |
|
if (!isClosed) { |
|
GocryptfsVolume(this@MainActivity, sessionId).close() |
|
isClosed = true |
|
} |
|
Arrays.fill(returnedHash, 0) |
|
} |
|
} |
|
fingerprintProtector.savePasswordHash(volume, returnedHash) |
|
} else { |
|
startExplorer(sessionId, volume.shortName) |
|
} |
|
} else { |
|
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() |
|
} |
|
} |
|
} |
|
|
|
private fun startExplorer(sessionId: Int, 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("originalSessionID", intent.getIntExtra("sessionID", -1)) |
|
explorerIntent.flags = Intent.FLAG_ACTIVITY_FORWARD_RESULT |
|
} |
|
if (explorerIntent == null) { |
|
explorerIntent = Intent(this, ExplorerActivity::class.java) //default opening |
|
} |
|
explorerIntent.putExtra("sessionID", sessionId) |
|
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) { |
|
val sessionID = intent.getIntExtra("sessionID", -1) |
|
if (sessionID != -1) { |
|
GocryptfsVolume(this, sessionID).close() |
|
RestrictedFileProvider.wipeAll(this) |
|
} |
|
} |
|
} |
|
} |
|
}
|
|
|