Multi volume openings
This commit is contained in:
parent
2d165c4a20
commit
1a1d3ea570
@ -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"
|
||||
|
@ -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" />
|
||||
|
@ -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) {
|
||||
|
@ -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,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() {
|
||||
super.onPause()
|
||||
sensorOrientationListener.remove(this)
|
||||
if (!isAskingPermissions && !usf_keep_open) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
sensorOrientationListener.addListener(this)
|
||||
if (encryptedVolume.isClosed()) {
|
||||
finish()
|
||||
} else {
|
||||
sensorOrientationListener.addListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOrientationChange(newOrientation: Int) {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
||||
|
42
app/src/main/java/sushi/hardcore/droidfs/VolumeManager.kt
Normal file
42
app/src/main/java/sushi/hardcore/droidfs/VolumeManager.kt
Normal 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()
|
||||
}
|
||||
}
|
49
app/src/main/java/sushi/hardcore/droidfs/VolumeManagerApp.kt
Normal file
49
app/src/main/java/sushi/hardcore/droidfs/VolumeManagerApp.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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,54 +41,59 @@ class VolumeOpener(
|
||||
|
||||
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
|
||||
fun openVolume(volume: VolumeData, isVolumeSaved: Boolean, callbacks: VolumeOpenerCallbacks) {
|
||||
if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE && BuildConfig.GOCRYPTFS_DISABLED) {
|
||||
Toast.makeText(activity, R.string.gocryptfs_disabled, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
} else if (volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE && BuildConfig.CRYFS_DISABLED) {
|
||||
Toast.makeText(activity, R.string.cryfs_disabled, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
var askForPassword = true
|
||||
fingerprintProtector?.let { fingerprintProtector ->
|
||||
volume.encryptedHash?.let { encryptedHash ->
|
||||
volume.iv?.let { iv ->
|
||||
askForPassword = false
|
||||
fingerprintProtector.listener = object : FingerprintProtector.Listener {
|
||||
override fun onHashStorageReset() {
|
||||
callbacks.onHashStorageReset()
|
||||
}
|
||||
override fun onPasswordHashDecrypted(hash: ByteArray) {
|
||||
object : LoadingTask<EncryptedVolume?>(activity, themeValue, R.string.loading_msg_open) {
|
||||
override suspend fun doTask(): EncryptedVolume? {
|
||||
val encryptedVolume = EncryptedVolume.init(volume, activity.filesDir.path, null, hash, null)
|
||||
Arrays.fill(hash, 0)
|
||||
return encryptedVolume
|
||||
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
|
||||
} else if (volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE && BuildConfig.CRYFS_DISABLED) {
|
||||
Toast.makeText(activity, R.string.cryfs_disabled, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
var askForPassword = true
|
||||
fingerprintProtector?.let { fingerprintProtector ->
|
||||
volume.encryptedHash?.let { encryptedHash ->
|
||||
volume.iv?.let { iv ->
|
||||
askForPassword = false
|
||||
fingerprintProtector.listener = object : FingerprintProtector.Listener {
|
||||
override fun onHashStorageReset() {
|
||||
callbacks.onHashStorageReset()
|
||||
}
|
||||
override fun onPasswordHashDecrypted(hash: ByteArray) {
|
||||
object : LoadingTask<EncryptedVolume?>(activity, themeValue, R.string.loading_msg_open) {
|
||||
override suspend fun doTask(): 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) {
|
||||
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(encryptedVolume, volume.shortName)
|
||||
}
|
||||
override fun onPasswordHashSaved() {}
|
||||
override fun onFailed(pending: Boolean) {
|
||||
if (!pending) {
|
||||
askForPassword(volume, isVolumeSaved, callbacks)
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onPasswordHashSaved() {}
|
||||
override fun onFailed(pending: Boolean) {
|
||||
if (!pending) {
|
||||
askForPassword(volume, isVolumeSaved, callbacks)
|
||||
}
|
||||
}
|
||||
fingerprintProtector.loadPasswordHash(volume.shortName, encryptedHash, iv)
|
||||
}
|
||||
fingerprintProtector.loadPasswordHash(volume.shortName, encryptedHash, iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (askForPassword) {
|
||||
askForPassword(volume, isVolumeSaved, callbacks)
|
||||
if (askForPassword) {
|
||||
askForPassword(volume, isVolumeSaved, callbacks)
|
||||
}
|
||||
} else {
|
||||
callbacks.onVolumeOpened(volumeId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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()
|
||||
@ -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 {
|
||||
putExtra("path", filePath)
|
||||
putExtra("volume", encryptedVolume)
|
||||
putExtra("sortOrder", sortOrderValues[currentSortOrderIndex])
|
||||
}
|
||||
isStartingActivity = true
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun openWithExternalApp(fullPath: String){
|
||||
isStartingActivity = true
|
||||
private fun openWithExternalApp(fullPath: String) {
|
||||
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
|
||||
if (app.isStartingExternalApp) {
|
||||
ExternalProvider.removeFilesAsync(this)
|
||||
}
|
||||
if (encryptedVolume.isClosed()) {
|
||||
finish()
|
||||
} else {
|
||||
if (encryptedVolume.isClosed()) {
|
||||
finish()
|
||||
} else {
|
||||
isStartingActivity = false
|
||||
ExternalProvider.removeFilesAsync(this)
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
}
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||