diff --git a/app/build.gradle b/app/build.gradle index d19ffd1..48fd242 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt index e00052e..7e3e069 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt @@ -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() } - } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt index ca18eec..01da3c6 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt @@ -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(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 - 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 + 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(){ diff --git a/app/src/main/java/sushi/hardcore/droidfs/CreateActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/CreateActivity.kt index 4722e60..98e8053 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/CreateActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/CreateActivity.kt @@ -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(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 - 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 + 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(){ diff --git a/app/src/main/java/sushi/hardcore/droidfs/OpenActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/OpenActivity.kt index ebbe83f..b96acb9 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/OpenActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/OpenActivity.kt @@ -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(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() { diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt index d99070f..2b10a26 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt @@ -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 } diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt index e95ff03..4d3364c 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt @@ -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 = 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 = 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 } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/fingerprint_stuff/FingerprintHandler.kt b/app/src/main/java/sushi/hardcore/droidfs/fingerprint_stuff/FingerprintHandler.kt deleted file mode 100644 index 58ed0d6..0000000 --- a/app/src/main/java/sushi/hardcore/droidfs/fingerprint_stuff/FingerprintHandler.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/fingerprint_stuff/onTouchedResultCodes.kt b/app/src/main/java/sushi/hardcore/droidfs/fingerprint_stuff/onTouchedResultCodes.kt deleted file mode 100644 index 3f6af5d..0000000 --- a/app/src/main/java/sushi/hardcore/droidfs/fingerprint_stuff/onTouchedResultCodes.kt +++ /dev/null @@ -1,7 +0,0 @@ -package sushi.hardcore.droidfs.fingerprint_stuff - -enum class onTouchedResultCodes { - SUCCEED, - FAILED, - ERROR -} \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/ExternalProvider.kt b/app/src/main/java/sushi/hardcore/droidfs/util/ExternalProvider.kt index 17478f4..a8e00c7 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/util/ExternalProvider.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/util/ExternalProvider.kt @@ -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) { - var contentType: String? = null - val uris = ArrayList() - 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) { + object : LoadingTask(activity, R.string.loading_msg_export){ + override fun doTask(activity: AppCompatActivity) { + var contentType: String? = null + val uris = ArrayList() + 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() 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() } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/LoadingTask.kt b/app/src/main/java/sushi/hardcore/droidfs/util/LoadingTask.kt new file mode 100644 index 0000000..241cc81 --- /dev/null +++ b/app/src/main/java/sushi/hardcore/droidfs/util/LoadingTask.kt @@ -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(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() } + } +} \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/Wiper.kt b/app/src/main/java/sushi/hardcore/droidfs/util/Wiper.kt index 7892592..54c65ca 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/util/Wiper.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/util/Wiper.kt @@ -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 { diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png index f2393a4..127b9cc 100644 Binary files a/app/src/main/res/drawable/logo.png and b/app/src/main/res/drawable/logo.png differ diff --git a/app/src/main/res/layout/activity_change_password.xml b/app/src/main/res/layout/activity_change_password.xml index 9ecd7ee..8992fcb 100644 --- a/app/src/main/res/layout/activity_change_password.xml +++ b/app/src/main/res/layout/activity_change_password.xml @@ -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"/>