Compare commits

...

20 Commits

Author SHA1 Message Date
Hardcore Sushi e2539a53b9
Set navigation bar color to background color 1 month ago
Hardcore Sushi 17c32f2144
Show volume type in MainActivity 1 month ago
Hardcore Sushi a5b6de1138
BUILD.md: prefer cloning from the Chapril repository 1 month ago
Hardcore Sushi d1ca164934
Allow changing password of CryFS volumes 1 month ago
Hardcore Sushi 1a21a43f05
Gocryptfs JNI cleanup 1 month ago
Hardcore Sushi 4d164944c1
Deleting files in background 1 month ago
Hardcore Sushi 8709abd7d7
Don't cancel file operations when changing configuration 1 month ago
Hardcore Sushi e01932acda
Allow opening CryFS volumes with password hash 1 month ago
Hardcore Sushi cf4927a90b
CryFS 2 months ago
CyanWolf cb5679515c
Update spanish translation 2 months ago
Muhmmad14333653 a728bd8d24
Update arabic translation 2 months ago
Hardcore Sushi 83dd759f36
Fix local reference table overflow in native_list_dir 3 months ago
Hardcore Sushi 5144947a4a
Media player fixes: better handling of RTL & orientation 3 months ago
Muhmmad14333653 6b52eed9d0
update arabic translation 3 months ago
Muhmmad14333653 2a257d91d0
update Arabic translation 3 months ago
Hardcore Sushi f837556af5
Fix explorer info bar color in dark mode 3 months ago
Hardcore Sushi b7ab267d16
Arabic translation 3 months ago
Hardcore Sushi 5ea0b8ad41
Actually fix camera icon tint bug 3 months ago
Hardcore Sushi ec348383c6
Fix camera icon tint bug 3 months ago
Hardcore Sushi c8d266150c
Fix explorer menu display 3 months ago
  1. 3
      .gitmodules
  2. 108
      BUILD.md
  3. 79
      README.md
  4. 45
      app/CMakeLists.txt
  5. 37
      app/build.gradle
  6. 1
      app/libcryfs
  7. 2
      app/libgocryptfs
  8. 26
      app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt
  9. 67
      app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt
  10. 4
      app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt
  11. 4
      app/src/main/java/sushi/hardcore/droidfs/FingerprintProtector.kt
  12. 243
      app/src/main/java/sushi/hardcore/droidfs/GocryptfsVolume.kt
  13. 116
      app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt
  14. 20
      app/src/main/java/sushi/hardcore/droidfs/SavedVolume.kt
  15. 87
      app/src/main/java/sushi/hardcore/droidfs/VolumeDatabase.kt
  16. 21
      app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt
  17. 4
      app/src/main/java/sushi/hardcore/droidfs/adapters/IconTextDialogAdapter.kt
  18. 30
      app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt
  19. 141
      app/src/main/java/sushi/hardcore/droidfs/add_volume/CreateVolumeFragment.kt
  20. 17
      app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt
  21. 14
      app/src/main/java/sushi/hardcore/droidfs/content_providers/ExternalProvider.kt
  22. 81
      app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt
  23. 126
      app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt
  24. 4
      app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt
  25. 16
      app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt
  26. 34
      app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerElement.kt
  27. 92
      app/src/main/java/sushi/hardcore/droidfs/file_operations/FileOperationService.kt
  28. 15
      app/src/main/java/sushi/hardcore/droidfs/file_operations/OperationFile.kt
  29. 18
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/EncryptedVolumeDataSource.kt
  30. 21
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt
  31. 4
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt
  32. 2
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt
  33. 2
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/PdfViewer.kt
  34. 14
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/TextEditor.kt
  35. 17
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/VideoPlayer.kt
  36. 151
      app/src/main/java/sushi/hardcore/droidfs/filesystems/CryfsVolume.kt
  37. 228
      app/src/main/java/sushi/hardcore/droidfs/filesystems/EncryptedVolume.kt
  38. 109
      app/src/main/java/sushi/hardcore/droidfs/filesystems/GocryptfsVolume.kt
  39. 14
      app/src/main/java/sushi/hardcore/droidfs/filesystems/Stat.kt
  40. 3
      app/src/main/java/sushi/hardcore/droidfs/util/ObjRef.kt
  41. 49
      app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt
  42. 18
      app/src/main/java/sushi/hardcore/droidfs/util/WidgetUtil.kt
  43. 23
      app/src/main/java/sushi/hardcore/droidfs/widgets/DoubleTapPlayerView.kt
  44. 281
      app/src/main/native/gocryptfs_jni.c
  45. 194
      app/src/main/native/libcryfs.c
  46. 6
      app/src/main/res/layout/adapter_volume.xml
  47. 1
      app/src/main/res/layout/audio_exo_styled_player_control_view.xml
  48. 8
      app/src/main/res/layout/dialog_open_volume.xml
  49. 1
      app/src/main/res/layout/exo_center_controls.xml
  50. 16
      app/src/main/res/layout/fragment_create_volume.xml
  51. 50
      app/src/main/res/values-ar/arrays.xml
  52. 242
      app/src/main/res/values-ar/strings.xml
  53. 9
      app/src/main/res/values-es/strings.xml
  54. 7
      app/src/main/res/values-pt-rBR/strings.xml
  55. 7
      app/src/main/res/values-ru/strings.xml
  56. 16
      app/src/main/res/values/arrays.xml
  57. 14
      app/src/main/res/values/strings.xml
  58. 4
      app/src/main/res/values/styles.xml
  59. 2
      build.gradle
  60. 25
      fastlane/metadata/android/ar/full_description.txt
  61. 1
      fastlane/metadata/android/ar/short_description.txt
  62. 1
      fastlane/metadata/android/ar/title.txt
  63. 14
      fastlane/metadata/android/en-US/full_description.txt
  64. 4
      gradle.properties
  65. 2
      gradle/wrapper/gradle-wrapper.properties

3
.gitmodules vendored

@ -4,3 +4,6 @@
[submodule "libpdfviewer"]
path = libpdfviewer
url = https://forge.chapril.org/hardcoresushi/libpdfviewer.git
[submodule "app/libcryfs"]
path = app/libcryfs
url = https://forge.chapril.org/hardcoresushi/libcryfs.git

108
BUILD.md

@ -0,0 +1,108 @@
# Introduction
DroidFS relies on modified versions of the original encrypted filesystems programs to open volumes. [CryFS](https://github.com/cryfs/cryfs) is written in C++ while [gocryptfs](https://github.com/rfjakob/gocryptfs) is written in [Go](https://golang.org). Thus, building DroidFS requires the compilation of native code. However, for the sake of simplicity, the application has been designed in a modular way: you can build a version of DroidFS that supports both Gocryptfs and CryFS, or only one of the two.
Moreover, DroidFS aims to be accessible to as many people as possible. If you encounter any problems or need help with the build, feel free to open an issue, a discussion, or contact me by [email](mailto:hardcore.sushi@disroot.org) or on [Matrix](https://matrix.org): @hardcoresushi:matrix.underworld.fr
# Setup
Install required packages:
```
$ sudo apt-get install openjdk-11-jdk-headless build-essential pkg-config git gnupg2 wget apksigner
```
You also need to manually install the [Android SDK](https://developer.android.com/studio/index.html#command-tools) and the [Android Native Development Kit (NDK)](https://developer.android.com/ndk/downloads) (r23 versions are recommended).
If you want a support for Gocryptfs volumes, you must install [Go](https://golang.org/doc/install) and libssl:
```
$ sudo apt-get install golang-go libssl-dev
```
The code should be authenticated before being built. To verify the signatures, you will need my PGP key:
```
$ gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys AFE384344A45E13A
```
Fingerprint: `B64E FE86 CEE1 D054 F082 1711 AFE3 8434 4A45 E13A` \
Email: `Hardcore Sushi <hardcore.sushi@disroot.org>`
# Download sources
Download DroidFS source code:
```
$ git clone --depth=1 https://forge.chapril.org/hardcoresushi/DroidFS.git
```
Verify sources:
```
$ cd DroidFS
$ git verify-commit HEAD
```
__Don't continue if the verification fails!__
Initialize submodules:
```
$ git submodule update --depth=1 --init
```
[FFmpeg](https://ffmpeg.org) is needed to record encrypted video:
```
$ cd app/ffmpeg
$ git clone --depth=1 https://git.ffmpeg.org/ffmpeg.git
```
If you want Gocryptfs support, you need to download OpenSSL:
```
$ cd ../libgocryptfs
$ wget https://www.openssl.org/source/openssl-1.1.1p.tar.gz
```
Verify OpenSSL signature:
```
$ wget https://www.openssl.org/source/openssl-1.1.1p.tar.gz.asc
$ gpg --verify openssl-1.1.1p.tar.gz.asc openssl-1.1.1p.tar.gz
```
Continue **ONLY** if the signature is **VALID**.
```
$ tar -xzf openssl-1.1.1p.tar.gz
```
If you want CryFS support, initialize libcryfs:
```
$ cd app/libcryfs
$ git submodule update --depth=1 --init
```
# Build
Retrieve your Android NDK installation path, usually something like `/home/\<user\>/Android/SDK/ndk/\<NDK version\>`. Then, make it available in your shell:
```
$ export ANDROID_NDK_HOME="<your ndk path>"
```
Start by compiling FFmpeg:
```
$ cd app/ffmpeg
$ ./build.sh ffmpeg
```
## libgocryptfs
This step is only required if you want Gocryptfs support.
```
$ cd app/libgocryptfs
$ OPENSSL_PATH="./openssl-1.1.1p" ./build.sh
```
## Compile APKs
Gradle build libgocryptfs and libcryfs by default.
To build DroidFS without Gocryptfs support, run:
```
$ ./gradlew assembleRelease -PdisableGocryptfs=true
```
To build DroidFS without CryFS support, run:
```
$ ./gradlew assembleRelease -PdisableCryFS=true
```
If you want to build DroidFS with support for both Gocryptfs and CryFS, just run:
```
$ ./gradlew assembleRelease
```
# Sign APKs
If the build succeeds, you will find the unsigned APKs in `app/build/outputs/apk/release/`. These APKs need to be signed in order to be installed on an Android device.
If you don't already have a keystore, you can create a new one by running:
```
$ keytool -genkey -keystore <output file> -alias <key alias> -keyalg EC -validity 10000
```
Then, sign the APK with:
```
$ apksigner sign --out droidfs.apk -v --ks <keystore> app/build/outputs/apk/release/<unsigned apk file>
```
Now you can install `droidfs.apk` on your device.

79
README.md

@ -1,6 +1,6 @@
# DroidFS
DroidFS is an alternative way to use encrypted overlay filesystems on Android that uses its own internal file explorer instead of mounting virtual volumes.
It currently only works with [gocryptfs](https://github.com/rfjakob/gocryptfs) but support for [CryFS](https://github.com/cryfs/cryfs) could be added in the future.
It currently supports [gocryptfs](https://github.com/rfjakob/gocryptfs) and [CryFS](https://github.com/cryfs/cryfs) (alpha).
<p align="center">
<img src="https://forge.chapril.org/hardcoresushi/DroidFS/raw/branch/master/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" height="500">
@ -46,7 +46,7 @@ It is strongly recommended to read the documentation of a feature before enablin
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75">
</a>
You can download DroidFS from [F-Droid](https://f-droid.org/packages/sushi.hardcore.droidfs) or from the "Releases" section in the repo.
You can download DroidFS from [F-Droid](https://f-droid.org/packages/sushi.hardcore.droidfs) or from the "Releases" section in this repository.
APKs available here are signed with my PGP key available on keyservers:
@ -83,83 +83,24 @@ DroidFS need some permissions to work properly. Here is why:
</ul>
# Limitations
DroidFS use some parts of the original gocryptfs code, which is designed to run on Linux x86 systems: it accesses the underlying file system with file paths and syscalls. However in Android, you can't access other apps files with file paths. Instead, you must use the [ContentProvider](https://developer.android.com/guide/topics/providers/content-providers) API. And obviously, the original gocryptfs code doesn't work with this API. This is why DroidFS can't open volumes provided by other applications, such as cloud storage clients. You can only use DroidFS with volumes located on shared storage or in the app's internal storage (hidden volumes). External storage such as SD cards are only supported in read-only access for now.
DroidFS works as a wrapper around modified versions of the original encrypted container implementations ([libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs) and [libcryfs](https://forge.chapril.org/hardcoresushi/libcryfs)). These programs were designed to run on standard x86 Linux systems: they access the underlying file system with file paths and syscalls. However, on Android, you can't access files from other applications using file paths. Instead, one has to use the [ContentProvider](https://developer.android.com/guide/topics/providers/content-providers) API. Obviously, neither Gocryptfs nor CryFS support this API. As a result, DroidFS cannot open volumes provided by other applications (such as cloud storage clients), nor can it allow other applications to access encrypted volumes once opened.
# Build
Most of the original gocryptfs code was used as is (written in Go) and compiled to native code. That's why you need [Go](https://golang.org) and the [Android Native Development Kit (NDK)](https://developer.android.com/ndk/) to build DroidFS from source.
Due to Android's storage restrictions, encrypted volumes located on SD cards must be placed under `/Android/data/sushi.hardcore.droidfs/` if you want DroidFS to be able to modify them.
#### Install dependencies
On debian:
```
$ sudo apt-get install build-essential pkg-config libssl-dev
```
Install [Go](https://golang.org/doc/install):
```
$ sudo apt-get install golang-go
```
You also need to install the Android SDK build tools and the [Android NDK](https://developer.android.com/studio/projects/install-ndk).
#### Download Sources
```
$ git clone --recurse-submodules https://github.com/hardcore-sushi/DroidFS.git
$ cd DroidFS
```
[libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs) needs OpenSSL:
```
$ cd app/libgocryptfs
$ wget https://www.openssl.org/source/openssl-1.1.1n.tar.gz
```
Verify OpenSSL signature:
```
$ wget https://www.openssl.org/source/openssl-1.1.1n.tar.gz.asc
$ gpg --verify openssl-1.1.1n.tar.gz.asc openssl-1.1.1n.tar.gz
```
Continue **ONLY** if the signature is **VALID**.
```
$ tar -xvzf openssl-1.1.1n.tar.gz
```
DroidFS also need [FFmpeg](https://ffmpeg.org) to record encrypted video:
```
$ cd app/ffmpeg
$ git clone --depth=1 https://git.ffmpeg.org/ffmpeg.git
```
#### Generate a keystore
APKs must be signed to be installed on an Android device. If you don't already have a keystore, you can generate one by running:
```
$ keytool -genkey -keystore <output file> -alias <key alias> -keyalg EC -validity 10000
```
#### Build
Retrieve your Android NDK installation path, usually something like "/home/\<user\>/Android/SDK/ndk/\<NDK version\>". Now you can build libgocryptfs:
```
$ cd DroidFS/app/libgocryptfs
$ env ANDROID_NDK_HOME="<your ndk path>" OPENSSL_PATH="./openssl-1.1.1n" ./build.sh
```
Then FFmpeg:
```
$ cd app/ffmpeg
$ env ANDROID_NDK_HOME="<your ndk path>" ./build.sh ffmpeg
```
Finally, compile the app:
```
$ ./gradlew assembleRelease
```
If the build succeeds, you will find the unsigned APKs in `app/build/outputs/apk/release/`. You need to sign them in order to install the app:
```
$ apksigner sign --out droidfs.apk -v --ks <keystore> app/build/outputs/apk/release/<unsigned apk file>
```
Now you can install `droidfs.apk` on your device.
# Building from source
You can follow the instructions in [BUILD.md](BUILD.md) to build DroidFS from source.
# Third party code
Thanks to these open source projects that DroidFS uses:
### Modified code:
- [libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs) (forked from [gocryptfs](https://github.com/rfjakob/gocryptfs)) to encrypt your data
- Encrypted filesystems (to protect your data):
- [libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs) (forked from [gocryptfs](https://github.com/rfjakob/gocryptfs))
- [libcryfs](https://forge.chapril.org/hardcoresushi/libcryfs) (forked from [CryFS](https://github.com/cryfs/cryfs))
- [libpdfviewer](https://forge.chapril.org/hardcoresushi/libpdfviewer) (forked from [PdfViewer](https://github.com/GrapheneOS/PdfViewer)) to open PDF files
- [DoubleTapPlayerView](https://github.com/vkay94/DoubleTapPlayerView) to add double-click controls to the video player
### Borrowed code:
- [MaterialFiles](https://github.com/zhanghai/MaterialFiles) for kotlin natural sorting implementation
- [MaterialFiles](https://github.com/zhanghai/MaterialFiles) for Kotlin natural sorting implementation
### Libraries:
- [Glide](https://github.com/bumptech/glide/) to display pictures
- [ExoPlayer](https://github.com/google/ExoPlayer) to play media files

45
app/CMakeLists.txt

@ -1,27 +1,27 @@
cmake_minimum_required(VERSION 3.10)
add_library(
gocryptfs
SHARED
IMPORTED
)
project(DroidFS)
set_target_properties(
gocryptfs
PROPERTIES IMPORTED_LOCATION
${PROJECT_SOURCE_DIR}/libgocryptfs/build/${ANDROID_ABI}/libgocryptfs.so
)
option(GOCRYPTFS "build libgocryptfs" ON)
option(CRYFS "build libcryfs" ON)
add_library(
gocryptfs_jni
SHARED
src/main/native/gocryptfs_jni.c
)
if (GOCRYPTFS)
add_library(gocryptfs SHARED IMPORTED)
set_target_properties(
gocryptfs
PROPERTIES IMPORTED_LOCATION
${PROJECT_SOURCE_DIR}/libgocryptfs/build/${ANDROID_ABI}/libgocryptfs.so
)
add_library(gocryptfs_jni SHARED src/main/native/gocryptfs_jni.c)
target_include_directories(gocryptfs_jni PRIVATE ${PROJECT_SOURCE_DIR}/libgocryptfs/build/${ANDROID_ABI})
target_link_libraries(gocryptfs_jni gocryptfs)
endif()
target_link_libraries(
gocryptfs_jni
gocryptfs
)
if (CRYFS)
add_subdirectory(${PROJECT_SOURCE_DIR}/libcryfs)
add_library(cryfs_jni SHARED src/main/native/libcryfs.c)
target_link_libraries(cryfs_jni libcryfs-jni)
endif()
add_library(
avformat
@ -65,14 +65,11 @@ add_library(
src/main/native/libmux.c
)
target_include_directories(mux PRIVATE ${PROJECT_SOURCE_DIR}/ffmpeg/build/${ANDROID_ABI})
target_link_libraries(
mux
avformat
avcodec
avutil
)
include_directories(
${PROJECT_SOURCE_DIR}/libgocryptfs/build/${ANDROID_ABI}
${PROJECT_SOURCE_DIR}/ffmpeg/build/${ANDROID_ABI}
)

37
app/build.gradle

@ -1,6 +1,18 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
if (hasProperty("disableCryFS")) {
ext.disableCryFS = getProperty("disableCryFS")
} else {
ext.disableCryFS = false
}
if (hasProperty("disableGocryptfs")) {
ext.disableGocryptfs = getProperty("disableGocryptfs")
} else {
ext.disableGocryptfs = false
}
android {
compileSdkVersion 31
buildToolsVersion "31"
@ -15,12 +27,21 @@ android {
minSdkVersion 21
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 29
versionCode 27
versionName "1.10.1"
versionCode 28
versionName "2.0.0-alpha1"
ndk {
abiFilters "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
}
externalNativeBuild.cmake {
if (project.ext.disableGocryptfs) {
arguments "-DGOCRYPTFS=OFF"
}
if (project.ext.disableCryFS) {
arguments "-DCRYFS=OFF"
}
}
}
if (!file("fdroid").exists()) {
@ -34,6 +55,8 @@ android {
applicationVariants.all { variant ->
variant.resValue "string", "versionName", variant.versionName
buildConfigField "boolean", "CRYFS_DISABLED", "${project.ext.disableCryFS}"
buildConfigField "boolean", "GOCRYPTFS_DISABLED", "${project.ext.disableGocryptfs}"
}
buildFeatures {
@ -42,9 +65,13 @@ android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
postprocessing {
removeUnusedCode true
removeUnusedResources true
obfuscate false
optimizeCode true
proguardFiles 'proguard-rules.pro'
}
}
}

1
app/libcryfs

@ -0,0 +1 @@
Subproject commit c5be03ad3abea3aef5b0fcc5aa3af8cba2befdd1

2
app/libgocryptfs

@ -1 +1 @@
Subproject commit 9e98192442b08362660b45f4e2e50221ba7bc65b
Subproject commit e6e4c201dbf3834de1a49a8b67b4b54239d24249

26
app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt

@ -30,6 +30,7 @@ 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.PathUtils
import sushi.hardcore.droidfs.video_recording.SeekableWriter
import sushi.hardcore.droidfs.video_recording.VideoCapture
@ -64,7 +65,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
private lateinit var sensorOrientationListener: SensorOrientationListener
private var previousOrientation: Float = 0f
private lateinit var orientedIcons: List<ImageView>
private lateinit var gocryptfsVolume: GocryptfsVolume
private lateinit var encryptedVolume: EncryptedVolume
private lateinit var outputDirectory: String
private var isFinishingIntentionally = false
private var isAskingPermissions = false
@ -93,7 +94,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
binding = ActivityCameraBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.hide()
gocryptfsVolume = GocryptfsVolume(applicationContext, intent.getIntExtra("sessionID", -1))
encryptedVolume = intent.getParcelableExtra("volume")!!
outputDirectory = intent.getStringExtra("path")!!
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -221,7 +222,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), AUDIO_PERMISSION_REQUEST_CODE)
}
}
binding.imageModeSwitch.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.icon_photo)?.also {
binding.imageModeSwitch.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.icon_photo)?.mutate()?.also {
it.setTint(ContextCompat.getColor(this, R.color.neutralIconTint))
})
imageCapture?.flashMode = ImageCapture.FLASH_MODE_OFF
@ -378,11 +379,12 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
private fun getOutputPath(isVideo: Boolean): String {
val baseName = if (isVideo) {"VID"} else {"IMG"}+'_'+dateFormat.format(Date())+'_'
var fileName: String
var outputPath: String
do {
fileName = baseName+(random.nextInt(fileNameRandomMax-fileNameRandomMin)+fileNameRandomMin)+'.'+ if (isVideo) {"mp4"} else {"jpg"}
} while (gocryptfsVolume.pathExists(fileName))
return PathUtils.pathJoin(outputDirectory, fileName)
val fileName = baseName+(random.nextInt(fileNameRandomMax-fileNameRandomMin)+fileNameRandomMin)+'.'+ if (isVideo) {"mp4"} else {"jpg"}
outputPath = PathUtils.pathJoin(outputDirectory, fileName)
} while (encryptedVolume.pathExists(outputPath))
return outputPath
}
private fun startTimerThen(action: () -> Unit) {
@ -415,7 +417,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
imageCapture.takePicture(outputOptions, executor, object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
binding.takePhotoButton.onPhotoTaken()
if (gocryptfsVolume.importFile(ByteArrayInputStream(outputBuff.toByteArray()), outputPath)) {
if (encryptedVolume.importFile(ByteArrayInputStream(outputBuff.toByteArray()), outputPath)) {
Toast.makeText(applicationContext, getString(R.string.picture_save_success, outputPath), Toast.LENGTH_SHORT).show()
} else {
CustomAlertDialogBuilder(this@CameraActivity, themeValue)
@ -447,17 +449,17 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
} else if (!isWaitingForTimer) {
val path = getOutputPath(true)
startTimerThen {
val handleId = gocryptfsVolume.openWriteMode(path)
val fileHandle = encryptedVolume.openFile(path)
videoCapture?.startRecording(VideoCapture.OutputFileOptions(object : SeekableWriter {
var offset = 0L
override fun write(byteArray: ByteArray) {
offset += gocryptfsVolume.writeFile(handleId, offset, byteArray, byteArray.size)
offset += encryptedVolume.write(fileHandle, offset, byteArray, byteArray.size)
}
override fun seek(offset: Long) {
this.offset = offset
}
override fun close() {
gocryptfsVolume.closeFile(handleId)
encryptedVolume.closeFile(fileHandle)
}
}), executor, object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved() {
@ -479,7 +481,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
override fun onDestroy() {
super.onDestroy()
if (!isFinishingIntentionally) {
gocryptfsVolume.close()
encryptedVolume.close()
RestrictedFileProvider.wipeAll(this)
}
}

67
app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt

@ -9,13 +9,18 @@ import android.view.View
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import sushi.hardcore.droidfs.databinding.ActivityChangePasswordBinding
import sushi.hardcore.droidfs.filesystems.CryfsVolume
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume
import sushi.hardcore.droidfs.util.ObjRef
import sushi.hardcore.droidfs.util.WidgetUtil
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.util.*
class ChangePasswordActivity: BaseActivity() {
private lateinit var binding: ActivityChangePasswordBinding
private lateinit var volume: Volume
private lateinit var volume: SavedVolume
private lateinit var volumeDatabase: VolumeDatabase
private var fingerprintProtector: FingerprintProtector? = null
private var usfFingerprint: Boolean = false
@ -65,14 +70,12 @@ class ChangePasswordActivity: BaseActivity() {
}
private fun changeVolumePassword() {
val newPassword = CharArray(binding.editNewPassword.text.length)
binding.editNewPassword.text.getChars(0, newPassword.size, newPassword, 0)
val newPasswordConfirm = CharArray(binding.editPasswordConfirm.text.length)
binding.editPasswordConfirm.text.getChars(0, newPasswordConfirm.size, newPasswordConfirm, 0)
val newPassword = WidgetUtil.encodeEditTextContent(binding.editNewPassword)
val newPasswordConfirm = WidgetUtil.encodeEditTextContent(binding.editPasswordConfirm)
@SuppressLint("NewApi")
if (!newPassword.contentEquals(newPasswordConfirm)) {
Toast.makeText(this, R.string.passwords_mismatch, Toast.LENGTH_SHORT).show()
Arrays.fill(newPassword, 0.toChar())
Arrays.fill(newPassword, 0)
} else {
var changeWithCurrentPassword = true
volume.encryptedHash?.let { encryptedHash ->
@ -90,7 +93,7 @@ class ChangePasswordActivity: BaseActivity() {
}
override fun onPasswordHashSaved() {}
override fun onFailed(pending: Boolean) {
Arrays.fill(newPassword, 0.toChar())
Arrays.fill(newPassword, 0)
}
}
it.loadPasswordHash(volume.name, encryptedHash, iv)
@ -101,30 +104,48 @@ class ChangePasswordActivity: BaseActivity() {
changeVolumePassword(newPassword)
}
}
Arrays.fill(newPasswordConfirm, 0.toChar())
Arrays.fill(newPasswordConfirm, 0)
}
private fun changeVolumePassword(newPassword: CharArray, givenHash: ByteArray? = null) {
var returnedHash: ByteArray? = null
if (binding.checkboxSavePassword.isChecked) {
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
private fun changeVolumePassword(newPassword: ByteArray, givenHash: ByteArray? = null) {
val returnedHash: ObjRef<ByteArray?>? = if (binding.checkboxSavePassword.isChecked) {
ObjRef(null)
} else {
null
}
var currentPassword: CharArray? = null
if (givenHash == null) {
currentPassword = CharArray(binding.editCurrentPassword.text.length)
binding.editCurrentPassword.text.getChars(0, currentPassword.size, currentPassword, 0)
val currentPassword = if (givenHash == null) {
WidgetUtil.encodeEditTextContent(binding.editCurrentPassword)
} else {
null
}
object : LoadingTask<Boolean>(this, themeValue, R.string.loading_msg_change_password) {
override suspend fun doTask(): Boolean {
val success = GocryptfsVolume.changePassword(volume.getFullPath(filesDir.path), currentPassword, givenHash, newPassword, returnedHash)
val success = if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE) {
GocryptfsVolume.changePassword(
volume.getFullPath(filesDir.path),
currentPassword,
givenHash,
newPassword,
returnedHash?.apply { value = ByteArray(GocryptfsVolume.KeyLen) }?.value
)
} else {
CryfsVolume.changePassword(
volume.getFullPath(filesDir.path),
filesDir.path,
currentPassword,
givenHash,
newPassword,
returnedHash
)
}
if (success) {
if (volumeDatabase.isHashSaved(volume.name)) {
volumeDatabase.removeHash(volume)
}
}
if (currentPassword != null)
Arrays.fill(currentPassword, 0.toChar())
Arrays.fill(newPassword, 0.toChar())
Arrays.fill(currentPassword, 0)
Arrays.fill(newPassword, 0)
if (givenHash != null)
Arrays.fill(givenHash, 0)
return success
@ -137,21 +158,21 @@ class ChangePasswordActivity: BaseActivity() {
it.listener = object : FingerprintProtector.Listener {
override fun onHashStorageReset() {
// retry
it.savePasswordHash(volume, returnedHash)
it.savePasswordHash(volume, returnedHash.value!!)
}
override fun onPasswordHashDecrypted(hash: ByteArray) {}
override fun onPasswordHashSaved() {
Arrays.fill(returnedHash, 0)
Arrays.fill(returnedHash.value!!, 0)
finish()
}
override fun onFailed(pending: Boolean) {
if (!pending) {
Arrays.fill(returnedHash, 0)
Arrays.fill(returnedHash.value!!, 0)
finish()
}
}
}
it.savePasswordHash(volume, returnedHash)
it.savePasswordHash(volume, returnedHash.value!!)
}
} else {
finish()

4
app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt

@ -5,13 +5,13 @@ import java.io.File
object ConstValues {
const val CREATOR = "DroidFS"
const val FILE_MODE = 384 //0600
const val DIRECTORY_MODE = 448 //0700
const val VOLUME_DATABASE_NAME = "SavedVolumes"
const val CRYFS_LOCAL_STATE_DIR = "cryfsLocalState"
const val SORT_ORDER_KEY = "sort_order"
val FAKE_URI: Uri = Uri.parse("fakeuri://droidfs")
const val MAX_KERNEL_WRITE = 128*1024
const val WIPE_PASSES = 2
const val IO_BUFF_SIZE = 16384
const val SLIDESHOW_DELAY: Long = 4000
const val DEFAULT_THEME_VALUE = "dark_green"
const val THUMBNAIL_MAX_SIZE_KEY = "thumbnail_max_size"

4
app/src/main/java/sushi/hardcore/droidfs/FingerprintProtector.kt

@ -133,7 +133,7 @@ class FingerprintProtector private constructor(
private lateinit var cipher: Cipher
private var isCipherReady = false
private var cipherActionMode: Int? = null
private lateinit var volume: Volume
private lateinit var volume: SavedVolume
private lateinit var dataToProcess: ByteArray
private fun resetHashStorage() {
@ -207,7 +207,7 @@ class FingerprintProtector private constructor(
.show()
}
fun savePasswordHash(volume: Volume, plainText: ByteArray) {
fun savePasswordHash(volume: SavedVolume, plainText: ByteArray) {
this.volume = volume
val biometricPromptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(activity.getString(R.string.encrypt_action_description))

243
app/src/main/java/sushi/hardcore/droidfs/GocryptfsVolume.kt

@ -1,243 +0,0 @@
package sushi.hardcore.droidfs
import android.content.Context
import android.net.Uri
import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.PathUtils
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
class GocryptfsVolume(val applicationContext: Context, var sessionID: Int) {
private external fun native_close(sessionID: Int)
private external fun native_is_closed(sessionID: Int): Boolean
private external fun native_list_dir(sessionID: Int, dir_path: String): MutableList<ExplorerElement>
private external fun native_open_read_mode(sessionID: Int, file_path: String): Int
private external fun native_open_write_mode(sessionID: Int, file_path: String, mode: Int): Int
private external fun native_read_file(sessionID: Int, handleID: Int, offset: Long, buff: ByteArray): Int
private external fun native_write_file(sessionID: Int, handleID: Int, offset: Long, buff: ByteArray, buff_size: Int): Int
private external fun native_truncate(sessionID: Int, handleID: Int, offset: Long): Boolean
private external fun native_path_exists(sessionID: Int, file_path: String): Boolean
private external fun native_get_size(sessionID: Int, file_path: String): Long
private external fun native_close_file(sessionID: Int, handleID: Int)
private external fun native_remove_file(sessionID: Int, file_path: String): Boolean
private external fun native_mkdir(sessionID: Int, dir_path: String, mode: Int): Boolean
private external fun native_rmdir(sessionID: Int, dir_path: String): Boolean
private external fun native_rename(sessionID: Int, old_path: String, new_path: String): Boolean
companion object {
const val KeyLen = 32
const val ScryptDefaultLogN = 16
const val DefaultBS = 4096
const val CONFIG_FILE_NAME = "gocryptfs.conf"
external fun createVolume(root_cipher_dir: String, password: CharArray, plainTextNames: Boolean, xchacha: Int, logN: Int, creator: String, returnedHash: ByteArray?): Boolean
external fun init(root_cipher_dir: String, password: CharArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int
external fun changePassword(root_cipher_dir: String, old_password: CharArray?, givenHash: ByteArray?, new_password: CharArray, returnedHash: ByteArray?): Boolean
fun isGocryptfsVolume(path: File): Boolean {
if (path.isDirectory){
return File(path, CONFIG_FILE_NAME).isFile
}
return false
}
init {
System.loadLibrary("gocryptfs_jni")
}
}
fun close() {
native_close(sessionID)
}
fun isClosed(): Boolean {
return native_is_closed(sessionID)
}
fun listDir(dir_path: String): MutableList<ExplorerElement> {
return native_list_dir(sessionID, dir_path)
}
fun mkdir(dir_path: String): Boolean {
return native_mkdir(sessionID, dir_path, ConstValues.DIRECTORY_MODE)
}
fun rmdir(dir_path: String): Boolean {
return native_rmdir(sessionID, dir_path)
}
fun removeFile(file_path: String): Boolean {
return native_remove_file(sessionID, file_path)
}
fun pathExists(file_path: String): Boolean {
return native_path_exists(sessionID, file_path)
}
fun getSize(file_path: String): Long {
return native_get_size(sessionID, file_path)
}
fun closeFile(handleID: Int) {
native_close_file(sessionID, handleID)
}
fun openReadMode(file_path: String): Int {
return native_open_read_mode(sessionID, file_path)
}
fun openWriteMode(file_path: String): Int {
return native_open_write_mode(sessionID, file_path, ConstValues.FILE_MODE)
}
fun readFile(handleID: Int, offset: Long, buff: ByteArray): Int {
return native_read_file(sessionID, handleID, offset, buff)
}
fun writeFile(handleID: Int, offset: Long, buff: ByteArray, buff_size: Int): Int {
return native_write_file(sessionID, handleID, offset, buff, buff_size)
}
fun truncate(handleID: Int, offset: Long): Boolean {
return native_truncate(sessionID, handleID, offset)
}
fun rename(old_path: String, new_path: String): Boolean {
return native_rename(sessionID, old_path, new_path)
}
fun exportFile(handleID: Int, os: OutputStream): Boolean {
var offset: Long = 0
val ioBuffer = ByteArray(DefaultBS)
var length: Int
while (readFile(handleID, offset, ioBuffer).also { length = it } > 0){
os.write(ioBuffer, 0, length)
offset += length.toLong()
}
os.close()
return true
}
fun exportFile(src_path: String, os: OutputStream): Boolean {
var success = false
val srcHandleId = openReadMode(src_path)
if (srcHandleId != -1) {
success = exportFile(srcHandleId, os)
closeFile(srcHandleId)
}
return success
}
fun exportFile(src_path: String, dst_path: String): Boolean {
return exportFile(src_path, FileOutputStream(dst_path))
}
fun exportFile(context: Context, src_path: String, output_path: Uri): Boolean {
val os = context.contentResolver.openOutputStream(output_path)
if (os != null){
return exportFile(src_path, os)
}
return false
}
fun importFile(inputStream: InputStream, dst_path: String): Boolean {
val dstHandleId = openWriteMode(dst_path)
if (dstHandleId != -1) {
var success = true
var offset: Long = 0
val ioBuffer = ByteArray(DefaultBS)
var length: Int
while (inputStream.read(ioBuffer).also { length = it } > 0) {
val written = writeFile(dstHandleId, offset, ioBuffer, length).toLong()
if (written == length.toLong()) {
offset += written
} else {
inputStream.close()
success = false
break
}
}
closeFile(dstHandleId)
inputStream.close()
return success
}
return false
}
fun importFile(context: Context, src_uri: Uri, dst_path: String): Boolean {
val inputStream = context.contentResolver.openInputStream(src_uri)
if (inputStream != null){
return importFile(inputStream, dst_path)
}
return false
}
fun recursiveMapFiles(rootPath: String): MutableList<ExplorerElement> {
val result = mutableListOf<ExplorerElement>()
val explorerElements = listDir(rootPath)
result.addAll(explorerElements)
for (e in explorerElements){
if (e.isDirectory){
result.addAll(recursiveMapFiles(e.fullPath))
}
}
return result
}
fun recursiveRemoveDirectory(plain_directory_path: String): String? {
val explorerElements = listDir(plain_directory_path)
for (e in explorerElements) {
val fullPath = PathUtils.pathJoin(plain_directory_path, e.name)
if (e.isDirectory) {
val result = recursiveRemoveDirectory(fullPath)
result?.let { return it }
} else {
if (!removeFile(fullPath)) {
return fullPath
}
}
}
return if (!rmdir(plain_directory_path)) {
plain_directory_path
} else {
null
}
}
fun loadWholeFile(fullPath: String, size: Long? = null, maxSize: Long? = null): Pair<ByteArray?, Int> {
val fileSize = size ?: getSize(fullPath)
return if (fileSize >= 0) {
maxSize?.let {
if (fileSize > it) {
return Pair(null, 0)
}
}
try {
val fileBuff = ByteArray(fileSize.toInt())
val handleID = openReadMode(fullPath)
if (handleID == -1) {
Pair(null, 3)
} else {
var offset: Long = 0
val ioBuffer = ByteArray(DefaultBS)
var length: Int
while (readFile(handleID, offset, ioBuffer).also { length = it } > 0) {
System.arraycopy(ioBuffer, 0, fileBuff, offset.toInt(), length)
offset += length.toLong()
}
closeFile(handleID)
if (offset == fileBuff.size.toLong()) {
Pair(fileBuff, 0)
} else {
Pair(null, 4)
}
}
} catch (e: OutOfMemoryError) {
Pair(null, 2)
}
} else {
Pair(null, 1)
}
}
}

116
app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt

@ -30,7 +30,10 @@ import sushi.hardcore.droidfs.explorers.ExplorerActivity
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
import sushi.hardcore.droidfs.file_operations.FileOperationService
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.ObjRef
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.util.WidgetUtil
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import sushi.hardcore.droidfs.widgets.EditTextDialog
import java.io.File
@ -155,7 +158,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
}
}
override fun onVolumeItemClick(volume: Volume, position: Int) {
override fun onVolumeItemClick(volume: SavedVolume, position: Int) {
if (volumeAdapter.selectedItems.isEmpty())
openVolume(volume, position)
else
@ -186,7 +189,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
invalidateOptionsMenu()
}
private fun removeVolumes(volumes: List<Volume>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) {
private fun removeVolumes(volumes: List<SavedVolume>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) {
if (i < volumes.size) {
if (volumes[i].isHidden) {
if (doDeleteVolumeContent == null) {
@ -306,7 +309,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
DocumentFile.fromFile(File(volume.name)),
DocumentFile.fromFile(filesDir),
) {
Volume(volume.shortName, true, volume.encryptedHash, volume.iv)
SavedVolume(volume.shortName, true, volume.type, volume.encryptedHash, volume.iv)
}
}
}
@ -377,9 +380,10 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
dstRootDirectory.name?.let { name ->
val path = PathUtils.getFullPathFromTreeUri(dstRootDirectory.uri, this)
if (path == null) null
else Volume(
else SavedVolume(
PathUtils.pathJoin(path, name),
false,
volume.type,
volume.encryptedHash,
volume.iv
)
@ -388,7 +392,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
}
}
private fun copyVolume(srcDocumentFile: DocumentFile, dstDocumentFile: DocumentFile, getResultVolume: (DocumentFile) -> Volume?) {
private fun copyVolume(srcDocumentFile: DocumentFile, dstDocumentFile: DocumentFile, getResultVolume: (DocumentFile) -> SavedVolume?) {
lifecycleScope.launch {
val result = fileOperationService.copyVolume(srcDocumentFile, dstDocumentFile)
when {
@ -415,13 +419,13 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
}
}
private fun renameVolume(volume: Volume, position: Int) {
private fun renameVolume(volume: SavedVolume, position: Int) {
with (EditTextDialog(this, R.string.new_volume_name) { newName ->
val srcPath = File(volume.getFullPath(filesDir.path))
val dstPath = File(srcPath.parent, newName).canonicalFile
val newDBName: String
val success = if (volume.isHidden) {
if (newName.contains("/")) {
if (newName.contains(PathUtils.SEPARATOR)) {
Toast.makeText(this, R.string.error_slash_in_name, Toast.LENGTH_SHORT).show()
renameVolume(volume, position)
return@EditTextDialog
@ -452,7 +456,14 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
}
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
private fun openVolume(volume: Volume, position: Int) {
private fun openVolume(volume: SavedVolume, position: Int) {
if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE && BuildConfig.GOCRYPTFS_DISABLED) {
Toast.makeText(this, R.string.gocryptfs_disabled, Toast.LENGTH_SHORT).show()
return
} else if (volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE && BuildConfig.CRYFS_DISABLED) {
Toast.makeText(this, R.string.cryfs_disabled, Toast.LENGTH_SHORT).show()
return
}
var askForPassword = true
fingerprintProtector?.let { fingerprintProtector ->
volume.encryptedHash?.let { encryptedHash ->
@ -463,21 +474,21 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
volumeAdapter.refresh()
}
override fun onPasswordHashDecrypted(hash: ByteArray) {
object : LoadingTask<Int>(this@MainActivity, themeValue, R.string.loading_msg_open) {
override suspend fun doTask(): Int {
val sessionId = GocryptfsVolume.init(volume.getFullPath(filesDir.path), null, hash, null)
object : LoadingTask<EncryptedVolume?>(this@MainActivity, themeValue, R.string.loading_msg_open) {
override suspend fun doTask(): EncryptedVolume? {
val encryptedVolume = EncryptedVolume.init(volume, filesDir.path, null, hash, null)
Arrays.fill(hash, 0)
return sessionId
return encryptedVolume
}
}.startTask(lifecycleScope) { sessionId ->
if (sessionId != -1) {
startExplorer(sessionId, volume.shortName)
} else {
}.startTask(lifecycleScope) { encryptedVolume ->
if (encryptedVolume == null) {
CustomAlertDialogBuilder(this@MainActivity, themeValue)
.setTitle(R.string.open_volume_failed)
.setMessage(R.string.open_failed_hash_msg)
.setPositiveButton(R.string.ok, null)
.show()
} else {
startExplorer(encryptedVolume, volume.shortName)
}
}
}
@ -496,7 +507,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
askForPassword(volume, position)
}
private fun onPasswordSubmitted(volume: Volume, position: Int, dialogBinding: DialogOpenVolumeBinding) {
private fun onPasswordSubmitted(volume: SavedVolume, position: Int, dialogBinding: DialogOpenVolumeBinding) {
if (dialogBinding.checkboxDefaultOpen.isChecked xor (defaultVolumeName == volume.name)) {
with (sharedPrefs.edit()) {
defaultVolumeName = if (dialogBinding.checkboxDefaultOpen.isChecked) {
@ -509,18 +520,16 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
apply()
}
}
val password = CharArray(dialogBinding.editPassword.text.length)
dialogBinding.editPassword.text.getChars(0, password.size, password, 0)
// openVolumeWithPassword is responsible for wiping the password
openVolumeWithPassword(
volume,
position,
password,
WidgetUtil.encodeEditTextContent(dialogBinding.editPassword),
dialogBinding.checkboxSavePassword.isChecked,
)
}
private fun askForPassword(volume: Volume, position: Int, savePasswordHash: Boolean = false) {
private fun askForPassword(volume: SavedVolume, position: Int, savePasswordHash: Boolean = false) {
val dialogBinding = DialogOpenVolumeBinding.inflate(layoutInflater)
if (!usfFingerprint || fingerprintProtector == null || volume.encryptedHash != null) {
dialogBinding.checkboxSavePassword.visibility = View.GONE
@ -550,20 +559,28 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
dialog.show()
}
private fun openVolumeWithPassword(volume: Volume, position: Int, password: CharArray, savePasswordHash: Boolean) {
val usfFingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
var returnedHash: ByteArray? = null
if (savePasswordHash && usfFingerprint) {
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
private fun openVolumeWithPassword(volume: SavedVolume, position: Int, password: ByteArray, savePasswordHash: Boolean) {
val returnedHash: ObjRef<ByteArray?>? = if (savePasswordHash) {
ObjRef(null)
} else {
null
}
object : LoadingTask<Int>(this, themeValue, R.string.loading_msg_open) {
override suspend fun doTask(): Int {
val sessionId = GocryptfsVolume.init(volume.getFullPath(filesDir.path), password, null, returnedHash)
Arrays.fill(password, 0.toChar())
return sessionId
object : LoadingTask<EncryptedVolume?>(this, themeValue, R.string.loading_msg_open) {
override suspend fun doTask(): EncryptedVolume? {
val encryptedVolume = EncryptedVolume.init(volume, filesDir.path, password, null, returnedHash)
Arrays.fill(password, 0)
return encryptedVolume
}
}.startTask(lifecycleScope) { sessionId ->
if (sessionId != -1) {
}.startTask(lifecycleScope) { encryptedVolume ->
if (encryptedVolume == null) {
CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.open_volume_failed)
.setMessage(R.string.open_volume_failed_msg)
.setPositiveButton(R.string.ok) { _, _ ->
askForPassword(volume, position, savePasswordHash)
}
.show()
} else {
val fingerprintProtector = fingerprintProtector
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
if (savePasswordHash && returnedHash != null && fingerprintProtector != null) {
@ -573,36 +590,28 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
}
override fun onPasswordHashDecrypted(hash: ByteArray) {}
override fun onPasswordHashSaved() {
Arrays.fill(returnedHash, 0)
Arrays.fill(returnedHash.value!!, 0)
volumeAdapter.onVolumeChanged(position)
startExplorer(sessionId, volume.shortName)
startExplorer(encryptedVolume, volume.shortName)
}
private var isClosed = false
override fun onFailed(pending: Boolean) {
if (!isClosed) {
GocryptfsVolume(this@MainActivity, sessionId).close()
encryptedVolume.close()
isClosed = true
}
Arrays.fill(returnedHash, 0)
Arrays.fill(returnedHash.value!!, 0)
}
}
fingerprintProtector.savePasswordHash(volume, returnedHash)
fingerprintProtector.savePasswordHash(volume, returnedHash.value!!)
} else {
startExplorer(sessionId, volume.shortName)
startExplorer(encryptedVolume, volume.shortName)
}
} else {
CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.open_volume_failed)
.setMessage(R.string.open_volume_failed_msg)
.setPositiveButton(R.string.ok) { _, _ ->
askForPassword(volume, position, savePasswordHash)
}
.show()
}
}
}
private fun startExplorer(sessionId: Int, volumeShortName: String) {
private fun startExplorer(encryptedVolume: EncryptedVolume, volumeShortName: String) {
var explorerIntent: Intent? = null
if (dropMode) { //import via android share menu
explorerIntent = Intent(this, ExplorerActivityDrop::class.java)
@ -610,13 +619,13 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
explorerIntent.putExtras(intent.extras!!) //forward extras
} else if (pickMode) {
explorerIntent = Intent(this, ExplorerActivityPick::class.java)
explorerIntent.putExtra("originalSessionID", intent.getIntExtra("sessionID", -1))
explorerIntent.putExtra("destinationVolume", intent.getParcelableExtra<EncryptedVolume>("volume")!!)
explorerIntent.flags = Intent.FLAG_ACTIVITY_FORWARD_RESULT
}
if (explorerIntent == null) {
explorerIntent = Intent(this, ExplorerActivity::class.java) //default opening
}
explorerIntent.putExtra("sessionID", sessionId)
explorerIntent.putExtra("volume", encryptedVolume)
explorerIntent.putExtra("volume_name", volumeShortName)
startActivity(explorerIntent)