Compare commits


116 Commits

Author SHA1 Message Date
solokot 967d4551c5
Update Russian translation
Signed-off-by: Hardcore Sushi <>
2024-02-12 16:51:55 +01:00
Ali Beyaz b747d2822a
Add Turkish translation
Signed-off-by: Hardcore Sushi <>
2024-02-12 16:47:30 +01:00
CyanWolf e5652666d8
Update Spanish
Signed-off-by: Hardcore Sushi <>
2024-02-11 18:12:17 +01:00
Muhmmad14333653 cda0e90b96
Update Arabic translations
Signed-off-by: Hardcore Sushi <>
2024-02-11 18:08:54 +01:00
Matéo Duparc 6f43bc7417
Avoid being killed by SELinux when retrieving volume path 2024-02-11 17:55:24 +01:00
Matéo Duparc c26ab661c2
Logcat activity 2024-01-30 18:29:49 +01:00
Matéo Duparc 1c15f9fac8
Allow choosing export method 2024-01-28 15:44:53 +01:00
Matéo Duparc b4635dc2e0
Directory loading indicator 2024-01-13 23:19:22 +01:00
Matéo Duparc f4e47c1827
Allow directory creation on exposed volumes 2024-01-13 21:41:58 +01:00
Matéo Duparc 5474d6eea5
Add .opus & Update build config 2024-01-13 21:25:31 +01:00
Matéo Duparc 719faa31ee
Fix README 2023-10-15 17:09:48 +02:00
Matéo Duparc a41cde1c53
DroidFS v2.1.3 2023-09-28 19:36:55 +02:00
Matéo Duparc b503f134d5
Fix Intent.getParcelableExtra() crash on Android 13 2023-09-24 19:04:49 +02:00
Matéo Duparc 3ba774fda3
Add Version.toString() 2023-09-19 13:47:59 +02:00
Matéo Duparc b2154d319e
Repair corrupted database due to v2.1.1 2023-09-19 13:39:35 +02:00
Matéo Duparc 571a79cc1d
Really fix database upgrade 2023-09-19 11:41:01 +02:00
Matéo Duparc 891a581329
Update dependencies 2023-09-17 20:10:15 +02:00
Matéo Duparc f1a9c1383c
Fix database upgrade 2023-09-17 19:11:52 +02:00
Matéo Duparc ac71ad887d
Fix README 2023-09-10 21:39:28 +02:00
Matéo Duparc e1fe329f49
Add v2.1.0 changelog 2023-09-10 21:11:04 +02:00
Matéo Duparc dfff597ae5
DroidFS v2.1.0 2023-09-10 21:01:39 +02:00
Matéo Duparc bd429648b3
Update documentation 2023-09-10 21:01:04 +02:00
Matéo Duparc 71ff37b170
Fixes 2023-09-10 19:17:51 +02:00
Matéo Duparc 4afe56b13c
Migrate to AndroidX Media3 2023-09-10 19:12:17 +02:00
Matéo Duparc 217334a959
Fix es & de translations 2023-09-09 16:15:03 +02:00
CyanWolf 2666313676
Update Spanish translation
Signed-off-by: Hardcore Sushi <>
2023-09-08 21:32:46 +02:00
solokot 04e154a6d9
Updated Russian translation
Signed-off-by: Hardcore Sushi <>
2023-09-08 21:30:28 +02:00
Torsten Pfützenreuter d3760e2194
Added German translation
Signed-off-by: Hardcore Sushi <>
2023-09-08 21:27:38 +02:00
Matéo Duparc d6c777875e
Fix VolumeProvider createDocument path 2023-09-08 21:22:20 +02:00
Matéo Duparc 8a18270b33
Update dependencies 2023-09-08 21:13:24 +02:00
Matéo Duparc 79db84f81d
Volume provider 2023-09-06 19:27:41 +02:00
Matéo Duparc 6d04349b2e
Prevent volume renaming when open 2023-09-06 19:27:04 +02:00
Matéo Duparc de0194a722
Always open volume after creation 2023-08-20 17:08:10 +02:00
Matéo Duparc 3127a15d9e
Fix ANR on recursive mapping 2023-08-20 16:42:40 +02:00
Matéo Duparc a08da2eacb
MemoryFileProvider 2023-08-20 14:56:46 +02:00
Matéo Duparc 1727170cb6
Limit the number of thumbnails loaded concurrently 2023-08-15 18:33:29 +02:00
Matéo Duparc 8776d2ee28
Add Support section in README 2023-08-15 18:06:39 +02:00
Matéo Duparc 5642e28b44
Fix 2023-05-12 20:39:58 +02:00
Matéo Duparc 1b7e5904be
New screenshots 2023-05-12 20:26:45 +02:00
Matéo Duparc cb3fc3c70e
Re-ask only on wrong password 2023-05-11 21:58:55 +02:00
Matéo Duparc 393c458495
Offload file discovery for copy in coroutine 2023-05-11 21:24:29 +02:00
Matéo Duparc cdf98a7190
Handle cryfs inaccessible base dir 2023-05-11 00:02:05 +02:00
Matéo Duparc 2ae41f0f79
Improve file oprations coroutines 2023-05-10 23:41:29 +02:00
Muhmmad14333653 f85f9d1c44
Update arabic translation 2023-05-08 21:36:22 +02:00
Matéo Duparc 9fc981fee8
Fix rotation when rebinding camera use cases 2023-05-08 21:32:04 +02:00
Matéo Duparc ad19b9e645
Update dependencies 2023-05-08 20:58:54 +02:00
Matéo Duparc 87ffbc3cc1
Fix unsafe features doc link 2023-05-06 23:57:23 +02:00
Matéo Duparc b3a25e03e7
Improve video recording: fix freezes & ExoPlayer errors 2023-05-06 23:40:37 +02:00
Matéo Duparc 4c412be7dc
Best error messages when opening volumes 2023-05-03 14:14:40 +02:00
Matéo Duparc f4f3239bb1
Fix volume copying 2023-05-02 14:24:59 +02:00
Matéo Duparc 481558bd56
Add ecryptfs & shufflecake in TODO & Update README 2023-04-29 20:21:46 +02:00
Matéo Duparc 8d0a797469
v2.0.1 changelog 2023-04-26 17:12:38 +02:00
Matéo Duparc a4ce35c95d
WiperService 2023-04-26 16:40:05 +02:00
Matéo Duparc e51bd2ceba & Update dependencies 2023-04-26 16:02:07 +02:00
Matéo Duparc 2bbf003df5
Recover unregistered hidden volumes 2023-04-25 15:06:20 +02:00
Matéo Duparc e83cfc9794
Fix password encoding 2023-04-24 13:37:14 +02:00
ctntt 9d1bfd606f
Themed/monochrome icon support
Signed-off-by: Hardcore Sushi <>
2023-04-23 11:43:13 +02:00
Matéo Duparc 49ec2eaf49
Stop always opening files in write mode 2023-04-20 16:38:15 +02:00
Matéo Duparc 8c9c6a20b9
Really fix 2023-04-19 16:23:32 +02:00
Matéo Duparc f6d1fc8b67
Fix gradle nosplits 2023-04-19 16:11:54 +02:00
solokot de3a1a9538
Updated Russian v2 translation
Signed-off-by: Hardcore Sushi <>
2023-04-19 15:36:05 +02:00
Matéo Duparc 0a089c46ca
Fix 2023-04-19 15:34:36 +02:00
Matéo Duparc 05f4610407
v2.0.0 2023-04-18 19:47:44 +02:00
CyanWolf 451f36c770
Update Spanish translation
Signed-off-by: Hardcore Sushi <>
2023-04-18 15:23:01 +02:00
Matéo Duparc df3f84f526
Target Android API level 32 2023-04-18 14:59:05 +02:00
Matéo Duparc 24215a8b31
Fix crash when default volume gets deleted 2023-04-18 13:53:40 +02:00
Matéo Duparc eb4e13af46
Disable settings buttons during video recording 2023-04-17 18:50:46 +02:00
Matéo Duparc aea17aa7cb
Update dependencies 2023-04-17 17:06:51 +02:00
Matéo Duparc e918a2f94c
New CameraX API 2023-04-17 15:52:20 +02:00
Matéo Duparc e6761d1798
Update README & fastlane full_description.txt 2023-03-15 18:08:39 +01:00
Matéo Duparc c434d79c06
Fix video title switching 2023-03-13 17:10:06 +01:00
Matéo Duparc 821c853a22
Hide navigation bar in full screen mode 2023-03-13 17:02:38 +01:00
Matéo Duparc 22b1522192
Optional password fallback 2023-03-08 12:03:05 +01:00
Matéo Duparc 5090a7aa03
Separate color selection & black theme 2023-03-08 11:43:13 +01:00
Matéo Duparc 1a1d3ea570
Multi volume openings 2023-03-07 23:25:17 +01:00
Matéo Duparc 2d165c4a20
Monospace font in text editor 2023-02-06 11:16:10 +01:00
Matéo Duparc 883874a5ab
Refactoring: Constants & FileTypes 2023-02-06 10:52:51 +01:00
Matéo Duparc 6e500c23e5
Adaptive icon 2023-02-05 14:43:30 +01:00
Matéo Duparc a726f7a7d0
Fix storage permission requests 2023-02-02 21:09:11 +01:00
Matéo Duparc 1e75e9a32f
Clear password fields onStop() 2023-02-02 19:37:10 +01:00
Matéo Duparc 5e9656970a
Update dependencies 2023-02-01 23:46:27 +01:00
Matéo Duparc 5dbef99949
Fix EncryptedVolumeDataSource EOF 2023-02-01 20:06:35 +01:00
Matéo Duparc d2f11c85d1
Android 11 support 2023-02-01 19:09:53 +01:00
Matéo Duparc 25dbcca854
Update libpdfviewer & Fix FFmpeg build & onBackPressed deprecation in AddVolumeActivity 2022-10-08 11:07:59 +02:00
CyanWolf 545275dabc
Update spanish translation 2022-10-06 18:32:54 +02:00
solokot 077f5cc856
Update russian translation 2022-10-06 18:32:53 +02:00
Muhmmad14333653 2e07ee5333
Update arabic translation 2022-10-06 18:32:53 +02:00
Matéo Duparc 34aad2596d
Async file loading in file viewers 2022-10-04 13:30:51 +02:00
Matéo Duparc cdc269f2f7
Add pink themes 2022-10-04 12:57:23 +02:00
Matéo Duparc 991e435e5e
Fix file viewers navigation bar color in dark mode 2022-10-04 12:23:25 +02:00
Matéo Duparc 7c2f87109a
Allow to open & create volumes without remembering 2022-09-30 21:22:37 +02:00
Matéo Duparc 4df1086734
More flexible password change when fingerprint is saved 2022-09-27 18:33:43 +02:00
Matéo Duparc 7cdfc32c31
Direct encrypted files read/write & More compliant EncryptedVolumeDataSource 2022-09-23 20:58:16 +02:00
Matéo Duparc 8f5afca823
Update dependencies & Fix some bugs 2022-09-23 12:09:22 +02:00
Matéo Duparc 11cc15536f
Add FLAG_GRANT_READ_URI_PERMISSION for external opens 2022-09-13 12:43:08 +02:00
Matéo Duparc 2d19895e6d
Truncate files after possible overwrite 2022-09-13 10:33:14 +02:00
Matéo Duparc e2539a53b9
Set navigation bar color to background color 2022-07-03 14:11:53 +02:00
Matéo Duparc 17c32f2144
Show volume type in MainActivity 2022-07-03 13:43:46 +02:00
Matéo Duparc a5b6de1138 prefer cloning from the Chapril repository 2022-06-30 22:06:33 +02:00
Matéo Duparc d1ca164934
Allow changing password of CryFS volumes 2022-06-30 21:43:40 +02:00
Matéo Duparc 1a21a43f05
Gocryptfs JNI cleanup 2022-06-29 22:18:11 +02:00
Matéo Duparc 4d164944c1
Deleting files in background 2022-06-29 15:17:36 +02:00
Matéo Duparc 8709abd7d7
Don't cancel file operations when changing configuration 2022-06-29 14:47:50 +02:00
Matéo Duparc e01932acda
Allow opening CryFS volumes with password hash 2022-06-29 13:43:56 +02:00
Matéo Duparc cf4927a90b
CryFS 2022-06-26 15:51:48 +02:00
CyanWolf cb5679515c
Update spanish translation
Fix little error
2022-06-26 14:09:52 +02:00
Muhmmad14333653 a728bd8d24
Update arabic translation 2022-06-26 14:07:41 +02:00
Matéo Duparc 83dd759f36
Fix local reference table overflow in native_list_dir 2022-05-24 18:05:58 +02:00
Matéo Duparc 5144947a4a
Media player fixes: better handling of RTL & orientation 2022-05-07 14:51:56 +02:00
Muhmmad14333653 6b52eed9d0
update arabic translation
Signed-off-by: Hardcore Sushi <>
2022-05-05 13:20:18 +02:00
Muhmmad14333653 2a257d91d0
update Arabic translation
Signed-off-by: Hardcore Sushi <>
2022-05-03 13:08:17 +02:00
Matéo Duparc f837556af5
Fix explorer info bar color in dark mode 2022-05-02 14:17:02 +02:00
Matéo Duparc b7ab267d16
Arabic translation 2022-05-02 14:11:02 +02:00
Matéo Duparc 5ea0b8ad41
Actually fix camera icon tint bug 2022-05-01 19:48:16 +02:00
Matéo Duparc ec348383c6
Fix camera icon tint bug 2022-05-01 16:59:18 +02:00
Matéo Duparc c8d266150c
Fix explorer menu display 2022-05-01 13:50:37 +02:00
196 changed files with 18502 additions and 5402 deletions

.gitmodules vendored
View File

@ -4,3 +4,6 @@
[submodule "libpdfviewer"]
path = libpdfviewer
url =
[submodule "app/libcryfs"]
path = app/libcryfs
url =

116 Normal file
View File

@ -0,0 +1,116 @@
# Introduction
DroidFS relies on modified versions of the original encrypted filesystems programs to open volumes. [CryFS]( is written in C++ while [gocryptfs]( is written in [Go]( 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]( or on [Matrix](
# 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]( and the [Android Native Development Kit (NDK)]( (r23 versions are recommended).
If you want a support for Gocryptfs volumes, you must install [Go]( 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:// --recv-keys AFE384344A45E13A
Fingerprint: `B64E FE86 CEE1 D054 F082 1711 AFE3 8434 4A45 E13A` \
Email: `Hardcore Sushi <>`
# Download sources
Download DroidFS source code:
$ git clone --depth=1
Verify sources:
$ cd DroidFS
$ git verify-commit HEAD
__Don't continue if the verification fails!__
Initialize submodules:
$ git submodule update --depth=1 --init
[FFmpeg]( is needed to record encrypted video:
$ cd app/ffmpeg
$ git clone --depth=1
If you want Gocryptfs support, you need to download OpenSSL:
$ cd ../libgocryptfs
$ wget
Verify OpenSSL signature:
$ wget
$ gpg --verify openssl-1.1.1w.tar.gz.asc openssl-1.1.1w.tar.gz
Continue **ONLY** if the signature is **VALID**.
$ tar -xzf openssl-1.1.1w.tar.gz
If you want CryFS support, initialize libcryfs:
$ cd app/libcryfs
$ git submodule update --depth=1 --init
To be able to open PDF files internally, [pdf.js]( must be downloaded:
$ mkdir libpdfviewer/app/pdfjs-dist && cd libpdfviewer/app/pdfjs-dist
$ wget
$ tar xf pdfjs-dist-3.8.162.tgz package/build/pdf.min.js package/build/pdf.worker.min.js
$ mv package/build . && rm pdfjs-dist-3.8.162.tgz
# 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
$ ./ ffmpeg
## libgocryptfs
This step is only required if you want Gocryptfs support.
$ cd app/libgocryptfs
$ OPENSSL_PATH="./openssl-1.1.1w" ./
## 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.

DONATE.txt Normal file
View File

@ -0,0 +1,20 @@
Hash: SHA256
Here are the DroidFS donation addresses:
Monero (XMR):
Bitcoin (BTC):

View File

@ -1,52 +1,75 @@
# 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]( but support for [CryFS]( could be added in the future.
An alternative way to use encrypted virtual filesystems on Android that uses its own internal file explorer instead of mounting volumes.
It currently supports [gocryptfs]( and [CryFS](
For mortals: Encrypted storage compatible with already existing softwares.
<p align="center">
<img src="" height="500">
<img src="" height="500">
<img src="" height="500">
<img src="" height="500">
<img src="" height="500">
# Support
The creator of DroidFS works as a freelance developer and privacy consultant. I am currently looking for new clients! If you are interested, take a look at the [website]( Alternatively, you can directly support DroidFS by making a [donation](
Thank you so much ❤️.
# Disclaimer
DroidFS is provided "as is", without any warranty of any kind.
It shouldn't be considered as an absolute safe way to store files.
DroidFS cannot protect you from screen recording apps, keyloggers, apk backdooring, compromised root accesses, memory dumps etc.
Do not use this app with volumes containing sensitive data unless you know exactly what you are doing.
# Features
- Compatible with original encrypted volume implementations
- Internal support for video, audio, images, text and PDF files
- Built-in camera to take on-the-fly encrypted photos and videos
- Unlocking volumes using fingerprint authentication
- Volume auto-locking when the app goes in background
_For upcoming features, see [](
# Unsafe features
DroidFS allows you to enable/disable unsafe features to fit your needs between security and comfort.
It is strongly recommended to read the documentation of a feature before enabling it.
Some available features are considered risky and are therefore disabled by default. It is strongly recommended that you read the following documentation if you wish to activate one of these options.
<li><h4>Allow screenshots:</h4>
Disable the secure flag of DroidFS activities. This will allow you to take screenshots from the app, but will also allow other apps to record the screen while using DroidFS.
Note: apps with root access don't care about this flag: they can take screenshots or record the screen of any app without any permissions.
<li><h4>Allow opening files with other applications *:</h4>
Decrypt and open file using external apps. These apps could save and send the files thus opened.
<li><h4>Allow exporting files:</h4>
Decrypt and write file to disk (external storage). Any app with storage permissions could access exported files.
<li><h4>Allow sharing files via the android share menu *:</h4>
<li><h4>Allow sharing files via the android share menu*:</h4>
Decrypt and share file with other apps. These apps could save and send the files thus shared.
<li><h4>Keep volume open when the app goes in background:</h4>
Don't close the volume when you leave the app but keep running it in the background. Anyone going back to the activity could have access to the volume.
<li><h4>Allow saving password hash using fingerprint:</h4>
Generate an AES-256 GCM key in the Android Keystore (protected by fingerprint authentication), then use it to encrypt the volume password hash and store it to the DroidFS internal storage. This require Android v6.0+. If your device is not encrypted, extracting the encryption key with physical access may be possible.
<li><h4>Keep volume open when the app goes in background:</h4>
Don't close the volume when you leave the app but keep running it in the background. Anyone going back to the activity could have access to the volume.
<li><h4>Allow opening files with other applications*:</h4>
Decrypt and open file using external apps. These apps could save and send the files thus opened.
<li><h4>Expose open volumes*:</h4>
Allow open volumes to be browsed in the system file explorer (<a href="">DocumentProvider</a> API). Encrypted files can then be selected from other applications, potentially with permanent access. This feature requires <i>"Keep volume open when the app goes in background"</i> to be enabled.
<li><h4>Grant write access:</h4>
Files opened with another applications can be modified by them. This applies to both previous unsafe features.
* Features requiring temporary writing of the plain file to disk (DroidFS internal storage). This file could be read by apps with root access or by physical access if your device is not encrypted.
\* These features can work in two ways: temporarily writing the plain file to disk (DroidFS internal storage) or sharing it via memory. By default, DroidFS will choose to keep the file only in memory as it's more secure, but will fallback to disk export if the file is too large to be held in memory. This behavior can be changed with the *"Export method"* parameter in the settings. Please note that some applications require the file to be stored on disk, and therefore do not work with memory-exported files.
# Download
<a href="">
<img src="" height="75">
You can download DroidFS from [F-Droid]( or from the "Releases" section in the repo.
You can download DroidFS from [F-Droid]( or from the "Releases" section in this repository.
APKs available here are signed with my PGP key available on keyservers:
@ -65,17 +88,17 @@ __Don't install the APK if the checksums don't match!__
F-Droid APKs should be signed with the F-Droid key. More details [here](
# Permissions
DroidFS need some permissions to work properly. Here is why:
DroidFS needs some permissions for certain features. However, you are free to deny them if you do not wish to use these features.
<li><h4>Read & write access to shared storage:</h4>
Required for creating, opening and modifying volumes and for importing/exporting files to/from volumes.
Required to access volumes located on shared storage.
<li><h4>Biometric/Fingerprint hardware:</h4>
Required to encrypt/decrypt password hashes using a fingerprint protected key.
Needed to take photos & videos directly encrypted inside DroidFS. You can deny this permission if you don't want to use it.
Required to take encrypted photos or videos directly from the app.
<li><h4>Record audio:</h4>
Required if you want sound on video recorded with DroidFS.
@ -83,83 +106,24 @@ DroidFS need some permissions to work properly. Here is why:
# 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]( 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]( and [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]( 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). If you want to synchronize your volumes on a cloud, the cloud application must synchronize the encrypted directory from disk.
# 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]( and the [Android Native Development Kit (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](
$ sudo apt-get install golang-go
You also need to install the Android SDK build tools and the [Android NDK](
#### Download Sources
$ git clone --recurse-submodules
$ cd DroidFS
[libgocryptfs]( needs OpenSSL:
$ cd app/libgocryptfs
$ wget
Verify OpenSSL signature:
$ wget
$ 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]( to record encrypted video:
$ cd app/ffmpeg
$ git clone --depth=1
#### 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" ./
Then FFmpeg:
$ cd app/ffmpeg
$ env ANDROID_NDK_HOME="<your ndk path>" ./ 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 []( to build DroidFS from source.
# Third party code
Thanks to these open source projects that DroidFS uses:
### Modified code:
- [libgocryptfs]( (forked from [gocryptfs]( to encrypt your data
- Encrypted filesystems (to protect your data):
- [libgocryptfs]( (forked from [gocryptfs](
- [libcryfs]( (forked from [CryFS](
- [libpdfviewer]( (forked from [PdfViewer]( to open PDF files
- [DoubleTapPlayerView]( to add double-click controls to the video player
### Borrowed code:
- [MaterialFiles]( for kotlin natural sorting implementation
- [MaterialFiles]( for Kotlin natural sorting implementation
### Libraries:
- [Glide]( to display pictures
- [Glide]( to display pictures
- [ExoPlayer]( to play media files

31 Normal file
View File

@ -0,0 +1,31 @@
Here's a list of features that it would be nice to have in DroidFS. As this is a FLOSS project, there are no special requirements on *when* or even *if* these features will be implemented, but contributions are greatly appreciated.
## Security
- [hardened_malloc]( compatibility ([#181](
- Internal keyboard for passwords
## UX
- File associations editor
- Optional discovery before file operations
- Modifiable CryFS scrypt parameters
- Alert dialog showing details of file operations
- Internal file browser to select volumes
## Encryption software support
- [Shufflecake]( plausible deniability for multiple hidden filesystems on Linux (would be absolutely awesome to have but quite difficult)
- [fscrypt]( filesystem encryption at the kernel level
## Health
- F-Droid ABI split
- OpenSSL & FFmpeg as git submodules (useful for F-Droid)
- Remove all android:configChanges from AndroidManifest.xml
- More efficient thumbnails cache
- Guide for translators
- Usage & code documentation
- Automated tests
## And:
- All the [feature requests on the GitHub repo](
- All the [feature requests on the Gitea repo](

View File

@ -1,27 +1,30 @@
cmake_minimum_required(VERSION 3.10)
option(GOCRYPTFS "build libgocryptfs" ON)
option(CRYFS "build libcryfs" ON)
add_library(memfile SHARED src/main/native/memfile.c)
target_link_libraries(memfile log)
add_library(gocryptfs SHARED IMPORTED)
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)
if (CRYFS)
add_library(cryfs_jni SHARED src/main/native/libcryfs.c)
target_link_libraries(cryfs_jni libcryfs-jni)
@ -65,14 +68,12 @@ add_library(
target_include_directories(mux PRIVATE ${PROJECT_SOURCE_DIR}/ffmpeg/build/${ANDROID_ABI})

View File

@ -1,50 +1,89 @@
apply plugin: ''
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
if (hasProperty("nosplits")) {
ext.splits = false
} else {
ext.splits = true
android {
compileSdkVersion 31
buildToolsVersion "31"
ndkVersion "23.1.7779620"
compileSdk 34
ndkVersion "26.1.10909125"
namespace "sushi.hardcore.droidfs"
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_17
kotlinOptions {
jvmTarget = "17"
defaultConfig {
applicationId "sushi.hardcore.droidfs"
minSdkVersion 21
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 29
versionCode 27
versionName "1.10.1"
targetSdkVersion 32
versionCode 36
versionName "2.1.3"
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()) {
if (project.ext.splits) {
splits {
abi {
enable true
reset() // fix unknown bug (
universalApk true
applicationVariants.all { variant ->
applicationVariants.configureEach { variant ->
variant.resValue "string", "versionName", variant.versionName
buildConfigField "boolean", "CRYFS_DISABLED", "${project.ext.disableCryFS}"
buildConfigField "boolean", "GOCRYPTFS_DISABLED", "${project.ext.disableGocryptfs}"
buildFeatures {
viewBinding true
buildConfig true
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), ''
postprocessing {
removeUnusedCode true
removeUnusedResources true
obfuscate false
optimizeCode true
proguardFiles ''
@ -53,32 +92,46 @@ android {
path file('CMakeLists.txt')
sourceSets {
main {
java {
exclude 'androidx/camera/video/originals/**'
dependencies {
implementation project(":libpdfviewer:app")
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.core:core-ktx:1.7.0'
implementation "androidx.appcompat:appcompat:1.4.1"
implementation "androidx.constraintlayout:constraintlayout:2.1.3"
implementation 'androidx.core:core-ktx:1.12.0'
implementation "androidx.appcompat:appcompat:1.6.1"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
def lifecycle_version = "2.6.2"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
implementation "androidx.sqlite:sqlite-ktx:2.2.0"
implementation "androidx.preference:preference-ktx:1.2.0"
implementation "androidx.sqlite:sqlite-ktx:2.3.1"
implementation "androidx.preference:preference-ktx:1.2.1"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation ''
implementation "com.github.bumptech.glide:glide:4.12.0"
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha04"
implementation ''
implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
def exoplayer_version = "2.17.1"
implementation "$exoplayer_version"
implementation "$exoplayer_version"
def media3_version = "1.1.1"
implementation "androidx.media3:media3-exoplayer:$media3_version"
implementation 'androidx.media3:media3-ui:1.1.1'
implementation "androidx.media3:media3-datasource:$media3_version"
implementation "androidx.concurrent:concurrent-futures:1.1.0"
def camerax_version = "1.1.0-beta03"
def camerax_version = "1.3.0-rc02"
implementation "$camerax_version"
implementation "$camerax_version"
implementation "$camerax_version"
implementation "$camerax_version"
def autoValueVersion = '1.10.4'
implementation "$autoValueVersion"
annotationProcessor "$autoValueVersion"

View File

@ -63,6 +63,7 @@ else
--disable-sndio \
--disable-schannel \
--disable-securetransport \
--disable-vulkan \
--disable-xlib \
--disable-zlib \
--disable-cuvid \

app/libcryfs Submodule

@ -0,0 +1 @@
Subproject commit 6388eaf433a4196f10389921d5e346c90ee3d793

@ -1 +1 @@
Subproject commit 9e98192442b08362660b45f4e2e50221ba7bc65b
Subproject commit 4f32853ae5ac70811b451cac60ed36fd5b93cbc8

View File

@ -1,31 +1,24 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
# For more details, see
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
-keep class sushi.hardcore.droidfs.SettingsActivity$**
-keep class sushi.hardcore.droidfs.explorers.ExplorerElement
-keepclassmembers class sushi.hardcore.droidfs.explorers.ExplorerElement {
static sushi.hardcore.droidfs.explorers.ExplorerElement new(...);
-keepclassmembers class sushi.hardcore.droidfs.video_recording.MediaMuxer {
-keepclassmembers class sushi.hardcore.droidfs.video_recording.FFmpegMuxer {
void writePacket(byte[]);
void seek(long);
# Required for Intent.getParcelableExtra() to work on Android 13
-keep class sushi.hardcore.droidfs.VolumeData {
public int describeContents();
-keep class sushi.hardcore.droidfs.VolumeData$* {
static public android.os.Parcelable$Creator CREATOR;
-keep class sushi.hardcore.droidfs.filesystems.EncryptedVolume {
public int describeContents();
-keep class sushi.hardcore.droidfs.filesystems.EncryptedVolume$* {
static public android.os.Parcelable$Creator CREATOR;

View File

@ -1,13 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android=""
android:protectionLevel="signature" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.CAMERA" />
@ -25,12 +21,14 @@
tools:node="remove" /> <!--removing this permission automatically added by exoplayer-->
<activity android:name=".MainActivity" android:exported="true">
<action android:name="android.intent.action.MAIN" />
@ -49,13 +47,15 @@
<activity android:name=".explorers.ExplorerActivity"/>
<activity android:name=".explorers.ExplorerActivityPick"/>
<activity android:name=".explorers.ExplorerActivityDrop"/>
<activity android:name=".file_viewers.ImageViewer" android:configChanges="screenSize|orientation" /> <!-- don't reload content on configuration change -->
<activity android:name=".file_viewers.ImageViewer"/>
<activity android:name=".file_viewers.VideoPlayer" android:configChanges="screenSize|orientation" />
<activity android:name=".file_viewers.PdfViewer" android:configChanges="screenSize|orientation" />
<activity android:name=".file_viewers.PdfViewer" android:configChanges="screenSize|orientation" android:theme="@style/AppTheme" />
<activity android:name=".file_viewers.AudioPlayer" android:configChanges="screenSize|orientation" />
<activity android:name=".file_viewers.TextEditor" android:configChanges="screenSize|orientation" />
<activity android:name=".CameraActivity" android:screenOrientation="nosensor" />
<activity android:name=".LogcatActivity"/>
<service android:name=".WiperService" android:exported="false" android:stopWithTask="false"/>
<service android:name=".file_operations.FileOperationService" android:exported="false"/>
<receiver android:name=".file_operations.NotificationBroadcastReceiver" android:exported="false">
@ -65,10 +65,21 @@
android:writePermission="${applicationId}.WRITE_TEMPORARY_STORAGE" />
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />

View File

@ -0,0 +1,14 @@
import java.nio.ByteBuffer
interface MediaMuxer {
fun setOrientationHint(degree: Int)
fun release()
fun addTrack(mediaFormat: MediaFormat): Int
fun start()
fun writeSampleData(trackIndex: Int, buffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo)
fun stop()

View File

@ -0,0 +1,16 @@
import android.location.Location
class MuxerOutputOptions(private val mediaMuxer: MediaMuxer): OutputOptions(MuxerOutputOptionsInternal()) {
private class MuxerOutputOptionsInternal: OutputOptionsInternal() {
override fun getFileSizeLimit(): Long = FILE_SIZE_UNLIMITED.toLong()
override fun getDurationLimitMillis(): Long = DURATION_UNLIMITED.toLong()
override fun getLocation(): Location? = null
fun getMediaMuxer(): MediaMuxer = mediaMuxer

View File

@ -0,0 +1,254 @@
* Copyright 2021 The Android Open Source Project
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* See the License for the specific language governing permissions and
* limitations under the License.
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RequiresPermission;
import androidx.core.content.PermissionChecker;
import androidx.core.util.Consumer;
import androidx.core.util.Preconditions;
import java.util.concurrent.Executor;
* A recording that can be started at a future time.
* <p>A pending recording allows for configuration of a recording before it is started. Once a
* pending recording is started with {@link #start(Executor, Consumer)}, any changes to the pending
* recording will not affect the actual recording; any modifications to the recording will need
* to occur through the controls of the {@link SucklessRecording} class returned by
* {@link #start(Executor, Consumer)}.
* <p>A pending recording can be created using one of the {@link Recorder} methods for starting a
* recording such as {@link Recorder#prepareRecording(Context, MediaStoreOutputOptions)}.
* <p>There may be more settings that can only be changed per-recorder instead of per-recording,
* because it requires expensive operations like reconfiguring the camera. For those settings, use
* the {@link Recorder.Builder} methods to configure before creating the {@link Recorder}
* instance, then create the pending recording with it.
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on
public final class SucklessPendingRecording {
private final Context mContext;
private final SucklessRecorder mRecorder;
private final OutputOptions mOutputOptions;
private Consumer<VideoRecordEvent> mEventListener;
private Executor mListenerExecutor;
private boolean mAudioEnabled = false;
private boolean mIsPersistent = false;
SucklessPendingRecording(@NonNull Context context, @NonNull SucklessRecorder recorder,
@NonNull OutputOptions options) {
// Application context is sufficient for all our needs, so store that to avoid leaking
// unused resources. For attribution, ContextUtil.getApplicationContext() will retain the
// attribution tag from the original context.
mContext = ContextUtil.getApplicationContext(context);
mRecorder = recorder;
mOutputOptions = options;
* Returns an application context which was retrieved from the {@link Context} used to
* create this object.
Context getApplicationContext() {
return mContext;
SucklessRecorder getRecorder() {
return mRecorder;
OutputOptions getOutputOptions() {
return mOutputOptions;
Executor getListenerExecutor() {
return mListenerExecutor;
Consumer<VideoRecordEvent> getEventListener() {
return mEventListener;
boolean isAudioEnabled() {
return mAudioEnabled;
boolean isPersistent() {
return mIsPersistent;
* Enables audio to be recorded for this recording.
* <p>This method must be called prior to {@link #start(Executor, Consumer)} to enable audio
* in the recording. If this method is not called, the {@link SucklessRecording} generated by
* {@link #start(Executor, Consumer)} will not contain audio, and
* {@link AudioStats#getAudioState()} will always return
* {@link AudioStats#AUDIO_STATE_DISABLED} for all {@link RecordingStats} send to the listener
* set passed to {@link #start(Executor, Consumer)}.
* <p>Recording with audio requires the {@link android.Manifest.permission#RECORD_AUDIO}
* permission; without it, recording will fail at {@link #start(Executor, Consumer)} with an
* {@link IllegalStateException}.
* @return this pending recording
* @throws IllegalStateException if the {@link Recorder} this recording is associated to
* doesn't support audio.
* @throws SecurityException if the {@link Manifest.permission#RECORD_AUDIO} permission
* is denied for the current application.
public SucklessPendingRecording withAudioEnabled() {
// Check permissions and throw a security exception if RECORD_AUDIO is not granted.
if (PermissionChecker.checkSelfPermission(mContext, Manifest.permission.RECORD_AUDIO)
== PermissionChecker.PERMISSION_DENIED) {
throw new SecurityException("Attempted to enable audio for recording but application "
+ "does not have RECORD_AUDIO permission granted.");
Preconditions.checkState(mRecorder.isAudioSupported(), "The Recorder this recording is "
+ "associated to doesn't support audio.");
mAudioEnabled = true;
return this;
* Configures the recording to be a persistent recording.
* <p>A persistent recording will only be stopped by explicitly calling
* {@link Recording#stop()} or {@link Recording#close()} and will ignore events that would
* normally cause recording to stop, such as lifecycle events or explicit unbinding of a
* {@link VideoCapture} use case that the recording's {@link Recorder} is attached to.
* <p>Even though lifecycle events or explicit unbinding use cases won't stop a persistent
* recording, it will still stop the camera from producing data, resulting in the in-progress
* persistent recording stopping getting data until the camera stream is activated again. For
* example, when the activity goes into background, the recording will keep waiting for new
* data to be recorded until the activity is back to foreground.
* <p>A {@link Recorder} instance is recommended to be associated with a single
* {@link VideoCapture} instance, especially when using persistent recording. Otherwise, there
* might be unexpected behavior. Any in-progress persistent recording created from the same
* {@link Recorder} should be stopped before starting a new recording, even if the
* {@link Recorder} is associated with a different {@link VideoCapture}.
* <p>To switch to a different camera stream while a recording is in progress, first create
* the recording as persistent recording, then rebind the {@link VideoCapture} it's
* associated with to a different camera. The implementation may be like:
* <pre>{@code
* // Prepare the Recorder and VideoCapture, then bind the VideoCapture to the </