Explorer loading dialogs & Logo fix

This commit is contained in:
Hardcore Sushi 2020-07-28 22:25:10 +02:00
parent 2571849bc3
commit 34d7f19927
19 changed files with 482 additions and 455 deletions

View File

@ -15,7 +15,7 @@ android {
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.1.0"
versionName "1.1.1"
ndk {
abiFilters 'x86_64', 'armeabi-v7a', 'arm64-v8a'

View File

@ -3,7 +3,6 @@ package sushi.hardcore.droidfs
import android.content.SharedPreferences
import android.os.Bundle
import android.view.WindowManager
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import com.jaredrummler.cyanea.app.CyaneaAppCompatActivity
@ -30,8 +29,4 @@ open class BaseActivity: CyaneaAppCompatActivity() {
}
}
}
protected fun toastFromThread(stringId: Int){
runOnUiThread { Toast.makeText(this, stringId, Toast.LENGTH_SHORT).show() }
}
}

View File

@ -8,8 +8,8 @@ import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.widget.AdapterView.OnItemClickListener
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_change_password.*
import kotlinx.android.synthetic.main.activity_change_password.checkbox_remember_path
import kotlinx.android.synthetic.main.activity_change_password.checkbox_save_password
@ -18,10 +18,7 @@ import kotlinx.android.synthetic.main.activity_change_password.saved_path_listvi
import kotlinx.android.synthetic.main.toolbar.*
import sushi.hardcore.droidfs.adapters.SavedVolumesAdapter
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.util.GocryptfsVolume
import sushi.hardcore.droidfs.util.WidgetUtil
import sushi.hardcore.droidfs.util.Wiper
import sushi.hardcore.droidfs.util.*
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import java.util.*
@ -59,8 +56,11 @@ class ChangePasswordActivity : BaseActivity() {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (sharedPrefs.getString(s.toString(), null) == null) {
edit_old_password.hint = null
edit_old_password.isEnabled = true
} else {
edit_old_password.text = null
edit_old_password.hint = getString(R.string.hash_saved_hint)
edit_old_password.isEnabled = false
}
}
})
@ -97,80 +97,70 @@ class ChangePasswordActivity : BaseActivity() {
}
private fun changePassword(givenHash: ByteArray?){
val dialogLoadingView = layoutInflater.inflate(R.layout.dialog_loading, null)
val dialogTextMessage = dialogLoadingView.findViewById<TextView>(R.id.text_message)
dialogTextMessage.text = getString(R.string.loading_msg_change_password)
val dialogLoading = ColoredAlertDialogBuilder(this)
.setView(dialogLoadingView)
.setTitle(R.string.loading)
.setCancelable(false)
.create()
dialogLoading.show()
Thread {
val newPassword = edit_new_password.text.toString().toCharArray()
val newPasswordConfirm = edit_new_password_confirm.text.toString().toCharArray()
if (!newPassword.contentEquals(newPasswordConfirm)) {
dialogLoading.dismiss()
toastFromThread(R.string.passwords_mismatch)
} else {
val oldPassword = edit_old_password.text.toString().toCharArray()
var returnedHash: ByteArray? = null
if (usf_fingerprint && checkbox_save_password.isChecked){
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
}
var changePasswordImmediately = true
if (givenHash == null){
val cipherText = sharedPrefs.getString(rootCipherDir, null)
if (cipherText != null){ //password hash saved
dialogLoading.dismiss()
fingerprintPasswordHashSaver.decrypt(cipherText, rootCipherDir, ::changePassword)
changePasswordImmediately = false
object : LoadingTask(this, R.string.loading_msg_change_password){
override fun doTask(activity: AppCompatActivity) {
val newPassword = edit_new_password.text.toString().toCharArray()
val newPasswordConfirm = edit_new_password_confirm.text.toString().toCharArray()
if (!newPassword.contentEquals(newPasswordConfirm)) {
stopTaskWithToast(R.string.passwords_mismatch)
} else {
val oldPassword = edit_old_password.text.toString().toCharArray()
var returnedHash: ByteArray? = null
if (usf_fingerprint && checkbox_save_password.isChecked){
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
}
}
if (changePasswordImmediately){
if (GocryptfsVolume.change_password(rootCipherDir, oldPassword, givenHash, newPassword, returnedHash)) {
val editor = sharedPrefs.edit()
if (sharedPrefs.getString(rootCipherDir, null) != null){
editor.remove(rootCipherDir)
editor.apply()
var changePasswordImmediately = true
if (givenHash == null){
val cipherText = sharedPrefs.getString(rootCipherDir, null)
if (cipherText != null){ //password hash saved
stopTask {
fingerprintPasswordHashSaver.decrypt(cipherText, rootCipherDir, ::changePassword)
}
changePasswordImmediately = false
}
var continueImmediately = true
if (checkbox_remember_path.isChecked) {
val oldSavedVolumesPaths = sharedPrefs.getStringSet(ConstValues.saved_volumes_key, HashSet()) as Set<String>
val newSavedVolumesPaths = oldSavedVolumesPaths.toMutableList()
if (!oldSavedVolumesPaths.contains(rootCipherDir)) {
newSavedVolumesPaths.add(rootCipherDir)
editor.putStringSet(ConstValues.saved_volumes_key, newSavedVolumesPaths.toSet())
}
if (changePasswordImmediately){
if (GocryptfsVolume.change_password(rootCipherDir, oldPassword, givenHash, newPassword, returnedHash)) {
val editor = sharedPrefs.edit()
if (sharedPrefs.getString(rootCipherDir, null) != null){
editor.remove(rootCipherDir)
editor.apply()
}
if (checkbox_save_password.isChecked && returnedHash != null){
dialogLoading.dismiss()
fingerprintPasswordHashSaver.encryptAndSave(returnedHash, rootCipherDir){ _ ->
onPasswordChanged()
var continueImmediately = true
if (checkbox_remember_path.isChecked) {
val oldSavedVolumesPaths = sharedPrefs.getStringSet(ConstValues.saved_volumes_key, HashSet()) as Set<String>
val newSavedVolumesPaths = oldSavedVolumesPaths.toMutableList()
if (!oldSavedVolumesPaths.contains(rootCipherDir)) {
newSavedVolumesPaths.add(rootCipherDir)
editor.putStringSet(ConstValues.saved_volumes_key, newSavedVolumesPaths.toSet())
editor.apply()
}
continueImmediately = false
if (checkbox_save_password.isChecked && returnedHash != null){
fingerprintPasswordHashSaver.encryptAndSave(returnedHash, rootCipherDir){ _ ->
stopTask { onPasswordChanged() }
}
continueImmediately = false
}
}
if (continueImmediately){
stopTask { onPasswordChanged() }
}
} else {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.error)
.setMessage(R.string.change_password_failed)
.setPositiveButton(R.string.ok, null)
.show()
}
}
if (continueImmediately){
dialogLoading.dismiss()
runOnUiThread { onPasswordChanged() }
}
} else {
dialogLoading.dismiss()
runOnUiThread {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(R.string.change_password_failed)
.setPositiveButton(R.string.ok, null)
.show()
}
}
Arrays.fill(oldPassword, 0.toChar())
}
Arrays.fill(oldPassword, 0.toChar())
Arrays.fill(newPassword, 0.toChar())
Arrays.fill(newPasswordConfirm, 0.toChar())
}
Arrays.fill(newPassword, 0.toChar())
Arrays.fill(newPasswordConfirm, 0.toChar())
}.start()
}
}
private fun onPasswordChanged(){

View File

@ -5,7 +5,7 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_create.*
import kotlinx.android.synthetic.main.activity_create.checkbox_remember_path
import kotlinx.android.synthetic.main.activity_create.checkbox_save_password
@ -14,10 +14,7 @@ import kotlinx.android.synthetic.main.activity_create.edit_volume_path
import kotlinx.android.synthetic.main.toolbar.*
import sushi.hardcore.droidfs.explorers.ExplorerActivity
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.util.GocryptfsVolume
import sushi.hardcore.droidfs.util.WidgetUtil
import sushi.hardcore.droidfs.util.Wiper
import sushi.hardcore.droidfs.util.*
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import java.io.File
import java.util.*
@ -64,99 +61,84 @@ class CreateActivity : BaseActivity() {
}
fun onClickCreate(view: View?) {
val dialogLoadingView = layoutInflater.inflate(R.layout.dialog_loading, null)
val dialogTextMessage = dialogLoadingView.findViewById<TextView>(R.id.text_message)
dialogTextMessage.text = getString(R.string.loading_msg_create)
val dialogLoading = ColoredAlertDialogBuilder(this)
.setView(dialogLoadingView)
.setTitle(R.string.loading)
.setCancelable(false)
.create()
dialogLoading.show()
Thread {
val password = edit_password.text.toString().toCharArray()
val passwordConfirm = edit_password_confirm.text.toString().toCharArray()
if (!password.contentEquals(passwordConfirm)) {
dialogLoading.dismiss()
toastFromThread(R.string.passwords_mismatch)
} else {
rootCipherDir = edit_volume_path.text.toString()
val volumePathFile = File(rootCipherDir)
var goodDirectory = false
if (!volumePathFile.isDirectory) {
if (volumePathFile.mkdirs()) {
goodDirectory = true
} else {
dialogLoading.dismiss()
toastFromThread(R.string.error_mkdir)
}
object: LoadingTask(this, R.string.loading_msg_create){
override fun doTask(activity: AppCompatActivity) {
val password = edit_password.text.toString().toCharArray()
val passwordConfirm = edit_password_confirm.text.toString().toCharArray()
if (!password.contentEquals(passwordConfirm)) {
stopTaskWithToast(R.string.passwords_mismatch)
} else {
val dirContent = volumePathFile.list()
if (dirContent != null){
if (dirContent.isEmpty()) {
rootCipherDir = edit_volume_path.text.toString()
val volumePathFile = File(rootCipherDir)
var goodDirectory = false
if (!volumePathFile.isDirectory) {
if (volumePathFile.mkdirs()) {
goodDirectory = true
} else {
dialogLoading.dismiss()
toastFromThread(R.string.dir_not_empty)
stopTaskWithToast(R.string.error_mkdir)
}
} else {
dialogLoading.dismiss()
toastFromThread(R.string.listdir_null_error_msg)
}
}
if (goodDirectory) {
if (GocryptfsVolume.create_volume(rootCipherDir, password, GocryptfsVolume.ScryptDefaultLogN, ConstValues.creator)) {
var returnedHash: ByteArray? = null
if (usf_fingerprint && checkbox_save_password.isChecked){
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
}
sessionID = GocryptfsVolume.init(rootCipherDir, password, null, returnedHash)
if (sessionID != -1) {
var startExplorerImmediately = true
if (checkbox_remember_path.isChecked) {
val oldSavedVolumesPaths = sharedPrefs.getStringSet(ConstValues.saved_volumes_key, HashSet()) as Set<String>
val editor = sharedPrefs.edit()
val newSavedVolumesPaths = oldSavedVolumesPaths.toMutableList()
if (oldSavedVolumesPaths.contains(rootCipherDir)) {
if (sharedPrefs.getString(rootCipherDir, null) != null){
editor.remove(rootCipherDir)
}
} else {
newSavedVolumesPaths.add(rootCipherDir)
editor.putStringSet(ConstValues.saved_volumes_key, newSavedVolumesPaths.toSet())
}
editor.apply()
if (checkbox_save_password.isChecked && returnedHash != null){
dialogLoading.dismiss()
fingerprintPasswordHashSaver.encryptAndSave(returnedHash, rootCipherDir){ _ ->
runOnUiThread { startExplorer() }
}
startExplorerImmediately = false
}
}
if (startExplorerImmediately){
dialogLoading.dismiss()
runOnUiThread { startExplorer() }
val dirContent = volumePathFile.list()
if (dirContent != null){
if (dirContent.isEmpty()) {
goodDirectory = true
} else {
stopTaskWithToast(R.string.dir_not_empty)
}
} else {
dialogLoading.dismiss()
toastFromThread(R.string.open_volume_failed)
stopTaskWithToast(R.string.listdir_null_error_msg)
}
} else {
dialogLoading.dismiss()
runOnUiThread {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(R.string.create_volume_failed)
.setPositiveButton(R.string.ok, null)
.show()
}
if (goodDirectory) {
if (GocryptfsVolume.create_volume(rootCipherDir, password, GocryptfsVolume.ScryptDefaultLogN, ConstValues.creator)) {
var returnedHash: ByteArray? = null
if (usf_fingerprint && checkbox_save_password.isChecked){
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
}
sessionID = GocryptfsVolume.init(rootCipherDir, password, null, returnedHash)
if (sessionID != -1) {
var startExplorerImmediately = true
if (checkbox_remember_path.isChecked) {
val oldSavedVolumesPaths = sharedPrefs.getStringSet(ConstValues.saved_volumes_key, HashSet()) as Set<String>
val editor = sharedPrefs.edit()
val newSavedVolumesPaths = oldSavedVolumesPaths.toMutableList()
if (oldSavedVolumesPaths.contains(rootCipherDir)) {
if (sharedPrefs.getString(rootCipherDir, null) != null){
editor.remove(rootCipherDir)
}
} else {
newSavedVolumesPaths.add(rootCipherDir)
editor.putStringSet(ConstValues.saved_volumes_key, newSavedVolumesPaths.toSet())
}
editor.apply()
if (checkbox_save_password.isChecked && returnedHash != null){
fingerprintPasswordHashSaver.encryptAndSave(returnedHash, rootCipherDir){ _ ->
stopTask { startExplorer() }
}
startExplorerImmediately = false
}
}
if (startExplorerImmediately){
stopTask { startExplorer() }
}
} else {
stopTaskWithToast(R.string.open_volume_failed)
}
} else {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.error)
.setMessage(R.string.create_volume_failed)
.setPositiveButton(R.string.ok, null)
.show()
}
}
}
}
Arrays.fill(password, 0.toChar())
Arrays.fill(passwordConfirm, 0.toChar())
}
Arrays.fill(password, 0.toChar())
Arrays.fill(passwordConfirm, 0.toChar())
}.start()
}
}
private fun startExplorer(){

View File

@ -7,6 +7,8 @@ import android.os.Bundle
import android.view.View
import android.widget.AdapterView.OnItemClickListener
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_open.checkbox_remember_path
import kotlinx.android.synthetic.main.activity_open.checkbox_save_password
import kotlinx.android.synthetic.main.activity_open.edit_password
@ -18,10 +20,7 @@ import sushi.hardcore.droidfs.explorers.ExplorerActivity
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.util.GocryptfsVolume
import sushi.hardcore.droidfs.util.WidgetUtil
import sushi.hardcore.droidfs.util.Wiper
import sushi.hardcore.droidfs.util.*
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import java.io.File
import java.util.*
@ -83,72 +82,65 @@ class OpenActivity : BaseActivity() {
}
fun onClickOpen(view: View?) {
val dialogLoadingView = layoutInflater.inflate(R.layout.dialog_loading, null)
val dialogTextMessage = dialogLoadingView.findViewById<TextView>(R.id.text_message)
dialogTextMessage.text = getString(R.string.loading_msg_open)
val dialogLoading = ColoredAlertDialogBuilder(this)
.setView(dialogLoadingView)
.setTitle(R.string.loading)
.setCancelable(false)
.create()
dialogLoading.show()
Thread {
rootCipherDir = edit_volume_path.text.toString() //fresh get in case of manual rewrite
if (rootCipherDir.isEmpty()) {
dialogLoading.dismiss()
toastFromThread(R.string.enter_volume_path)
} else {
val password = edit_password.text.toString().toCharArray()
var returnedHash: ByteArray? = null
if (usf_fingerprint && checkbox_save_password.isChecked){
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
}
sessionID = GocryptfsVolume.init(rootCipherDir, password, null, returnedHash)
if (sessionID != -1) {
var startExplorerImmediately = true
if (checkbox_remember_path.isChecked) {
savedVolumesAdapter.addVolumePath(rootCipherDir)
if (checkbox_save_password.isChecked && returnedHash != null){
fingerprintPasswordHashSaver.encryptAndSave(returnedHash, rootCipherDir) { success ->
dialogLoading.dismiss()
if (success){
startExplorer()
object : LoadingTask(this, R.string.loading_msg_open){
override fun doTask(activity: AppCompatActivity) {
rootCipherDir = edit_volume_path.text.toString() //fresh get in case of manual rewrite
if (rootCipherDir.isEmpty()) {
stopTaskWithToast(R.string.enter_volume_path)
} else {
val password = edit_password.text.toString().toCharArray()
var returnedHash: ByteArray? = null
if (usf_fingerprint && checkbox_save_password.isChecked){
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
}
sessionID = GocryptfsVolume.init(rootCipherDir, password, null, returnedHash)
if (sessionID != -1) {
var startExplorerImmediately = true
if (checkbox_remember_path.isChecked) {
savedVolumesAdapter.addVolumePath(rootCipherDir)
if (checkbox_save_password.isChecked && returnedHash != null){
fingerprintPasswordHashSaver.encryptAndSave(returnedHash, rootCipherDir) { _ ->
stopTask { startExplorer() }
}
startExplorerImmediately = false
}
startExplorerImmediately = false
}
if (startExplorerImmediately){
stopTask { startExplorer() }
}
} else {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.open_volume_failed)
.setMessage(R.string.open_volume_failed_msg)
.setPositiveButton(R.string.ok, null)
.show()
}
}
if (startExplorerImmediately){
dialogLoading.dismiss()
startExplorer()
}
Arrays.fill(password, 0.toChar())
}
}
}
}
private fun openUsingPasswordHash(passwordHash: ByteArray){
object : LoadingTask(this, R.string.loading_msg_open){
override fun doTask(activity: AppCompatActivity) {
sessionID = GocryptfsVolume.init(rootCipherDir, null, passwordHash, null)
if (sessionID != -1){
stopTask { startExplorer() }
} else {
dialogLoading.dismiss()
runOnUiThread {
ColoredAlertDialogBuilder(this)
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.open_volume_failed)
.setMessage(R.string.open_volume_failed_msg)
.setMessage(R.string.open_failed_hash_msg)
.setPositiveButton(R.string.ok, null)
.show()
}
}
Arrays.fill(password, 0.toChar())
Arrays.fill(passwordHash, 0)
}
}.start()
}
private fun openUsingPasswordHash(passwordHash: ByteArray){
sessionID = GocryptfsVolume.init(rootCipherDir, null, passwordHash, null)
if (sessionID != -1){
startExplorer()
} else {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.open_volume_failed)
.setMessage(R.string.open_failed_hash_msg)
.setPositiveButton(R.string.ok, null)
.show()
}
Arrays.fill(passwordHash, 0)
}
private fun startExplorer() {

View File

@ -133,6 +133,11 @@ open class BaseExplorerActivity : BaseActivity() {
invalidateOptionsMenu()
}
protected fun unselectAll(){
explorerAdapter.unSelectAll()
invalidateOptionsMenu()
}
private fun sortExplorerElements() {
when (sortModesValues[currentSortModeIndex]) {
"name" -> {
@ -199,8 +204,7 @@ open class BaseExplorerActivity : BaseActivity() {
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath))
}
} else {
explorerAdapter.unSelectAll()
invalidateOptionsMenu()
unselectAll()
}
}
@ -287,8 +291,7 @@ open class BaseExplorerActivity : BaseActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
explorerAdapter.unSelectAll()
invalidateOptionsMenu()
unselectAll()
true
}
R.id.explorer_menu_sort -> {
@ -331,8 +334,7 @@ open class BaseExplorerActivity : BaseActivity() {
R.id.explorer_menu_external_open -> {
if (usf_open){
ExternalProvider.open(this, gocryptfsVolume, PathUtils.path_join(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name))
explorerAdapter.unSelectAll()
invalidateOptionsMenu()
unselectAll()
}
true
}

View File

@ -8,15 +8,14 @@ import android.view.MenuItem
import android.view.View
import android.view.WindowManager
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.github.clans.fab.FloatingActionMenu
import kotlinx.android.synthetic.main.activity_explorer.*
import sushi.hardcore.droidfs.OpenActivity
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.util.ExternalProvider
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.util.GocryptfsVolume
import sushi.hardcore.droidfs.util.Wiper
import sushi.hardcore.droidfs.util.*
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import java.io.File
import java.util.*
@ -77,7 +76,7 @@ class ExplorerActivity : BaseExplorerActivity() {
fun onClickAddFile(view: View?) {
fam_explorer.close(true)
val i = Intent(Intent.ACTION_GET_CONTENT)
val i = Intent(Intent.ACTION_OPEN_DOCUMENT)
i.type = "*/*"
i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
i.addCategory(Intent.CATEGORY_OPENABLE)
@ -95,139 +94,175 @@ class ExplorerActivity : BaseExplorerActivity() {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PICK_FILES_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK && data != null) {
val uris: MutableList<Uri> = ArrayList()
val singleUri = data.data
if (singleUri == null) { //multiples choices
val clipData = data.clipData
if (clipData != null){
for (i in 0 until clipData.itemCount) {
uris.add(clipData.getItemAt(i).uri)
object : LoadingTask(this, R.string.loading_msg_import){
override fun doTask(activity: AppCompatActivity) {
val uris: MutableList<Uri> = ArrayList()
val singleUri = data.data
if (singleUri == null) { //multiples choices
val clipData = data.clipData
if (clipData != null){
for (i in 0 until clipData.itemCount) {
uris.add(clipData.getItemAt(i).uri)
}
}
} else {
uris.add(singleUri)
}
}
} else {
uris.add(singleUri)
}
if (uris.isNotEmpty()){
var success = true
for (uri in uris) {
val dstPath = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(this, uri))
contentResolver.openInputStream(uri)?.let {
success = gocryptfsVolume.import_file(it, dstPath)
var success = true
for (uri in uris) {
val dstPath = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(activity, uri))
contentResolver.openInputStream(uri)?.let {
success = gocryptfsVolume.import_file(it, dstPath)
}
if (!success) {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.error)
.setMessage(getString(R.string.import_failed, uri))
.setPositiveButton(R.string.ok, null)
.show()
}
break
}
}
if (!success) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(getString(R.string.import_failed, uri))
.setPositiveButton(R.string.ok, null)
.show()
break
}
}
if (success) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.success_import)
.setMessage("""
${getString(R.string.success_import_msg)}
${getString(R.string.ask_for_wipe)}
""".trimIndent())
.setPositiveButton(R.string.yes) { _, _ ->
success = true
for (uri in uris) {
if (!Wiper.wipe(this, uri)) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(getString(R.string.wipe_failed, uri))
.setPositiveButton(R.string.ok, null)
.show()
success = false
break
if (success) {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.success_import)
.setMessage("""
${getString(R.string.success_import_msg)}
${getString(R.string.ask_for_wipe)}
""".trimIndent())
.setPositiveButton(R.string.yes) { _, _ ->
object : LoadingTask(activity, R.string.loading_msg_wipe){
override fun doTask(activity: AppCompatActivity) {
success = true
for (uri in uris) {
val errorMsg = Wiper.wipe(activity, uri)
if (errorMsg != null) {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.error)
.setMessage(getString(R.string.wipe_failed, errorMsg))
.setPositiveButton(R.string.ok, null)
.show()
}
success = false
break
}
}
if (success) {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.wipe_successful)
.setMessage(R.string.wipe_success_msg)
.setPositiveButton(R.string.ok, null)
.show()
}
}
}
}
}
if (success) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.wipe_successful)
.setMessage(R.string.wipe_success_msg)
.setPositiveButton(R.string.ok, null)
.show()
}
}
.setNegativeButton(getString(R.string.no), null)
.show()
.setNegativeButton(getString(R.string.no), null)
.show()
}
}
}
override fun doFinally(activity: AppCompatActivity){
setCurrentPath(currentDirectoryPath)
}
setCurrentPath(currentDirectoryPath)
}
}
} else if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK && data != null) {
val uri = data.data
val outputDir = PathUtils.getFullPathFromTreeUri(uri, this)
var failedItem: String? = null
for (i in explorerAdapter.selectedItems) {
val element = explorerAdapter.getItem(i)
val fullPath = PathUtils.path_join(currentDirectoryPath, element.name)
failedItem = if (element.isDirectory) {
recursiveExportDirectory(fullPath, outputDir)
} else {
if (gocryptfsVolume.export_file(fullPath, PathUtils.path_join(outputDir, element.name))) null else fullPath
}
if (failedItem != null) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(getString(R.string.export_failed, failedItem))
.setPositiveButton(R.string.ok, null)
.show()
break
}
}
if (failedItem == null) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.success_export)
.setMessage(R.string.success_export_msg)
.setPositiveButton(R.string.ok, null)
.show()
}
}
explorerAdapter.unSelectAll()
invalidateOptionsMenu()
} else if (requestCode == PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK && data != null) {
val remoteSessionID = data.getIntExtra("sessionID", -1)
val remoteGocryptfsVolume = GocryptfsVolume(remoteSessionID)
val path = data.getStringExtra("path")
var failedItem: String? = null
if (path == null) {
val paths = data.getStringArrayListExtra("paths")
val types = data.getIntegerArrayListExtra("types")
if (types != null && paths != null){
for (i in paths.indices) {
failedItem = if (types[i] == 0) { //directory
recursiveImportDirectoryFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)
object : LoadingTask(this, R.string.loading_msg_export){
override fun doTask(activity: AppCompatActivity) {
val uri = data.data
val outputDir = PathUtils.getFullPathFromTreeUri(uri, activity)
var failedItem: String? = null
for (i in explorerAdapter.selectedItems) {
val element = explorerAdapter.getItem(i)
val fullPath = PathUtils.path_join(currentDirectoryPath, element.name)
failedItem = if (element.isDirectory) {
recursiveExportDirectory(fullPath, outputDir)
} else {
if (importFileFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)) null else paths[i]
if (gocryptfsVolume.export_file(fullPath, PathUtils.path_join(outputDir, element.name))) null else fullPath
}
if (failedItem != null) {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.error)
.setMessage(getString(R.string.export_failed, failedItem))
.setPositiveButton(R.string.ok, null)
.show()
}
break
}
}
if (failedItem == null) {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.success_export)
.setMessage(R.string.success_export_msg)
.setPositiveButton(R.string.ok, null)
.show()
}
}
}
override fun doFinally(activity: AppCompatActivity) {
unselectAll()
}
} else {
failedItem = if (importFileFromOtherVolume(remoteGocryptfsVolume, path, currentDirectoryPath)) null else path
}
if (failedItem == null) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.success_import)
.setMessage(R.string.success_import_msg)
.setPositiveButton(R.string.ok, null)
.show()
} else {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(getString(R.string.import_failed, failedItem))
.setPositiveButton(R.string.ok, null)
.show()
}
} else if (requestCode == PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK && data != null) {
object : LoadingTask(this, R.string.loading_msg_import){
override fun doTask(activity: AppCompatActivity) {
val remoteSessionID = data.getIntExtra("sessionID", -1)
val remoteGocryptfsVolume = GocryptfsVolume(remoteSessionID)
val path = data.getStringExtra("path")
var failedItem: String? = null
if (path == null) {
val paths = data.getStringArrayListExtra("paths")
val types = data.getIntegerArrayListExtra("types")
if (types != null && paths != null){
for (i in paths.indices) {
failedItem = if (types[i] == 0) { //directory
recursiveImportDirectoryFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)
} else {
if (importFileFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)) null else paths[i]
}
if (failedItem != null) {
break
}
}
}
} else {
failedItem = if (importFileFromOtherVolume(remoteGocryptfsVolume, path, currentDirectoryPath)) null else path
}
if (failedItem == null) {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.success_import)
.setMessage(R.string.success_import_msg)
.setPositiveButton(R.string.ok, null)
.show()
}
} else {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.error)
.setMessage(getString(R.string.import_failed, failedItem))
.setPositiveButton(R.string.ok, null)
.show()
}
}
remoteGocryptfsVolume.close()
}
override fun doFinally(activity: AppCompatActivity) {
setCurrentPath(currentDirectoryPath)
}
}
remoteGocryptfsVolume.close()
setCurrentPath(currentDirectoryPath)
}
}
}
@ -285,8 +320,7 @@ class ExplorerActivity : BaseExplorerActivity() {
paths.add(PathUtils.path_join(currentDirectoryPath, e.name))
}
ExternalProvider.share(this, gocryptfsVolume, paths)
explorerAdapter.unSelectAll()
invalidateOptionsMenu()
unselectAll()
true
}
R.id.explorer_menu_decrypt -> {
@ -407,8 +441,7 @@ class ExplorerActivity : BaseExplorerActivity() {
break
}
}
explorerAdapter.unSelectAll()
invalidateOptionsMenu()
unselectAll()
setCurrentPath(currentDirectoryPath) //refresh
}
}

View File

@ -1,33 +0,0 @@
package sushi.hardcore.droidfs.fingerprint_stuff
import android.content.Context
import android.hardware.fingerprint.FingerprintManager
import android.os.Build
import android.os.CancellationSignal
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
@RequiresApi(Build.VERSION_CODES.M)
class FingerprintHandler(private val context: Context) : FingerprintManager.AuthenticationCallback(){
private lateinit var cancellationSignal: CancellationSignal
private lateinit var onTouched: (resultCode: onTouchedResultCodes) -> Unit
fun startAuth(fingerprintManager: FingerprintManager, cryptoObject: FingerprintManager.CryptoObject, onTouched: (resultCode: onTouchedResultCodes) -> Unit){
cancellationSignal = CancellationSignal()
this.onTouched = onTouched
fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, this, null)
}
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult?) {
onTouched(onTouchedResultCodes.SUCCEED)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
onTouched(onTouchedResultCodes.ERROR)
}
override fun onAuthenticationFailed() {
onTouched(onTouchedResultCodes.FAILED)
}
}

View File

@ -1,7 +0,0 @@
package sushi.hardcore.droidfs.fingerprint_stuff
enum class onTouchedResultCodes {
SUCCEED,
FAILED,
ERROR
}

View File

@ -3,12 +3,14 @@ package sushi.hardcore.droidfs.util
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.provider.RestrictedFileProvider
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import java.io.File
import java.net.URLConnection
import java.util.*
import kotlin.collections.ArrayList
object ExternalProvider {
private const val content_type_all = "*/*"
@ -37,55 +39,78 @@ object ExternalProvider {
return Pair(tmpFileUri, getContentType(fileName, previous_content_type))
}
}
ColoredAlertDialogBuilder(context)
.setTitle(R.string.error)
.setMessage(context.getString(R.string.export_failed, file_path))
.setPositiveButton(R.string.ok, null)
.show()
return Pair(null, null)
}
fun share(context: Context, gocryptfsVolume: GocryptfsVolume, file_paths: List<String>) {
var contentType: String? = null
val uris = ArrayList<Uri>()
for (path in file_paths) {
val result = exportFile(context, gocryptfsVolume, path, contentType)
contentType = if (result.first != null) {
uris.add(result.first!!)
result.second
} else {
return
fun share(activity: AppCompatActivity, gocryptfsVolume: GocryptfsVolume, file_paths: List<String>) {
object : LoadingTask(activity, R.string.loading_msg_export){
override fun doTask(activity: AppCompatActivity) {
var contentType: String? = null
val uris = ArrayList<Uri>()
for (path in file_paths) {
val result = exportFile(activity, gocryptfsVolume, path, contentType)
contentType = if (result.first != null) {
uris.add(result.first!!)
result.second
} else {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.error)
.setMessage(activity.getString(R.string.export_failed, path))
.setPositiveButton(R.string.ok, null)
.show()
}
return
}
}
val shareIntent = Intent()
shareIntent.type = contentType
if (uris.size == 1) {
shareIntent.action = Intent.ACTION_SEND
shareIntent.putExtra(Intent.EXTRA_STREAM, uris[0])
} else {
shareIntent.action = Intent.ACTION_SEND_MULTIPLE
shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
}
stopTask {
activity.startActivity(Intent.createChooser(shareIntent, activity.getString(R.string.share_chooser)))
}
}
}
val shareIntent = Intent()
shareIntent.type = contentType
if (uris.size == 1) {
shareIntent.action = Intent.ACTION_SEND
shareIntent.putExtra(Intent.EXTRA_STREAM, uris[0])
} else {
shareIntent.action = Intent.ACTION_SEND_MULTIPLE
shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
}
context.startActivity(Intent.createChooser(shareIntent, context.getString(R.string.share_chooser)))
}
fun open(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String) {
val result = exportFile(context, gocryptfsVolume, file_path, null)
result.first?.let {
val openIntent = Intent()
openIntent.action = Intent.ACTION_VIEW
openIntent.setDataAndType(result.first, result.second)
context.startActivity(openIntent)
fun open(activity: AppCompatActivity, gocryptfsVolume: GocryptfsVolume, file_path: String) {
object : LoadingTask(activity, R.string.loading_msg_export) {
override fun doTask(activity: AppCompatActivity) {
val result = exportFile(activity, gocryptfsVolume, file_path, null)
if (result.first != null) {
val openIntent = Intent(Intent.ACTION_VIEW)
openIntent.setDataAndType(result.first, result.second)
stopTask { activity.startActivity(openIntent) }
} else {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.error)
.setMessage(activity.getString(R.string.export_failed, file_path))
.setPositiveButton(R.string.ok, null)
.show()
}
}
}
}
}
fun removeFiles(context: Context) {
Thread{
val wiped = ArrayList<Uri>()
for (uri in storedFiles) {
if (Wiper.wipe(context, uri)){
storedFiles.remove(uri)
if (Wiper.wipe(context, uri) == null){
wiped.add(uri)
}
}
for (uri in wiped){
storedFiles.remove(uri)
}
}.start()
}
}

View File

@ -0,0 +1,39 @@
package sushi.hardcore.droidfs.util
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
abstract class LoadingTask(private val activity: AppCompatActivity, private val loadingMessageResId: Int) {
private val dialogLoadingView = activity.layoutInflater.inflate(R.layout.dialog_loading, null)
private val dialogLoading: AlertDialog = ColoredAlertDialogBuilder(activity)
.setView(dialogLoadingView)
.setTitle(R.string.loading)
.setCancelable(false)
.create()
init {
dialogLoadingView.findViewById<TextView>(R.id.text_message).text = activity.getString(loadingMessageResId)
startTask()
}
abstract fun doTask(activity: AppCompatActivity)
open fun doFinally(activity: AppCompatActivity){}
private fun startTask() {
dialogLoading.show()
Thread {
doTask(activity)
activity.runOnUiThread { doFinally(activity) }
}.start()
}
protected fun stopTask(onUiThread: () -> Unit){
dialogLoading.dismiss()
activity.runOnUiThread {
onUiThread()
}
}
protected fun stopTaskWithToast(stringId: Int){
stopTask { Toast.makeText(activity, stringId, Toast.LENGTH_SHORT).show() }
}
}

View File

@ -4,7 +4,8 @@ import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import android.widget.EditText
import sushi.hardcore.droidfs.ConstValues.Companion.wipe_passes
import sushi.hardcore.droidfs.ConstValues
import sushi.hardcore.droidfs.R
import java.io.*
import java.lang.Exception
import java.lang.StringBuilder
@ -14,7 +15,7 @@ import kotlin.math.ceil
object Wiper {
private const val buff_size = 4096
fun wipe(context: Context, uri: Uri): Boolean {
fun wipe(context: Context, uri: Uri): String? {
val cursor = context.contentResolver.query(uri, null, null, null, null)
cursor?.let {
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
@ -26,11 +27,11 @@ object Wiper {
val buff = ByteArray(buff_size)
Arrays.fill(buff, 0.toByte())
val writes = ceil(size.toDouble() / buff_size).toInt()
for (i in 0 until wipe_passes) {
for (i in 0 until ConstValues.wipe_passes) {
for (j in 0 until writes) {
os!!.write(buff)
}
if (i < wipe_passes - 1) {
if (i < ConstValues.wipe_passes - 1) {
//reopening to flush and seek
os!!.close()
os = context.contentResolver.openOutputStream(uri)
@ -42,26 +43,25 @@ object Wiper {
(os as FileOutputStream).channel.truncate(0) //truncate to 0 if cannot delete
}
os!!.close()
return true
return null
} catch (e: Exception) {
e.printStackTrace()
return e.message
}
}
return false
return context.getString(R.string.query_cursor_null_error_msg)
}
@JvmStatic
fun wipe(file: File): Boolean{
fun wipe(file: File): String? {
val size = file.length()
try {
var os = FileOutputStream(file)
val buff = ByteArray(buff_size)
Arrays.fill(buff, 0.toByte())
val writes = ceil(size.toDouble() / buff_size).toInt()
for (i in 0 until wipe_passes) {
for (i in 0 until ConstValues.wipe_passes) {
for (j in 0 until writes) {
os.write(buff)
}
if (i < wipe_passes - 1) {
if (i < ConstValues.wipe_passes - 1) {
//reopening to flush and seek
os.close()
os = FileOutputStream(file)
@ -73,10 +73,9 @@ object Wiper {
os.channel.truncate(0) //truncate to 0 if cannot delete
}
os.close()
return true
return null
} catch (e: Exception) {
e.printStackTrace()
return false
return e.message
}
}
private fun randomString(minSize: Int, maxSize: Int): String {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -126,21 +126,22 @@
android:id="@+id/saved_path_listview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="50dp"
android:layout_marginTop="20dp"
android:layout_marginHorizontal="@dimen/action_activity_listview_margin_horizontal"
android:layout_marginTop="@dimen/action_activity_listview_margin_top"
android:background="@drawable/listview_border"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:padding="@dimen/warning_msg_padding"
android:gravity="center"
android:text="@string/create_password_warning"/>
<Button
android:layout_width="match_parent"
android:layout_height="@dimen/action_activity_button_height"
android:layout_marginHorizontal="@dimen/action_activity_button_hor_margin"
android:layout_marginHorizontal="@dimen/action_activity_button_horizontal_margin"
android:layout_marginBottom="@dimen/action_activity_button_margin_bottom"
android:onClick="onClickChangePassword"
android:text="@string/change_volume_password"
style="@style/button"/>

View File

@ -105,14 +105,14 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:padding="@dimen/warning_msg_padding"
android:gravity="center"
android:text="@string/create_password_warning"/>
<Button
android:layout_width="match_parent"
android:layout_height="@dimen/action_activity_button_height"
android:layout_marginHorizontal="@dimen/action_activity_button_hor_margin"
android:layout_marginHorizontal="@dimen/action_activity_button_horizontal_margin"
android:onClick="onClickCreate"
android:text="@string/create_volume"
style="@style/button"/>

View File

@ -86,20 +86,21 @@
android:id="@+id/saved_path_listview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="50dp"
android:layout_marginTop="20dp"/>
android:layout_marginHorizontal="@dimen/action_activity_listview_margin_horizontal"
android:layout_marginTop="@dimen/action_activity_listview_margin_top"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:padding="@dimen/warning_msg_padding"
android:gravity="center"
android:text="@string/open_activity_warning"/>
<Button
android:layout_width="match_parent"
android:layout_height="@dimen/action_activity_button_height"
android:layout_marginHorizontal="@dimen/action_activity_button_hor_margin"
android:layout_marginHorizontal="@dimen/action_activity_button_horizontal_margin"
android:layout_marginBottom="@dimen/action_activity_button_margin_bottom"
android:onClick="onClickOpen"
android:text="@string/open_volume"
style="@style/button"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -7,7 +7,11 @@
<dimen name="edit_text_label_size">15sp</dimen>
<dimen name="open_activity_label_width">90dp</dimen>
<dimen name="change_password_activity_label_width">100dp</dimen>
<dimen name="action_activity_button_hor_margin">60dp</dimen>
<dimen name="action_activity_button_horizontal_margin">60dp</dimen>
<dimen name="action_activity_button_height">60dp</dimen>
<dimen name="action_activity_listview_margin_horizontal">50dp</dimen>
<dimen name="action_activity_listview_margin_top">20dp</dimen>
<dimen name="warning_msg_padding">20dp</dimen>
<dimen name="action_activity_button_margin_bottom">20dp</dimen>
<dimen name="adapter_text_size">18sp</dimen>
</resources>

View File

@ -50,7 +50,7 @@
<string name="yes">YES</string>
<string name="no">NO</string>
<string name="ask_for_wipe">Do you want to wipe the original files ?</string>
<string name="wipe_failed">Wiping of %1$s failed</string>
<string name="wipe_failed">Wiping failed: %1$s</string>
<string name="wipe_successful">Files successfully wiped !</string>
<string name="wipe_success_msg">The imported files have been successfully wiped from their original locations.</string>
<string name="create_password_warning">Warning !\nThis password will be the only way to decrypt the volume and access the files inside.\nChoose a very strong password (not \"123456\" or \"password\"), do not lose it and keep it secure (preferably only in your mind).\n\nDroidFS cannot protect you from screen recording apps, keyloggers, apk backdooring, compromised root accesses, memory dumps etc.\nDo not type passwords in insecure environments.</string>
@ -62,6 +62,7 @@
<string name="sort_order">Sort order:</string>
<string name="old_password">Old password:</string>
<string name="new_password">New password:</string>
<string name="new_password_confirmation">New Password (confirmation):</string>
<string name="success_change_password">Password successfully changed !</string>
<string name="success_change_password_msg">The volume\'s password has been successfully changed.</string>
<string name="change_password_failed">Failed to change the volume\'s password. Check the selected volume path and the entered old password.</string>
@ -118,12 +119,15 @@
<string name="discard">Discard</string>
<string name="word_wrap">Word Wrap</string>
<string name="outofmemoryerror_msg">OutOfMemoryError: This file is too large to be loaded in memory.</string>
<string name="new_file">New File</string>
<string name="new_file">Create New File</string>
<string name="enter_file_name">File name:</string>
<string name="file_creation_failed">Failed to create the file.</string>
<string name="loading">Loading...</string>
<string name="loading_msg_create">Creating volume...</string>
<string name="loading_msg_change_password">Changing password...</string>
<string name="new_password_confirmation">New Password (confirmation):</string>
<string name="loading_msg_open">Opening volume...</string>
<string name="loading">Loading…</string>
<string name="loading_msg_create">Creating volume…</string>
<string name="loading_msg_change_password">Changing password…</string>
<string name="loading_msg_open">Opening volume…</string>
<string name="loading_msg_import">Importing selected files…</string>
<string name="loading_msg_wipe">Wiping original files…</string>
<string name="loading_msg_export">Exporting files…</string>
<string name="query_cursor_null_error_msg">Unable to access this file</string>
</resources>