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 untrusted user: 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 "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
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.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.preference:preference-ktx:1.2.0"

View File

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

View File

@ -4,7 +4,6 @@ import android.content.SharedPreferences
import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
open class BaseActivity: AppCompatActivity() {
protected lateinit var sharedPrefs: SharedPreferences
@ -13,7 +12,7 @@ open class BaseActivity: AppCompatActivity() {
private var shouldCheckTheme = true
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)!!
if (shouldCheckTheme && applyCustomTheme) {
when (themeValue) {

View File

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

View File

@ -20,12 +20,10 @@ import kotlinx.coroutines.launch
import sushi.hardcore.droidfs.Constants.DEFAULT_VOLUME_KEY
import sushi.hardcore.droidfs.adapters.VolumeAdapter
import sushi.hardcore.droidfs.add_volume.AddVolumeActivity
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
import sushi.hardcore.droidfs.databinding.ActivityMainBinding
import sushi.hardcore.droidfs.databinding.DialogDeleteVolumeBinding
import sushi.hardcore.droidfs.explorers.ExplorerRouter
import sushi.hardcore.droidfs.file_operations.FileOperationService
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.IntentUtils
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
@ -33,11 +31,15 @@ import sushi.hardcore.droidfs.widgets.EditTextDialog
import java.io.File
class MainActivity : BaseActivity(), VolumeAdapter.Listener {
companion object {
private const val OPEN_DEFAULT_VOLUME = "openDefault"
}
private lateinit var binding: ActivityMainBinding
private lateinit var volumeDatabase: VolumeDatabase
private lateinit var volumeManager: VolumeManager
private lateinit var volumeAdapter: VolumeAdapter
private lateinit var volumeOpener: VolumeOpener
private var usfKeepOpen: Boolean = false
private var addVolume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if ((explorerRouter.pickMode || explorerRouter.dropMode) && result.resultCode != AddVolumeActivity.RESULT_USER_BACK) {
setResult(result.resultCode, result.data) // forward result
@ -54,7 +56,6 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
}
private lateinit var fileOperationService: FileOperationService
private lateinit var explorerRouter: ExplorerRouter
private var shouldCloseVolume = true // used when launched to pick file from another volume
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -80,10 +81,12 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
.show()
}
explorerRouter = ExplorerRouter(this, intent)
volumeManager = (application as VolumeManagerApp).volumeManager
volumeDatabase = VolumeDatabase(this)
volumeAdapter = VolumeAdapter(
this,
volumeDatabase,
(application as VolumeManagerApp).volumeManager,
!explorerRouter.pickMode && !explorerRouter.dropMode,
!explorerRouter.dropMode,
this,
@ -100,30 +103,31 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
addVolume.launch(Intent(this, AddVolumeActivity::class.java).also {
if (explorerRouter.dropMode || explorerRouter.pickMode) {
IntentUtils.forwardIntent(intent, it)
shouldCloseVolume = false
}
})
}
usfKeepOpen = sharedPrefs.getBoolean("usf_keep_open", false)
volumeOpener = VolumeOpener(this)
volumeOpener.defaultVolumeName?.let { name ->
try {
openVolume(volumeAdapter.volumes.first { it.name == name })
} catch (e: NoSuchElementException) {
unsetDefaultVolume()
}
}
onBackPressedDispatcher.addCallback(this) {
if (volumeAdapter.selectedItems.isNotEmpty()) {
unselectAll()
} else {
if (explorerRouter.pickMode) {
shouldCloseVolume = false
}
isEnabled = false
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 {
bindService(it, object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
@ -148,6 +152,11 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
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) {
title = if (size == 0) {
getString(R.string.app_name)
@ -179,6 +188,11 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
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) {
if (i < volumes.size) {
if (volumes[i].isHidden) {
@ -196,12 +210,12 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
.setTitle(R.string.warning)
.setView(dialogBinding.root)
.setPositiveButton(R.string.forget_only) { _, _ ->
volumeDatabase.removeVolume(volumes[i].name)
removeVolume(volumes[i])
removeVolumes(volumes, i + 1, if (dialogBinding.checkboxApplyToAll.isChecked) false else null)
}
.setNegativeButton(R.string.delete_volume) { _, _ ->
PathUtils.recursiveRemoveDirectory(File(volumes[i].getFullPath(filesDir.path)))
volumeDatabase.removeVolume(volumes[i].name)
removeVolume(volumes[i])
removeVolumes(volumes, i + 1, if (dialogBinding.checkboxApplyToAll.isChecked) true else null)
}
.setOnCancelListener {
@ -213,11 +227,11 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
if (doDeleteVolumeContent) {
PathUtils.recursiveRemoveDirectory(File(volumes[i].getFullPath(filesDir.path)))
}
volumeDatabase.removeVolume(volumes[i].name)
removeVolume(volumes[i])
removeVolumes(volumes, i + 1, doDeleteVolumeContent)
}
} else {
volumeDatabase.removeVolume(volumes[i].name)
removeVolume(volumes[i])
removeVolumes(volumes, i + 1, doDeleteVolumeContent)
}
} else {
@ -241,9 +255,6 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
return when (item.itemId) {
android.R.id.home -> {
if (explorerRouter.pickMode || explorerRouter.dropMode) {
if (explorerRouter.pickMode) {
shouldCloseVolume = false
}
finish()
} else {
unselectAll()
@ -255,6 +266,15 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
invalidateOptionsMenu()
true
}
R.id.lock -> {
volumeAdapter.selectedItems.forEach {
volumeManager.getVolumeId(volumeAdapter.volumes[it])?.let { id ->
volumeManager.closeVolume(id)
}
}
unselectAll()
true
}
R.id.remove -> {
val selectedVolumes = volumeAdapter.selectedItems.map { i -> volumeAdapter.volumes[i] }
removeVolumes(selectedVolumes)
@ -325,6 +345,9 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
menu.findItem(R.id.settings).isVisible = !explorerRouter.pickMode && !explorerRouter.dropMode
val isSelecting = volumeAdapter.selectedItems.isNotEmpty()
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.delete_password_hash).isVisible =
isSelecting &&
@ -456,11 +479,8 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
volumeAdapter.refresh()
}
override fun onVolumeOpened(encryptedVolume: EncryptedVolume, volumeShortName: String) {
startActivity(explorerRouter.getExplorerIntent(encryptedVolume, volumeShortName))
if (explorerRouter.pickMode) {
shouldCloseVolume = false
}
override fun onVolumeOpened(id: Int) {
startActivity(explorerRouter.getExplorerIntent(id, volume.shortName))
if (explorerRouter.dropMode || explorerRouter.pickMode) {
finish()
}
@ -468,18 +488,8 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
})
}
override fun onResume() {
super.onResume()
shouldCloseVolume = true
}
override fun onStop() {
super.onStop()
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 {
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 {
fun onHashStorageReset() {}
fun onVolumeOpened(encryptedVolume: EncryptedVolume, volumeShortName: String)
fun onVolumeOpened(id: Int)
}
private val volumeDatabase = VolumeDatabase(activity)
@ -31,6 +31,7 @@ class VolumeOpener(
var themeValue = sharedPrefs.getString(Constants.THEME_VALUE_KEY, Constants.DEFAULT_THEME_VALUE)!!
var defaultVolumeName: String? = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null)
private var dialogBinding: DialogOpenVolumeBinding? = null
private val volumeManager = (activity.application as VolumeManagerApp).volumeManager
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -40,6 +41,8 @@ class VolumeOpener(
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
fun openVolume(volume: VolumeData, isVolumeSaved: Boolean, callbacks: VolumeOpenerCallbacks) {
val volumeId = volumeManager.getVolumeId(volume)
if (volumeId == null) {
if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE && BuildConfig.GOCRYPTFS_DISABLED) {
Toast.makeText(activity, R.string.gocryptfs_disabled, Toast.LENGTH_SHORT).show()
return
@ -71,7 +74,7 @@ class VolumeOpener(
.setPositiveButton(R.string.ok, null)
.show()
} else {
callbacks.onVolumeOpened(encryptedVolume, volume.shortName)
callbacks.onVolumeOpened(volumeManager.insert(encryptedVolume, volume))
}
}
}
@ -89,6 +92,9 @@ class VolumeOpener(
if (askForPassword) {
askForPassword(volume, isVolumeSaved, callbacks)
}
} else {
callbacks.onVolumeOpened(volumeId)
}
}
fun wipeSensitive() {
@ -188,7 +194,7 @@ class VolumeOpener(
override fun onPasswordHashDecrypted(hash: ByteArray) {}
override fun onPasswordHashSaved() {
Arrays.fill(returnedHash.value!!, 0)
callbacks.onVolumeOpened(encryptedVolume, volume.shortName)
callbacks.onVolumeOpened(volumeManager.insert(encryptedVolume, volume))
}
private var isClosed = false
override fun onFailed(pending: Boolean) {
@ -201,7 +207,7 @@ class VolumeOpener(
}
fingerprintProtector.savePasswordHash(volume, returnedHash.value!!)
} 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.LinearLayout
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.VolumeData
import sushi.hardcore.droidfs.VolumeDatabase
import sushi.hardcore.droidfs.VolumeManager
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
class VolumeAdapter(
private val context: Context,
private val volumeDatabase: VolumeDatabase,
private val volumeManager: VolumeManager,
private val allowSelection: Boolean,
private val showReadOnly: Boolean,
private val listener: Listener,
@ -80,16 +83,12 @@ class VolumeAdapter(
fun bind(position: Int) {
val volume = volumes[position]
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)
context.getString(R.string.hidden_volume)
else
volume.name
itemView.findViewById<ImageView>(R.id.icon_fingerprint).visibility = if (volume.encryptedHash == null) {
View.GONE
} else {
View.VISIBLE
}
itemView.findViewById<ImageView>(R.id.icon_unlocked).isVisible = volumeManager.isOpen(volume)
itemView.findViewById<ImageView>(R.id.icon_fingerprint).isVisible = volume.encryptedHash != null
itemView.findViewById<TextView>(R.id.text_info).text = context.getString(
if (volume.canWrite(context.filesDir.path)) {
R.string.volume_type

View File

@ -4,11 +4,8 @@ import android.os.Bundle
import android.view.MenuItem
import androidx.activity.addCallback
import sushi.hardcore.droidfs.*
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
import sushi.hardcore.droidfs.databinding.ActivityAddVolumeBinding
import sushi.hardcore.droidfs.explorers.ExplorerRouter
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.IntentUtils
class AddVolumeActivity: BaseActivity() {
@ -19,15 +16,12 @@ class AddVolumeActivity: BaseActivity() {
private lateinit var binding: ActivityAddVolumeBinding
private lateinit var explorerRouter: ExplorerRouter
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?) {
super.onCreate(savedInstanceState)
binding = ActivityAddVolumeBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
usfKeepOpen = sharedPrefs.getBoolean("usf_keep_open", false)
explorerRouter = ExplorerRouter(this, intent)
volumeOpener = VolumeOpener(this)
if (savedInstanceState == null) {
@ -41,7 +35,6 @@ class AddVolumeActivity: BaseActivity() {
}
onBackPressedDispatcher.addCallback(this) {
setResult(RESULT_USER_BACK)
shouldCloseVolume = false
isEnabled = false
onBackPressedDispatcher.onBackPressed()
}
@ -53,7 +46,6 @@ class AddVolumeActivity: BaseActivity() {
supportFragmentManager.popBackStack()
else {
setResult(RESULT_USER_BACK)
shouldCloseVolume = false
finish()
}
}
@ -70,21 +62,19 @@ class AddVolumeActivity: BaseActivity() {
)
}
fun startExplorer(encryptedVolume: EncryptedVolume, volumeShortName: String) {
startActivity(explorerRouter.getExplorerIntent(encryptedVolume, volumeShortName))
shouldCloseVolume = false
fun startExplorer(volumeId: Int, volumeShortName: String) {
startActivity(explorerRouter.getExplorerIntent(volumeId, volumeShortName))
finish()
}
fun onVolumeSelected(volume: VolumeData, rememberVolume: Boolean) {
if (rememberVolume) {
setResult(RESULT_USER_BACK)
shouldCloseVolume = false
finish()
} else {
volumeOpener.openVolume(volume, false, object : VolumeOpener.VolumeOpenerCallbacks {
override fun onVolumeOpened(encryptedVolume: EncryptedVolume, volumeShortName: String) {
startExplorer(encryptedVolume, volumeShortName)
override fun onVolumeOpened(id: Int) {
startExplorer(id, volume.shortName)
}
})
}
@ -106,18 +96,4 @@ class AddVolumeActivity: BaseActivity() {
.addToBackStack(null)
.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 {
null
}
val encryptedVolume = if (rememberVolume) {
null
} else {
ObjRef<EncryptedVolume?>(null)
}
val encryptedVolume = ObjRef<EncryptedVolume?>(null)
object: LoadingTask<Byte>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) {
private fun generateResult(success: Boolean, volumeType: Byte): Byte {
return if (success) {
@ -223,6 +219,9 @@ class CreateVolumeFragment: Fragment() {
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
if (isVolumeSaved && binding.checkboxSavePassword.isChecked && returnedHash != null) {
fingerprintProtector!!.let {
@ -235,31 +234,31 @@ class CreateVolumeFragment: Fragment() {
override fun onPasswordHashDecrypted(hash: ByteArray) {} // shouldn't happen here
override fun onPasswordHashSaved() {
Arrays.fill(returnedHash.value!!, 0)
onVolumeCreated(encryptedVolume?.value, volume.shortName)
onVolumeCreated(volumeId, volume.shortName)
}
override fun onFailed(pending: Boolean) {
if (!pending) {
Arrays.fill(returnedHash.value!!, 0)
onVolumeCreated(encryptedVolume?.value, volume.shortName)
onVolumeCreated(volumeId, volume.shortName)
}
}
}
it.savePasswordHash(volume, returnedHash.value!!)
}
} 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 {
if (rememberVolume || encryptedVolume == null) {
if (rememberVolume || id == null) {
finish()
} 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.VolumeData
import sushi.hardcore.droidfs.VolumeDatabase
import sushi.hardcore.droidfs.VolumeManagerApp
import sushi.hardcore.droidfs.databinding.DialogSdcardErrorBinding
import sushi.hardcore.droidfs.databinding.FragmentSelectPathBinding
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
@ -62,6 +63,7 @@ class SelectPathFragment: Fragment() {
if (uri != null)
onDirectoryPicked(uri)
}
private lateinit var app: VolumeManagerApp
private var themeValue = Constants.DEFAULT_THEME_VALUE
private lateinit var volumeDatabase: VolumeDatabase
private lateinit var filesDir: String
@ -81,6 +83,7 @@ class SelectPathFragment: Fragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
app = requireActivity().application as VolumeManagerApp
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
originalRememberVolume = sharedPrefs.getBoolean(Constants.REMEMBER_VOLUME_KEY, true)
binding.switchRemember.isChecked = originalRememberVolume
@ -105,6 +108,7 @@ class SelectPathFragment: Fragment() {
if (Environment.isExternalStorageManager()) {
launchPickDirectory()
} else {
app.isStartingExternalApp = true
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) {
@ -119,6 +123,7 @@ class SelectPathFragment: Fragment() {
) {
launchPickDirectory()
} else {
app.isStartingExternalApp = true
askStoragePermissions.launch(
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
@ -149,7 +154,7 @@ class SelectPathFragment: Fragment() {
}
private fun launchPickDirectory() {
(activity as AddVolumeActivity).shouldCloseVolume = false
app.isStartingExternalApp = true
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.Wiper
import java.io.File
import java.util.*
import java.util.UUID
import java.util.regex.Pattern
class RestrictedFileProvider: ContentProvider() {

View File

@ -13,6 +13,7 @@ import android.view.View
import android.widget.ImageButton
import android.widget.TextView
import android.widget.Toast
import androidx.activity.addCallback
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
@ -22,20 +23,15 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.coroutines.*
import sushi.hardcore.droidfs.BaseActivity
import sushi.hardcore.droidfs.Constants
import sushi.hardcore.droidfs.FileTypes
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.*
import sushi.hardcore.droidfs.adapters.ExplorerElementAdapter
import sushi.hardcore.droidfs.adapters.OpenAsDialogAdapter
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.OperationFile
import sushi.hardcore.droidfs.file_viewers.*
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.filesystems.Stat
import sushi.hardcore.droidfs.util.IntentUtils
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import sushi.hardcore.droidfs.widgets.EditTextDialog
@ -46,6 +42,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
private var foldersFirst = true
private var mapFolders = true
private var currentSortOrderIndex = 0
private var volumeId = -1
protected lateinit var encryptedVolume: EncryptedVolume
private lateinit var volumeName: String
private lateinit var explorerViewModel: ExplorerViewModel
@ -58,10 +55,8 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
protected val taskScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
protected lateinit var explorerElements: MutableList<ExplorerElement>
protected lateinit var explorerAdapter: ExplorerElementAdapter
private var isCreating = true
protected var isStartingActivity = false
protected lateinit var app: VolumeManagerApp
private var usf_open = false
protected var usf_keep_open = false
private lateinit var linearLayoutManager: LinearLayoutManager
private var isUsingListLayout = true
private lateinit var layoutIcon: ImageButton
@ -76,10 +71,11 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
app = application as VolumeManagerApp
usf_open = sharedPrefs.getBoolean("usf_open", false)
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
volumeName = intent.getStringExtra("volume_name") ?: ""
encryptedVolume = IntentUtils.getParcelableExtra(intent, "volume")!!
volumeName = intent.getStringExtra("volumeName") ?: ""
volumeId = intent.getIntExtra("volumeId", -1)
encryptedVolume = app.volumeManager.getVolume(volumeId)!!
sortOrderEntries = resources.getStringArray(R.array.sort_orders_entries)
sortOrderValues = resources.getStringArray(R.array.sort_orders_values)
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
@ -118,6 +114,19 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
isUsingListLayout = sharedPrefs.getBoolean("useListLayout", true)
layoutIcon = findViewById(R.id.layout_icon)
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 {
isUsingListLayout = !isUsingListLayout
setRecyclerViewLayout()
@ -177,12 +186,11 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
putExtra("volume", encryptedVolume)
putExtra("sortOrder", sortOrderValues[currentSortOrderIndex])
}
isStartingActivity = true
startActivity(intent)
}
private fun openWithExternalApp(fullPath: String) {
isStartingActivity = true
app.isStartingExternalApp = true
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)
.setTitle(R.string.warning)
.setMessage(R.string.ask_close_volume)
.setPositiveButton(R.string.ok) { _, _ -> closeVolumeOnUserExit() }
.setMessage(R.string.ask_lock_volume)
.setPositiveButton(R.string.ok) { _, _ ->
app.volumeManager.closeVolume(volumeId)
finish()
}
.setNegativeButton(R.string.cancel, null)
.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){
if (folderName.isEmpty()) {
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 iconColor = ContextCompat.getColor(this, R.color.neutralIconTint)
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)
menu.findItem(R.id.sort).isVisible = noItemSelected
menu.findItem(R.id.lock).isVisible = noItemSelected
menu.findItem(R.id.close).isVisible = noItemSelected
supportActionBar?.setDisplayHomeAsUpEnabled(!noItemSelected)
if (!noItemSelected) {
@ -577,55 +575,33 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
true
}
R.id.close -> {
askCloseVolume()
finish()
true
}
R.id.lock -> {
askLockVolume()
true
}
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() {
super.onDestroy()
if (!isChangingConfigurations) { //activity won't be recreated
closeVolumeOnDestroy()
}
}
override fun onPause() {
super.onPause()
if (!isChangingConfigurations){
if (isStartingActivity){
isStartingActivity = false
} else if (!usf_keep_open){
finish()
}
taskScope.cancel()
}
}
override fun onResume() {
super.onResume()
if (isCreating){
isCreating = false
} else {
if (app.isStartingExternalApp) {
ExternalProvider.removeFilesAsync(this)
}
if (encryptedVolume.isClosed()) {
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.MenuItem
import android.widget.Toast
import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.documentfile.provider.DocumentFile
import com.google.android.material.floatingactionbutton.FloatingActionButton
@ -60,9 +61,7 @@ class ExplorerActivity : BaseExplorerActivity() {
}
if (operationFiles.size > 0){
checkPathOverwrite(operationFiles, currentDirectoryPath) { items ->
if (items == null) {
remoteEncryptedVolume.close()
} else {
if (items != null) {
// stop loading thumbnails while writing files
explorerAdapter.loadThumbnails = false
taskScope.launch {
@ -78,12 +77,9 @@ class ExplorerActivity : BaseExplorerActivity() {
}
explorerAdapter.loadThumbnails = true
setCurrentPath(currentDirectoryPath)
remoteEncryptedVolume.close()
}
}
}
} else {
remoteEncryptedVolume.close()
}
}
}
@ -169,6 +165,16 @@ class ExplorerActivity : BaseExplorerActivity() {
override fun 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 {
if (currentItemAction != ItemsActions.NONE){
openDialogCreateFolder()
@ -189,15 +195,14 @@ class ExplorerActivity : BaseExplorerActivity() {
val intent = Intent(this, MainActivity::class.java)
intent.action = "pick"
intent.putExtra("volume", encryptedVolume)
isStartingActivity = true
pickFromOtherVolumes.launch(intent)
}
"importFiles" -> {
isStartingActivity = true
app.isStartingExternalApp = true
pickFiles.launch(arrayOf("*/*"))
}
"importFolder" -> {
isStartingActivity = true
app.isStartingExternalApp = true
pickImportDirectory.launch(null)
}
"createFile" -> {
@ -212,7 +217,6 @@ class ExplorerActivity : BaseExplorerActivity() {
val intent = Intent(this, CameraActivity::class.java)
intent.putExtra("path", currentDirectoryPath)
intent.putExtra("volume", encryptedVolume)
isStartingActivity = true
startActivity(intent)
}
}
@ -257,6 +261,7 @@ class ExplorerActivity : BaseExplorerActivity() {
val result = super.onCreateOptionsMenu(menu)
if (currentItemAction != ItemsActions.NONE) {
menu.findItem(R.id.validate).isVisible = true
menu.findItem(R.id.lock).isVisible = false
menu.findItem(R.id.close).isVisible = false
supportActionBar?.setDisplayHomeAsUpEnabled(true)
} else {
@ -405,13 +410,13 @@ class ExplorerActivity : BaseExplorerActivity() {
for (i in explorerAdapter.selectedItems) {
paths.add(explorerElements[i].fullPath)
}
isStartingActivity = true
app.isStartingExternalApp = true
ExternalProvider.share(this, themeValue, encryptedVolume, paths)
unselectAll()
true
}
R.id.decrypt -> {
isStartingActivity = true
app.isStartingExternalApp = true
pickExportDirectory.launch(null)
true
}
@ -506,13 +511,4 @@ class ExplorerActivity : BaseExplorerActivity() {
itemsToProcess.clear()
}
}
override fun onBackPressed() {
if (currentItemAction != ItemsActions.NONE) {
cancelItemAction()
invalidateOptionsMenu()
} else {
super.onBackPressed()
}
}
}

View File

@ -22,9 +22,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
}
override fun onExplorerElementClick(position: Int) {
val wasSelecting = explorerAdapter.selectedItems.isNotEmpty()
if (explorerAdapter.selectedItems.isEmpty()) {
if (!wasSelecting) {
val fullPath = PathUtils.pathJoin(currentDirectoryPath, explorerElements[position].name)
when {
explorerElements[position].isDirectory -> {
@ -39,7 +37,6 @@ class ExplorerActivityPick : BaseExplorerActivity() {
}
}
}
}
invalidateOptionsMenu()
}
@ -81,17 +78,4 @@ class ExplorerActivityPick : BaseExplorerActivity() {
isFinishingIntentionally = true
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 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
if (dropMode) { //import via android share menu
explorerIntent = Intent(context, ExplorerActivityDrop::class.java)
@ -22,8 +22,8 @@ class ExplorerRouter(private val context: Context, private val intent: Intent) {
if (explorerIntent == null) {
explorerIntent = Intent(context, ExplorerActivity::class.java) //default opening
}
explorerIntent.putExtra("volume", encryptedVolume)
explorerIntent.putExtra("volume_name", volumeShortName)
explorerIntent.putExtra("volumeId", volumeId)
explorerIntent.putExtra("volumeName", volumeShortName)
return explorerIntent
}
}

View File

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

View File

@ -7,6 +7,7 @@ import android.view.Menu
import android.view.MenuItem
import android.widget.EditText
import android.widget.Toast
import androidx.activity.addCallback
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.io.File
@ -29,6 +30,9 @@ class TextEditor: FileViewerActivity() {
loadWholeFile(filePath) {
try {
loadLayout(String(it))
onBackPressedDispatcher.addCallback(this) {
checkSaveAndExit()
}
} catch (e: OutOfMemoryError){
CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.error)
@ -124,8 +128,4 @@ class TextEditor: FileViewerActivity() {
}
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 {
if (volume == null) {
it.close()
} else {
volume.value = it
}
} != null
}

View File

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

View File

@ -1,8 +1,6 @@
#include <jni.h>
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <sys/stat.h>
#include "libgocryptfs.h"
const int KeyLen = 32;
@ -15,38 +13,34 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_nativeCre
jint xchacha,
jint logN,
jstring jcreator,
jbyteArray jreturned_hash,
jboolean open_after_creation) {
jbyteArray jreturned_hash) {
const char* root_cipher_dir = (*env)->GetStringUTFChars(env, jroot_cipher_dir, NULL);
const char* creator = (*env)->GetStringUTFChars(env, jcreator, NULL);
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);
GoSlice go_password = {password, password_len, password_len};
size_t returned_hash_len;
GoSlice go_returned_hash;
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);
} else if (open_after_creation) {
} else {
returned_hash_len = 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.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)->ReleaseStringUTFChars(env, jcreator, creator);
GoInt sessionID = -2;
if (result && open_after_creation) {
if (result) {
GoSlice null_slice = {NULL, 0, 0};
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)) {
(*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) {
((unsigned char*) go_returned_hash.data)[i] = 0;
}
free(go_returned_hash.data);
}
return sessionID*open_after_creation+result*!open_after_creation;
return sessionID;
}
JNIEXPORT jint JNICALL
@ -81,13 +75,13 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_nativeIni
jbyte* given_hash;
GoSlice go_given_hash = {NULL, 0, 0};
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);
go_password.data = password;
go_password.len = password_len;
go_password.cap = password_len;
} 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);
go_given_hash.data = given_hash;
go_given_hash.len = given_hash_len;
@ -98,7 +92,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_nativeIni
jbyte* returned_hash;
GoSlice go_returned_hash = {NULL, 0, 0};
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);
go_returned_hash.data = returned_hash;
go_returned_hash.len = returned_hash_len;
@ -145,20 +139,20 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_changePas
jbyte* given_hash;
GoSlice go_given_hash = {NULL, 0, 0};
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);
go_old_password.data = old_password;
go_old_password.len = old_password_len;
go_old_password.cap = old_password_len;
} 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);
go_given_hash.data = given_hash;
go_given_hash.len = 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);
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;
GoSlice go_returned_hash = {NULL, 0, 0};
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);
go_returned_hash.data = returned_hash;
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);
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);
@ -317,7 +311,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1write_1file(JNIE
jbyte* buff = (*env)->GetByteArrayElements(env, jbuff, NULL);
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);
@ -331,7 +325,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1read_1file(JNIEn
jbyte* buff = (*env)->GetByteArrayElements(env, jbuff, NULL);
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);
return read;
@ -345,7 +339,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1truncate(JNIEnv
const char* path = (*env)->GetStringUTFChars(env, jpath, NULL);
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);
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);
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);

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
android:id="@+id/selectable_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
tools:ignore="UselessParent">
tools:ignore="UselessParent"
android:paddingEnd="4dp">
<ImageView
android:id="@+id/image_icon"
android:layout_width="50dp"
android:layout_height="50dp"/>
android:layout_height="50dp"
android:src="@drawable/icon_volume"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_toEndOf="@id/image_icon"
android:layout_toStartOf="@id/icon_fingerprint">
android:layout_toStartOf="@id/layout_icons">
<TextView
android:id="@+id/text_volume_name"
@ -63,6 +65,21 @@
</LinearLayout>
<LinearLayout
android:id="@+id/layout_icons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:layout_alignParentEnd="true">
<ImageView
android:id="@+id/icon_unlocked"
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"
@ -70,9 +87,9 @@
android:src="@drawable/icon_fingerprint"
android:contentDescription="@string/password_hash_saved"
android:visibility="gone"
android:layout_alignParentEnd="true"
android:layout_marginTop="11dp"
android:layout_marginEnd="5dp"/>
android:layout_marginStart="5dp"/>
</LinearLayout>
</RelativeLayout>

View File

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

View File

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

View File

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

View File

@ -9,6 +9,13 @@
android:title="@string/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
android:id="@+id/remove"
app:showAsAction="ifRoom"

View File

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

View File

@ -11,7 +11,6 @@
<string name="mkdir">Crear carpeta</string>
<string name="dir_empty">Directorio vacío</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="cancel">Cancelar</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="dir_empty">Pasta vazia</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="cancel">Cancelar</string>
<string name="enter_folder_name">Nome da pasta:</string>

View File

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

View File

@ -11,7 +11,7 @@
<string name="mkdir">Create folder</string>
<string name="dir_empty">Directory Empty</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="cancel">Cancel</string>
<string name="enter_folder_name">Folder Name:</string>
@ -253,4 +253,7 @@
<string name="remember_volume">Remember volume</string>
<string name="open_volume">Open 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>

View File

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

View File

@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
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"
}
}