Compare commits

...

104 Commits

Author SHA1 Message Date
solokot 967d4551c5
Update Russian translation
Signed-off-by: Hardcore Sushi <hardcore.sushi@disroot.org>
2024-02-12 16:51:55 +01:00
Ali Beyaz b747d2822a
Add Turkish translation
Signed-off-by: Hardcore Sushi <hardcore.sushi@disroot.org>
2024-02-12 16:47:30 +01:00
CyanWolf e5652666d8
Update Spanish
Signed-off-by: Hardcore Sushi <hardcore.sushi@disroot.org>
2024-02-11 18:12:17 +01:00
Muhmmad14333653 cda0e90b96
Update Arabic translations
Signed-off-by: Hardcore Sushi <hardcore.sushi@disroot.org>
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 <hardcore.sushi@disroot.org>
2023-09-08 21:32:46 +02:00
solokot 04e154a6d9
Updated Russian translation
Signed-off-by: Hardcore Sushi <hardcore.sushi@disroot.org>
2023-09-08 21:30:28 +02:00
Torsten Pfützenreuter d3760e2194
Added German translation
Signed-off-by: Hardcore Sushi <hardcore.sushi@disroot.org>
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 TODO.md 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
TODO.md & 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 <hardcore.sushi@disroot.org>
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 proguard-rules.pro 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 <hardcore.sushi@disroot.org>
2023-04-19 15:36:05 +02:00
Matéo Duparc 0a089c46ca
Fix proguard-rules.pro 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 <hardcore.sushi@disroot.org>
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
BUILD.md: 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
189 changed files with 17117 additions and 4981 deletions

View File

@ -24,7 +24,7 @@ Email: `Hardcore Sushi <hardcore.sushi@disroot.org>`
# Download sources
Download DroidFS source code:
```
$ git clone --depth=1 https://github.com/hardcore-sushi/DroidFS.git
$ git clone --depth=1 https://forge.chapril.org/hardcoresushi/DroidFS.git
```
Verify sources:
```
@ -45,16 +45,16 @@ $ 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
$ wget https://www.openssl.org/source/openssl-1.1.1w.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
$ wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz.asc
$ 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.1p.tar.gz
$ tar -xzf openssl-1.1.1w.tar.gz
```
If you want CryFS support, initialize libcryfs:
```
@ -62,6 +62,14 @@ $ cd app/libcryfs
$ git submodule update --depth=1 --init
```
To be able to open PDF files internally, [pdf.js](https://github.com/mozilla/pdf.js) must be downloaded:
```
$ mkdir libpdfviewer/app/pdfjs-dist && cd libpdfviewer/app/pdfjs-dist
$ wget https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.8.162.tgz
$ 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:
```
@ -76,7 +84,7 @@ $ ./build.sh ffmpeg
This step is only required if you want Gocryptfs support.
```
$ cd app/libgocryptfs
$ OPENSSL_PATH="./openssl-1.1.1p" ./build.sh
$ OPENSSL_PATH="./openssl-1.1.1w" ./build.sh
```
## Compile APKs
Gradle build libgocryptfs and libcryfs by default.

20
DONATE.txt Normal file
View File

@ -0,0 +1,20 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Here are the DroidFS donation addresses:
Monero (XMR):
86f82JEMd33WfapNZETukJW17eEa6RR4rW3wNEZ2CAZh228EYpDaar4DdDPUc4U3YT4CcFdW4c7462Uzx9Em2BB92Aj9fbT
Bitcoin (BTC):
bc1qeyvpy3tj4rr4my5f5wz9s8a4g4nh4l0kj4h6xy
-----BEGIN PGP SIGNATURE-----
iHUEARYIAB0WIQS2Tv6GzuHQVPCCFxGv44Q0SkXhOgUCZNuhaAAKCRCv44Q0SkXh
OqEUAP0d67oFlGp5IlBHwNI/p2KMHka3LzHdQTBQs40Jus3tVQEAsTZEy/sc6Nwp
C8mAXUTebijFgrlYYQkfVS0RBXHwggo=
=E6ia
-----END PGP SIGNATURE-----

View File

@ -1,45 +1,68 @@
# 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 supports [gocryptfs](https://github.com/rfjakob/gocryptfs) and [CryFS](https://github.com/cryfs/cryfs) (alpha).
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](https://github.com/rfjakob/gocryptfs) and [CryFS](https://github.com/cryfs/cryfs).
For mortals: Encrypted storage compatible with already existing softwares.
<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">
<img src="https://forge.chapril.org/hardcoresushi/DroidFS/raw/branch/master/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" height="500">
<img src="https://forge.chapril.org/hardcoresushi/DroidFS/raw/branch/master/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" height="500">
<img src="https://forge.chapril.org/hardcoresushi/DroidFS/raw/branch/master/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" height="500">
<img src="https://forge.chapril.org/hardcoresushi/DroidFS/raw/branch/master/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png" height="500">
</p>
# 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](https://arkensys.dedyn.io). Alternatively, you can directly support DroidFS by making a [donation](https://forge.chapril.org/hardcoresushi/DroidFS/src/branch/master/DONATE.txt).
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 [TODO.md](https://forge.chapril.org/hardcoresushi/DroidFS/src/branch/master/TODO.md)._
# 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.
<ul>
<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>
<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>
<li><h4>Allow exporting files:</h4>
Decrypt and write file to disk (external storage). Any app with storage permissions could access exported files.
</li>
<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>
<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>
<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>
<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>
<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>
<li><h4>Expose open volumes*:</h4>
Allow open volumes to be browsed in the system file explorer (<a href="https://developer.android.com/guide/topics/providers/document-provider">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>
<li><h4>Grant write access:</h4>
Files opened with another applications can be modified by them. This applies to both previous unsafe features.
</li>
</ul>
* 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="https://f-droid.org/packages/sushi.hardcore.droidfs">
@ -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](https://f-droid.org/docs/Release_Channels_and_Signing_Keys).
# 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.
<ul>
<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>
<li><h4>Biometric/Fingerprint hardware:</h4>
Required to encrypt/decrypt password hashes using a fingerprint protected key.
</li>
<li><h4>Camera:</h4>
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>
<li><h4>Record audio:</h4>
Required if you want sound on video recorded with DroidFS.
@ -83,7 +106,7 @@ DroidFS need some permissions to work properly. Here is why:
</ul>
# Limitations
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.
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). If you want to synchronize your volumes on a cloud, the cloud application must synchronize the encrypted directory from disk.
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.
@ -102,5 +125,5 @@ Thanks to these open source projects that DroidFS uses:
### Borrowed code:
- [MaterialFiles](https://github.com/zhanghai/MaterialFiles) for Kotlin natural sorting implementation
### Libraries:
- [Glide](https://github.com/bumptech/glide/) to display pictures
- [Glide](https://github.com/bumptech/glide) to display pictures
- [ExoPlayer](https://github.com/google/ExoPlayer) to play media files

31
TODO.md Normal file
View File

@ -0,0 +1,31 @@
# TODO
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](https://github.com/GrapheneOS/hardened_malloc) compatibility ([#181](https://github.com/hardcore-sushi/DroidFS/issues/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](https://shufflecake.net): plausible deniability for multiple hidden filesystems on Linux (would be absolutely awesome to have but quite difficult)
- [fscrypt](https://www.kernel.org/doc/html/latest/filesystems/fscrypt.html): 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](https://github.com/hardcore-sushi/DroidFS/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
- All the [feature requests on the Gitea repo](https://forge.chapril.org/hardcoresushi/DroidFS/issues?q=&state=open&labels=748)

View File

@ -5,6 +5,9 @@ project(DroidFS)
option(GOCRYPTFS "build libgocryptfs" ON)
option(CRYFS "build libcryfs" ON)
add_library(memfile SHARED src/main/native/memfile.c)
target_link_libraries(memfile log)
if (GOCRYPTFS)
add_library(gocryptfs SHARED IMPORTED)
set_target_properties(
@ -72,4 +75,5 @@ target_link_libraries(
avformat
avcodec
avutil
log
)

View File

@ -13,22 +13,32 @@ if (hasProperty("disableGocryptfs")) {
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 28
versionName "2.0.0-alpha1"
targetSdkVersion 32
versionCode 36
versionName "2.1.3"
ndk {
abiFilters "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
@ -44,16 +54,17 @@ android {
}
}
if (!file("fdroid").exists()) {
if (project.ext.splits) {
splits {
abi {
enable true
reset() // fix unknown bug (https://ru.stackoverflow.com/questions/1557805/abis-armeabi-mips-mips64-riscv64-are-not-supported-for-platform)
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}"
@ -61,6 +72,7 @@ android {
buildFeatures {
viewBinding true
buildConfig true
}
buildTypes {
@ -80,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 'com.google.android.material:material:1.5.0'
implementation "com.github.bumptech.glide:glide:4.12.0"
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha04"
implementation 'com.google.android.material:material:1.9.0'
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 "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-ui:$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 "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:$camerax_version"
implementation "androidx.camera:camera-extensions:$camerax_version"
def autoValueVersion = '1.10.4'
implementation "com.google.auto.value:auto-value-annotations:$autoValueVersion"
annotationProcessor "com.google.auto.value:auto-value:$autoValueVersion"
}

View File

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

@ -1 +1 @@
Subproject commit 356cf8a1604776cb2cc4f4ff873936f7b396bd49
Subproject commit 6388eaf433a4196f10389921d5e346c90ee3d793

@ -1 +1 @@
Subproject commit e6e4c201dbf3834de1a49a8b67b4b54239d24249
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
# http://developer.android.com/guide/developing/tools/proguard.html
# 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="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="sushi.hardcore.droidfs"
android:installLocation="auto">
<permission
android:name="${applicationId}.WRITE_TEMPORARY_STORAGE"
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-->
<application
android:allowBackup="false"
android:icon="@mipmap/icon_launcher"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@style/BaseTheme">
android:theme="@style/BaseTheme"
android:name=".VolumeManagerApp"
android:fullBackupContent="false"
android:dataExtractionRules="@xml/backup_rules">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<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 @@
</receiver>
<provider
android:name=".content_providers.RestrictedFileProvider"
android:name=".content_providers.TemporaryFileProvider"
android:authorities="${applicationId}.temporary_provider"
android:exported="true"/>
<provider
android:authorities="${applicationId}.volume_provider"
android:name=".content_providers.VolumeProvider"
android:exported="true"
android:writePermission="${applicationId}.WRITE_TEMPORARY_STORAGE" />
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
</application>
</manifest>

View File

@ -0,0 +1,14 @@
package androidx.camera.video
import android.media.MediaCodec
import android.media.MediaFormat
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 @@
package androidx.camera.video
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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.video;
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.camera.core.impl.utils.ContextUtil;
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 package-info.java
@SuppressLint("RestrictedApi")
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.
*/
@NonNull
Context getApplicationContext() {
return mContext;
}
@NonNull
SucklessRecorder getRecorder() {
return mRecorder;
}
@NonNull
OutputOptions getOutputOptions() {
return mOutputOptions;
}
@Nullable
Executor getListenerExecutor() {
return mListenerExecutor;
}
@Nullable
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.
*/
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
@NonNull
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 back camera.
* Recorder recorder = Recorder.Builder().build();
* VideoCapture videoCapture = VideoCapture.withOutput(recorder);
* cameraProvider.bindToLifecycle(
* lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, videoCapture);
*
* // Prepare the persistent recording and start it.
* Recording recording = recorder
* .prepareRecording(context, outputOptions)
* .asPersistentRecording()
* .start(eventExecutor, eventListener);
*
* // Record from the back camera for a period of time.
*
* // Rebind the VideoCapture to the front camera.
* cameraProvider.unbindAll();
* cameraProvider.bindToLifecycle(
* lifecycleOwner, CameraSelector.DEFAULT_FRONT_CAMERA, videoCapture);
*
* // Record from the front camera for a period of time.
*
* // Stop the recording explicitly.
* recording.stop();
* }</pre>
*
* <p>The audio data will still be recorded after the {@link VideoCapture} is unbound.
* {@link Recording#pause() Pause} the recording first and {@link Recording#resume() resume} it
* later to stop recording audio while rebinding use cases.
*
* <p>If the recording is unable to receive data from the new camera, possibly because of
* incompatible surface combination, an exception will be thrown when binding to lifecycle.
*/
@ExperimentalPersistentRecording
@NonNull
public SucklessPendingRecording asPersistentRecording() {
mIsPersistent = true;
return this;
}
/**
* Starts the recording, making it an active recording.
*
* <p>Only a single recording can be active at a time, so if another recording is active,
* this will throw an {@link IllegalStateException}.
*
* <p>If there are no errors starting the recording, the returned {@link SucklessRecording}
* can be used to {@link SucklessRecording#pause() pause}, {@link SucklessRecording#resume() resume},
* or {@link SucklessRecording#stop() stop} the recording.
*
* <p>Upon successfully starting the recording, a {@link VideoRecordEvent.Start} event will
* be the first event sent to the provided event listener.
*
* <p>If errors occur while starting the recording, a {@link VideoRecordEvent.Finalize} event
* will be the first event sent to the provided listener, and information about the error can
* be found in that event's {@link VideoRecordEvent.Finalize#getError()} method. The returned
* {@link SucklessRecording} will be in a finalized state, and all controls will be no-ops.
*
* <p>If the returned {@link SucklessRecording} is garbage collected, the recording will be
* automatically stopped. A reference to the active recording must be maintained as long as
* the recording needs to be active. If the recording is garbage collected, the
* {@link VideoRecordEvent.Finalize} event will contain error
* {@link VideoRecordEvent.Finalize#ERROR_RECORDING_GARBAGE_COLLECTED}.
*
* <p>The {@link Recording} will be stopped automatically if the {@link VideoCapture} its
* {@link Recorder} is attached to is unbound unless it's created
* {@link #asPersistentRecording() as a persistent recording}.
*
* @throws IllegalStateException if the associated Recorder currently has an unfinished
* active recording.
* @param listenerExecutor the executor that the event listener will be run on.
* @param listener the event listener to handle video record events.
*/
@NonNull
@CheckResult
public SucklessRecording start(
@NonNull Executor listenerExecutor,
@NonNull Consumer<VideoRecordEvent> listener) {
Preconditions.checkNotNull(listenerExecutor, "Listener Executor can't be null.");
Preconditions.checkNotNull(listener, "Event listener can't be null");
mListenerExecutor = listenerExecutor;
mEventListener = listener;
return mRecorder.start(this);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,263 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.video;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.annotation.SuppressLint;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.camera.core.impl.utils.CloseGuardHelper;
import androidx.core.util.Consumer;
import androidx.core.util.Preconditions;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Provides controls for the currently active recording.
*
* <p>An active recording is created by starting a pending recording with
* {@link PendingRecording#start(Executor, Consumer)}. If there are no errors starting the
* recording, upon creation, an active recording will provide controls to pause, resume or stop a
* recording. If errors occur while starting the recording, the active recording will be
* instantiated in a {@link VideoRecordEvent.Finalize finalized} state, and all controls will be
* no-ops. The state of the recording can be observed by the video record event listener provided
* to {@link PendingRecording#start(Executor, Consumer)} when starting the recording.
*
* <p>Either {@link #stop()} or {@link #close()} can be called when it is desired to
* stop the recording. If {@link #stop()} or {@link #close()} are not called on this object
* before it is no longer referenced, it will be automatically stopped at a future point in time
* when the object is garbage collected, and no new recordings can be started from the same
* {@link Recorder} that generated the object until that occurs.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@SuppressLint("RestrictedApi")
public final class SucklessRecording implements AutoCloseable {
// Indicates the recording has been explicitly stopped by users.
private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
private final SucklessRecorder mRecorder;
private final long mRecordingId;
private final OutputOptions mOutputOptions;
private final boolean mIsPersistent;
private final CloseGuardHelper mCloseGuard = CloseGuardHelper.create();
SucklessRecording(@NonNull SucklessRecorder recorder, long recordingId, @NonNull OutputOptions options,
boolean isPersistent, boolean finalizedOnCreation) {
mRecorder = recorder;
mRecordingId = recordingId;
mOutputOptions = options;
mIsPersistent = isPersistent;
if (finalizedOnCreation) {
mIsClosed.set(true);
} else {
mCloseGuard.open("stop");
}
}
/**
* Creates an {@link SucklessRecording} from a {@link PendingRecording} and recording ID.
*
* <p>The recording ID is expected to be unique to the recorder that generated the pending
* recording.
*/
@NonNull
static SucklessRecording from(@NonNull SucklessPendingRecording pendingRecording, long recordingId) {
Preconditions.checkNotNull(pendingRecording, "The given PendingRecording cannot be null.");
return new SucklessRecording(pendingRecording.getRecorder(),
recordingId,
pendingRecording.getOutputOptions(),
pendingRecording.isPersistent(),
/*finalizedOnCreation=*/false);
}
/**
* Creates an {@link SucklessRecording} from a {@link PendingRecording} and recording ID in a
* finalized state.
*
* <p>This can be used if there was an error setting up the active recording and it would not
* be able to be started.
*
* <p>The recording ID is expected to be unique to the recorder that generated the pending
* recording.
*/
@NonNull
static SucklessRecording createFinalizedFrom(@NonNull SucklessPendingRecording pendingRecording,
long recordingId) {
Preconditions.checkNotNull(pendingRecording, "The given PendingRecording cannot be null.");
return new SucklessRecording(pendingRecording.getRecorder(),
recordingId,
pendingRecording.getOutputOptions(),
pendingRecording.isPersistent(),
/*finalizedOnCreation=*/true);
}