Multi volume openings

This commit is contained in:
Matéo Duparc 2023-03-07 23:25:17 +01:00
parent 2d165c4a20
commit 1a1d3ea570
Signed by: hardcoresushi
GPG Key ID: AFE384344A45E13A
38 changed files with 436 additions and 393 deletions

View File

@ -88,9 +88,11 @@ dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.core:core-ktx:1.9.0'
implementation "androidx.appcompat:appcompat:1.6.0" implementation "androidx.appcompat:appcompat:1.6.1"
implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" def lifecycle_version = "2.5.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
implementation "androidx.sqlite:sqlite-ktx:2.3.0" implementation "androidx.sqlite:sqlite-ktx:2.3.0"
implementation "androidx.preference:preference-ktx:1.2.0" implementation "androidx.preference:preference-ktx:1.2.0"

View File

@ -30,7 +30,8 @@
android:label="@string/app_name" android:label="@string/app_name"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/BaseTheme"> android:theme="@style/BaseTheme"
android:name=".VolumeManagerApp">
<activity android:name=".MainActivity" android:exported="true"> <activity android:name=".MainActivity" android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@ -4,7 +4,6 @@ import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.view.WindowManager import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
open class BaseActivity: AppCompatActivity() { open class BaseActivity: AppCompatActivity() {
protected lateinit var sharedPrefs: SharedPreferences protected lateinit var sharedPrefs: SharedPreferences
@ -13,7 +12,7 @@ open class BaseActivity: AppCompatActivity() {
private var shouldCheckTheme = true private var shouldCheckTheme = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this) sharedPrefs = (application as VolumeManagerApp).sharedPreferences
themeValue = sharedPrefs.getString(Constants.THEME_VALUE_KEY, Constants.DEFAULT_THEME_VALUE)!! themeValue = sharedPrefs.getString(Constants.THEME_VALUE_KEY, Constants.DEFAULT_THEME_VALUE)!!
if (shouldCheckTheme && applyCustomTheme) { if (shouldCheckTheme && applyCustomTheme) {
when (themeValue) { when (themeValue) {

View File

@ -17,7 +17,6 @@ import android.view.animation.RotateAnimation
import android.widget.ImageView import android.widget.ImageView
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.Toast import android.widget.Toast
import androidx.activity.addCallback
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.camera.camera2.interop.Camera2CameraInfo import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.core.* import androidx.camera.core.*
@ -29,7 +28,6 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
import sushi.hardcore.droidfs.databinding.ActivityCameraBinding import sushi.hardcore.droidfs.databinding.ActivityCameraBinding
import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.util.IntentUtils
@ -63,14 +61,11 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
binding.imageTimer.setImageResource(R.drawable.icon_timer_off) binding.imageTimer.setImageResource(R.drawable.icon_timer_off)
} }
} }
private var usf_keep_open = false
private lateinit var sensorOrientationListener: SensorOrientationListener private lateinit var sensorOrientationListener: SensorOrientationListener
private var previousOrientation: Float = 0f private var previousOrientation: Float = 0f
private lateinit var orientedIcons: List<ImageView> private lateinit var orientedIcons: List<ImageView>
private lateinit var encryptedVolume: EncryptedVolume private lateinit var encryptedVolume: EncryptedVolume
private lateinit var outputDirectory: String private lateinit var outputDirectory: String
private var isFinishingIntentionally = false
private var isAskingPermissions = false
private var permissionsGranted = false private var permissionsGranted = false
private lateinit var executor: Executor private lateinit var executor: Executor
private lateinit var cameraProvider: ProcessCameraProvider private lateinit var cameraProvider: ProcessCameraProvider
@ -92,7 +87,6 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
binding = ActivityCameraBinding.inflate(layoutInflater) binding = ActivityCameraBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
supportActionBar?.hide() supportActionBar?.hide()
@ -103,7 +97,6 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){
permissionsGranted = true permissionsGranted = true
} else { } else {
isAskingPermissions = true
requestPermissions(arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST_CODE) requestPermissions(arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST_CODE)
} }
} else { } else {
@ -220,7 +213,6 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
binding.takePhotoButton.visibility = View.GONE binding.takePhotoButton.visibility = View.GONE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
isAskingPermissions = true
requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), AUDIO_PERMISSION_REQUEST_CODE) requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), AUDIO_PERMISSION_REQUEST_CODE)
} }
} }
@ -280,17 +272,11 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
else -> false else -> false
} }
} }
onBackPressedDispatcher.addCallback(this) {
isFinishingIntentionally = true
isEnabled = false
onBackPressedDispatcher.onBackPressed()
}
} }
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
isAskingPermissions = false
if (grantResults.size == 1) { if (grantResults.size == 1) {
when (requestCode) { when (requestCode) {
CAMERA_PERMISSION_REQUEST_CODE -> if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { CAMERA_PERMISSION_REQUEST_CODE -> if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
@ -301,10 +287,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
.setTitle(R.string.error) .setTitle(R.string.error)
.setMessage(R.string.camera_perm_needed) .setMessage(R.string.camera_perm_needed)
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ -> finish() }.show()
isFinishingIntentionally = true
finish()
}.show()
} }
AUDIO_PERMISSION_REQUEST_CODE -> if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { AUDIO_PERMISSION_REQUEST_CODE -> if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (videoCapture != null) { if (videoCapture != null) {
@ -431,10 +414,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
.setTitle(R.string.error) .setTitle(R.string.error)
.setMessage(R.string.picture_save_failed) .setMessage(R.string.picture_save_failed)
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ -> finish() }
isFinishingIntentionally = true
finish()
}
.show() .show()
} }
} }
@ -485,32 +465,18 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
} }
} }
override fun onDestroy() {
super.onDestroy()
if (!isFinishingIntentionally) {
encryptedVolume.close()
RestrictedFileProvider.wipeAll(this)
}
}
override fun onStop() {
super.onStop()
if (!isFinishing && !usf_keep_open){
finish()
}
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
sensorOrientationListener.remove(this) sensorOrientationListener.remove(this)
if (!isAskingPermissions && !usf_keep_open) {
finish()
}
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
sensorOrientationListener.addListener(this) if (encryptedVolume.isClosed()) {
finish()
} else {
sensorOrientationListener.addListener(this)
}
} }
override fun onOrientationChange(newOrientation: Int) { override fun onOrientationChange(newOrientation: Int) {

View File

@ -20,12 +20,10 @@ import kotlinx.coroutines.launch
import sushi.hardcore.droidfs.Constants.DEFAULT_VOLUME_KEY import sushi.hardcore.droidfs.Constants.DEFAULT_VOLUME_KEY
import sushi.hardcore.droidfs.adapters.VolumeAdapter import sushi.hardcore.droidfs.adapters.VolumeAdapter
import sushi.hardcore.droidfs.add_volume.AddVolumeActivity import sushi.hardcore.droidfs.add_volume.AddVolumeActivity
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
import sushi.hardcore.droidfs.databinding.ActivityMainBinding import sushi.hardcore.droidfs.databinding.ActivityMainBinding
import sushi.hardcore.droidfs.databinding.DialogDeleteVolumeBinding import sushi.hardcore.droidfs.databinding.DialogDeleteVolumeBinding
import sushi.hardcore.droidfs.explorers.ExplorerRouter import sushi.hardcore.droidfs.explorers.ExplorerRouter
import sushi.hardcore.droidfs.file_operations.FileOperationService import sushi.hardcore.droidfs.file_operations.FileOperationService
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.util.IntentUtils
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
@ -33,11 +31,15 @@ import sushi.hardcore.droidfs.widgets.EditTextDialog
import java.io.File import java.io.File
class MainActivity : BaseActivity(), VolumeAdapter.Listener { class MainActivity : BaseActivity(), VolumeAdapter.Listener {
companion object {
private const val OPEN_DEFAULT_VOLUME = "openDefault"
}
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private lateinit var volumeDatabase: VolumeDatabase private lateinit var volumeDatabase: VolumeDatabase
private lateinit var volumeManager: VolumeManager
private lateinit var volumeAdapter: VolumeAdapter private lateinit var volumeAdapter: VolumeAdapter
private lateinit var volumeOpener: VolumeOpener private lateinit var volumeOpener: VolumeOpener
private var usfKeepOpen: Boolean = false
private var addVolume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> private var addVolume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if ((explorerRouter.pickMode || explorerRouter.dropMode) && result.resultCode != AddVolumeActivity.RESULT_USER_BACK) { if ((explorerRouter.pickMode || explorerRouter.dropMode) && result.resultCode != AddVolumeActivity.RESULT_USER_BACK) {
setResult(result.resultCode, result.data) // forward result setResult(result.resultCode, result.data) // forward result
@ -54,7 +56,6 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
} }
private lateinit var fileOperationService: FileOperationService private lateinit var fileOperationService: FileOperationService
private lateinit var explorerRouter: ExplorerRouter private lateinit var explorerRouter: ExplorerRouter
private var shouldCloseVolume = true // used when launched to pick file from another volume
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -80,10 +81,12 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
.show() .show()
} }
explorerRouter = ExplorerRouter(this, intent) explorerRouter = ExplorerRouter(this, intent)
volumeManager = (application as VolumeManagerApp).volumeManager
volumeDatabase = VolumeDatabase(this) volumeDatabase = VolumeDatabase(this)
volumeAdapter = VolumeAdapter( volumeAdapter = VolumeAdapter(
this, this,
volumeDatabase, volumeDatabase,
(application as VolumeManagerApp).volumeManager,
!explorerRouter.pickMode && !explorerRouter.dropMode, !explorerRouter.pickMode && !explorerRouter.dropMode,
!explorerRouter.dropMode, !explorerRouter.dropMode,
this, this,
@ -100,30 +103,31 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
addVolume.launch(Intent(this, AddVolumeActivity::class.java).also { addVolume.launch(Intent(this, AddVolumeActivity::class.java).also {
if (explorerRouter.dropMode || explorerRouter.pickMode) { if (explorerRouter.dropMode || explorerRouter.pickMode) {
IntentUtils.forwardIntent(intent, it) IntentUtils.forwardIntent(intent, it)
shouldCloseVolume = false
} }
}) })
} }
usfKeepOpen = sharedPrefs.getBoolean("usf_keep_open", false)
volumeOpener = VolumeOpener(this) volumeOpener = VolumeOpener(this)
volumeOpener.defaultVolumeName?.let { name ->
try {
openVolume(volumeAdapter.volumes.first { it.name == name })
} catch (e: NoSuchElementException) {
unsetDefaultVolume()
}
}
onBackPressedDispatcher.addCallback(this) { onBackPressedDispatcher.addCallback(this) {
if (volumeAdapter.selectedItems.isNotEmpty()) { if (volumeAdapter.selectedItems.isNotEmpty()) {
unselectAll() unselectAll()
} else { } else {
if (explorerRouter.pickMode) {
shouldCloseVolume = false
}
isEnabled = false isEnabled = false
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
} }
} }
volumeOpener.defaultVolumeName?.let { name ->
val state = savedInstanceState?.getBoolean(OPEN_DEFAULT_VOLUME)
if (state == true || state == null) {
val volumeData = volumeAdapter.volumes.first { it.name == name }
if (!volumeManager.isOpen(volumeData)) {
try {
openVolume(volumeData)
} catch (e: NoSuchElementException) {
unsetDefaultVolume()
}
}
}
}
Intent(this, FileOperationService::class.java).also { Intent(this, FileOperationService::class.java).also {
bindService(it, object : ServiceConnection { bindService(it, object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) { override fun onServiceConnected(className: ComponentName, service: IBinder) {
@ -148,6 +152,11 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
volumeOpener.defaultVolumeName = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null) volumeOpener.defaultVolumeName = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null)
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(OPEN_DEFAULT_VOLUME, false)
}
override fun onSelectionChanged(size: Int) { override fun onSelectionChanged(size: Int) {
title = if (size == 0) { title = if (size == 0) {
getString(R.string.app_name) getString(R.string.app_name)
@ -179,6 +188,11 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
invalidateOptionsMenu() invalidateOptionsMenu()
} }
private fun removeVolume(volume: VolumeData) {
volumeManager.getVolumeId(volume)?.let { volumeManager.closeVolume(it) }
volumeDatabase.removeVolume(volume.name)
}
private fun removeVolumes(volumes: List<VolumeData>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) { private fun removeVolumes(volumes: List<VolumeData>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) {
if (i < volumes.size) { if (i < volumes.size) {
if (volumes[i].isHidden) { if (volumes[i].isHidden) {
@ -196,12 +210,12 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
.setTitle(R.string.warning) .setTitle(R.string.warning)
.setView(dialogBinding.root) .setView(dialogBinding.root)
.setPositiveButton(R.string.forget_only) { _, _ -> .setPositiveButton(R.string.forget_only) { _, _ ->
volumeDatabase.removeVolume(volumes[i].name) removeVolume(volumes[i])
removeVolumes(volumes, i + 1, if (dialogBinding.checkboxApplyToAll.isChecked) false else null) removeVolumes(volumes, i + 1, if (dialogBinding.checkboxApplyToAll.isChecked) false else null)
} }
.setNegativeButton(R.string.delete_volume) { _, _ -> .setNegativeButton(R.string.delete_volume) { _, _ ->
PathUtils.recursiveRemoveDirectory(File(volumes[i].getFullPath(filesDir.path))) PathUtils.recursiveRemoveDirectory(File(volumes[i].getFullPath(filesDir.path)))
volumeDatabase.removeVolume(volumes[i].name) removeVolume(volumes[i])
removeVolumes(volumes, i + 1, if (dialogBinding.checkboxApplyToAll.isChecked) true else null) removeVolumes(volumes, i + 1, if (dialogBinding.checkboxApplyToAll.isChecked) true else null)
} }
.setOnCancelListener { .setOnCancelListener {
@ -213,11 +227,11 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
if (doDeleteVolumeContent) { if (doDeleteVolumeContent) {
PathUtils.recursiveRemoveDirectory(File(volumes[i].getFullPath(filesDir.path))) PathUtils.recursiveRemoveDirectory(File(volumes[i].getFullPath(filesDir.path)))
} }
volumeDatabase.removeVolume(volumes[i].name) removeVolume(volumes[i])
removeVolumes(volumes, i + 1, doDeleteVolumeContent) removeVolumes(volumes, i + 1, doDeleteVolumeContent)
} }
} else { } else {
volumeDatabase.removeVolume(volumes[i].name) removeVolume(volumes[i])
removeVolumes(volumes, i + 1, doDeleteVolumeContent) removeVolumes(volumes, i + 1, doDeleteVolumeContent)
} }
} else { } else {
@ -241,9 +255,6 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
return when (item.itemId) { return when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
if (explorerRouter.pickMode || explorerRouter.dropMode) { if (explorerRouter.pickMode || explorerRouter.dropMode) {
if (explorerRouter.pickMode) {
shouldCloseVolume = false
}
finish() finish()
} else { } else {
unselectAll() unselectAll()
@ -255,6 +266,15 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
invalidateOptionsMenu() invalidateOptionsMenu()
true true
} }
R.id.lock -> {
volumeAdapter.selectedItems.forEach {
volumeManager.getVolumeId(volumeAdapter.volumes[it])?.let { id ->
volumeManager.closeVolume(id)
}
}
unselectAll()
true
}
R.id.remove -> { R.id.remove -> {
val selectedVolumes = volumeAdapter.selectedItems.map { i -> volumeAdapter.volumes[i] } val selectedVolumes = volumeAdapter.selectedItems.map { i -> volumeAdapter.volumes[i] }
removeVolumes(selectedVolumes) removeVolumes(selectedVolumes)
@ -325,6 +345,9 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
menu.findItem(R.id.settings).isVisible = !explorerRouter.pickMode && !explorerRouter.dropMode menu.findItem(R.id.settings).isVisible = !explorerRouter.pickMode && !explorerRouter.dropMode
val isSelecting = volumeAdapter.selectedItems.isNotEmpty() val isSelecting = volumeAdapter.selectedItems.isNotEmpty()
menu.findItem(R.id.select_all).isVisible = isSelecting menu.findItem(R.id.select_all).isVisible = isSelecting
menu.findItem(R.id.lock).isVisible = isSelecting && volumeAdapter.selectedItems.any {
i -> volumeManager.isOpen(volumeAdapter.volumes[i])
}
menu.findItem(R.id.remove).isVisible = isSelecting menu.findItem(R.id.remove).isVisible = isSelecting
menu.findItem(R.id.delete_password_hash).isVisible = menu.findItem(R.id.delete_password_hash).isVisible =
isSelecting && isSelecting &&
@ -456,11 +479,8 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
volumeAdapter.refresh() volumeAdapter.refresh()
} }
override fun onVolumeOpened(encryptedVolume: EncryptedVolume, volumeShortName: String) { override fun onVolumeOpened(id: Int) {
startActivity(explorerRouter.getExplorerIntent(encryptedVolume, volumeShortName)) startActivity(explorerRouter.getExplorerIntent(id, volume.shortName))
if (explorerRouter.pickMode) {
shouldCloseVolume = false
}
if (explorerRouter.dropMode || explorerRouter.pickMode) { if (explorerRouter.dropMode || explorerRouter.pickMode) {
finish() finish()
} }
@ -468,18 +488,8 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
}) })
} }
override fun onResume() {
super.onResume()
shouldCloseVolume = true
}
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
volumeOpener.wipeSensitive() volumeOpener.wipeSensitive()
if (explorerRouter.pickMode && !usfKeepOpen && shouldCloseVolume) {
IntentUtils.getParcelableExtra<EncryptedVolume>(intent, "volume")?.close()
RestrictedFileProvider.wipeAll(this)
finish()
}
} }
} }

View File

@ -44,6 +44,17 @@ class VolumeData(val name: String, val isHidden: Boolean = false, val type: Byte
} }
} }
override fun equals(other: Any?): Boolean {
if (other !is VolumeData) {
return false
}
return other.name == name && other.isHidden == isHidden
}
override fun hashCode(): Int {
return name.hashCode()+isHidden.hashCode()
}
companion object { companion object {
const val VOLUMES_DIRECTORY = "volumes" const val VOLUMES_DIRECTORY = "volumes"

View File

@ -0,0 +1,42 @@
package sushi.hardcore.droidfs
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
class VolumeManager {
private var id = 0
private val volumes = HashMap<Int, EncryptedVolume>()
private val volumesData = HashMap<VolumeData, Int>()
fun insert(volume: EncryptedVolume, data: VolumeData): Int {
volumes[id] = volume
volumesData[data] = id
return id++
}
fun isOpen(volume: VolumeData): Boolean {
return volumesData.containsKey(volume)
}
fun getVolumeId(volume: VolumeData): Int? {
return volumesData[volume]
}
fun getVolume(id: Int): EncryptedVolume? {
return volumes[id]
}
fun closeVolume(id: Int) {
volumes.remove(id)?.let { volume ->
volume.close()
volumesData.filter { it.value == id }.forEach {
volumesData.remove(it.key)
}
}
}
fun closeAll() {
volumes.forEach { it.value.close() }
volumes.clear()
volumesData.clear()
}
}

View File

@ -0,0 +1,49 @@
package sushi.hardcore.droidfs
import android.app.Application
import android.content.SharedPreferences
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.preference.PreferenceManager
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
class VolumeManagerApp : Application(), DefaultLifecycleObserver {
companion object {
private const val USF_KEEP_OPEN_KEY = "usf_keep_open"
}
lateinit var sharedPreferences: SharedPreferences
private val sharedPreferencesListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
if (key == USF_KEEP_OPEN_KEY) {
reloadUsfKeepOpen()
}
}
private var usfKeepOpen = false
var isStartingExternalApp = false
val volumeManager = VolumeManager()
override fun onCreate() {
super<Application>.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this).apply {
registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
}
reloadUsfKeepOpen()
}
private fun reloadUsfKeepOpen() {
usfKeepOpen = sharedPreferences.getBoolean(USF_KEEP_OPEN_KEY, false)
}
override fun onResume(owner: LifecycleOwner) {
isStartingExternalApp = false
}
override fun onStop(owner: LifecycleOwner) {
if (!isStartingExternalApp && !usfKeepOpen) {
volumeManager.closeAll()
RestrictedFileProvider.wipeAll(applicationContext)
}
}
}

View File

@ -22,7 +22,7 @@ class VolumeOpener(
) { ) {
interface VolumeOpenerCallbacks { interface VolumeOpenerCallbacks {
fun onHashStorageReset() {} fun onHashStorageReset() {}
fun onVolumeOpened(encryptedVolume: EncryptedVolume, volumeShortName: String) fun onVolumeOpened(id: Int)
} }
private val volumeDatabase = VolumeDatabase(activity) private val volumeDatabase = VolumeDatabase(activity)
@ -31,6 +31,7 @@ class VolumeOpener(
var themeValue = sharedPrefs.getString(Constants.THEME_VALUE_KEY, Constants.DEFAULT_THEME_VALUE)!! var themeValue = sharedPrefs.getString(Constants.THEME_VALUE_KEY, Constants.DEFAULT_THEME_VALUE)!!
var defaultVolumeName: String? = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null) var defaultVolumeName: String? = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null)
private var dialogBinding: DialogOpenVolumeBinding? = null private var dialogBinding: DialogOpenVolumeBinding? = null
private val volumeManager = (activity.application as VolumeManagerApp).volumeManager
init { init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -40,54 +41,59 @@ class VolumeOpener(
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23 @SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
fun openVolume(volume: VolumeData, isVolumeSaved: Boolean, callbacks: VolumeOpenerCallbacks) { fun openVolume(volume: VolumeData, isVolumeSaved: Boolean, callbacks: VolumeOpenerCallbacks) {
if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE && BuildConfig.GOCRYPTFS_DISABLED) { val volumeId = volumeManager.getVolumeId(volume)
Toast.makeText(activity, R.string.gocryptfs_disabled, Toast.LENGTH_SHORT).show() if (volumeId == null) {
return if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE && BuildConfig.GOCRYPTFS_DISABLED) {
} else if (volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE && BuildConfig.CRYFS_DISABLED) { Toast.makeText(activity, R.string.gocryptfs_disabled, Toast.LENGTH_SHORT).show()
Toast.makeText(activity, R.string.cryfs_disabled, Toast.LENGTH_SHORT).show() return
return } else if (volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE && BuildConfig.CRYFS_DISABLED) {
} Toast.makeText(activity, R.string.cryfs_disabled, Toast.LENGTH_SHORT).show()
var askForPassword = true return
fingerprintProtector?.let { fingerprintProtector -> }
volume.encryptedHash?.let { encryptedHash -> var askForPassword = true
volume.iv?.let { iv -> fingerprintProtector?.let { fingerprintProtector ->
askForPassword = false volume.encryptedHash?.let { encryptedHash ->
fingerprintProtector.listener = object : FingerprintProtector.Listener { volume.iv?.let { iv ->
override fun onHashStorageReset() { askForPassword = false
callbacks.onHashStorageReset() fingerprintProtector.listener = object : FingerprintProtector.Listener {
} override fun onHashStorageReset() {
override fun onPasswordHashDecrypted(hash: ByteArray) { callbacks.onHashStorageReset()
object : LoadingTask<EncryptedVolume?>(activity, themeValue, R.string.loading_msg_open) { }
override suspend fun doTask(): EncryptedVolume? { override fun onPasswordHashDecrypted(hash: ByteArray) {
val encryptedVolume = EncryptedVolume.init(volume, activity.filesDir.path, null, hash, null) object : LoadingTask<EncryptedVolume?>(activity, themeValue, R.string.loading_msg_open) {
Arrays.fill(hash, 0) override suspend fun doTask(): EncryptedVolume? {
return encryptedVolume val encryptedVolume = EncryptedVolume.init(volume, activity.filesDir.path, null, hash, null)
Arrays.fill(hash, 0)
return encryptedVolume
}
}.startTask(activity.lifecycleScope) { encryptedVolume ->
if (encryptedVolume == null) {
CustomAlertDialogBuilder(activity, themeValue)
.setTitle(R.string.open_volume_failed)
.setMessage(R.string.open_failed_hash_msg)
.setPositiveButton(R.string.ok, null)
.show()
} else {
callbacks.onVolumeOpened(volumeManager.insert(encryptedVolume, volume))
}
} }
}.startTask(activity.lifecycleScope) { encryptedVolume -> }
if (encryptedVolume == null) { override fun onPasswordHashSaved() {}
CustomAlertDialogBuilder(activity, themeValue) override fun onFailed(pending: Boolean) {
.setTitle(R.string.open_volume_failed) if (!pending) {
.setMessage(R.string.open_failed_hash_msg) askForPassword(volume, isVolumeSaved, callbacks)
.setPositiveButton(R.string.ok, null)
.show()
} else {
callbacks.onVolumeOpened(encryptedVolume, volume.shortName)
} }
} }
} }
override fun onPasswordHashSaved() {} fingerprintProtector.loadPasswordHash(volume.shortName, encryptedHash, iv)
override fun onFailed(pending: Boolean) {
if (!pending) {
askForPassword(volume, isVolumeSaved, callbacks)
}
}
} }
fingerprintProtector.loadPasswordHash(volume.shortName, encryptedHash, iv)
} }
} }
} if (askForPassword) {
if (askForPassword) { askForPassword(volume, isVolumeSaved, callbacks)
askForPassword(volume, isVolumeSaved, callbacks) }
} else {
callbacks.onVolumeOpened(volumeId)
} }
} }
@ -188,7 +194,7 @@ class VolumeOpener(
override fun onPasswordHashDecrypted(hash: ByteArray) {} override fun onPasswordHashDecrypted(hash: ByteArray) {}
override fun onPasswordHashSaved() { override fun onPasswordHashSaved() {
Arrays.fill(returnedHash.value!!, 0) Arrays.fill(returnedHash.value!!, 0)
callbacks.onVolumeOpened(encryptedVolume, volume.shortName) callbacks.onVolumeOpened(volumeManager.insert(encryptedVolume, volume))
} }
private var isClosed = false private var isClosed = false
override fun onFailed(pending: Boolean) { override fun onFailed(pending: Boolean) {
@ -201,7 +207,7 @@ class VolumeOpener(
} }
fingerprintProtector.savePasswordHash(volume, returnedHash.value!!) fingerprintProtector.savePasswordHash(volume, returnedHash.value!!)
} else { } else {
callbacks.onVolumeOpened(encryptedVolume, volume.shortName) callbacks.onVolumeOpened(volumeManager.insert(encryptedVolume, volume))
} }
} }
} }

View File

@ -8,15 +8,18 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.VolumeData import sushi.hardcore.droidfs.VolumeData
import sushi.hardcore.droidfs.VolumeDatabase import sushi.hardcore.droidfs.VolumeDatabase
import sushi.hardcore.droidfs.VolumeManager
import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.EncryptedVolume
class VolumeAdapter( class VolumeAdapter(
private val context: Context, private val context: Context,
private val volumeDatabase: VolumeDatabase, private val volumeDatabase: VolumeDatabase,
private val volumeManager: VolumeManager,
private val allowSelection: Boolean, private val allowSelection: Boolean,
private val showReadOnly: Boolean, private val showReadOnly: Boolean,
private val listener: Listener, private val listener: Listener,
@ -80,16 +83,12 @@ class VolumeAdapter(
fun bind(position: Int) { fun bind(position: Int) {
val volume = volumes[position] val volume = volumes[position]
itemView.findViewById<TextView>(R.id.text_volume_name).text = volume.shortName itemView.findViewById<TextView>(R.id.text_volume_name).text = volume.shortName
itemView.findViewById<ImageView>(R.id.image_icon).setImageResource(R.drawable.icon_volume)
itemView.findViewById<TextView>(R.id.text_path).text = if (volume.isHidden) itemView.findViewById<TextView>(R.id.text_path).text = if (volume.isHidden)
context.getString(R.string.hidden_volume) context.getString(R.string.hidden_volume)
else else
volume.name volume.name
itemView.findViewById<ImageView>(R.id.icon_fingerprint).visibility = if (volume.encryptedHash == null) { itemView.findViewById<ImageView>(R.id.icon_unlocked).isVisible = volumeManager.isOpen(volume)
View.GONE itemView.findViewById<ImageView>(R.id.icon_fingerprint).isVisible = volume.encryptedHash != null
} else {
View.VISIBLE
}
itemView.findViewById<TextView>(R.id.text_info).text = context.getString( itemView.findViewById<TextView>(R.id.text_info).text = context.getString(
if (volume.canWrite(context.filesDir.path)) { if (volume.canWrite(context.filesDir.path)) {
R.string.volume_type R.string.volume_type

View File

@ -4,11 +4,8 @@ import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.addCallback import androidx.activity.addCallback
import sushi.hardcore.droidfs.* import sushi.hardcore.droidfs.*
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
import sushi.hardcore.droidfs.databinding.ActivityAddVolumeBinding import sushi.hardcore.droidfs.databinding.ActivityAddVolumeBinding
import sushi.hardcore.droidfs.explorers.ExplorerRouter import sushi.hardcore.droidfs.explorers.ExplorerRouter
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.IntentUtils
class AddVolumeActivity: BaseActivity() { class AddVolumeActivity: BaseActivity() {
@ -19,15 +16,12 @@ class AddVolumeActivity: BaseActivity() {
private lateinit var binding: ActivityAddVolumeBinding private lateinit var binding: ActivityAddVolumeBinding
private lateinit var explorerRouter: ExplorerRouter private lateinit var explorerRouter: ExplorerRouter
private lateinit var volumeOpener: VolumeOpener private lateinit var volumeOpener: VolumeOpener
private var usfKeepOpen = false
var shouldCloseVolume = true // used when launched to pick file from another volume
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityAddVolumeBinding.inflate(layoutInflater) binding = ActivityAddVolumeBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
usfKeepOpen = sharedPrefs.getBoolean("usf_keep_open", false)
explorerRouter = ExplorerRouter(this, intent) explorerRouter = ExplorerRouter(this, intent)
volumeOpener = VolumeOpener(this) volumeOpener = VolumeOpener(this)
if (savedInstanceState == null) { if (savedInstanceState == null) {
@ -41,7 +35,6 @@ class AddVolumeActivity: BaseActivity() {
} }
onBackPressedDispatcher.addCallback(this) { onBackPressedDispatcher.addCallback(this) {
setResult(RESULT_USER_BACK) setResult(RESULT_USER_BACK)
shouldCloseVolume = false
isEnabled = false isEnabled = false
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
} }
@ -53,7 +46,6 @@ class AddVolumeActivity: BaseActivity() {
supportFragmentManager.popBackStack() supportFragmentManager.popBackStack()
else { else {
setResult(RESULT_USER_BACK) setResult(RESULT_USER_BACK)
shouldCloseVolume = false
finish() finish()
} }
} }
@ -70,21 +62,19 @@ class AddVolumeActivity: BaseActivity() {
) )
} }
fun startExplorer(encryptedVolume: EncryptedVolume, volumeShortName: String) { fun startExplorer(volumeId: Int, volumeShortName: String) {
startActivity(explorerRouter.getExplorerIntent(encryptedVolume, volumeShortName)) startActivity(explorerRouter.getExplorerIntent(volumeId, volumeShortName))
shouldCloseVolume = false
finish() finish()
} }
fun onVolumeSelected(volume: VolumeData, rememberVolume: Boolean) { fun onVolumeSelected(volume: VolumeData, rememberVolume: Boolean) {
if (rememberVolume) { if (rememberVolume) {
setResult(RESULT_USER_BACK) setResult(RESULT_USER_BACK)
shouldCloseVolume = false
finish() finish()
} else { } else {
volumeOpener.openVolume(volume, false, object : VolumeOpener.VolumeOpenerCallbacks { volumeOpener.openVolume(volume, false, object : VolumeOpener.VolumeOpenerCallbacks {
override fun onVolumeOpened(encryptedVolume: EncryptedVolume, volumeShortName: String) { override fun onVolumeOpened(id: Int) {
startExplorer(encryptedVolume, volumeShortName) startExplorer(id, volume.shortName)
} }
}) })
} }
@ -106,18 +96,4 @@ class AddVolumeActivity: BaseActivity() {
.addToBackStack(null) .addToBackStack(null)
.commit() .commit()
} }
override fun onStart() {
super.onStart()
shouldCloseVolume = true
}
override fun onStop() {
super.onStop()
if (explorerRouter.pickMode && !usfKeepOpen && shouldCloseVolume) {
IntentUtils.getParcelableExtra<EncryptedVolume>(intent, "volume")?.close()
RestrictedFileProvider.wipeAll(this)
finish()
}
}
} }

View File

@ -158,11 +158,7 @@ class CreateVolumeFragment: Fragment() {
} else { } else {
null null
} }
val encryptedVolume = if (rememberVolume) { val encryptedVolume = ObjRef<EncryptedVolume?>(null)
null
} else {
ObjRef<EncryptedVolume?>(null)
}
object: LoadingTask<Byte>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) { object: LoadingTask<Byte>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) {
private fun generateResult(success: Boolean, volumeType: Byte): Byte { private fun generateResult(success: Boolean, volumeType: Byte): Byte {
return if (success) { return if (success) {
@ -223,6 +219,9 @@ class CreateVolumeFragment: Fragment() {
isVolumeSaved = saveVolume(volume) isVolumeSaved = saveVolume(volume)
} }
} }
val volumeId = encryptedVolume.value?.let {
(activity?.application as VolumeManagerApp).volumeManager.insert(it, volume)
}
@SuppressLint("NewApi") // if fingerprintProtector is null checkboxSavePassword is hidden @SuppressLint("NewApi") // if fingerprintProtector is null checkboxSavePassword is hidden
if (isVolumeSaved && binding.checkboxSavePassword.isChecked && returnedHash != null) { if (isVolumeSaved && binding.checkboxSavePassword.isChecked && returnedHash != null) {
fingerprintProtector!!.let { fingerprintProtector!!.let {
@ -235,31 +234,31 @@ class CreateVolumeFragment: Fragment() {
override fun onPasswordHashDecrypted(hash: ByteArray) {} // shouldn't happen here override fun onPasswordHashDecrypted(hash: ByteArray) {} // shouldn't happen here
override fun onPasswordHashSaved() { override fun onPasswordHashSaved() {
Arrays.fill(returnedHash.value!!, 0) Arrays.fill(returnedHash.value!!, 0)
onVolumeCreated(encryptedVolume?.value, volume.shortName) onVolumeCreated(volumeId, volume.shortName)
} }
override fun onFailed(pending: Boolean) { override fun onFailed(pending: Boolean) {
if (!pending) { if (!pending) {
Arrays.fill(returnedHash.value!!, 0) Arrays.fill(returnedHash.value!!, 0)
onVolumeCreated(encryptedVolume?.value, volume.shortName) onVolumeCreated(volumeId, volume.shortName)
} }
} }
} }
it.savePasswordHash(volume, returnedHash.value!!) it.savePasswordHash(volume, returnedHash.value!!)
} }
} else { } else {
onVolumeCreated(encryptedVolume?.value, volume.shortName) onVolumeCreated(volumeId, volume.shortName)
} }
} }
} }
} }
} }
private fun onVolumeCreated(encryptedVolume: EncryptedVolume?, volumeShortName: String) { private fun onVolumeCreated(id: Int?, volumeShortName: String) {
(activity as AddVolumeActivity).apply { (activity as AddVolumeActivity).apply {
if (rememberVolume || encryptedVolume == null) { if (rememberVolume || id == null) {
finish() finish()
} else { } else {
startExplorer(encryptedVolume, volumeShortName) startExplorer(id, volumeShortName)
} }
} }
} }

View File

@ -24,6 +24,7 @@ import sushi.hardcore.droidfs.Constants
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.VolumeData import sushi.hardcore.droidfs.VolumeData
import sushi.hardcore.droidfs.VolumeDatabase import sushi.hardcore.droidfs.VolumeDatabase
import sushi.hardcore.droidfs.VolumeManagerApp
import sushi.hardcore.droidfs.databinding.DialogSdcardErrorBinding import sushi.hardcore.droidfs.databinding.DialogSdcardErrorBinding
import sushi.hardcore.droidfs.databinding.FragmentSelectPathBinding import sushi.hardcore.droidfs.databinding.FragmentSelectPathBinding
import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.EncryptedVolume
@ -62,6 +63,7 @@ class SelectPathFragment: Fragment() {
if (uri != null) if (uri != null)
onDirectoryPicked(uri) onDirectoryPicked(uri)
} }
private lateinit var app: VolumeManagerApp
private var themeValue = Constants.DEFAULT_THEME_VALUE private var themeValue = Constants.DEFAULT_THEME_VALUE
private lateinit var volumeDatabase: VolumeDatabase private lateinit var volumeDatabase: VolumeDatabase
private lateinit var filesDir: String private lateinit var filesDir: String
@ -81,6 +83,7 @@ class SelectPathFragment: Fragment() {
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
app = requireActivity().application as VolumeManagerApp
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext()) sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
originalRememberVolume = sharedPrefs.getBoolean(Constants.REMEMBER_VOLUME_KEY, true) originalRememberVolume = sharedPrefs.getBoolean(Constants.REMEMBER_VOLUME_KEY, true)
binding.switchRemember.isChecked = originalRememberVolume binding.switchRemember.isChecked = originalRememberVolume
@ -105,6 +108,7 @@ class SelectPathFragment: Fragment() {
if (Environment.isExternalStorageManager()) { if (Environment.isExternalStorageManager()) {
launchPickDirectory() launchPickDirectory()
} else { } else {
app.isStartingExternalApp = true
startActivity(Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.parse("package:"+requireContext().packageName))) startActivity(Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.parse("package:"+requireContext().packageName)))
} }
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -119,6 +123,7 @@ class SelectPathFragment: Fragment() {
) { ) {
launchPickDirectory() launchPickDirectory()
} else { } else {
app.isStartingExternalApp = true
askStoragePermissions.launch( askStoragePermissions.launch(
arrayOf( arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,
@ -149,7 +154,7 @@ class SelectPathFragment: Fragment() {
} }
private fun launchPickDirectory() { private fun launchPickDirectory() {
(activity as AddVolumeActivity).shouldCloseVolume = false app.isStartingExternalApp = true
PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue) PathUtils.safePickDirectory(pickDirectory, requireContext(), themeValue)
} }

View File

@ -15,7 +15,7 @@ import sushi.hardcore.droidfs.util.SQLUtil.appendSelectionArgs
import sushi.hardcore.droidfs.util.SQLUtil.concatenateWhere import sushi.hardcore.droidfs.util.SQLUtil.concatenateWhere
import sushi.hardcore.droidfs.util.Wiper import sushi.hardcore.droidfs.util.Wiper
import java.io.File import java.io.File
import java.util.* import java.util.UUID
import java.util.regex.Pattern import java.util.regex.Pattern
class RestrictedFileProvider: ContentProvider() { class RestrictedFileProvider: ContentProvider() {

View File

@ -13,6 +13,7 @@ import android.view.View
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.addCallback
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -22,20 +23,15 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.coroutines.* import kotlinx.coroutines.*
import sushi.hardcore.droidfs.BaseActivity import sushi.hardcore.droidfs.*
import sushi.hardcore.droidfs.Constants
import sushi.hardcore.droidfs.FileTypes
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.adapters.ExplorerElementAdapter import sushi.hardcore.droidfs.adapters.ExplorerElementAdapter
import sushi.hardcore.droidfs.adapters.OpenAsDialogAdapter import sushi.hardcore.droidfs.adapters.OpenAsDialogAdapter
import sushi.hardcore.droidfs.content_providers.ExternalProvider import sushi.hardcore.droidfs.content_providers.ExternalProvider
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
import sushi.hardcore.droidfs.file_operations.FileOperationService import sushi.hardcore.droidfs.file_operations.FileOperationService
import sushi.hardcore.droidfs.file_operations.OperationFile import sushi.hardcore.droidfs.file_operations.OperationFile
import sushi.hardcore.droidfs.file_viewers.* import sushi.hardcore.droidfs.file_viewers.*
import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.filesystems.Stat import sushi.hardcore.droidfs.filesystems.Stat
import sushi.hardcore.droidfs.util.IntentUtils
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import sushi.hardcore.droidfs.widgets.EditTextDialog import sushi.hardcore.droidfs.widgets.EditTextDialog
@ -46,6 +42,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
private var foldersFirst = true private var foldersFirst = true
private var mapFolders = true private var mapFolders = true
private var currentSortOrderIndex = 0 private var currentSortOrderIndex = 0
private var volumeId = -1
protected lateinit var encryptedVolume: EncryptedVolume protected lateinit var encryptedVolume: EncryptedVolume
private lateinit var volumeName: String private lateinit var volumeName: String
private lateinit var explorerViewModel: ExplorerViewModel private lateinit var explorerViewModel: ExplorerViewModel
@ -58,10 +55,8 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
protected val taskScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) protected val taskScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
protected lateinit var explorerElements: MutableList<ExplorerElement> protected lateinit var explorerElements: MutableList<ExplorerElement>
protected lateinit var explorerAdapter: ExplorerElementAdapter protected lateinit var explorerAdapter: ExplorerElementAdapter
private var isCreating = true protected lateinit var app: VolumeManagerApp
protected var isStartingActivity = false
private var usf_open = false private var usf_open = false
protected var usf_keep_open = false
private lateinit var linearLayoutManager: LinearLayoutManager private lateinit var linearLayoutManager: LinearLayoutManager
private var isUsingListLayout = true private var isUsingListLayout = true
private lateinit var layoutIcon: ImageButton private lateinit var layoutIcon: ImageButton
@ -76,10 +71,11 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
app = application as VolumeManagerApp
usf_open = sharedPrefs.getBoolean("usf_open", false) usf_open = sharedPrefs.getBoolean("usf_open", false)
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false) volumeName = intent.getStringExtra("volumeName") ?: ""
volumeName = intent.getStringExtra("volume_name") ?: "" volumeId = intent.getIntExtra("volumeId", -1)
encryptedVolume = IntentUtils.getParcelableExtra(intent, "volume")!! encryptedVolume = app.volumeManager.getVolume(volumeId)!!
sortOrderEntries = resources.getStringArray(R.array.sort_orders_entries) sortOrderEntries = resources.getStringArray(R.array.sort_orders_entries)
sortOrderValues = resources.getStringArray(R.array.sort_orders_values) sortOrderValues = resources.getStringArray(R.array.sort_orders_values)
foldersFirst = sharedPrefs.getBoolean("folders_first", true) foldersFirst = sharedPrefs.getBoolean("folders_first", true)
@ -118,6 +114,19 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
isUsingListLayout = sharedPrefs.getBoolean("useListLayout", true) isUsingListLayout = sharedPrefs.getBoolean("useListLayout", true)
layoutIcon = findViewById(R.id.layout_icon) layoutIcon = findViewById(R.id.layout_icon)
setRecyclerViewLayout() setRecyclerViewLayout()
onBackPressedDispatcher.addCallback(this) {
if (explorerAdapter.selectedItems.isEmpty()) {
val parentPath = PathUtils.getParentPath(currentDirectoryPath)
if (parentPath == currentDirectoryPath) {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
} else {
setCurrentPath(PathUtils.getParentPath(currentDirectoryPath))
}
} else {
unselectAll()
}
}
layoutIcon.setOnClickListener { layoutIcon.setOnClickListener {
isUsingListLayout = !isUsingListLayout isUsingListLayout = !isUsingListLayout
setRecyclerViewLayout() setRecyclerViewLayout()
@ -171,18 +180,17 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
} }
} }
private fun startFileViewer(cls: Class<*>, filePath: String){ private fun startFileViewer(cls: Class<*>, filePath: String) {
val intent = Intent(this, cls).apply { val intent = Intent(this, cls).apply {
putExtra("path", filePath) putExtra("path", filePath)
putExtra("volume", encryptedVolume) putExtra("volume", encryptedVolume)
putExtra("sortOrder", sortOrderValues[currentSortOrderIndex]) putExtra("sortOrder", sortOrderValues[currentSortOrderIndex])
} }
isStartingActivity = true
startActivity(intent) startActivity(intent)
} }
private fun openWithExternalApp(fullPath: String){ private fun openWithExternalApp(fullPath: String) {
isStartingActivity = true app.isStartingExternalApp = true
ExternalProvider.open(this, themeValue, encryptedVolume, fullPath) ExternalProvider.open(this, themeValue, encryptedVolume, fullPath)
} }
@ -332,28 +340,18 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
} }
} }
private fun askCloseVolume() { private fun askLockVolume() {
CustomAlertDialogBuilder(this, themeValue) CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.warning) .setTitle(R.string.warning)
.setMessage(R.string.ask_close_volume) .setMessage(R.string.ask_lock_volume)
.setPositiveButton(R.string.ok) { _, _ -> closeVolumeOnUserExit() } .setPositiveButton(R.string.ok) { _, _ ->
app.volumeManager.closeVolume(volumeId)
finish()
}
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show() .show()
} }
override fun onBackPressed() {
if (explorerAdapter.selectedItems.isEmpty()) {
val parentPath = PathUtils.getParentPath(currentDirectoryPath)
if (parentPath == currentDirectoryPath) {
askCloseVolume()
} else {
setCurrentPath(PathUtils.getParentPath(currentDirectoryPath))
}
} else {
unselectAll()
}
}
private fun createFolder(folderName: String){ private fun createFolder(folderName: String){
if (folderName.isEmpty()) { if (folderName.isEmpty()) {
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
@ -508,9 +506,9 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
val noItemSelected = explorerAdapter.selectedItems.isEmpty() val noItemSelected = explorerAdapter.selectedItems.isEmpty()
val iconColor = ContextCompat.getColor(this, R.color.neutralIconTint) val iconColor = ContextCompat.getColor(this, R.color.neutralIconTint)
setMenuIconTint(menu, iconColor, R.id.sort, R.drawable.icon_sort) setMenuIconTint(menu, iconColor, R.id.sort, R.drawable.icon_sort)
setMenuIconTint(menu, iconColor, R.id.decrypt, R.drawable.icon_decrypt)
setMenuIconTint(menu, iconColor, R.id.share, R.drawable.icon_share) setMenuIconTint(menu, iconColor, R.id.share, R.drawable.icon_share)
menu.findItem(R.id.sort).isVisible = noItemSelected menu.findItem(R.id.sort).isVisible = noItemSelected
menu.findItem(R.id.lock).isVisible = noItemSelected
menu.findItem(R.id.close).isVisible = noItemSelected menu.findItem(R.id.close).isVisible = noItemSelected
supportActionBar?.setDisplayHomeAsUpEnabled(!noItemSelected) supportActionBar?.setDisplayHomeAsUpEnabled(!noItemSelected)
if (!noItemSelected) { if (!noItemSelected) {
@ -577,55 +575,33 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
true true
} }
R.id.close -> { R.id.close -> {
askCloseVolume() finish()
true
}
R.id.lock -> {
askLockVolume()
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }
protected open fun closeVolumeOnUserExit() {
finish()
}
protected open fun closeVolumeOnDestroy() {
taskScope.cancel()
if (!encryptedVolume.isClosed()) {
encryptedVolume.close()
}
RestrictedFileProvider.wipeAll(this) //additional security
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
if (!isChangingConfigurations) { //activity won't be recreated if (!isChangingConfigurations) { //activity won't be recreated
closeVolumeOnDestroy() taskScope.cancel()
}
}
override fun onPause() {
super.onPause()
if (!isChangingConfigurations){
if (isStartingActivity){
isStartingActivity = false
} else if (!usf_keep_open){
finish()
}
} }
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (isCreating){ if (app.isStartingExternalApp) {
isCreating = false ExternalProvider.removeFilesAsync(this)
}
if (encryptedVolume.isClosed()) {
finish()
} else { } else {
if (encryptedVolume.isClosed()) { setCurrentPath(currentDirectoryPath)
finish()
} else {
isStartingActivity = false
ExternalProvider.removeFilesAsync(this)
setCurrentPath(currentDirectoryPath)
}
} }
} }
} }

View File

@ -6,6 +6,7 @@ import android.net.Uri
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.Toast import android.widget.Toast
import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
@ -60,9 +61,7 @@ class ExplorerActivity : BaseExplorerActivity() {
} }
if (operationFiles.size > 0){ if (operationFiles.size > 0){
checkPathOverwrite(operationFiles, currentDirectoryPath) { items -> checkPathOverwrite(operationFiles, currentDirectoryPath) { items ->
if (items == null) { if (items != null) {
remoteEncryptedVolume.close()
} else {
// stop loading thumbnails while writing files // stop loading thumbnails while writing files
explorerAdapter.loadThumbnails = false explorerAdapter.loadThumbnails = false
taskScope.launch { taskScope.launch {
@ -78,12 +77,9 @@ class ExplorerActivity : BaseExplorerActivity() {
} }
explorerAdapter.loadThumbnails = true explorerAdapter.loadThumbnails = true
setCurrentPath(currentDirectoryPath) setCurrentPath(currentDirectoryPath)
remoteEncryptedVolume.close()
} }
} }
} }
} else {
remoteEncryptedVolume.close()
} }
} }
} }
@ -169,6 +165,16 @@ class ExplorerActivity : BaseExplorerActivity() {
override fun init() { override fun init() {
super.init() super.init()
onBackPressedDispatcher.addCallback(this) {
if (currentItemAction != ItemsActions.NONE) {
cancelItemAction()
invalidateOptionsMenu()
} else {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
isEnabled = true
}
}
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { findViewById<FloatingActionButton>(R.id.fab).setOnClickListener {
if (currentItemAction != ItemsActions.NONE){ if (currentItemAction != ItemsActions.NONE){
openDialogCreateFolder() openDialogCreateFolder()
@ -189,15 +195,14 @@ class ExplorerActivity : BaseExplorerActivity() {
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)
intent.action = "pick" intent.action = "pick"
intent.putExtra("volume", encryptedVolume) intent.putExtra("volume", encryptedVolume)
isStartingActivity = true
pickFromOtherVolumes.launch(intent) pickFromOtherVolumes.launch(intent)
} }
"importFiles" -> { "importFiles" -> {
isStartingActivity = true app.isStartingExternalApp = true
pickFiles.launch(arrayOf("*/*")) pickFiles.launch(arrayOf("*/*"))
} }
"importFolder" -> { "importFolder" -> {
isStartingActivity = true app.isStartingExternalApp = true
pickImportDirectory.launch(null) pickImportDirectory.launch(null)
} }
"createFile" -> { "createFile" -> {
@ -212,7 +217,6 @@ class ExplorerActivity : BaseExplorerActivity() {
val intent = Intent(this, CameraActivity::class.java) val intent = Intent(this, CameraActivity::class.java)
intent.putExtra("path", currentDirectoryPath) intent.putExtra("path", currentDirectoryPath)
intent.putExtra("volume", encryptedVolume) intent.putExtra("volume", encryptedVolume)
isStartingActivity = true
startActivity(intent) startActivity(intent)
} }
} }
@ -257,6 +261,7 @@ class ExplorerActivity : BaseExplorerActivity() {
val result = super.onCreateOptionsMenu(menu) val result = super.onCreateOptionsMenu(menu)
if (currentItemAction != ItemsActions.NONE) { if (currentItemAction != ItemsActions.NONE) {
menu.findItem(R.id.validate).isVisible = true menu.findItem(R.id.validate).isVisible = true
menu.findItem(R.id.lock).isVisible = false
menu.findItem(R.id.close).isVisible = false menu.findItem(R.id.close).isVisible = false
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
} else { } else {
@ -405,13 +410,13 @@ class ExplorerActivity : BaseExplorerActivity() {
for (i in explorerAdapter.selectedItems) { for (i in explorerAdapter.selectedItems) {
paths.add(explorerElements[i].fullPath) paths.add(explorerElements[i].fullPath)
} }
isStartingActivity = true app.isStartingExternalApp = true
ExternalProvider.share(this, themeValue, encryptedVolume, paths) ExternalProvider.share(this, themeValue, encryptedVolume, paths)
unselectAll() unselectAll()
true true
} }
R.id.decrypt -> { R.id.decrypt -> {
isStartingActivity = true app.isStartingExternalApp = true
pickExportDirectory.launch(null) pickExportDirectory.launch(null)
true true
} }
@ -506,13 +511,4 @@ class ExplorerActivity : BaseExplorerActivity() {
itemsToProcess.clear() itemsToProcess.clear()
} }
} }
override fun onBackPressed() {
if (currentItemAction != ItemsActions.NONE) {
cancelItemAction()
invalidateOptionsMenu()
} else {
super.onBackPressed()
}
}
} }

View File

@ -22,21 +22,18 @@ class ExplorerActivityPick : BaseExplorerActivity() {
} }
override fun onExplorerElementClick(position: Int) { override fun onExplorerElementClick(position: Int) {
val wasSelecting = explorerAdapter.selectedItems.isNotEmpty()
if (explorerAdapter.selectedItems.isEmpty()) { if (explorerAdapter.selectedItems.isEmpty()) {
if (!wasSelecting) { val fullPath = PathUtils.pathJoin(currentDirectoryPath, explorerElements[position].name)
val fullPath = PathUtils.pathJoin(currentDirectoryPath, explorerElements[position].name) when {
when { explorerElements[position].isDirectory -> {
explorerElements[position].isDirectory -> { setCurrentPath(fullPath)
setCurrentPath(fullPath) }
} explorerElements[position].isParentFolder -> {
explorerElements[position].isParentFolder -> { setCurrentPath(PathUtils.getParentPath(currentDirectoryPath))
setCurrentPath(PathUtils.getParentPath(currentDirectoryPath)) }
} else -> {
else -> { resultIntent.putExtra("path", fullPath)
resultIntent.putExtra("path", fullPath) returnActivityResult()
returnActivityResult()
}
} }
} }
} }
@ -81,17 +78,4 @@ class ExplorerActivityPick : BaseExplorerActivity() {
isFinishingIntentionally = true isFinishingIntentionally = true
finish() finish()
} }
override fun closeVolumeOnDestroy() {
if (!isFinishingIntentionally && !usf_keep_open){
IntentUtils.getParcelableExtra<EncryptedVolume>(intent, "destinationVolume")?.close()
super.closeVolumeOnDestroy()
}
}
override fun closeVolumeOnUserExit() {
isFinishingIntentionally = true
super.closeVolumeOnUserExit()
super.closeVolumeOnDestroy()
}
} }

View File

@ -9,7 +9,7 @@ class ExplorerRouter(private val context: Context, private val intent: Intent) {
var pickMode = intent.action == "pick" var pickMode = intent.action == "pick"
var dropMode = (intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_SEND_MULTIPLE) && intent.extras != null var dropMode = (intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_SEND_MULTIPLE) && intent.extras != null
fun getExplorerIntent(encryptedVolume: EncryptedVolume, volumeShortName: String): Intent { fun getExplorerIntent(volumeId: Int, volumeShortName: String): Intent {
var explorerIntent: Intent? = null var explorerIntent: Intent? = null
if (dropMode) { //import via android share menu if (dropMode) { //import via android share menu
explorerIntent = Intent(context, ExplorerActivityDrop::class.java) explorerIntent = Intent(context, ExplorerActivityDrop::class.java)
@ -22,8 +22,8 @@ class ExplorerRouter(private val context: Context, private val intent: Intent) {
if (explorerIntent == null) { if (explorerIntent == null) {
explorerIntent = Intent(context, ExplorerActivity::class.java) //default opening explorerIntent = Intent(context, ExplorerActivity::class.java) //default opening
} }
explorerIntent.putExtra("volume", encryptedVolume) explorerIntent.putExtra("volumeId", volumeId)
explorerIntent.putExtra("volume_name", volumeShortName) explorerIntent.putExtra("volumeName", volumeShortName)
return explorerIntent return explorerIntent
} }
} }

View File

@ -2,7 +2,6 @@ package sushi.hardcore.droidfs.file_viewers
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.activity.addCallback
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
@ -14,7 +13,6 @@ import kotlinx.coroutines.withContext
import sushi.hardcore.droidfs.BaseActivity import sushi.hardcore.droidfs.BaseActivity
import sushi.hardcore.droidfs.FileTypes import sushi.hardcore.droidfs.FileTypes
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.util.IntentUtils
@ -27,8 +25,6 @@ abstract class FileViewerActivity: BaseActivity() {
private lateinit var originalParentPath: String private lateinit var originalParentPath: String
private lateinit var windowInsetsController: WindowInsetsControllerCompat private lateinit var windowInsetsController: WindowInsetsControllerCompat
private var windowTypeMask = 0 private var windowTypeMask = 0
private var isFinishingIntentionally = false
private var usf_keep_open = false
private var foldersFirst = true private var foldersFirst = true
private var wasMapped = false private var wasMapped = false
protected val mappedPlaylist = mutableListOf<ExplorerElement>() protected val mappedPlaylist = mutableListOf<ExplorerElement>()
@ -43,17 +39,11 @@ abstract class FileViewerActivity: BaseActivity() {
filePath = intent.getStringExtra("path")!! filePath = intent.getStringExtra("path")!!
originalParentPath = PathUtils.getParentPath(filePath) originalParentPath = PathUtils.getParentPath(filePath)
encryptedVolume = IntentUtils.getParcelableExtra(intent, "volume")!! encryptedVolume = IntentUtils.getParcelableExtra(intent, "volume")!!
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
foldersFirst = sharedPrefs.getBoolean("folders_first", true) foldersFirst = sharedPrefs.getBoolean("folders_first", true)
windowInsetsController = WindowInsetsControllerCompat(window, window.decorView) windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
windowInsetsController.addOnControllableInsetsChangedListener { _, typeMask -> windowInsetsController.addOnControllableInsetsChangedListener { _, typeMask ->
windowTypeMask = typeMask windowTypeMask = typeMask
} }
onBackPressedDispatcher.addCallback(this) {
isFinishingIntentionally = true
isEnabled = false
onBackPressedDispatcher.onBackPressed()
}
if (fullscreenMode) { if (fullscreenMode) {
fixNavBarColor() fixNavBarColor()
hideSystemUi() hideSystemUi()
@ -156,21 +146,12 @@ abstract class FileViewerActivity: BaseActivity() {
} }
protected fun goBackToExplorer() { protected fun goBackToExplorer() {
isFinishingIntentionally = true
finish() finish()
} }
override fun onDestroy() { override fun onResume() {
super.onDestroy() super.onResume()
if (!isFinishingIntentionally) { if (encryptedVolume.isClosed()) {
encryptedVolume.close()
RestrictedFileProvider.wipeAll(this)
}
}
override fun onPause() {
super.onPause()
if (!usf_keep_open) {
finish() finish()
} }
} }

View File

@ -7,6 +7,7 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.EditText import android.widget.EditText
import android.widget.Toast import android.widget.Toast
import androidx.activity.addCallback
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.io.File import java.io.File
@ -29,6 +30,9 @@ class TextEditor: FileViewerActivity() {
loadWholeFile(filePath) { loadWholeFile(filePath) {
try { try {
loadLayout(String(it)) loadLayout(String(it))
onBackPressedDispatcher.addCallback(this) {
checkSaveAndExit()
}
} catch (e: OutOfMemoryError){ } catch (e: OutOfMemoryError){
CustomAlertDialogBuilder(this, themeValue) CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.error) .setTitle(R.string.error)
@ -124,8 +128,4 @@ class TextEditor: FileViewerActivity() {
} }
return true return true
} }
override fun onBackPressed() {
checkSaveAndExit()
}
} }

View File

@ -67,13 +67,9 @@ class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
} }
} }
fun create(baseDir: String, localStateDir: String, password: ByteArray, returnedHash: ObjRef<ByteArray?>?, cipher: String?, volume: ObjRef<EncryptedVolume?>?): Boolean { fun create(baseDir: String, localStateDir: String, password: ByteArray, returnedHash: ObjRef<ByteArray?>?, cipher: String?, volume: ObjRef<EncryptedVolume?>): Boolean {
return init(baseDir, localStateDir, password, null, returnedHash, true, cipher)?.also { return init(baseDir, localStateDir, password, null, returnedHash, true, cipher)?.also {
if (volume == null) { volume.value = it
it.close()
} else {
volume.value = it
}
} != null } != null
} }

View File

@ -36,7 +36,6 @@ class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() {
logN: Int, logN: Int,
creator: String, creator: String,
returnedHash: ByteArray?, returnedHash: ByteArray?,
openAfterCreation: Boolean,
): Int ): Int
private external fun nativeInit(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int private external fun nativeInit(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int
external fun changePassword( external fun changePassword(
@ -53,20 +52,26 @@ class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() {
plainTextNames: Boolean, plainTextNames: Boolean,
xchacha: Int, xchacha: Int,
returnedHash: ByteArray?, returnedHash: ByteArray?,
volume: ObjRef<EncryptedVolume?>? volume: ObjRef<EncryptedVolume?>
): Boolean { ): Boolean {
val openAfterCreation = volume != null return when (val result = nativeCreateVolume(
val result = nativeCreateVolume(root_cipher_dir, password, plainTextNames, xchacha, ScryptDefaultLogN, VOLUME_CREATOR, returnedHash, openAfterCreation) root_cipher_dir,
return if (!openAfterCreation) { password,
result == 1 plainTextNames,
} else if (result == -1) { xchacha,
Log.e("gocryptfs", "Failed to open volume after creation") ScryptDefaultLogN,
true VOLUME_CREATOR,
} else if (result == -2) { returnedHash,
false )) {
} else { -1 -> {
volume!!.value = GocryptfsVolume(result) Log.e("gocryptfs", "Failed to open volume after creation")
true true
}
-2 -> false
else -> {
volume.value = GocryptfsVolume(result)
true
}
} }
} }

View File

@ -1,8 +1,6 @@
#include <jni.h> #include <jni.h>
#include <stdio.h>
#include <malloc.h> #include <malloc.h>
#include <string.h> #include <string.h>
#include <sys/stat.h>
#include "libgocryptfs.h" #include "libgocryptfs.h"
const int KeyLen = 32; const int KeyLen = 32;
@ -15,38 +13,34 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_nativeCre
jint xchacha, jint xchacha,
jint logN, jint logN,
jstring jcreator, jstring jcreator,
jbyteArray jreturned_hash, jbyteArray jreturned_hash) {
jboolean open_after_creation) {
const char* root_cipher_dir = (*env)->GetStringUTFChars(env, jroot_cipher_dir, NULL); const char* root_cipher_dir = (*env)->GetStringUTFChars(env, jroot_cipher_dir, NULL);
const char* creator = (*env)->GetStringUTFChars(env, jcreator, NULL); const char* creator = (*env)->GetStringUTFChars(env, jcreator, NULL);
GoString gofilename = {root_cipher_dir, strlen(root_cipher_dir)}, gocreator = {creator, strlen(creator)}; GoString gofilename = {root_cipher_dir, strlen(root_cipher_dir)}, gocreator = {creator, strlen(creator)};
const size_t password_len = (*env)->GetArrayLength(env, jpassword); const size_t password_len = (const size_t) (*env)->GetArrayLength(env, jpassword);
jbyte* password = (*env)->GetByteArrayElements(env, jpassword, NULL); jbyte* password = (*env)->GetByteArrayElements(env, jpassword, NULL);
GoSlice go_password = {password, password_len, password_len}; GoSlice go_password = {password, password_len, password_len};
size_t returned_hash_len; size_t returned_hash_len;
GoSlice go_returned_hash; GoSlice go_returned_hash;
if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) { if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) {
returned_hash_len = (*env)->GetArrayLength(env, jreturned_hash); returned_hash_len = (size_t) (*env)->GetArrayLength(env, jreturned_hash);
go_returned_hash.data = (*env)->GetByteArrayElements(env, jreturned_hash, NULL); go_returned_hash.data = (*env)->GetByteArrayElements(env, jreturned_hash, NULL);
} else if (open_after_creation) { } else {
returned_hash_len = KeyLen; returned_hash_len = KeyLen;
go_returned_hash.data = malloc(KeyLen); go_returned_hash.data = malloc(KeyLen);
} else {
returned_hash_len = 0;
go_returned_hash.data = NULL;
} }
go_returned_hash.len = returned_hash_len; go_returned_hash.len = returned_hash_len;
go_returned_hash.cap = returned_hash_len; go_returned_hash.cap = returned_hash_len;
GoUint8 result = gcf_create_volume(gofilename, go_password, plainTextNames, xchacha, logN, gocreator, go_returned_hash); GoUint8 result = gcf_create_volume(gofilename, go_password, plainTextNames, (GoInt8) xchacha, logN, gocreator, go_returned_hash);
(*env)->ReleaseByteArrayElements(env, jpassword, password, 0); (*env)->ReleaseByteArrayElements(env, jpassword, password, 0);
(*env)->ReleaseStringUTFChars(env, jcreator, creator); (*env)->ReleaseStringUTFChars(env, jcreator, creator);
GoInt sessionID = -2; GoInt sessionID = -2;
if (result && open_after_creation) { if (result) {
GoSlice null_slice = {NULL, 0, 0}; GoSlice null_slice = {NULL, 0, 0};
sessionID = gcf_init(gofilename, null_slice, go_returned_hash, null_slice); sessionID = gcf_init(gofilename, null_slice, go_returned_hash, null_slice);
} }
@ -55,14 +49,14 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_nativeCre
if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) { if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) {
(*env)->ReleaseByteArrayElements(env, jreturned_hash, go_returned_hash.data, 0); (*env)->ReleaseByteArrayElements(env, jreturned_hash, go_returned_hash.data, 0);
} else if (open_after_creation) { } else {
for (unsigned int i=0; i<returned_hash_len; ++i) { for (unsigned int i=0; i<returned_hash_len; ++i) {
((unsigned char*) go_returned_hash.data)[i] = 0; ((unsigned char*) go_returned_hash.data)[i] = 0;
} }
free(go_returned_hash.data); free(go_returned_hash.data);
} }
return sessionID*open_after_creation+result*!open_after_creation; return sessionID;
} }
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
@ -81,13 +75,13 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_nativeIni
jbyte* given_hash; jbyte* given_hash;
GoSlice go_given_hash = {NULL, 0, 0}; GoSlice go_given_hash = {NULL, 0, 0};
if ((*env)->IsSameObject(env, jgiven_hash, NULL)){ if ((*env)->IsSameObject(env, jgiven_hash, NULL)){
password_len = (*env)->GetArrayLength(env, jpassword); password_len = (size_t) (*env)->GetArrayLength(env, jpassword);
password = (*env)->GetByteArrayElements(env, jpassword, NULL); password = (*env)->GetByteArrayElements(env, jpassword, NULL);
go_password.data = password; go_password.data = password;
go_password.len = password_len; go_password.len = password_len;
go_password.cap = password_len; go_password.cap = password_len;
} else { } else {
given_hash_len = (*env)->GetArrayLength(env, jgiven_hash); given_hash_len = (size_t) (*env)->GetArrayLength(env, jgiven_hash);
given_hash = (*env)->GetByteArrayElements(env, jgiven_hash, NULL); given_hash = (*env)->GetByteArrayElements(env, jgiven_hash, NULL);
go_given_hash.data = given_hash; go_given_hash.data = given_hash;
go_given_hash.len = given_hash_len; go_given_hash.len = given_hash_len;
@ -98,7 +92,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_nativeIni
jbyte* returned_hash; jbyte* returned_hash;
GoSlice go_returned_hash = {NULL, 0, 0}; GoSlice go_returned_hash = {NULL, 0, 0};
if (!(*env)->IsSameObject(env, jreturned_hash, NULL)){ if (!(*env)->IsSameObject(env, jreturned_hash, NULL)){
returned_hash_len = (*env)->GetArrayLength(env, jreturned_hash); returned_hash_len = (size_t) (*env)->GetArrayLength(env, jreturned_hash);
returned_hash = (*env)->GetByteArrayElements(env, jreturned_hash, NULL); returned_hash = (*env)->GetByteArrayElements(env, jreturned_hash, NULL);
go_returned_hash.data = returned_hash; go_returned_hash.data = returned_hash;
go_returned_hash.len = returned_hash_len; go_returned_hash.len = returned_hash_len;
@ -145,20 +139,20 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_changePas
jbyte* given_hash; jbyte* given_hash;
GoSlice go_given_hash = {NULL, 0, 0}; GoSlice go_given_hash = {NULL, 0, 0};
if ((*env)->IsSameObject(env, jgiven_hash, NULL)){ if ((*env)->IsSameObject(env, jgiven_hash, NULL)){
old_password_len = (*env)->GetArrayLength(env, jold_password); old_password_len = (size_t) (*env)->GetArrayLength(env, jold_password);
old_password = (*env)->GetByteArrayElements(env, jold_password, NULL); old_password = (*env)->GetByteArrayElements(env, jold_password, NULL);
go_old_password.data = old_password; go_old_password.data = old_password;
go_old_password.len = old_password_len; go_old_password.len = old_password_len;
go_old_password.cap = old_password_len; go_old_password.cap = old_password_len;
} else { } else {
given_hash_len = (*env)->GetArrayLength(env, jgiven_hash); given_hash_len = (size_t) (*env)->GetArrayLength(env, jgiven_hash);
given_hash = (*env)->GetByteArrayElements(env, jgiven_hash, NULL); given_hash = (*env)->GetByteArrayElements(env, jgiven_hash, NULL);
go_given_hash.data = given_hash; go_given_hash.data = given_hash;
go_given_hash.len = given_hash_len; go_given_hash.len = given_hash_len;
go_given_hash.cap = given_hash_len; go_given_hash.cap = given_hash_len;
} }
size_t new_password_len = (*env)->GetArrayLength(env, jnew_password); size_t new_password_len = (size_t) (*env)->GetArrayLength(env, jnew_password);
jbyte* new_password = (*env)->GetByteArrayElements(env, jnew_password, NULL); jbyte* new_password = (*env)->GetByteArrayElements(env, jnew_password, NULL);
GoSlice go_new_password = {new_password, new_password_len, new_password_len}; GoSlice go_new_password = {new_password, new_password_len, new_password_len};
@ -166,7 +160,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_changePas
jbyte* returned_hash; jbyte* returned_hash;
GoSlice go_returned_hash = {NULL, 0, 0}; GoSlice go_returned_hash = {NULL, 0, 0};
if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) { if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) {
returned_hash_len = (*env)->GetArrayLength(env, jreturned_hash); returned_hash_len = (size_t) (*env)->GetArrayLength(env, jreturned_hash);
returned_hash = (*env)->GetByteArrayElements(env, jreturned_hash, NULL); returned_hash = (*env)->GetByteArrayElements(env, jreturned_hash, NULL);
go_returned_hash.data = returned_hash; go_returned_hash.data = returned_hash;
go_returned_hash.len = returned_hash_len; go_returned_hash.len = returned_hash_len;
@ -303,7 +297,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1open_1write_1mod
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL); const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
GoString go_file_path = {file_path, strlen(file_path)}; GoString go_file_path = {file_path, strlen(file_path)};
GoInt handleID = gcf_open_write_mode(sessionID, go_file_path, mode); GoInt handleID = gcf_open_write_mode(sessionID, go_file_path, (GoUint32) mode);
(*env)->ReleaseStringUTFChars(env, jfile_path, file_path); (*env)->ReleaseStringUTFChars(env, jfile_path, file_path);
@ -317,7 +311,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1write_1file(JNIE
jbyte* buff = (*env)->GetByteArrayElements(env, jbuff, NULL); jbyte* buff = (*env)->GetByteArrayElements(env, jbuff, NULL);
GoSlice go_buff = {buff+src_offset, length, length}; GoSlice go_buff = {buff+src_offset, length, length};
int written = gcf_write_file(sessionID, handleID, file_offset, go_buff); int written = gcf_write_file(sessionID, handleID, (GoUint64) file_offset, go_buff);
(*env)->ReleaseByteArrayElements(env, jbuff, buff, 0); (*env)->ReleaseByteArrayElements(env, jbuff, buff, 0);
@ -331,7 +325,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1read_1file(JNIEn
jbyte* buff = (*env)->GetByteArrayElements(env, jbuff, NULL); jbyte* buff = (*env)->GetByteArrayElements(env, jbuff, NULL);
GoSlice go_buff = {buff+dst_offset, length, length}; GoSlice go_buff = {buff+dst_offset, length, length};
int read = gcf_read_file(sessionID, handleID, file_offset, go_buff); int read = gcf_read_file(sessionID, handleID, (GoUint64) file_offset, go_buff);
(*env)->ReleaseByteArrayElements(env, jbuff, buff, 0); (*env)->ReleaseByteArrayElements(env, jbuff, buff, 0);
return read; return read;
@ -345,7 +339,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1truncate(JNIEnv
const char* path = (*env)->GetStringUTFChars(env, jpath, NULL); const char* path = (*env)->GetStringUTFChars(env, jpath, NULL);
GoString go_path = {path, strlen(path)}; GoString go_path = {path, strlen(path)};
GoUint8 result = gcf_truncate(sessionID, go_path, offset); GoUint8 result = gcf_truncate(sessionID, go_path, (GoUint64) offset);
(*env)->ReleaseStringUTFChars(env, jpath, path); (*env)->ReleaseStringUTFChars(env, jpath, path);
return result; return result;
@ -377,7 +371,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1mkdir(JNIEnv *en
const char* dir_path = (*env)->GetStringUTFChars(env, jdir_path, NULL); const char* dir_path = (*env)->GetStringUTFChars(env, jdir_path, NULL);
GoString go_dir_path = {dir_path, strlen(dir_path)}; GoString go_dir_path = {dir_path, strlen(dir_path)};
GoUint8 result = gcf_mkdir(sessionID, go_dir_path, mode); GoUint8 result = gcf_mkdir(sessionID, go_dir_path, (GoUint32) mode);
(*env)->ReleaseStringUTFChars(env, jdir_path, dir_path); (*env)->ReleaseStringUTFChars(env, jdir_path, dir_path);

View File

@ -1,4 +0,0 @@
<vector android:height="24dp" android:viewportHeight="500"
android:viewportWidth="500" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/colorAccent" android:pathData="M68.29,431.711c0,20.078 16.264,36.34 36.343,36.34h290.734c20.078,0 36.345,-16.262 36.345,-36.34V250c0,-20.079 -16.267,-36.342 -36.345,-36.342H177.317v-63.597c0,-40.157 32.525,-72.685 72.683,-72.685c40.158,0 72.685,32.528 72.685,72.685v4.541c0,12.538 10.176,22.715 22.711,22.715c12.537,0 22.717,-10.177 22.717,-22.715v-4.541c0,-65.232 -52.882,-118.111 -118.112,-118.111c-65.24,0 -118.111,52.879 -118.111,118.111v63.597h-27.256c-20.079,0 -36.343,16.263 -36.343,36.342V431.711zM213.658,313.599c0,-20.078 16.263,-36.341 36.342,-36.341s36.341,16.263 36.341,36.341c0,12.812 -6.634,24.079 -16.625,30.529c0,0 3.55,21.446 7.542,46.699c0,7.538 -6.087,13.625 -13.629,13.625h-27.258c-7.541,0 -13.627,-6.087 -13.627,-13.625l7.542,-46.699C220.294,337.678 213.658,326.41 213.658,313.599z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/colorAccent" android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z"/>
</vector>

View File

@ -8,21 +8,23 @@
<RelativeLayout <RelativeLayout
android:id="@+id/selectable_container" android:id="@+id/selectable_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
tools:ignore="UselessParent"> tools:ignore="UselessParent"
android:paddingEnd="4dp">
<ImageView <ImageView
android:id="@+id/image_icon" android:id="@+id/image_icon"
android:layout_width="50dp" android:layout_width="50dp"
android:layout_height="50dp"/> android:layout_height="50dp"
android:src="@drawable/icon_volume"/>
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:layout_toEndOf="@id/image_icon" android:layout_toEndOf="@id/image_icon"
android:layout_toStartOf="@id/icon_fingerprint"> android:layout_toStartOf="@id/layout_icons">
<TextView <TextView
android:id="@+id/text_volume_name" android:id="@+id/text_volume_name"
@ -63,16 +65,31 @@
</LinearLayout> </LinearLayout>
<ImageView <LinearLayout
android:id="@+id/icon_fingerprint" android:id="@+id/layout_icons"
android:layout_width="26dp" android:layout_width="wrap_content"
android:layout_height="26dp" android:layout_height="match_parent"
android:src="@drawable/icon_fingerprint" android:gravity="center_vertical"
android:contentDescription="@string/password_hash_saved" android:layout_alignParentEnd="true">
android:visibility="gone"
android:layout_alignParentEnd="true" <ImageView
android:layout_marginTop="11dp" android:id="@+id/icon_unlocked"
android:layout_marginEnd="5dp"/> android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/icon_lock_open"
android:contentDescription="@string/volume_unlocked"
android:visibility="gone"/>
<ImageView
android:id="@+id/icon_fingerprint"
android:layout_width="26dp"
android:layout_height="26dp"
android:src="@drawable/icon_fingerprint"
android:contentDescription="@string/password_hash_saved"
android:visibility="gone"
android:layout_marginStart="5dp"/>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -30,9 +30,8 @@
<item <item
android:id="@+id/decrypt" android:id="@+id/decrypt"
app:showAsAction="ifRoom" app:showAsAction="never"
android:visible="false" android:visible="false"
android:icon="@drawable/icon_decrypt"
android:title="@string/decrypt_files"/> android:title="@string/decrypt_files"/>
<item <item
@ -72,6 +71,12 @@
android:title="@string/sort_by" android:title="@string/sort_by"
android:icon="@drawable/icon_sort"/> android:icon="@drawable/icon_sort"/>
<item
android:id="@+id/lock"
app:showAsAction="ifRoom"
android:title="@string/lock_volume"
android:icon="@drawable/icon_lock"/>
<item <item
android:id="@+id/close" android:id="@+id/close"
app:showAsAction="ifRoom" app:showAsAction="ifRoom"

View File

@ -19,6 +19,12 @@
android:title="@string/sort_by" android:title="@string/sort_by"
android:icon="@drawable/icon_sort"/> android:icon="@drawable/icon_sort"/>
<item
android:id="@+id/lock"
app:showAsAction="ifRoom"
android:title="@string/lock_volume"
android:icon="@drawable/icon_lock"/>
<item <item
android:id="@+id/close" android:id="@+id/close"
app:showAsAction="ifRoom" app:showAsAction="ifRoom"

View File

@ -38,6 +38,12 @@
android:title="@string/sort_by" android:title="@string/sort_by"
android:icon="@drawable/icon_sort"/> android:icon="@drawable/icon_sort"/>
<item
android:id="@+id/lock"
app:showAsAction="ifRoom"
android:title="@string/lock_volume"
android:icon="@drawable/icon_lock"/>
<item <item
android:id="@+id/close" android:id="@+id/close"
app:showAsAction="ifRoom" app:showAsAction="ifRoom"

View File

@ -9,6 +9,13 @@
android:title="@string/select_all" android:title="@string/select_all"
android:icon="@drawable/icon_select_all"/> android:icon="@drawable/icon_select_all"/>
<item
android:id="@+id/lock"
app:showAsAction="ifRoom"
android:visible="false"
android:title="@string/lock"
android:icon="@drawable/icon_lock"/>
<item <item
android:id="@+id/remove" android:id="@+id/remove"
app:showAsAction="ifRoom" app:showAsAction="ifRoom"

View File

@ -11,7 +11,6 @@
<string name="mkdir">إنشاء مجلد</string> <string name="mkdir">إنشاء مجلد</string>
<string name="dir_empty">مجلد فارغ</string> <string name="dir_empty">مجلد فارغ</string>
<string name="warning">تحذير !</string> <string name="warning">تحذير !</string>
<string name="ask_close_volume">هل تريد إغلاق المجلد المشفر ?</string>
<string name="ok">حسنا</string> <string name="ok">حسنا</string>
<string name="cancel">إلغاء</string> <string name="cancel">إلغاء</string>
<string name="enter_folder_name">اسم المجلد:</string> <string name="enter_folder_name">اسم المجلد:</string>

View File

@ -11,7 +11,6 @@
<string name="mkdir">Crear carpeta</string> <string name="mkdir">Crear carpeta</string>
<string name="dir_empty">Directorio vacío</string> <string name="dir_empty">Directorio vacío</string>
<string name="warning">¡Advertencia!</string> <string name="warning">¡Advertencia!</string>
<string name="ask_close_volume">¿Estás seguro de que quieres cerrar este volumen?</string>
<string name="ok">Aceptar</string> <string name="ok">Aceptar</string>
<string name="cancel">Cancelar</string> <string name="cancel">Cancelar</string>
<string name="enter_folder_name">Nombre de la carpeta:</string> <string name="enter_folder_name">Nombre de la carpeta:</string>

View File

@ -11,7 +11,6 @@
<string name="mkdir">Criar pasta</string> <string name="mkdir">Criar pasta</string>
<string name="dir_empty">Pasta vazia</string> <string name="dir_empty">Pasta vazia</string>
<string name="warning">Aviso!</string> <string name="warning">Aviso!</string>
<string name="ask_close_volume">Tem certeza que quer fechar este volume?</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="cancel">Cancelar</string> <string name="cancel">Cancelar</string>
<string name="enter_folder_name">Nome da pasta:</string> <string name="enter_folder_name">Nome da pasta:</string>

View File

@ -10,7 +10,6 @@
<string name="mkdir">Создать папку</string> <string name="mkdir">Создать папку</string>
<string name="dir_empty">Пусто</string> <string name="dir_empty">Пусто</string>
<string name="warning">Предупреждение!</string> <string name="warning">Предупреждение!</string>
<string name="ask_close_volume">Закрыть том?</string>
<string name="cancel">Отмена</string> <string name="cancel">Отмена</string>
<string name="enter_folder_name">Имя папки:</string> <string name="enter_folder_name">Имя папки:</string>
<string name="error">Ошибка</string> <string name="error">Ошибка</string>

View File

@ -11,7 +11,7 @@
<string name="mkdir">Create folder</string> <string name="mkdir">Create folder</string>
<string name="dir_empty">Directory Empty</string> <string name="dir_empty">Directory Empty</string>
<string name="warning">Warning !</string> <string name="warning">Warning !</string>
<string name="ask_close_volume">Are you sure you want to close this volume ?</string> <string name="ask_lock_volume">Are you sure you want to lock this volume ?</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="enter_folder_name">Folder Name:</string> <string name="enter_folder_name">Folder Name:</string>
@ -253,4 +253,7 @@
<string name="remember_volume">Remember volume</string> <string name="remember_volume">Remember volume</string>
<string name="open_volume">Open volume</string> <string name="open_volume">Open volume</string>
<string name="choose_existing_volume">Please choose an existing volume</string> <string name="choose_existing_volume">Please choose an existing volume</string>
<string name="volume_unlocked">Volume unlocked</string>
<string name="lock_volume">Lock volume</string>
<string name="lock">Lock</string>
</resources> </resources>

View File

@ -15,7 +15,7 @@
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:icon="@drawable/icon_decrypt" android:icon="@drawable/icon_lock_open"
android:key="usf_decrypt" android:key="usf_decrypt"
android:title="@string/usf_decrypt" /> android:title="@string/usf_decrypt" />
<SwitchPreference <SwitchPreference
@ -31,7 +31,7 @@
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:icon="@drawable/icon_decrypt" android:icon="@drawable/icon_lock_open"
android:key="usf_keep_open" android:key="usf_keep_open"
android:title="@string/usf_keep_open" /> android:title="@string/usf_keep_open" />

View File

@ -5,7 +5,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.4.1' classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }