Compare commits

...

83 Commits

Author SHA1 Message Date
Hardcore Sushi 5144947a4a
Media player fixes: better handling of RTL & orientation 2 weeks ago
Muhmmad14333653 6b52eed9d0
update arabic translation 2 weeks ago
Muhmmad14333653 2a257d91d0
update Arabic translation 2 weeks ago
Hardcore Sushi f837556af5
Fix explorer info bar color in dark mode 2 weeks ago
Hardcore Sushi b7ab267d16
Arabic translation 2 weeks ago
Hardcore Sushi 5ea0b8ad41
Actually fix camera icon tint bug 2 weeks ago
Hardcore Sushi ec348383c6
Fix camera icon tint bug 2 weeks ago
Hardcore Sushi c8d266150c
Fix explorer menu display 2 weeks ago
CyanWolf 4bbc9360b4
Update Spanish translation 4 weeks ago
Hardcore Sushi 8aa2be2b05
Update libgocryptfs & Small UX fix 4 weeks ago
Hardcore Sushi e2248220c4
DroidFS v1.10.0 4 weeks ago
Hardcore Sushi 7959b20b3f
Update libgocryptfs 4 weeks ago
solokot 8cebe499f0
Updated Russian translation 4 weeks ago
cyanwolfg a22b9d8fa8
Update Spanish translation 4 weeks ago
Hardcore Sushi cba1418417
Fix move operation 4 weeks ago
Hardcore Sushi b6b8bba666
Save checkbox state if opening volume fails 4 weeks ago
Hardcore Sushi e00abdf5bb
Switch to Kotlin coroutines 4 weeks ago
Hardcore Sushi 72cce1d7e1
Show a fingerprint icon when password hash is saved 1 month ago
Hardcore Sushi 55b0ac0daa
Prefill text field with current name when renaming a volume 1 month ago
Hardcore Sushi 53f28e9475
Pin passwords 1 month ago
Hardcore Sushi f1d4b07726
Show total number of selected items 1 month ago
Hardcore Sushi 339309b00d
Fix image viewer rotation handling 1 month ago
Hardcore Sushi e6a1285e0a
Fix camera output rotation 1 month ago
Hardcore Sushi ab48f9219b
CameraActivity: only bind 2 use cases at most + some other fixes 1 month ago
Hardcore Sushi c521c7f998
Open default volume on application startup 1 month ago
Hardcore Sushi 1d13dfbde3
Always show total size & Add some explorer info bar translations 1 month ago
Hardcore Sushi 36ab66fb43
Show number of files & folders in current directory 1 month ago
Hardcore Sushi 1caabc2554
Fix explorer layouts 1 month ago
Hardcore Sushi f541504e07
Refactor RecyclerView adapters 1 month ago
Hardcore Sushi 4de5b41102
Thumbnails cache & Don't do full reload on selection change 1 month ago
Hardcore Sushi 4f9aa55dfe
Explorer grid layout 1 month ago
Hardcore Sushi 91de54018d
Prompt for password if fingerprint authentification fails 2 months ago
Hardcore Sushi 2697eaf11b
Spanish translation 2 months ago
Hardcore Sushi 9e69805ade
Update ptbr translation 2 months ago
Hardcore Sushi 18d0f50094
Update to libpdfviewer 13 2 months ago
Hardcore Sushi e32e106ce3
Fix video player controls 2 months ago
Hardcore Sushi 4608a7a165
Fix MainSettingsFragment crash 2 months ago
Hardcore Sushi 985be2de59
Add HEIC to the image extension list 2 months ago
solokot f07d99efed
Updated Russian translation 2 months ago
Hardcore Sushi 4a55d826d9
Volume renaming 2 months ago
Hardcore Sushi 2ee7a5b871
Allow changing thumbnail max size 2 months ago
Hardcore Sushi 72321b8ec5
Switch to StyledPlayerView 2 months ago
Hardcore Sushi 7226cc8218
Fix UI bug on too long volume paths 2 months ago
Hardcore Sushi 55be5cd0e7
DroidFS v1.9.0 2 months ago
solokot 3c4515e4e9
Update Russian translation 2 months ago
Hardcore Sushi 29eb34e1d5
New screenshots 2 months ago
Hardcore Sushi d6f727a142
Fix error message when creating volume on external SD card 2 months ago
Hardcore Sushi 6d5fc465c7
Fix UI bug on alert dialogs 2 months ago
Hardcore Sushi ed0b5eb483
Add PDF in OpenAs dialog & libpdfviewer dialog crash fix 2 months ago
Hardcore Sushi fd0296f801
Update libpdfviewer 2 months ago
Hardcore Sushi 58391802be
More accurate directory size 2 months ago
Hardcore Sushi e01b5a3098
Volume copy 2 months ago
Hardcore Sushi bea0906f65
Display file name on video player 2 months ago
Hardcore Sushi 71a314b0a0
New home UI 2 months ago
Hardcore Sushi 842667cdee
Update ptbr translation 3 months ago
Hardcore Sushi e5bcc5cfc2
Update dependencies 3 months ago
Hardcore Sushi 32508344fe
Update PGP key 3 months ago
Hardcore Sushi ee3df7c3bf
Icon for PDF files 3 months ago
Hardcore Sushi b18232615d
PDF viewer 3 months ago
Hardcore Sushi 83efc53edc
Update PGP keyserver 4 months ago
Hardcore Sushi f546e64c34
ImageViwer: retrieve screen size with Resources.getSystem().displayMetrics 4 months ago
Hardcore Sushi 822aba9481
Better build explanation 4 months ago
Hardcore Sushi 3007bf756c
Fix video player UI bug 4 months ago
solokot 87cd88232e
Update russian translation 4 months ago
Hardcore Sushi 832fd1d34b
DoubleTapPlayerView 4 months ago
Hardcore Sushi 3ae7e4df70
Remove DialogSingleChoiceAdapter 4 months ago
Hardcore Sushi 55883172a1
Consider WebP as image 4 months ago
Hardcore Sushi b366fa1877
Fill viewport in text editor 4 months ago
Hardcore Sushi 95eed07719
Natural file name sorting 4 months ago
Hardcore Sushi f15b17c936
Fix scroll issue on OpenActivity & ChangePasswordActivity 4 months ago
Hardcore Sushi 5d6f53b37a
Change icon of mode button when switching between photo & video mode 4 months ago
Hardcore Sushi 23d017780f
Flush previous image before loading the next in ImageViewer 5 months ago
Hardcore Sushi bee2997f90
Update dependencies 5 months ago
Hardcore Sushi 80c5277936
Reset flash state when switching to back camera in video mode 5 months ago
solokot a9d4284b43
Updated Russian translation 5 months ago
Hardcore Sushi 7ca9398766
Fix size formatting 5 months ago
Hardcore Sushi bd4c935c4c
Downscale image when decoding with BitmapFactory 5 months ago
Hardcore Sushi b65ee230be
Fix flash and timer for video recording 5 months ago
Hardcore Sushi 8b4adfbe21
Update dependencies 5 months ago
Hardcore Sushi 57e93f0b49
Fix video player auto rotation 5 months ago
Hang Hang be802aa5af
Legacy fullscreen mode option & Button to toggle screen orientation in video player 5 months ago
Hardcore Sushi d1a556b8c6
Invalidate options menu when changing explorer elements 5 months ago
solokot 83525159e3
Updated Russian translation to v1.7.0 6 months ago
  1. 3
      .gitmodules
  2. 76
      README.md
  3. 33
      app/build.gradle
  4. 9
      app/ffmpeg/build.sh
  5. 2
      app/libgocryptfs
  6. 64
      app/src/main/AndroidManifest.xml
  7. 6
      app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt
  8. 364
      app/src/main/java/sushi/hardcore/droidfs/CameraActivity.kt
  9. 277
      app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt
  10. 71
      app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt
  11. 180
      app/src/main/java/sushi/hardcore/droidfs/CreateActivity.kt
  12. 259
      app/src/main/java/sushi/hardcore/droidfs/FingerprintProtector.kt
  13. 107
      app/src/main/java/sushi/hardcore/droidfs/GocryptfsVolume.kt
  14. 53
      app/src/main/java/sushi/hardcore/droidfs/LoadingTask.kt
  15. 617
      app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt
  16. 265
      app/src/main/java/sushi/hardcore/droidfs/OpenActivity.kt
  17. 13
      app/src/main/java/sushi/hardcore/droidfs/SensorOrientationListener.kt
  18. 94
      app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt
  19. 53
      app/src/main/java/sushi/hardcore/droidfs/Volume.kt
  20. 397
      app/src/main/java/sushi/hardcore/droidfs/VolumeActionActivity.kt
  21. 31
      app/src/main/java/sushi/hardcore/droidfs/VolumeDatabase.kt
  22. 31
      app/src/main/java/sushi/hardcore/droidfs/adapters/DialogSingleChoiceAdapter.kt
  23. 207
      app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt
  24. 4
      app/src/main/java/sushi/hardcore/droidfs/adapters/IconTextDialogAdapter.kt
  25. 1
      app/src/main/java/sushi/hardcore/droidfs/adapters/OpenAsDialogAdapter.kt
  26. 113
      app/src/main/java/sushi/hardcore/droidfs/adapters/SavedVolumesAdapter.kt
  27. 80
      app/src/main/java/sushi/hardcore/droidfs/adapters/SelectableAdapter.kt
  28. 111
      app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt
  29. 6
      app/src/main/java/sushi/hardcore/droidfs/add_volume/Action.kt
  30. 79
      app/src/main/java/sushi/hardcore/droidfs/add_volume/AddVolumeActivity.kt
  31. 207
      app/src/main/java/sushi/hardcore/droidfs/add_volume/CreateVolumeFragment.kt
  32. 275
      app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt
  33. 72
      app/src/main/java/sushi/hardcore/droidfs/collation/ByteString.kt
  34. 47
      app/src/main/java/sushi/hardcore/droidfs/collation/ByteStringBuilder.kt
  35. 115
      app/src/main/java/sushi/hardcore/droidfs/collation/CollatorFileNameExtensions.kt
  36. 87
      app/src/main/java/sushi/hardcore/droidfs/content_providers/ExternalProvider.kt
  37. 357
      app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt
  38. 268
      app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt
  39. 4
      app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt
  40. 6
      app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt
  41. 7
      app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerElement.kt
  42. 345
      app/src/main/java/sushi/hardcore/droidfs/file_operations/FileOperationService.kt
  43. 16
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/AudioPlayer.kt
  44. 19
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt
  45. 2
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/GocryptfsDataSource.kt
  46. 78
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt
  47. 24
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt
  48. 47
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/PdfViewer.kt
  49. 18
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/TextEditor.kt
  50. 24
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/VideoPlayer.kt
  51. 22
      app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt
  52. 20
      app/src/main/java/sushi/hardcore/droidfs/util/WidgetUtil.kt
  53. 29
      app/src/main/java/sushi/hardcore/droidfs/util/Wiper.kt
  54. 7
      app/src/main/java/sushi/hardcore/droidfs/video_recording/VideoCapture.java
  55. 198
      app/src/main/java/sushi/hardcore/droidfs/widgets/CircleClipTapView.kt
  56. 2
      app/src/main/java/sushi/hardcore/droidfs/widgets/CustomAlertDialogBuilder.kt
  57. 216
      app/src/main/java/sushi/hardcore/droidfs/widgets/DoubleTapOverlay.kt
  58. 169
      app/src/main/java/sushi/hardcore/droidfs/widgets/DoubleTapPlayerView.kt
  59. 39
      app/src/main/java/sushi/hardcore/droidfs/widgets/EditTextDialog.kt
  60. 46
      app/src/main/java/sushi/hardcore/droidfs/widgets/NonScrollableColoredBorderListView.kt
  61. 30
      app/src/main/native/gocryptfs_jni.c
  62. 5
      app/src/main/res/drawable/icon_arrow_back.xml
  63. 2
      app/src/main/res/drawable/icon_delete.xml
  64. 9
      app/src/main/res/drawable/icon_file_pdf.xml
  65. 9
      app/src/main/res/drawable/icon_hidden.xml
  66. 9
      app/src/main/res/drawable/icon_image_crossed_out.xml
  67. 6
      app/src/main/res/drawable/icon_mod.xml
  68. 16
      app/src/main/res/drawable/icon_num_pad.xml
  69. 0
      app/src/main/res/drawable/icon_photo.xml
  70. 11
      app/src/main/res/drawable/icon_triangle.xml
  71. 10
      app/src/main/res/drawable/icon_view_grid.xml
  72. 10
      app/src/main/res/drawable/icon_view_list.xml
  73. 9
      app/src/main/res/drawable/icon_volume.xml
  74. 10
      app/src/main/res/drawable/listview_border.xml
  75. BIN
      app/src/main/res/drawable/logo.png
  76. 8
      app/src/main/res/layout/action_bar.xml
  77. 12
      app/src/main/res/layout/activity_add_volume.xml
  78. 7
      app/src/main/res/layout/activity_audio_player.xml
  79. 173
      app/src/main/res/layout/activity_change_password.xml
  80. 100
      app/src/main/res/layout/activity_create.xml
  81. 2
      app/src/main/res/layout/activity_explorer.xml
  82. 2
      app/src/main/res/layout/activity_explorer_base.xml
  83. 2
      app/src/main/res/layout/activity_explorer_drop.xml
  84. 11
      app/src/main/res/layout/activity_image_viewer.xml
  85. 71
      app/src/main/res/layout/activity_main.xml
  86. 70
      app/src/main/res/layout/activity_open.xml
  87. 16
      app/src/main/res/layout/activity_settings.xml
  88. 35
      app/src/main/res/layout/activity_text_editor.xml
  89. 30
      app/src/main/res/layout/activity_text_editor_wrap.xml
  90. 61
      app/src/main/res/layout/activity_video_player.xml
  91. 14
      app/src/main/res/layout/adapter_colored_dialog_single_choice.xml
  92. 56
      app/src/main/res/layout/adapter_explorer_element.xml
  93. 33
      app/src/main/res/layout/adapter_explorer_element_grid.xml
  94. 30
      app/src/main/res/layout/adapter_explorer_element_list.xml
  95. 25
      app/src/main/res/layout/adapter_saved_volume.xml
  96. 81
      app/src/main/res/layout/adapter_volume.xml
  97. 123
      app/src/main/res/layout/audio_exo_styled_player_control_view.xml
  98. 20
      app/src/main/res/layout/checkboxes_section.xml
  99. 24
      app/src/main/res/layout/dialog_delete_volume.xml
  100. 4
      app/src/main/res/layout/dialog_edit_text.xml
  101. Some files were not shown because too many files have changed in this diff Show More

3
.gitmodules vendored

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

76
README.md

@ -8,7 +8,7 @@ It currently only works with [gocryptfs](https://github.com/rfjakob/gocryptfs) b
<img src="https://forge.chapril.org/hardcoresushi/DroidFS/raw/branch/master/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" height="500">
</p>
# Disclamer
# 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.
@ -48,10 +48,10 @@ It is strongly recommended to read the documentation of a feature before enablin
You can download DroidFS from [F-Droid](https://f-droid.org/packages/sushi.hardcore.droidfs) or from the "Releases" section in the repo.
APKs availables here are signed with my PGP key available on keyservers:
APKs available here are signed with my PGP key available on keyservers:
`gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys 007F84120107191E` \
Fingerprint: `BD5621479E7B74D36A405BE8007F84120107191E` \
`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>`
To verify APKs, save the PGP-signed message to a file and run `gpg --verify <the file>`. __Don't install any APK if the verification fails !__
@ -88,34 +88,35 @@ DroidFS use some parts of the original gocryptfs code, which is designed to run
# 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.
#### Install Requirements
- [Android Studio](https://developer.android.com/studio/)
- [Android NDK and CMake](https://developer.android.com/studio/projects/install-ndk) (OpenSSL build fails with NDK versions higher than v22. It should pass with NDK v21.4.7075529 and lower)
- [Go](https://golang.org/doc/install) (on debian: `$ sudo apt-get install golang-go`)
#### Download Sources
#### Install dependencies
On debian:
```
$ sudo apt-get install build-essential pkg-config libssl-dev
```
$ git clone https://github.com/hardcore-sushi/DroidFS.git
Install [Go](https://golang.org/doc/install):
```
Download [libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs):
$ 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
$ git submodule update --init
```
libgocryptfs needs OpenSSL:
[libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs) needs OpenSSL:
```
$ cd app/libgocryptfs
$ wget https://www.openssl.org/source/openssl-1.1.1l.tar.gz
$ wget https://www.openssl.org/source/openssl-1.1.1n.tar.gz
```
Verify OpenSSL signature:
```
$ wget https://www.openssl.org/source/openssl-1.1.1l.tar.gz.asc
$ gpg --verify openssl-1.1.1l.tar.gz.asc openssl-1.1.1l.tar.gz
$ 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.1l.tar.gz
$ tar -xvzf openssl-1.1.1n.tar.gz
```
DroidFS also need [FFmpeg](https://ffmpeg.org) to record encrypted video:
```
@ -123,35 +124,42 @@ $ cd app/ffmpeg
$ git clone --depth=1 https://git.ffmpeg.org/ffmpeg.git
```
#### Build
First, we need to install some dependencies:
#### 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:
```
$ sudo apt-get install libcrypto++-dev libssl-dev pkg-config
$ keytool -genkey -keystore <output file> -alias <key alias> -keyalg EC -validity 10000
```
And also Go dependencies:
```
$ go get golang.org/x/sys/unix golang.org/x/sys/cpu golang.org/x/crypto/hkdf github.com/jacobsa/crypto/siv github.com/rfjakob/eme
```
Then, retrieve your Android NDK installation path, usually someting like "/home/\<user\>/Android/SDK/ndk/\<NDK version\>". We can now build libgocryptfs:
#### 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.1l" ./build.sh
$ env ANDROID_NDK_HOME="<your ndk path>" OPENSSL_PATH="./openssl-1.1.1n" ./build.sh
```
And also FFmpeg:
Then FFmpeg:
```
$ cd app/ffmpeg
$ env ANDROID_NDK_HOME="<your ndk path>" ./build.sh ffmpeg
```
Then, open the DroidFS project with Android Studio. \
If a device (virtual or physical) is connected, just click on "Run". \
If you want to generate a signed APK, you can follow this [post](https://stackoverflow.com/a/28938286).
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.
# Third party code
Thanks to these open source projects that DroidFS uses:
### Modified code:
- [gocryptfs](https://github.com/rfjakob/gocryptfs) to encrypt your data
- [libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs) (forked from [gocryptfs](https://github.com/rfjakob/gocryptfs)) to encrypt your data
- [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
### Libraries:
- [Cyanea](https://github.com/jaredrummler/Cyanea) to customize UI
- [Glide](https://github.com/bumptech/glide/) to display pictures
- [ExoPlayer](https://github.com/google/ExoPlayer) to play media files

33
app/build.gradle

@ -4,7 +4,7 @@ apply plugin: 'kotlin-android'
android {
compileSdkVersion 31
buildToolsVersion "31"
ndkVersion "23.0.7599858"
ndkVersion "23.1.7779620"
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
@ -13,9 +13,10 @@ android {
defaultConfig {
applicationId "sushi.hardcore.droidfs"
minSdkVersion 21
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 29
versionCode 20
versionName "1.7.0"
versionCode 27
versionName "1.10.1"
ndk {
abiFilters "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
@ -55,29 +56,29 @@ android {
}
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.3.1"
implementation "androidx.constraintlayout:constraintlayout:2.1.1"
implementation "androidx.appcompat:appcompat:1.4.1"
implementation "androidx.constraintlayout:constraintlayout:2.1.3"
implementation "androidx.sqlite:sqlite-ktx:2.1.0"
implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.sqlite:sqlite-ktx:2.2.0"
implementation "androidx.preference:preference-ktx:1.2.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation 'com.google.android.material:material:1.4.0'
implementation 'com.google.android.material:material:1.5.0'
implementation "com.github.bumptech.glide:glide:4.12.0"
implementation "androidx.biometric:biometric:1.1.0"
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha04"
def exoplayer_version = "2.16.0"
def exoplayer_version = "2.17.1"
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
implementation "androidx.concurrent:concurrent-futures:1.1.0"
def camerax_v1 = "1.1.0-alpha10"
implementation "androidx.camera:camera-camera2:$camerax_v1"
implementation "androidx.camera:camera-lifecycle:$camerax_v1"
def camerax_v2 = "1.0.0-alpha30"
implementation "androidx.camera:camera-view:$camerax_v2"
implementation "androidx.camera:camera-extensions:$camerax_v2"
def camerax_version = "1.1.0-beta03"
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"
}

9
app/ffmpeg/build.sh

@ -1,9 +1,11 @@
#!/bin/bash
if [ -z ${ANDROID_NDK_HOME+x} ]; then
echo "Error: \$ANDROID_NDK_HOME is not defined."
echo "Error: \$ANDROID_NDK_HOME is not defined." >&2
exit 1
elif [ $# -lt 1 ]; then
echo "Usage: $0 <FFmpeg source directory> [<ABI>]"
echo "Usage: $0 <FFmpeg source directory> [<ABI>]" >&2
exit 1
else
FFMPEG_DIR=$1
compile_for_arch() {
@ -76,7 +78,8 @@ else
mkdir -p build/$1/libavformat build/$1/libavcodec build/$1/libavutil &&
cp $FFMPEG_DIR/libavformat/*.h $FFMPEG_DIR/libavformat/libavformat.so build/$1/libavformat &&
cp $FFMPEG_DIR/libavcodec/*.h $FFMPEG_DIR/libavcodec/libavcodec.so build/$1/libavcodec &&
cp $FFMPEG_DIR/libavutil/*.h $FFMPEG_DIR/libavutil/libavutil.so build/$1/libavutil
cp $FFMPEG_DIR/libavutil/*.h $FFMPEG_DIR/libavutil/libavutil.so build/$1/libavutil ||
exit 1
}
export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH

2
app/libgocryptfs

@ -1 +1 @@
Subproject commit 19731536027d0b64c19742846bf8dda6920110ba
Subproject commit 9e98192442b08362660b45f4e2e50221ba7bc65b

64
app/src/main/AndroidManifest.xml

@ -31,60 +31,30 @@
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@style/BaseTheme">
<activity
android:name=".CameraActivity"
android:screenOrientation="nosensor" />
<activity
android:name=".SettingsActivity"
android:label="@string/title_activity_settings"
android:parentActivityName=".MainActivity" />
<activity android:name=".explorers.ExplorerActivity" />
<activity android:name=".explorers.ExplorerActivityPick" />
<activity android:name=".explorers.ExplorerActivityDrop" />
<activity
android:name=".OpenActivity"
android:parentActivityName=".MainActivity"
android:screenOrientation="nosensor"
android:windowSoftInputMode="adjustPan">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:label="@string/share_menu_label">
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<activity
android:name=".CreateActivity"
android:parentActivityName=".MainActivity"
android:screenOrientation="nosensor" />
<activity
android:name=".ChangePasswordActivity"
android:parentActivityName=".MainActivity"
android:screenOrientation="nosensor"
android:windowSoftInputMode="adjustPan" />
<activity
android:name=".MainActivity"
android:screenOrientation="nosensor">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".file_viewers.ImageViewer"
android:configChanges="screenSize|orientation" /> <!-- don't reload content on configuration change -->
<activity
android:name=".file_viewers.VideoPlayer"
android:configChanges="screenSize|orientation" />
<activity
android:name=".file_viewers.AudioPlayer"
android:configChanges="screenSize|orientation" />
<activity
android:name=".file_viewers.TextEditor"
android:configChanges="screenSize|orientation" />
<activity android:name=".SettingsActivity" android:label="@string/title_activity_settings"/>
<activity android:name=".add_volume.AddVolumeActivity" android:windowSoftInputMode="adjustResize"/>
<activity android:name=".ChangePasswordActivity" android:windowSoftInputMode="adjustResize"/>
<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.VideoPlayer" android:configChanges="screenSize|orientation" />
<activity android:name=".file_viewers.PdfViewer" android:configChanges="screenSize|orientation" />
<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" />
<service android:name=".file_operations.FileOperationService" android:exported="false"/>

6
app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt

@ -8,13 +8,13 @@ import androidx.preference.PreferenceManager
open class BaseActivity: AppCompatActivity() {
protected lateinit var sharedPrefs: SharedPreferences
protected lateinit var themeValue: String
protected var shouldCheckTheme = true
lateinit var themeValue: String
private var shouldCheckTheme = true
override fun onCreate(savedInstanceState: Bundle?) {
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
if (shouldCheckTheme) {
themeValue = sharedPrefs.getString("theme", "dark_green")!!
themeValue = sharedPrefs.getString("theme", ConstValues.DEFAULT_THEME_VALUE)!!
when (themeValue) {
"black_green" -> setTheme(R.style.BlackGreen)
"dark_red" -> setTheme(R.style.DarkRed)

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

@ -10,34 +10,33 @@ import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.util.Size
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
import android.view.WindowManager
import android.view.*
import android.view.animation.Animation
import android.view.animation.LinearInterpolator
import android.view.animation.RotateAnimation
import android.widget.EditText
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.core.*
//import androidx.camera.core.VideoCapture
import androidx.camera.extensions.ExtensionMode
import androidx.camera.extensions.ExtensionsManager
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import sushi.hardcore.droidfs.adapters.DialogSingleChoiceAdapter
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
import sushi.hardcore.droidfs.databinding.ActivityCameraBinding
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.video_recording.SeekableWriter
import sushi.hardcore.droidfs.video_recording.VideoCapture
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.io.*
import sushi.hardcore.droidfs.widgets.EditTextDialog
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.Executor
@ -80,10 +79,12 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
private var camera: Camera? = null
private var resolutions: List<Size>? = null
private var currentResolutionIndex: Int = 0
private var currentResolution: Size? = null
private var captureMode = ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
private var isBackCamera = true
private var isInVideoMode = false
private var isRecording = false
private var isWaitingForTimer = false
private lateinit var binding: ActivityCameraBinding
override fun onCreate(savedInstanceState: Bundle?) {
@ -91,6 +92,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
binding = ActivityCameraBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.hide()
gocryptfsVolume = GocryptfsVolume(applicationContext, intent.getIntExtra("sessionID", -1))
outputDirectory = intent.getStringExtra("path")!!
@ -110,13 +112,12 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
ProcessCameraProvider.getInstance(this).apply {
addListener({
cameraProvider = get()
setupCamera()
}, executor)
}
ExtensionsManager.getInstance(this).apply {
addListener({
extensionsManager = get()
setupCamera()
ExtensionsManager.getInstanceAsync(this@CameraActivity, cameraProvider).apply {
addListener({
extensionsManager = get()
setupCamera()
}, executor)
}
}, executor)
}
@ -128,7 +129,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
}
CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.camera_optimization)
.setSingleChoiceItems(DialogSingleChoiceAdapter(this, arrayOf(R.string.maximize_quality, R.string.minimize_latency).map { getString(it) }), currentIndex) { dialog, which ->
.setSingleChoiceItems(arrayOf(getString(R.string.maximize_quality), getString(R.string.minimize_latency)), currentIndex) { dialog, which ->
val resId: Int
val newCaptureMode = if (which == 0) {
resId = R.drawable.icon_high_quality
@ -140,7 +141,11 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
if (newCaptureMode != captureMode) {
captureMode = newCaptureMode
binding.imageCaptureMode.setImageResource(resId)
setupCamera()
if (!isInVideoMode) {
cameraProvider.unbind(imageCapture)
refreshImageCapture()
cameraProvider.bindToLifecycle(this, cameraSelector, imageCapture)
}
}
dialog.dismiss()
}
@ -151,59 +156,63 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
resolutions?.let {
CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.choose_resolution)
.setSingleChoiceItems(DialogSingleChoiceAdapter(this, it.map { size -> size.toString() }), currentResolutionIndex) { dialog, which ->
setupCamera(resolutions!![which])
dialog.dismiss()
.setSingleChoiceItems(it.map { size -> size.toString() }.toTypedArray(), currentResolutionIndex) { dialog, which ->
currentResolution = resolutions!![which]
currentResolutionIndex = which
setupCamera()
dialog.dismiss()
}
.setNegativeButton(R.string.cancel, null)
.show()
}
}
binding.imageTimer.setOnClickListener {
val dialogEditTextView = layoutInflater.inflate(R.layout.dialog_edit_text, null)
val dialogEditText = dialogEditTextView.findViewById<EditText>(R.id.dialog_edit_text)
dialogEditText.inputType = InputType.TYPE_CLASS_NUMBER
val dialog = CustomAlertDialogBuilder(this, themeValue)
.setView(dialogEditTextView)
.setTitle(getString(R.string.enter_timer_duration))
.setPositiveButton(R.string.ok) { _, _ ->
val enteredValue = dialogEditText.text.toString()
if (enteredValue.isEmpty()){
Toast.makeText(this, getString(R.string.timer_empty_error_msg), Toast.LENGTH_SHORT).show()
} else {
timerDuration = enteredValue.toInt()
}
with (EditTextDialog(this, R.string.enter_timer_duration) {
try {
timerDuration = it.toInt()
} catch (e: NumberFormatException) {
Toast.makeText(this, R.string.invalid_number, Toast.LENGTH_SHORT).show()
}
.setNegativeButton(R.string.cancel, null)
.create()
dialogEditText.setOnEditorActionListener { _, _, _ ->
timerDuration = dialogEditText.text.toString().toInt()
dialog.dismiss()
true
}) {
binding.dialogEditText.inputType = InputType.TYPE_CLASS_NUMBER
show()
}
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
dialog.show()
}
binding.imageFlash.setOnClickListener {
binding.imageFlash.setImageResource(when (imageCapture?.flashMode) {
ImageCapture.FLASH_MODE_AUTO -> {
imageCapture?.flashMode = ImageCapture.FLASH_MODE_ON
R.drawable.icon_flash_on
}
ImageCapture.FLASH_MODE_ON -> {
imageCapture?.flashMode = ImageCapture.FLASH_MODE_OFF
R.drawable.icon_flash_off
binding.imageFlash.setImageResource(if (isInVideoMode) {
when (imageCapture?.flashMode) {
ImageCapture.FLASH_MODE_ON -> {
camera?.cameraControl?.enableTorch(false)
imageCapture?.flashMode = ImageCapture.FLASH_MODE_OFF
R.drawable.icon_flash_off
}
else -> {
camera?.cameraControl?.enableTorch(true)
imageCapture?.flashMode = ImageCapture.FLASH_MODE_ON
R.drawable.icon_flash_on
}
}
else -> {
imageCapture?.flashMode = ImageCapture.FLASH_MODE_AUTO
R.drawable.icon_flash_auto
} else {
when (imageCapture?.flashMode) {
ImageCapture.FLASH_MODE_AUTO -> {
imageCapture?.flashMode = ImageCapture.FLASH_MODE_ON
R.drawable.icon_flash_on
}
ImageCapture.FLASH_MODE_ON -> {
imageCapture?.flashMode = ImageCapture.FLASH_MODE_OFF
R.drawable.icon_flash_off
}
else -> {
imageCapture?.flashMode = ImageCapture.FLASH_MODE_AUTO
R.drawable.icon_flash_auto
}
}
})
}
binding.imageModeSwitch.setOnClickListener {
isInVideoMode = !isInVideoMode
if (isInVideoMode) {
setupCamera()
binding.imageFlash.setImageResource(if (isInVideoMode) {
binding.recordVideoButton.visibility = View.VISIBLE
binding.takePhotoButton.visibility = View.GONE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -212,10 +221,18 @@ 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)?.mutate()?.also {
it.setTint(ContextCompat.getColor(this, R.color.neutralIconTint))
})
imageCapture?.flashMode = ImageCapture.FLASH_MODE_OFF
R.drawable.icon_flash_off
} else {
binding.recordVideoButton.visibility = View.GONE
binding.takePhotoButton.visibility = View.VISIBLE
}
binding.imageModeSwitch.setImageResource(R.drawable.icon_video)
imageCapture?.flashMode = ImageCapture.FLASH_MODE_AUTO
R.drawable.icon_flash_auto
})
}
binding.imageCameraSwitch.setOnClickListener {
isBackCamera = if (isBackCamera) {
@ -223,8 +240,14 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
false
} else {
binding.imageCameraSwitch.setImageResource(R.drawable.icon_camera_front)
if (isInVideoMode) {
//reset flash state
imageCapture?.flashMode = ImageCapture.FLASH_MODE_OFF
binding.imageFlash.setImageResource(R.drawable.icon_flash_off)
}
true
}
resolutions = null
setupCamera()
}
binding.takePhotoButton.onClick = ::onClickTakePhoto
@ -287,85 +310,72 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
private fun adaptPreviewSize(resolution: Size) {
val screenWidth = resources.displayMetrics.widthPixels
binding.cameraPreview.layoutParams = if (screenWidth < resolution.width) {
RelativeLayout.LayoutParams(
screenWidth,
(resolution.height * (screenWidth.toFloat() / resolution.width)).toInt()
)
} else {
RelativeLayout.LayoutParams(resolution.width, resolution.height)
val screenHeight = resources.displayMetrics.heightPixels
var height = (resolution.height * (screenWidth.toFloat() / resolution.width)).toInt()
var width = screenWidth
if (height > screenHeight) {
width = (width * (screenHeight.toFloat() / height)).toInt()
height = screenHeight
}
binding.cameraPreview.layoutParams = RelativeLayout.LayoutParams(width, height).apply {
addRule(RelativeLayout.CENTER_IN_PARENT)
}
(binding.cameraPreview.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.CENTER_IN_PARENT)
}
@SuppressLint("RestrictedApi")
private fun setupCamera(resolution: Size? = null){
if (permissionsGranted && ::extensionsManager.isInitialized && ::cameraProvider.isInitialized) {
imageCapture = ImageCapture.Builder()
.setCaptureMode(captureMode)
.setFlashMode(imageCapture?.flashMode ?: ImageCapture.FLASH_MODE_AUTO)
.apply {
resolution?.let {
setTargetResolution(it)
}
}
.build()
videoCapture = VideoCapture.Builder().apply {
resolution?.let {
private fun refreshImageCapture() {
imageCapture = ImageCapture.Builder()
.setCaptureMode(captureMode)
.setFlashMode(imageCapture?.flashMode ?: ImageCapture.FLASH_MODE_AUTO)
.apply {
currentResolution?.let {
setTargetResolution(it)
}
}.build()
}
.build()
}
private fun refreshVideoCapture() {
videoCapture = VideoCapture.Builder().apply {
currentResolution?.let {
setTargetResolution(it)
}
}.build()
}
@SuppressLint("RestrictedApi")
private fun setupCamera() {
if (permissionsGranted && ::extensionsManager.isInitialized && ::cameraProvider.isInitialized) {
cameraSelector = if (isBackCamera){ CameraSelector.DEFAULT_BACK_CAMERA } else { CameraSelector.DEFAULT_FRONT_CAMERA }
if (extensionsManager.isExtensionAvailable(cameraProvider, cameraSelector, ExtensionMode.HDR)) {
cameraSelector = extensionsManager.getExtensionEnabledCameraSelector(cameraProvider, cameraSelector, ExtensionMode.HDR)
if (extensionsManager.isExtensionAvailable(cameraSelector, ExtensionMode.AUTO)) {
cameraSelector = extensionsManager.getExtensionEnabledCameraSelector(cameraSelector, ExtensionMode.AUTO)
}
cameraProvider.unbindAll()
camera = cameraProvider.bindToLifecycle(this, cameraSelector, cameraPreview, imageCapture, videoCapture)
adaptPreviewSize(resolution ?: imageCapture!!.attachedSurfaceResolution!!.swap())
val currentUseCase = (if (isInVideoMode) {
refreshVideoCapture()
camera = cameraProvider.bindToLifecycle(this, cameraSelector, cameraPreview, videoCapture)
videoCapture
} else {
refreshImageCapture()
camera = cameraProvider.bindToLifecycle(this, cameraSelector, cameraPreview, imageCapture)
imageCapture
})!!
adaptPreviewSize(currentResolution ?: currentUseCase.attachedSurfaceResolution!!.swap())
if (resolutions == null) {
val info = Camera2CameraInfo.from(camera!!.cameraInfo)
val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
val characteristics = cameraManager.getCameraCharacteristics(info.cameraId)
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)?.let { streamConfigurationMap ->
resolutions = streamConfigurationMap.getOutputSizes(imageCapture!!.imageFormat).map { it.swap() }
resolutions = streamConfigurationMap.getOutputSizes(currentUseCase.imageFormat).map { it.swap() }
}
}
}
}
private fun takePhoto(outputPath: String) {
val imageCapture = imageCapture ?: return
val outputBuff = ByteArrayOutputStream()
val outputOptions = ImageCapture.OutputFileOptions.Builder(outputBuff).build()
imageCapture.takePicture(outputOptions, executor, object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
binding.takePhotoButton.onPhotoTaken()
if (gocryptfsVolume.importFile(ByteArrayInputStream(outputBuff.toByteArray()), outputPath)){
Toast.makeText(applicationContext, getString(R.string.picture_save_success, outputPath), Toast.LENGTH_SHORT).show()
} else {
CustomAlertDialogBuilder(this@CameraActivity, themeValue)
.setTitle(R.string.error)
.setMessage(R.string.picture_save_failed)
.setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ ->
isFinishingIntentionally = true
finish()
}
.show()
}
}
override fun onError(exception: ImageCaptureException) {
binding.takePhotoButton.onPhotoTaken()
Toast.makeText(applicationContext, exception.message, Toast.LENGTH_SHORT).show()
}
})
}
private fun getOutputPath(isVideo: Boolean): String {
val baseName = if (isVideo) {"VID"} else {"IMG"}+'_'+dateFormat.format(Date())+'_'
var fileName: String
@ -375,57 +385,94 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
return PathUtils.pathJoin(outputDirectory, fileName)
}
private fun onClickTakePhoto() {
val path = getOutputPath(false)
private fun startTimerThen(action: () -> Unit) {
if (timerDuration > 0){
binding.textTimer.visibility = View.VISIBLE
Thread{
isWaitingForTimer = true
lifecycleScope.launch {
for (i in timerDuration downTo 1){
runOnUiThread { binding.textTimer.text = i.toString() }
Thread.sleep(1000)
binding.textTimer.text = i.toString()
delay(1000)
}
runOnUiThread {
takePhoto(path)
if (!isFinishing) {
action()
binding.textTimer.visibility = View.GONE
}
}.start()
isWaitingForTimer = false
}
} else {
takePhoto(path)
action()
}
}
private fun onClickTakePhoto() {
if (!isWaitingForTimer) {
val outputPath = getOutputPath(false)
startTimerThen {
imageCapture?.let { imageCapture ->
val outputBuff = ByteArrayOutputStream()
val outputOptions = ImageCapture.OutputFileOptions.Builder(outputBuff).build()
imageCapture.takePicture(outputOptions, executor, object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
binding.takePhotoButton.onPhotoTaken()
if (gocryptfsVolume.importFile(ByteArrayInputStream(outputBuff.toByteArray()), outputPath)) {
Toast.makeText(applicationContext, getString(R.string.picture_save_success, outputPath), Toast.LENGTH_SHORT).show()
} else {
CustomAlertDialogBuilder(this@CameraActivity, themeValue)
.setTitle(R.string.error)
.setMessage(R.string.picture_save_failed)
.setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ ->
isFinishingIntentionally = true
finish()
}
.show()
}
}
override fun onError(exception: ImageCaptureException) {
binding.takePhotoButton.onPhotoTaken()
Toast.makeText(applicationContext, exception.message, Toast.LENGTH_SHORT).show()
}
})
}
}
}
}
@SuppressLint("MissingPermission")
private fun onClickRecordVideo() {
isRecording = if (isRecording) {
if (isRecording) {
videoCapture?.stopRecording()
false
} else {
isRecording = false
} else if (!isWaitingForTimer) {
val path = getOutputPath(true)
val handleId = gocryptfsVolume.openWriteMode(path)
videoCapture?.startRecording(VideoCapture.OutputFileOptions(object : SeekableWriter {
var offset = 0L
override fun write(byteArray: ByteArray) {
offset += gocryptfsVolume.writeFile(handleId, offset, byteArray, byteArray.size)
}
override fun seek(offset: Long) {
this.offset = offset
}
override fun close() {
gocryptfsVolume.closeFile(handleId)
}
}), executor, object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved() {
Toast.makeText(applicationContext, getString(R.string.video_save_success, path), Toast.LENGTH_SHORT).show()
binding.recordVideoButton.setImageResource(R.drawable.record_video_button)
}
override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT).show()
cause?.printStackTrace()
binding.recordVideoButton.setImageResource(R.drawable.record_video_button)
}
})
binding.recordVideoButton.setImageResource(R.drawable.stop_recording_video_button)
true
startTimerThen {
val handleId = gocryptfsVolume.openWriteMode(path)
videoCapture?.startRecording(VideoCapture.OutputFileOptions(object : SeekableWriter {
var offset = 0L
override fun write(byteArray: ByteArray) {
offset += gocryptfsVolume.writeFile(handleId, offset, byteArray, byteArray.size)
}
override fun seek(offset: Long) {
this.offset = offset
}
override fun close() {
gocryptfsVolume.closeFile(handleId)
}
}), executor, object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved() {
Toast.makeText(applicationContext, getString(R.string.video_save_success, path), Toast.LENGTH_SHORT).show()
binding.recordVideoButton.setImageResource(R.drawable.record_video_button)
}
override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT).show()
cause?.printStackTrace()
binding.recordVideoButton.setImageResource(R.drawable.record_video_button)
}
})
binding.recordVideoButton.setImageResource(R.drawable.stop_recording_video_button)
isRecording = true
}
}
}
@ -463,21 +510,24 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
}
override fun onOrientationChange(newOrientation: Int) {
val reversedOrientation = when (newOrientation){
90 -> 270
270 -> 90
else -> newOrientation
}.toFloat()
val realOrientation = when (newOrientation) {
Surface.ROTATION_0 -> 0f
Surface.ROTATION_90 -> 90f
Surface.ROTATION_180 -> 180f
else -> 270f
}
val rotateAnimation = RotateAnimation(previousOrientation, when {
reversedOrientation - previousOrientation > 180 -> reversedOrientation - 360
reversedOrientation - previousOrientation < -180 -> reversedOrientation + 360
else -> reversedOrientation
realOrientation - previousOrientation > 180 -> realOrientation - 360
realOrientation - previousOrientation < -180 -> realOrientation + 360
else -> realOrientation
}, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f)
rotateAnimation.duration = 300
rotateAnimation.interpolator = LinearInterpolator()
rotateAnimation.fillAfter = true
orientedIcons.map { it.startAnimation(rotateAnimation) }
previousOrientation = reversedOrientation
previousOrientation = realOrientation
imageCapture?.targetRotation = newOrientation
videoCapture?.setTargetRotation(newOrientation)
}
}

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

@ -1,195 +1,168 @@
package sushi.hardcore.droidfs
import android.annotation.SuppressLint
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.AdapterView.OnItemClickListener
import android.text.InputType
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import sushi.hardcore.droidfs.adapters.SavedVolumesAdapter
import androidx.lifecycle.lifecycleScope
import sushi.hardcore.droidfs.databinding.ActivityChangePasswordBinding
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.util.WidgetUtil
import sushi.hardcore.droidfs.util.Wiper
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.io.File
import java.util.*
class ChangePasswordActivity : VolumeActionActivity() {
private lateinit var savedVolumesAdapter: SavedVolumesAdapter
class ChangePasswordActivity: BaseActivity() {
private lateinit var binding: ActivityChangePasswordBinding
private lateinit var volume: Volume
private lateinit var volumeDatabase: VolumeDatabase
private var fingerprintProtector: FingerprintProtector? = null
private var usfFingerprint: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
volume = intent.getParcelableExtra("volume")!!
binding = ActivityChangePasswordBinding.inflate(layoutInflater)
setContentView(binding.root)
setupLayout()
setupFingerprintStuff()
savedVolumesAdapter = SavedVolumesAdapter(this, themeValue, volumeDatabase)
if (savedVolumesAdapter.count > 0){
binding.savedPathListview.adapter = savedVolumesAdapter
binding.savedPathListview.onItemClickListener = OnItemClickListener { _, _, position, _ ->
val volume = savedVolumesAdapter.getItem(position)
currentVolumeName = volume.name
if (volume.isHidden){
switchHiddenVolume.isChecked = true
editVolumeName.setText(currentVolumeName)
} else {
switchHiddenVolume.isChecked = false
editVolumePath.setText(currentVolumeName)
}
onClickSwitchHiddenVolume()
title = getString(R.string.change_password)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
binding.textVolumeName.text = volume.name
volumeDatabase = VolumeDatabase(this)
usfFingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fingerprintProtector = FingerprintProtector.new(this, themeValue, volumeDatabase)
if (fingerprintProtector != null && volume.encryptedHash != null) {
binding.textCurrentPasswordLabel.visibility = View.GONE
binding.editCurrentPassword.visibility = View.GONE
}
} else {
WidgetUtil.hideWithPadding(binding.savedPathListview)