Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
d2327052dc | |||
a92695f8d5 | |||
a8fb5960f2 | |||
da7cb57b56 | |||
43a3f72935 | |||
53f4e3507b | |||
b2ab69c8f2 | |||
b5a8b02c5c | |||
dbd04848bd |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -4,3 +4,6 @@
|
|||||||
[submodule "libpdfviewer"]
|
[submodule "libpdfviewer"]
|
||||||
path = libpdfviewer
|
path = libpdfviewer
|
||||||
url = https://forge.chapril.org/hardcoresushi/libpdfviewer.git
|
url = https://forge.chapril.org/hardcoresushi/libpdfviewer.git
|
||||||
|
[submodule "app/libcryfs"]
|
||||||
|
path = app/libcryfs
|
||||||
|
url = https://forge.chapril.org/hardcoresushi/libcryfs.git
|
||||||
|
112
BUILD.md
Normal file
112
BUILD.md
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
# Introduction
|
||||||
|
DroidFS relies on modified versions of the original encrypted filesystems programs to open volumes. [CryFS](https://github.com/cryfs/cryfs) is written in C++ while [gocryptfs](https://github.com/rfjakob/gocryptfs) is written in Go. Thus, building DroidFS requires the compilation of native code. However, for the sake of simplicity, the application has been designed in a modular way: you can build a version of DroidFS that supports both Gocryptfs and CryFS, or only one of the two.
|
||||||
|
|
||||||
|
Moreover, DroidFS aims to be accessible to as many people as possible. If you encounter any problems or need help with the build, feel free to open an issue, a discussion, or contact me by [email](mailto:hardcore.sushi@disroot.org) or on [Matrix](https://matrix.org): @hardcoresushi:matrix.underworld.fr
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
Install required packages:
|
||||||
|
```
|
||||||
|
$ sudo apt-get install openjdk-11-jdk-headless build-essential pkg-config git gnupg2 wget apksigner
|
||||||
|
```
|
||||||
|
You also need to manually install the [Android SDK](https://developer.android.com/studio/index.html#command-tools) and the [Android Native Development Kit (NDK)](https://developer.android.com/ndk/downloads) (r23 versions are recommended).
|
||||||
|
|
||||||
|
If you want a support for Gocryptfs volumes, you must install [Go](https://golang.org/doc/install) and libssl:
|
||||||
|
```
|
||||||
|
$ sudo apt-get install golang-go libssl-dev
|
||||||
|
```
|
||||||
|
For CryFS support, you need [Python](https://www.python.org):
|
||||||
|
```
|
||||||
|
$ sudo apt-get install python3
|
||||||
|
```
|
||||||
|
The code should be authenticated before being built. To verify the signatures, you will need my PGP key:
|
||||||
|
```
|
||||||
|
$ gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys AFE384344A45E13A
|
||||||
|
```
|
||||||
|
Fingerprint: `B64E FE86 CEE1 D054 F082 1711 AFE3 8434 4A45 E13A` \
|
||||||
|
Email: `Hardcore Sushi <hardcore.sushi@disroot.org>`
|
||||||
|
|
||||||
|
# Download sources
|
||||||
|
Download DroidFS source code:
|
||||||
|
```
|
||||||
|
$ git clone --depth=1 https://github.com/hardcore-sushi/DroidFS.git
|
||||||
|
```
|
||||||
|
Verify sources:
|
||||||
|
```
|
||||||
|
$ cd DroidFS
|
||||||
|
$ git verify-commit HEAD
|
||||||
|
```
|
||||||
|
__Don't continue if the verification fails!__
|
||||||
|
|
||||||
|
Initialize submodules:
|
||||||
|
```
|
||||||
|
$ git submodule update --depth=1 --init
|
||||||
|
```
|
||||||
|
[FFmpeg](https://ffmpeg.org) is needed to record encrypted video:
|
||||||
|
```
|
||||||
|
$ cd app/ffmpeg
|
||||||
|
$ git clone --depth=1 https://git.ffmpeg.org/ffmpeg.git
|
||||||
|
```
|
||||||
|
If you want Gocryptfs support, you need to download OpenSSL:
|
||||||
|
```
|
||||||
|
$ cd ../libgocryptfs
|
||||||
|
$ wget https://www.openssl.org/source/openssl-1.1.1p.tar.gz
|
||||||
|
```
|
||||||
|
Verify OpenSSL signature:
|
||||||
|
```
|
||||||
|
$ wget https://www.openssl.org/source/openssl-1.1.1p.tar.gz.asc
|
||||||
|
$ gpg --verify openssl-1.1.1p.tar.gz.asc openssl-1.1.1p.tar.gz
|
||||||
|
```
|
||||||
|
Continue **ONLY** if the signature is **VALID**.
|
||||||
|
```
|
||||||
|
$ tar -xzf openssl-1.1.1p.tar.gz
|
||||||
|
```
|
||||||
|
If you want CryFS support, initialize libcryfs:
|
||||||
|
```
|
||||||
|
$ cd app/libcryfs
|
||||||
|
$ git submodule update --depth=1 --init
|
||||||
|
```
|
||||||
|
|
||||||
|
# Build
|
||||||
|
Retrieve your Android NDK installation path, usually something like `/home/\<user\>/Android/SDK/ndk/\<NDK version\>`. Then, make it available in your shell:
|
||||||
|
```
|
||||||
|
$ export ANDROID_NDK_HOME="<your ndk path>"
|
||||||
|
```
|
||||||
|
Start by compiling FFmpeg:
|
||||||
|
```
|
||||||
|
$ cd app/ffmpeg
|
||||||
|
$ ./build.sh ffmpeg
|
||||||
|
```
|
||||||
|
## libgocryptfs
|
||||||
|
This step is only required if you want Gocryptfs support.
|
||||||
|
```
|
||||||
|
$ cd app/libgocryptfs
|
||||||
|
$ OPENSSL_PATH="./openssl-1.1.1p" ./build.sh
|
||||||
|
```
|
||||||
|
## Compile APKs
|
||||||
|
Gradle build libgocryptfs and libcryfs by default.
|
||||||
|
|
||||||
|
To build DroidFS without Gocryptfs support, run:
|
||||||
|
```
|
||||||
|
$ ./gradlew assembleRelease -PdisableGocryptfs=true
|
||||||
|
```
|
||||||
|
To build DroidFS without CryFS support, run:
|
||||||
|
```
|
||||||
|
$ ./gradlew assembleRelease -PdisableCryFS=true
|
||||||
|
```
|
||||||
|
If you want to build DroidFS with support for both Gocryptfs and CryFS, just run:
|
||||||
|
```
|
||||||
|
$ ./gradlew assembleRelease
|
||||||
|
```
|
||||||
|
|
||||||
|
# Sign APKs
|
||||||
|
If the build succeeds, you will find the unsigned APKs in `app/build/outputs/apk/release/`. These APKs need to be signed in order to be installed on an Android device.
|
||||||
|
|
||||||
|
If you don't already have a keystore, you can create a new one by running:
|
||||||
|
```
|
||||||
|
$ keytool -genkey -keystore <output file> -alias <key alias> -keyalg EC -validity 10000
|
||||||
|
```
|
||||||
|
Then, sign the APK with:
|
||||||
|
```
|
||||||
|
$ apksigner sign --out droidfs.apk -v --ks <keystore> app/build/outputs/apk/release/<unsigned apk file>
|
||||||
|
```
|
||||||
|
Now you can install `droidfs.apk` on your device.
|
75
README.md
75
README.md
@ -1,6 +1,6 @@
|
|||||||
# DroidFS
|
# 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.
|
DroidFS is an alternative way to use encrypted overlay filesystems on Android that uses its own internal file explorer instead of mounting virtual volumes.
|
||||||
It currently only works with [gocryptfs](https://github.com/rfjakob/gocryptfs) but support for [CryFS](https://github.com/cryfs/cryfs) could be added in the future.
|
It currently supports [gocryptfs](https://github.com/rfjakob/gocryptfs) and [CryFS](https://github.com/cryfs/cryfs) (alpha).
|
||||||
|
|
||||||
<p align="center">
|
<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/1.png" height="500">
|
||||||
@ -83,79 +83,20 @@ DroidFS need some permissions to work properly. Here is why:
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
# Limitations
|
# Limitations
|
||||||
DroidFS use some parts of the original gocryptfs code, which is designed to run on Linux x86 systems: it accesses the underlying file system with file paths and syscalls. However in Android, you can't access other apps files with file paths. Instead, you must use the [ContentProvider](https://developer.android.com/guide/topics/providers/content-providers) API. And obviously, the original gocryptfs code doesn't work with this API. This is why DroidFS can't open volumes provided by other applications, such as cloud storage clients. You can only use DroidFS with volumes located on shared storage or in the app's internal storage (hidden volumes). External storage such as SD cards are only supported in read-only access for now.
|
DroidFS works as a wrapper around modified versions of the original encrypted container implementations ([libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs) and [libcryfs](https://forge.chapril.org/hardcoresushi/libcryfs)). These programs were designed to run on standard x86 Linux systems: they access the underlying file system with file paths and syscalls. However, on Android, you can't access files from other applications using file paths. Instead, one has to use the [ContentProvider](https://developer.android.com/guide/topics/providers/content-providers) API. Obviously, neither Gocryptfs nor CryFS support this API. As a result, DroidFS cannot open volumes provided by other applications (such as cloud storage clients), nor can it allow other applications to access encrypted volumes once opened.
|
||||||
|
|
||||||
# Build
|
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.
|
||||||
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 dependencies
|
# Building from source
|
||||||
On debian:
|
You can follow the instructions in [BUILD.md](BUILD.md) to build DroidFS from source.
|
||||||
```
|
|
||||||
$ sudo apt-get install build-essential pkg-config libssl-dev
|
|
||||||
```
|
|
||||||
Install [Go](https://golang.org/doc/install):
|
|
||||||
```
|
|
||||||
$ sudo apt-get install golang-go
|
|
||||||
```
|
|
||||||
You also need to install the Android SDK build tools and the [Android NDK](https://developer.android.com/studio/projects/install-ndk).
|
|
||||||
|
|
||||||
#### Download Sources
|
|
||||||
```
|
|
||||||
$ git clone --recurse-submodules https://github.com/hardcore-sushi/DroidFS.git
|
|
||||||
$ cd DroidFS
|
|
||||||
```
|
|
||||||
[libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs) needs OpenSSL:
|
|
||||||
```
|
|
||||||
$ cd app/libgocryptfs
|
|
||||||
$ wget https://www.openssl.org/source/openssl-1.1.1n.tar.gz
|
|
||||||
```
|
|
||||||
Verify OpenSSL signature:
|
|
||||||
```
|
|
||||||
$ wget https://www.openssl.org/source/openssl-1.1.1n.tar.gz.asc
|
|
||||||
$ gpg --verify openssl-1.1.1n.tar.gz.asc openssl-1.1.1n.tar.gz
|
|
||||||
```
|
|
||||||
Continue **ONLY** if the signature is **VALID**.
|
|
||||||
```
|
|
||||||
$ tar -xvzf openssl-1.1.1n.tar.gz
|
|
||||||
```
|
|
||||||
DroidFS also need [FFmpeg](https://ffmpeg.org) to record encrypted video:
|
|
||||||
```
|
|
||||||
$ cd app/ffmpeg
|
|
||||||
$ git clone --depth=1 https://git.ffmpeg.org/ffmpeg.git
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Generate a keystore
|
|
||||||
APKs must be signed to be installed on an Android device. If you don't already have a keystore, you can generate one by running:
|
|
||||||
```
|
|
||||||
$ keytool -genkey -keystore <output file> -alias <key alias> -keyalg EC -validity 10000
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Build
|
|
||||||
Retrieve your Android NDK installation path, usually something like "/home/\<user\>/Android/SDK/ndk/\<NDK version\>". Now you can build libgocryptfs:
|
|
||||||
```
|
|
||||||
$ cd DroidFS/app/libgocryptfs
|
|
||||||
$ env ANDROID_NDK_HOME="<your ndk path>" OPENSSL_PATH="./openssl-1.1.1n" ./build.sh
|
|
||||||
```
|
|
||||||
Then FFmpeg:
|
|
||||||
```
|
|
||||||
$ cd app/ffmpeg
|
|
||||||
$ env ANDROID_NDK_HOME="<your ndk path>" ./build.sh ffmpeg
|
|
||||||
```
|
|
||||||
Finally, compile the app:
|
|
||||||
```
|
|
||||||
$ ./gradlew assembleRelease
|
|
||||||
```
|
|
||||||
If the build succeeds, you will find the unsigned APKs in `app/build/outputs/apk/release/`. You need to sign them in order to install the app:
|
|
||||||
```
|
|
||||||
$ apksigner sign --out droidfs.apk -v --ks <keystore> app/build/outputs/apk/release/<unsigned apk file>
|
|
||||||
```
|
|
||||||
Now you can install `droidfs.apk` on your device.
|
|
||||||
|
|
||||||
# Third party code
|
# Third party code
|
||||||
Thanks to these open source projects that DroidFS uses:
|
Thanks to these open source projects that DroidFS uses:
|
||||||
|
|
||||||
### Modified code:
|
### Modified code:
|
||||||
- [libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs) (forked from [gocryptfs](https://github.com/rfjakob/gocryptfs)) to encrypt your data
|
- Encrypted filesystems (to protect your data):
|
||||||
|
- [libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs) (forked from [gocryptfs](https://github.com/rfjakob/gocryptfs))
|
||||||
|
- [libcryfs](https://forge.chapril.org/hardcoresushi/libcryfs) (forked from [cryfs](https://github.com/cryfs/cryfs))
|
||||||
- [libpdfviewer](https://forge.chapril.org/hardcoresushi/libpdfviewer) (forked from [PdfViewer](https://github.com/GrapheneOS/PdfViewer)) to open PDF files
|
- [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
|
- [DoubleTapPlayerView](https://github.com/vkay94/DoubleTapPlayerView) to add double-click controls to the video player
|
||||||
### Borrowed code:
|
### Borrowed code:
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
add_library(
|
project(DroidFS)
|
||||||
gocryptfs
|
|
||||||
SHARED
|
|
||||||
IMPORTED
|
|
||||||
)
|
|
||||||
|
|
||||||
set_target_properties(
|
option(GOCRYPTFS "build libgocryptfs" ON)
|
||||||
|
option(CRYFS "build libcryfs" ON)
|
||||||
|
|
||||||
|
if (GOCRYPTFS)
|
||||||
|
add_library(gocryptfs SHARED IMPORTED)
|
||||||
|
set_target_properties(
|
||||||
gocryptfs
|
gocryptfs
|
||||||
PROPERTIES IMPORTED_LOCATION
|
PROPERTIES IMPORTED_LOCATION
|
||||||
${PROJECT_SOURCE_DIR}/libgocryptfs/build/${ANDROID_ABI}/libgocryptfs.so
|
${PROJECT_SOURCE_DIR}/libgocryptfs/build/${ANDROID_ABI}/libgocryptfs.so
|
||||||
)
|
)
|
||||||
|
add_library(gocryptfs_jni SHARED src/main/native/gocryptfs_jni.c)
|
||||||
|
target_include_directories(gocryptfs_jni PRIVATE ${PROJECT_SOURCE_DIR}/libgocryptfs/build/${ANDROID_ABI})
|
||||||
|
target_link_libraries(gocryptfs_jni gocryptfs)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_library(
|
if (CRYFS)
|
||||||
gocryptfs_jni
|
add_subdirectory(${PROJECT_SOURCE_DIR}/libcryfs)
|
||||||
SHARED
|
add_library(cryfs_jni SHARED src/main/native/libcryfs.c)
|
||||||
src/main/native/gocryptfs_jni.c
|
target_link_libraries(cryfs_jni libcryfs-jni)
|
||||||
)
|
endif()
|
||||||
|
|
||||||
target_link_libraries(
|
|
||||||
gocryptfs_jni
|
|
||||||
gocryptfs
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(
|
add_library(
|
||||||
avformat
|
avformat
|
||||||
@ -65,14 +65,11 @@ add_library(
|
|||||||
src/main/native/libmux.c
|
src/main/native/libmux.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_include_directories(mux PRIVATE ${PROJECT_SOURCE_DIR}/ffmpeg/build/${ANDROID_ABI})
|
||||||
|
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
mux
|
mux
|
||||||
avformat
|
avformat
|
||||||
avcodec
|
avcodec
|
||||||
avutil
|
avutil
|
||||||
)
|
)
|
||||||
|
|
||||||
include_directories(
|
|
||||||
${PROJECT_SOURCE_DIR}/libgocryptfs/build/${ANDROID_ABI}
|
|
||||||
${PROJECT_SOURCE_DIR}/ffmpeg/build/${ANDROID_ABI}
|
|
||||||
)
|
|
@ -1,6 +1,18 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
if (hasProperty("disableCryFS")) {
|
||||||
|
ext.disableCryFS = getProperty("disableCryFS")
|
||||||
|
} else {
|
||||||
|
ext.disableCryFS = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasProperty("disableGocryptfs")) {
|
||||||
|
ext.disableGocryptfs = getProperty("disableGocryptfs")
|
||||||
|
} else {
|
||||||
|
ext.disableGocryptfs = false
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
compileSdkVersion 31
|
||||||
buildToolsVersion "31"
|
buildToolsVersion "31"
|
||||||
@ -21,6 +33,15 @@ android {
|
|||||||
ndk {
|
ndk {
|
||||||
abiFilters "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
|
abiFilters "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
externalNativeBuild.cmake {
|
||||||
|
if (project.ext.disableGocryptfs) {
|
||||||
|
arguments "-DGOCRYPTFS=OFF"
|
||||||
|
}
|
||||||
|
if (project.ext.disableCryFS) {
|
||||||
|
arguments "-DCRYFS=OFF"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file("fdroid").exists()) {
|
if (!file("fdroid").exists()) {
|
||||||
@ -34,6 +55,8 @@ android {
|
|||||||
|
|
||||||
applicationVariants.all { variant ->
|
applicationVariants.all { variant ->
|
||||||
variant.resValue "string", "versionName", variant.versionName
|
variant.resValue "string", "versionName", variant.versionName
|
||||||
|
buildConfigField "boolean", "CRYFS_DISABLED", "${project.ext.disableCryFS}"
|
||||||
|
buildConfigField "boolean", "GOCRYPTFS_DISABLED", "${project.ext.disableGocryptfs}"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
|
1
app/libcryfs
Submodule
1
app/libcryfs
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 335815d25ac90f3d4964f88b542746ab5b270397
|
@ -1 +1 @@
|
|||||||
Subproject commit 9e98192442b08362660b45f4e2e50221ba7bc65b
|
Subproject commit 7afeb9f3a4e0316365e843d3f4afd50201fbb253
|
@ -30,6 +30,7 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
||||||
import sushi.hardcore.droidfs.databinding.ActivityCameraBinding
|
import sushi.hardcore.droidfs.databinding.ActivityCameraBinding
|
||||||
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.video_recording.SeekableWriter
|
import sushi.hardcore.droidfs.video_recording.SeekableWriter
|
||||||
import sushi.hardcore.droidfs.video_recording.VideoCapture
|
import sushi.hardcore.droidfs.video_recording.VideoCapture
|
||||||
@ -64,7 +65,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|||||||
private lateinit var sensorOrientationListener: SensorOrientationListener
|
private lateinit var sensorOrientationListener: SensorOrientationListener
|
||||||
private var previousOrientation: Float = 0f
|
private var previousOrientation: Float = 0f
|
||||||
private lateinit var orientedIcons: List<ImageView>
|
private lateinit var orientedIcons: List<ImageView>
|
||||||
private lateinit var gocryptfsVolume: GocryptfsVolume
|
private lateinit var encryptedVolume: EncryptedVolume
|
||||||
private lateinit var outputDirectory: String
|
private lateinit var outputDirectory: String
|
||||||
private var isFinishingIntentionally = false
|
private var isFinishingIntentionally = false
|
||||||
private var isAskingPermissions = false
|
private var isAskingPermissions = false
|
||||||
@ -93,7 +94,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|||||||
binding = ActivityCameraBinding.inflate(layoutInflater)
|
binding = ActivityCameraBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
supportActionBar?.hide()
|
supportActionBar?.hide()
|
||||||
gocryptfsVolume = GocryptfsVolume(applicationContext, intent.getIntExtra("sessionID", -1))
|
encryptedVolume = intent.getParcelableExtra("volume")!!
|
||||||
outputDirectory = intent.getStringExtra("path")!!
|
outputDirectory = intent.getStringExtra("path")!!
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
@ -378,11 +379,12 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|||||||
|
|
||||||
private fun getOutputPath(isVideo: Boolean): String {
|
private fun getOutputPath(isVideo: Boolean): String {
|
||||||
val baseName = if (isVideo) {"VID"} else {"IMG"}+'_'+dateFormat.format(Date())+'_'
|
val baseName = if (isVideo) {"VID"} else {"IMG"}+'_'+dateFormat.format(Date())+'_'
|
||||||
var fileName: String
|
var outputPath: String
|
||||||
do {
|
do {
|
||||||
fileName = baseName+(random.nextInt(fileNameRandomMax-fileNameRandomMin)+fileNameRandomMin)+'.'+ if (isVideo) {"mp4"} else {"jpg"}
|
val fileName = baseName+(random.nextInt(fileNameRandomMax-fileNameRandomMin)+fileNameRandomMin)+'.'+ if (isVideo) {"mp4"} else {"jpg"}
|
||||||
} while (gocryptfsVolume.pathExists(fileName))
|
outputPath = PathUtils.pathJoin(outputDirectory, fileName)
|
||||||
return PathUtils.pathJoin(outputDirectory, fileName)
|
} while (encryptedVolume.pathExists(outputPath))
|
||||||
|
return outputPath
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startTimerThen(action: () -> Unit) {
|
private fun startTimerThen(action: () -> Unit) {
|
||||||
@ -415,7 +417,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|||||||
imageCapture.takePicture(outputOptions, executor, object : ImageCapture.OnImageSavedCallback {
|
imageCapture.takePicture(outputOptions, executor, object : ImageCapture.OnImageSavedCallback {
|
||||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||||
binding.takePhotoButton.onPhotoTaken()
|
binding.takePhotoButton.onPhotoTaken()
|
||||||
if (gocryptfsVolume.importFile(ByteArrayInputStream(outputBuff.toByteArray()), outputPath)) {
|
if (encryptedVolume.importFile(ByteArrayInputStream(outputBuff.toByteArray()), outputPath)) {
|
||||||
Toast.makeText(applicationContext, getString(R.string.picture_save_success, outputPath), Toast.LENGTH_SHORT).show()
|
Toast.makeText(applicationContext, getString(R.string.picture_save_success, outputPath), Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
CustomAlertDialogBuilder(this@CameraActivity, themeValue)
|
CustomAlertDialogBuilder(this@CameraActivity, themeValue)
|
||||||
@ -447,17 +449,17 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|||||||
} else if (!isWaitingForTimer) {
|
} else if (!isWaitingForTimer) {
|
||||||
val path = getOutputPath(true)
|
val path = getOutputPath(true)
|
||||||
startTimerThen {
|
startTimerThen {
|
||||||
val handleId = gocryptfsVolume.openWriteMode(path)
|
val fileHandle = encryptedVolume.openFile(path)
|
||||||
videoCapture?.startRecording(VideoCapture.OutputFileOptions(object : SeekableWriter {
|
videoCapture?.startRecording(VideoCapture.OutputFileOptions(object : SeekableWriter {
|
||||||
var offset = 0L
|
var offset = 0L
|
||||||
override fun write(byteArray: ByteArray) {
|
override fun write(byteArray: ByteArray) {
|
||||||
offset += gocryptfsVolume.writeFile(handleId, offset, byteArray, byteArray.size)
|
offset += encryptedVolume.write(fileHandle, offset, byteArray, byteArray.size)
|
||||||
}
|
}
|
||||||
override fun seek(offset: Long) {
|
override fun seek(offset: Long) {
|
||||||
this.offset = offset
|
this.offset = offset
|
||||||
}
|
}
|
||||||
override fun close() {
|
override fun close() {
|
||||||
gocryptfsVolume.closeFile(handleId)
|
encryptedVolume.closeFile(fileHandle)
|
||||||
}
|
}
|
||||||
}), executor, object : VideoCapture.OnVideoSavedCallback {
|
}), executor, object : VideoCapture.OnVideoSavedCallback {
|
||||||
override fun onVideoSaved() {
|
override fun onVideoSaved() {
|
||||||
@ -479,7 +481,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
if (!isFinishingIntentionally) {
|
if (!isFinishingIntentionally) {
|
||||||
gocryptfsVolume.close()
|
encryptedVolume.close()
|
||||||
RestrictedFileProvider.wipeAll(this)
|
RestrictedFileProvider.wipeAll(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,14 @@ import android.view.View
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import sushi.hardcore.droidfs.databinding.ActivityChangePasswordBinding
|
import sushi.hardcore.droidfs.databinding.ActivityChangePasswordBinding
|
||||||
|
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class ChangePasswordActivity: BaseActivity() {
|
class ChangePasswordActivity: BaseActivity() {
|
||||||
|
|
||||||
private lateinit var binding: ActivityChangePasswordBinding
|
private lateinit var binding: ActivityChangePasswordBinding
|
||||||
private lateinit var volume: Volume
|
private lateinit var volume: SavedVolume
|
||||||
private lateinit var volumeDatabase: VolumeDatabase
|
private lateinit var volumeDatabase: VolumeDatabase
|
||||||
private var fingerprintProtector: FingerprintProtector? = null
|
private var fingerprintProtector: FingerprintProtector? = null
|
||||||
private var usfFingerprint: Boolean = false
|
private var usfFingerprint: Boolean = false
|
||||||
|
@ -5,13 +5,13 @@ import java.io.File
|
|||||||
|
|
||||||
object ConstValues {
|
object ConstValues {
|
||||||
const val CREATOR = "DroidFS"
|
const val CREATOR = "DroidFS"
|
||||||
const val FILE_MODE = 384 //0600
|
|
||||||
const val DIRECTORY_MODE = 448 //0700
|
|
||||||
const val VOLUME_DATABASE_NAME = "SavedVolumes"
|
const val VOLUME_DATABASE_NAME = "SavedVolumes"
|
||||||
|
const val CRYFS_LOCAL_STATE_DIR = "cryfsLocalState"
|
||||||
const val SORT_ORDER_KEY = "sort_order"
|
const val SORT_ORDER_KEY = "sort_order"
|
||||||
val FAKE_URI: Uri = Uri.parse("fakeuri://droidfs")
|
val FAKE_URI: Uri = Uri.parse("fakeuri://droidfs")
|
||||||
const val MAX_KERNEL_WRITE = 128*1024
|
const val MAX_KERNEL_WRITE = 128*1024
|
||||||
const val WIPE_PASSES = 2
|
const val WIPE_PASSES = 2
|
||||||
|
const val IO_BUFF_SIZE = 16384
|
||||||
const val SLIDESHOW_DELAY: Long = 4000
|
const val SLIDESHOW_DELAY: Long = 4000
|
||||||
const val DEFAULT_THEME_VALUE = "dark_green"
|
const val DEFAULT_THEME_VALUE = "dark_green"
|
||||||
const val THUMBNAIL_MAX_SIZE_KEY = "thumbnail_max_size"
|
const val THUMBNAIL_MAX_SIZE_KEY = "thumbnail_max_size"
|
||||||
|
@ -133,7 +133,7 @@ class FingerprintProtector private constructor(
|
|||||||
private lateinit var cipher: Cipher
|
private lateinit var cipher: Cipher
|
||||||
private var isCipherReady = false
|
private var isCipherReady = false
|
||||||
private var cipherActionMode: Int? = null
|
private var cipherActionMode: Int? = null
|
||||||
private lateinit var volume: Volume
|
private lateinit var volume: SavedVolume
|
||||||
private lateinit var dataToProcess: ByteArray
|
private lateinit var dataToProcess: ByteArray
|
||||||
|
|
||||||
private fun resetHashStorage() {
|
private fun resetHashStorage() {
|
||||||
@ -207,7 +207,7 @@ class FingerprintProtector private constructor(
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun savePasswordHash(volume: Volume, plainText: ByteArray) {
|
fun savePasswordHash(volume: SavedVolume, plainText: ByteArray) {
|
||||||
this.volume = volume
|
this.volume = volume
|
||||||
val biometricPromptInfo = BiometricPrompt.PromptInfo.Builder()
|
val biometricPromptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||||
.setTitle(activity.getString(R.string.encrypt_action_description))
|
.setTitle(activity.getString(R.string.encrypt_action_description))
|
||||||
|
@ -1,243 +0,0 @@
|
|||||||
package sushi.hardcore.droidfs
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
class GocryptfsVolume(val applicationContext: Context, var sessionID: Int) {
|
|
||||||
private external fun native_close(sessionID: Int)
|
|
||||||
private external fun native_is_closed(sessionID: Int): Boolean
|
|
||||||
private external fun native_list_dir(sessionID: Int, dir_path: String): MutableList<ExplorerElement>
|
|
||||||
private external fun native_open_read_mode(sessionID: Int, file_path: String): Int
|
|
||||||
private external fun native_open_write_mode(sessionID: Int, file_path: String, mode: Int): Int
|
|
||||||
private external fun native_read_file(sessionID: Int, handleID: Int, offset: Long, buff: ByteArray): Int
|
|
||||||
private external fun native_write_file(sessionID: Int, handleID: Int, offset: Long, buff: ByteArray, buff_size: Int): Int
|
|
||||||
private external fun native_truncate(sessionID: Int, handleID: Int, offset: Long): Boolean
|
|
||||||
private external fun native_path_exists(sessionID: Int, file_path: String): Boolean
|
|
||||||
private external fun native_get_size(sessionID: Int, file_path: String): Long
|
|
||||||
private external fun native_close_file(sessionID: Int, handleID: Int)
|
|
||||||
private external fun native_remove_file(sessionID: Int, file_path: String): Boolean
|
|
||||||
private external fun native_mkdir(sessionID: Int, dir_path: String, mode: Int): Boolean
|
|
||||||
private external fun native_rmdir(sessionID: Int, dir_path: String): Boolean
|
|
||||||
private external fun native_rename(sessionID: Int, old_path: String, new_path: String): Boolean
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val KeyLen = 32
|
|
||||||
const val ScryptDefaultLogN = 16
|
|
||||||
const val DefaultBS = 4096
|
|
||||||
const val CONFIG_FILE_NAME = "gocryptfs.conf"
|
|
||||||
external fun createVolume(root_cipher_dir: String, password: CharArray, plainTextNames: Boolean, xchacha: Int, logN: Int, creator: String, returnedHash: ByteArray?): Boolean
|
|
||||||
external fun init(root_cipher_dir: String, password: CharArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int
|
|
||||||
external fun changePassword(root_cipher_dir: String, old_password: CharArray?, givenHash: ByteArray?, new_password: CharArray, returnedHash: ByteArray?): Boolean
|
|
||||||
|
|
||||||
fun isGocryptfsVolume(path: File): Boolean {
|
|
||||||
if (path.isDirectory){
|
|
||||||
return File(path, CONFIG_FILE_NAME).isFile
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
System.loadLibrary("gocryptfs_jni")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun close() {
|
|
||||||
native_close(sessionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isClosed(): Boolean {
|
|
||||||
return native_is_closed(sessionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun listDir(dir_path: String): MutableList<ExplorerElement> {
|
|
||||||
return native_list_dir(sessionID, dir_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mkdir(dir_path: String): Boolean {
|
|
||||||
return native_mkdir(sessionID, dir_path, ConstValues.DIRECTORY_MODE)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rmdir(dir_path: String): Boolean {
|
|
||||||
return native_rmdir(sessionID, dir_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeFile(file_path: String): Boolean {
|
|
||||||
return native_remove_file(sessionID, file_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun pathExists(file_path: String): Boolean {
|
|
||||||
return native_path_exists(sessionID, file_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSize(file_path: String): Long {
|
|
||||||
return native_get_size(sessionID, file_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun closeFile(handleID: Int) {
|
|
||||||
native_close_file(sessionID, handleID)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun openReadMode(file_path: String): Int {
|
|
||||||
return native_open_read_mode(sessionID, file_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun openWriteMode(file_path: String): Int {
|
|
||||||
return native_open_write_mode(sessionID, file_path, ConstValues.FILE_MODE)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun readFile(handleID: Int, offset: Long, buff: ByteArray): Int {
|
|
||||||
return native_read_file(sessionID, handleID, offset, buff)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun writeFile(handleID: Int, offset: Long, buff: ByteArray, buff_size: Int): Int {
|
|
||||||
return native_write_file(sessionID, handleID, offset, buff, buff_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun truncate(handleID: Int, offset: Long): Boolean {
|
|
||||||
return native_truncate(sessionID, handleID, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rename(old_path: String, new_path: String): Boolean {
|
|
||||||
return native_rename(sessionID, old_path, new_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exportFile(handleID: Int, os: OutputStream): Boolean {
|
|
||||||
var offset: Long = 0
|
|
||||||
val ioBuffer = ByteArray(DefaultBS)
|
|
||||||
var length: Int
|
|
||||||
while (readFile(handleID, offset, ioBuffer).also { length = it } > 0){
|
|
||||||
os.write(ioBuffer, 0, length)
|
|
||||||
offset += length.toLong()
|
|
||||||
}
|
|
||||||
os.close()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exportFile(src_path: String, os: OutputStream): Boolean {
|
|
||||||
var success = false
|
|
||||||
val srcHandleId = openReadMode(src_path)
|
|
||||||
if (srcHandleId != -1) {
|
|
||||||
success = exportFile(srcHandleId, os)
|
|
||||||
closeFile(srcHandleId)
|
|
||||||
}
|
|
||||||
return success
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exportFile(src_path: String, dst_path: String): Boolean {
|
|
||||||
return exportFile(src_path, FileOutputStream(dst_path))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exportFile(context: Context, src_path: String, output_path: Uri): Boolean {
|
|
||||||
val os = context.contentResolver.openOutputStream(output_path)
|
|
||||||
if (os != null){
|
|
||||||
return exportFile(src_path, os)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun importFile(inputStream: InputStream, dst_path: String): Boolean {
|
|
||||||
val dstHandleId = openWriteMode(dst_path)
|
|
||||||
if (dstHandleId != -1) {
|
|
||||||
var success = true
|
|
||||||
var offset: Long = 0
|
|
||||||
val ioBuffer = ByteArray(DefaultBS)
|
|
||||||
var length: Int
|
|
||||||
while (inputStream.read(ioBuffer).also { length = it } > 0) {
|
|
||||||
val written = writeFile(dstHandleId, offset, ioBuffer, length).toLong()
|
|
||||||
if (written == length.toLong()) {
|
|
||||||
offset += written
|
|
||||||
} else {
|
|
||||||
inputStream.close()
|
|
||||||
success = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closeFile(dstHandleId)
|
|
||||||
inputStream.close()
|
|
||||||
return success
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun importFile(context: Context, src_uri: Uri, dst_path: String): Boolean {
|
|
||||||
val inputStream = context.contentResolver.openInputStream(src_uri)
|
|
||||||
if (inputStream != null){
|
|
||||||
return importFile(inputStream, dst_path)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun recursiveMapFiles(rootPath: String): MutableList<ExplorerElement> {
|
|
||||||
val result = mutableListOf<ExplorerElement>()
|
|
||||||
val explorerElements = listDir(rootPath)
|
|
||||||
result.addAll(explorerElements)
|
|
||||||
for (e in explorerElements){
|
|
||||||
if (e.isDirectory){
|
|
||||||
result.addAll(recursiveMapFiles(e.fullPath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun recursiveRemoveDirectory(plain_directory_path: String): String? {
|
|
||||||
val explorerElements = listDir(plain_directory_path)
|
|
||||||
for (e in explorerElements) {
|
|
||||||
val fullPath = PathUtils.pathJoin(plain_directory_path, e.name)
|
|
||||||
if (e.isDirectory) {
|
|
||||||
val result = recursiveRemoveDirectory(fullPath)
|
|
||||||
result?.let { return it }
|
|
||||||
} else {
|
|
||||||
if (!removeFile(fullPath)) {
|
|
||||||
return fullPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return if (!rmdir(plain_directory_path)) {
|
|
||||||
plain_directory_path
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadWholeFile(fullPath: String, size: Long? = null, maxSize: Long? = null): Pair<ByteArray?, Int> {
|
|
||||||
val fileSize = size ?: getSize(fullPath)
|
|
||||||
return if (fileSize >= 0) {
|
|
||||||
maxSize?.let {
|
|
||||||
if (fileSize > it) {
|
|
||||||
return Pair(null, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
val fileBuff = ByteArray(fileSize.toInt())
|
|
||||||
val handleID = openReadMode(fullPath)
|
|
||||||
if (handleID == -1) {
|
|
||||||
Pair(null, 3)
|
|
||||||
} else {
|
|
||||||
var offset: Long = 0
|
|
||||||
val ioBuffer = ByteArray(DefaultBS)
|
|
||||||
var length: Int
|
|
||||||
while (readFile(handleID, offset, ioBuffer).also { length = it } > 0) {
|
|
||||||
System.arraycopy(ioBuffer, 0, fileBuff, offset.toInt(), length)
|
|
||||||
offset += length.toLong()
|
|
||||||
}
|
|
||||||
closeFile(handleID)
|
|
||||||
if (offset == fileBuff.size.toLong()) {
|
|
||||||
Pair(fileBuff, 0)
|
|
||||||
} else {
|
|
||||||
Pair(null, 4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: OutOfMemoryError) {
|
|
||||||
Pair(null, 2)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Pair(null, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,7 +30,10 @@ import sushi.hardcore.droidfs.explorers.ExplorerActivity
|
|||||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
|
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
|
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
|
||||||
import sushi.hardcore.droidfs.file_operations.FileOperationService
|
import sushi.hardcore.droidfs.file_operations.FileOperationService
|
||||||
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
|
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
|
import sushi.hardcore.droidfs.util.WidgetUtil
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -155,7 +158,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVolumeItemClick(volume: Volume, position: Int) {
|
override fun onVolumeItemClick(volume: SavedVolume, position: Int) {
|
||||||
if (volumeAdapter.selectedItems.isEmpty())
|
if (volumeAdapter.selectedItems.isEmpty())
|
||||||
openVolume(volume, position)
|
openVolume(volume, position)
|
||||||
else
|
else
|
||||||
@ -186,7 +189,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeVolumes(volumes: List<Volume>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) {
|
private fun removeVolumes(volumes: List<SavedVolume>, i: Int = 0, doDeleteVolumeContent: Boolean? = null) {
|
||||||
if (i < volumes.size) {
|
if (i < volumes.size) {
|
||||||
if (volumes[i].isHidden) {
|
if (volumes[i].isHidden) {
|
||||||
if (doDeleteVolumeContent == null) {
|
if (doDeleteVolumeContent == null) {
|
||||||
@ -306,7 +309,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
DocumentFile.fromFile(File(volume.name)),
|
DocumentFile.fromFile(File(volume.name)),
|
||||||
DocumentFile.fromFile(filesDir),
|
DocumentFile.fromFile(filesDir),
|
||||||
) {
|
) {
|
||||||
Volume(volume.shortName, true, volume.encryptedHash, volume.iv)
|
SavedVolume(volume.shortName, true, volume.type, volume.encryptedHash, volume.iv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -339,7 +342,11 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
val onlyOneAndWriteable =
|
val onlyOneAndWriteable =
|
||||||
onlyOneSelected &&
|
onlyOneSelected &&
|
||||||
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].canWrite(filesDir.path)
|
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].canWrite(filesDir.path)
|
||||||
menu.findItem(R.id.change_password).isVisible = onlyOneAndWriteable
|
menu.findItem(R.id.change_password).isVisible =
|
||||||
|
onlyOneAndWriteable &&
|
||||||
|
// Only gocryptfs volumes support password change
|
||||||
|
!BuildConfig.GOCRYPTFS_DISABLED &&
|
||||||
|
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE
|
||||||
menu.findItem(R.id.remove_default_open).isVisible =
|
menu.findItem(R.id.remove_default_open).isVisible =
|
||||||
onlyOneSelected &&
|
onlyOneSelected &&
|
||||||
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].name == defaultVolumeName
|
volumeAdapter.volumes[volumeAdapter.selectedItems.first()].name == defaultVolumeName
|
||||||
@ -377,9 +384,10 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
dstRootDirectory.name?.let { name ->
|
dstRootDirectory.name?.let { name ->
|
||||||
val path = PathUtils.getFullPathFromTreeUri(dstRootDirectory.uri, this)
|
val path = PathUtils.getFullPathFromTreeUri(dstRootDirectory.uri, this)
|
||||||
if (path == null) null
|
if (path == null) null
|
||||||
else Volume(
|
else SavedVolume(
|
||||||
PathUtils.pathJoin(path, name),
|
PathUtils.pathJoin(path, name),
|
||||||
false,
|
false,
|
||||||
|
volume.type,
|
||||||
volume.encryptedHash,
|
volume.encryptedHash,
|
||||||
volume.iv
|
volume.iv
|
||||||
)
|
)
|
||||||
@ -388,7 +396,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyVolume(srcDocumentFile: DocumentFile, dstDocumentFile: DocumentFile, getResultVolume: (DocumentFile) -> Volume?) {
|
private fun copyVolume(srcDocumentFile: DocumentFile, dstDocumentFile: DocumentFile, getResultVolume: (DocumentFile) -> SavedVolume?) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val result = fileOperationService.copyVolume(srcDocumentFile, dstDocumentFile)
|
val result = fileOperationService.copyVolume(srcDocumentFile, dstDocumentFile)
|
||||||
when {
|
when {
|
||||||
@ -415,13 +423,13 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renameVolume(volume: Volume, position: Int) {
|
private fun renameVolume(volume: SavedVolume, position: Int) {
|
||||||
with (EditTextDialog(this, R.string.new_volume_name) { newName ->
|
with (EditTextDialog(this, R.string.new_volume_name) { newName ->
|
||||||
val srcPath = File(volume.getFullPath(filesDir.path))
|
val srcPath = File(volume.getFullPath(filesDir.path))
|
||||||
val dstPath = File(srcPath.parent, newName).canonicalFile
|
val dstPath = File(srcPath.parent, newName).canonicalFile
|
||||||
val newDBName: String
|
val newDBName: String
|
||||||
val success = if (volume.isHidden) {
|
val success = if (volume.isHidden) {
|
||||||
if (newName.contains("/")) {
|
if (newName.contains(PathUtils.SEPARATOR)) {
|
||||||
Toast.makeText(this, R.string.error_slash_in_name, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.error_slash_in_name, Toast.LENGTH_SHORT).show()
|
||||||
renameVolume(volume, position)
|
renameVolume(volume, position)
|
||||||
return@EditTextDialog
|
return@EditTextDialog
|
||||||
@ -452,7 +460,14 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
|
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
|
||||||
private fun openVolume(volume: Volume, position: Int) {
|
private fun openVolume(volume: SavedVolume, position: Int) {
|
||||||
|
if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE && BuildConfig.GOCRYPTFS_DISABLED) {
|
||||||
|
Toast.makeText(this, R.string.gocryptfs_disabled, Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
} else if (volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE && BuildConfig.CRYFS_DISABLED) {
|
||||||
|
Toast.makeText(this, R.string.cryfs_disabled, Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
var askForPassword = true
|
var askForPassword = true
|
||||||
fingerprintProtector?.let { fingerprintProtector ->
|
fingerprintProtector?.let { fingerprintProtector ->
|
||||||
volume.encryptedHash?.let { encryptedHash ->
|
volume.encryptedHash?.let { encryptedHash ->
|
||||||
@ -463,21 +478,21 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
volumeAdapter.refresh()
|
volumeAdapter.refresh()
|
||||||
}
|
}
|
||||||
override fun onPasswordHashDecrypted(hash: ByteArray) {
|
override fun onPasswordHashDecrypted(hash: ByteArray) {
|
||||||
object : LoadingTask<Int>(this@MainActivity, themeValue, R.string.loading_msg_open) {
|
object : LoadingTask<EncryptedVolume?>(this@MainActivity, themeValue, R.string.loading_msg_open) {
|
||||||
override suspend fun doTask(): Int {
|
override suspend fun doTask(): EncryptedVolume? {
|
||||||
val sessionId = GocryptfsVolume.init(volume.getFullPath(filesDir.path), null, hash, null)
|
val encryptedVolume = EncryptedVolume.init(volume, filesDir.path, null, hash, null)
|
||||||
Arrays.fill(hash, 0)
|
Arrays.fill(hash, 0)
|
||||||
return sessionId
|
return encryptedVolume
|
||||||
}
|
}
|
||||||
}.startTask(lifecycleScope) { sessionId ->
|
}.startTask(lifecycleScope) { encryptedVolume ->
|
||||||
if (sessionId != -1) {
|
if (encryptedVolume == null) {
|
||||||
startExplorer(sessionId, volume.shortName)
|
|
||||||
} else {
|
|
||||||
CustomAlertDialogBuilder(this@MainActivity, themeValue)
|
CustomAlertDialogBuilder(this@MainActivity, themeValue)
|
||||||
.setTitle(R.string.open_volume_failed)
|
.setTitle(R.string.open_volume_failed)
|
||||||
.setMessage(R.string.open_failed_hash_msg)
|
.setMessage(R.string.open_failed_hash_msg)
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
|
} else {
|
||||||
|
startExplorer(encryptedVolume, volume.shortName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -496,7 +511,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
askForPassword(volume, position)
|
askForPassword(volume, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onPasswordSubmitted(volume: Volume, position: Int, dialogBinding: DialogOpenVolumeBinding) {
|
private fun onPasswordSubmitted(volume: SavedVolume, position: Int, dialogBinding: DialogOpenVolumeBinding) {
|
||||||
if (dialogBinding.checkboxDefaultOpen.isChecked xor (defaultVolumeName == volume.name)) {
|
if (dialogBinding.checkboxDefaultOpen.isChecked xor (defaultVolumeName == volume.name)) {
|
||||||
with (sharedPrefs.edit()) {
|
with (sharedPrefs.edit()) {
|
||||||
defaultVolumeName = if (dialogBinding.checkboxDefaultOpen.isChecked) {
|
defaultVolumeName = if (dialogBinding.checkboxDefaultOpen.isChecked) {
|
||||||
@ -509,20 +524,18 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val password = CharArray(dialogBinding.editPassword.text.length)
|
|
||||||
dialogBinding.editPassword.text.getChars(0, password.size, password, 0)
|
|
||||||
// openVolumeWithPassword is responsible for wiping the password
|
// openVolumeWithPassword is responsible for wiping the password
|
||||||
openVolumeWithPassword(
|
openVolumeWithPassword(
|
||||||
volume,
|
volume,
|
||||||
position,
|
position,
|
||||||
password,
|
WidgetUtil.encodeEditTextContent(dialogBinding.editPassword),
|
||||||
dialogBinding.checkboxSavePassword.isChecked,
|
dialogBinding.checkboxSavePassword.isChecked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun askForPassword(volume: Volume, position: Int, savePasswordHash: Boolean = false) {
|
private fun askForPassword(volume: SavedVolume, position: Int, savePasswordHash: Boolean = false) {
|
||||||
val dialogBinding = DialogOpenVolumeBinding.inflate(layoutInflater)
|
val dialogBinding = DialogOpenVolumeBinding.inflate(layoutInflater)
|
||||||
if (!usfFingerprint || fingerprintProtector == null || volume.encryptedHash != null) {
|
if (!usfFingerprint || fingerprintProtector == null || volume.encryptedHash != null || volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE) {
|
||||||
dialogBinding.checkboxSavePassword.visibility = View.GONE
|
dialogBinding.checkboxSavePassword.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
dialogBinding.checkboxSavePassword.isChecked = savePasswordHash
|
dialogBinding.checkboxSavePassword.isChecked = savePasswordHash
|
||||||
@ -550,20 +563,28 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openVolumeWithPassword(volume: Volume, position: Int, password: CharArray, savePasswordHash: Boolean) {
|
private fun openVolumeWithPassword(volume: SavedVolume, position: Int, password: ByteArray, savePasswordHash: Boolean) {
|
||||||
val usfFingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
|
val usfFingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
|
||||||
var returnedHash: ByteArray? = null
|
var returnedHash: ByteArray? = null
|
||||||
if (savePasswordHash && usfFingerprint) {
|
if (savePasswordHash && usfFingerprint) {
|
||||||
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
|
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
|
||||||
}
|
}
|
||||||
object : LoadingTask<Int>(this, themeValue, R.string.loading_msg_open) {
|
object : LoadingTask<EncryptedVolume?>(this, themeValue, R.string.loading_msg_open) {
|
||||||
override suspend fun doTask(): Int {
|
override suspend fun doTask(): EncryptedVolume? {
|
||||||
val sessionId = GocryptfsVolume.init(volume.getFullPath(filesDir.path), password, null, returnedHash)
|
val encryptedVolume = EncryptedVolume.init(volume, filesDir.path, password, null, returnedHash)
|
||||||
Arrays.fill(password, 0.toChar())
|
Arrays.fill(password, 0)
|
||||||
return sessionId
|
return encryptedVolume
|
||||||
}
|
}
|
||||||
}.startTask(lifecycleScope) { sessionId ->
|
}.startTask(lifecycleScope) { encryptedVolume ->
|
||||||
if (sessionId != -1) {
|
if (encryptedVolume == null) {
|
||||||
|
CustomAlertDialogBuilder(this, themeValue)
|
||||||
|
.setTitle(R.string.open_volume_failed)
|
||||||
|
.setMessage(R.string.open_volume_failed_msg)
|
||||||
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
|
askForPassword(volume, position, savePasswordHash)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
val fingerprintProtector = fingerprintProtector
|
val fingerprintProtector = fingerprintProtector
|
||||||
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
|
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
|
||||||
if (savePasswordHash && returnedHash != null && fingerprintProtector != null) {
|
if (savePasswordHash && returnedHash != null && fingerprintProtector != null) {
|
||||||
@ -575,12 +596,12 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
override fun onPasswordHashSaved() {
|
override fun onPasswordHashSaved() {
|
||||||
Arrays.fill(returnedHash, 0)
|
Arrays.fill(returnedHash, 0)
|
||||||
volumeAdapter.onVolumeChanged(position)
|
volumeAdapter.onVolumeChanged(position)
|
||||||
startExplorer(sessionId, volume.shortName)
|
startExplorer(encryptedVolume, volume.shortName)
|
||||||
}
|
}
|
||||||
private var isClosed = false
|
private var isClosed = false
|
||||||
override fun onFailed(pending: Boolean) {
|
override fun onFailed(pending: Boolean) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
GocryptfsVolume(this@MainActivity, sessionId).close()
|
encryptedVolume.close()
|
||||||
isClosed = true
|
isClosed = true
|
||||||
}
|
}
|
||||||
Arrays.fill(returnedHash, 0)
|
Arrays.fill(returnedHash, 0)
|
||||||
@ -588,21 +609,13 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
}
|
}
|
||||||
fingerprintProtector.savePasswordHash(volume, returnedHash)
|
fingerprintProtector.savePasswordHash(volume, returnedHash)
|
||||||
} else {
|
} else {
|
||||||
startExplorer(sessionId, volume.shortName)
|
startExplorer(encryptedVolume, volume.shortName)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
CustomAlertDialogBuilder(this, themeValue)
|
|
||||||
.setTitle(R.string.open_volume_failed)
|
|
||||||
.setMessage(R.string.open_volume_failed_msg)
|
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
|
||||||
askForPassword(volume, position, savePasswordHash)
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startExplorer(sessionId: Int, volumeShortName: String) {
|
private fun startExplorer(encryptedVolume: EncryptedVolume, volumeShortName: String) {
|
||||||
var explorerIntent: Intent? = null
|
var explorerIntent: Intent? = null
|
||||||
if (dropMode) { //import via android share menu
|
if (dropMode) { //import via android share menu
|
||||||
explorerIntent = Intent(this, ExplorerActivityDrop::class.java)
|
explorerIntent = Intent(this, ExplorerActivityDrop::class.java)
|
||||||
@ -610,13 +623,13 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
explorerIntent.putExtras(intent.extras!!) //forward extras
|
explorerIntent.putExtras(intent.extras!!) //forward extras
|
||||||
} else if (pickMode) {
|
} else if (pickMode) {
|
||||||
explorerIntent = Intent(this, ExplorerActivityPick::class.java)
|
explorerIntent = Intent(this, ExplorerActivityPick::class.java)
|
||||||
explorerIntent.putExtra("originalSessionID", intent.getIntExtra("sessionID", -1))
|
explorerIntent.putExtra("destinationVolume", intent.getParcelableExtra<EncryptedVolume>("volume")!!)
|
||||||
explorerIntent.flags = Intent.FLAG_ACTIVITY_FORWARD_RESULT
|
explorerIntent.flags = Intent.FLAG_ACTIVITY_FORWARD_RESULT
|
||||||
}
|
}
|
||||||
if (explorerIntent == null) {
|
if (explorerIntent == null) {
|
||||||
explorerIntent = Intent(this, ExplorerActivity::class.java) //default opening
|
explorerIntent = Intent(this, ExplorerActivity::class.java) //default opening
|
||||||
}
|
}
|
||||||
explorerIntent.putExtra("sessionID", sessionId)
|
explorerIntent.putExtra("volume", encryptedVolume)
|
||||||
explorerIntent.putExtra("volume_name", volumeShortName)
|
explorerIntent.putExtra("volume_name", volumeShortName)
|
||||||
startActivity(explorerIntent)
|
startActivity(explorerIntent)
|
||||||
if (pickMode)
|
if (pickMode)
|
||||||
@ -640,12 +653,9 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
|
|||||||
if (pickMode && !usfKeepOpen) {
|
if (pickMode && !usfKeepOpen) {
|
||||||
finish()
|
finish()
|
||||||
if (shouldCloseVolume) {
|
if (shouldCloseVolume) {
|
||||||
val sessionID = intent.getIntExtra("sessionID", -1)
|
intent.getParcelableExtra<EncryptedVolume>("volume")?.close()
|
||||||
if (sessionID != -1) {
|
|
||||||
GocryptfsVolume(this, sessionID).close()
|
|
||||||
RestrictedFileProvider.wipeAll(this)
|
RestrictedFileProvider.wipeAll(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,12 @@ import android.os.Parcelable
|
|||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class Volume(val name: String, val isHidden: Boolean = false, var encryptedHash: ByteArray? = null, var iv: ByteArray? = null): Parcelable {
|
class SavedVolume(val name: String, val isHidden: Boolean = false, val type: Byte, var encryptedHash: ByteArray? = null, var iv: ByteArray? = null): Parcelable {
|
||||||
|
|
||||||
constructor(parcel: Parcel) : this(
|
constructor(parcel: Parcel) : this(
|
||||||
parcel.readString()!!,
|
parcel.readString()!!,
|
||||||
parcel.readByte() != 0.toByte(),
|
parcel.readByte() != 0.toByte(),
|
||||||
|
parcel.readByte(),
|
||||||
parcel.createByteArray(),
|
parcel.createByteArray(),
|
||||||
parcel.createByteArray()
|
parcel.createByteArray()
|
||||||
)
|
)
|
||||||
@ -20,7 +21,7 @@ class Volume(val name: String, val isHidden: Boolean = false, var encryptedHash:
|
|||||||
|
|
||||||
fun getFullPath(filesDir: String): String {
|
fun getFullPath(filesDir: String): String {
|
||||||
return if (isHidden)
|
return if (isHidden)
|
||||||
PathUtils.pathJoin(filesDir, name)
|
getHiddenVolumeFullPath(filesDir, name)
|
||||||
else
|
else
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
@ -37,18 +38,23 @@ class Volume(val name: String, val isHidden: Boolean = false, var encryptedHash:
|
|||||||
with (dest) {
|
with (dest) {
|
||||||
writeString(name)
|
writeString(name)
|
||||||
writeByte(if (isHidden) 1 else 0)
|
writeByte(if (isHidden) 1 else 0)
|
||||||
|
writeByte(type)
|
||||||
writeByteArray(encryptedHash)
|
writeByteArray(encryptedHash)
|
||||||
writeByteArray(iv)
|
writeByteArray(iv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object CREATOR : Parcelable.Creator<Volume> {
|
companion object {
|
||||||
override fun createFromParcel(parcel: Parcel): Volume {
|
const val VOLUMES_DIRECTORY = "volumes"
|
||||||
return Volume(parcel)
|
|
||||||
|
@JvmField
|
||||||
|
val CREATOR = object : Parcelable.Creator<SavedVolume> {
|
||||||
|
override fun createFromParcel(parcel: Parcel) = SavedVolume(parcel)
|
||||||
|
override fun newArray(size: Int) = arrayOfNulls<SavedVolume>(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun newArray(size: Int): Array<Volume?> {
|
fun getHiddenVolumeFullPath(filesDir: String, name: String): String {
|
||||||
return arrayOfNulls(size)
|
return PathUtils.pathJoin(filesDir, VOLUMES_DIRECTORY, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,20 +4,25 @@ import android.content.ContentValues
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
|
import android.util.Log
|
||||||
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
|
class VolumeDatabase(private val context: Context): SQLiteOpenHelper(context, ConstValues.VOLUME_DATABASE_NAME, null, 4) {
|
||||||
ConstValues.VOLUME_DATABASE_NAME, null, 3) {
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TABLE_NAME = "Volumes"
|
const val TABLE_NAME = "Volumes"
|
||||||
const val COLUMN_NAME = "name"
|
const val COLUMN_NAME = "name"
|
||||||
const val COLUMN_HIDDEN = "hidden"
|
const val COLUMN_HIDDEN = "hidden"
|
||||||
|
const val COLUMN_TYPE = "type"
|
||||||
const val COLUMN_HASH = "hash"
|
const val COLUMN_HASH = "hash"
|
||||||
const val COLUMN_IV = "iv"
|
const val COLUMN_IV = "iv"
|
||||||
|
|
||||||
private fun contentValuesFromVolume(volume: Volume): ContentValues {
|
private fun contentValuesFromVolume(volume: SavedVolume): ContentValues {
|
||||||
val contentValues = ContentValues()
|
val contentValues = ContentValues()
|
||||||
contentValues.put(COLUMN_NAME, volume.name)
|
contentValues.put(COLUMN_NAME, volume.name)
|
||||||
contentValues.put(COLUMN_HIDDEN, volume.isHidden)
|
contentValues.put(COLUMN_HIDDEN, volume.isHidden)
|
||||||
|
contentValues.put(COLUMN_TYPE, byteArrayOf(volume.type))
|
||||||
contentValues.put(COLUMN_HASH, volume.encryptedHash)
|
contentValues.put(COLUMN_HASH, volume.encryptedHash)
|
||||||
contentValues.put(COLUMN_IV, volume.iv)
|
contentValues.put(COLUMN_IV, volume.iv)
|
||||||
return contentValues
|
return contentValues
|
||||||
@ -25,11 +30,57 @@ class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
|
|||||||
}
|
}
|
||||||
override fun onCreate(db: SQLiteDatabase) {
|
override fun onCreate(db: SQLiteDatabase) {
|
||||||
db.execSQL(
|
db.execSQL(
|
||||||
"CREATE TABLE IF NOT EXISTS $TABLE_NAME ($COLUMN_NAME TEXT PRIMARY KEY, $COLUMN_HIDDEN SHORT, $COLUMN_HASH BLOB, $COLUMN_IV BLOB);"
|
"CREATE TABLE IF NOT EXISTS $TABLE_NAME (" +
|
||||||
|
"$COLUMN_NAME TEXT PRIMARY KEY," +
|
||||||
|
"$COLUMN_HIDDEN SHORT," +
|
||||||
|
"$COLUMN_TYPE BLOB," +
|
||||||
|
"$COLUMN_HASH BLOB," +
|
||||||
|
"$COLUMN_IV BLOB" +
|
||||||
|
");"
|
||||||
)
|
)
|
||||||
|
File(context.filesDir, SavedVolume.VOLUMES_DIRECTORY).mkdir()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {}
|
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
|
// Adding type column and set it to GOCRYPTFS_VOLUME_TYPE for all existing volumes
|
||||||
|
db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN $COLUMN_TYPE BLOB;")
|
||||||
|
db.update(TABLE_NAME, ContentValues().apply {
|
||||||
|
put(COLUMN_TYPE, byteArrayOf(EncryptedVolume.GOCRYPTFS_VOLUME_TYPE))
|
||||||
|
}, null, null)
|
||||||
|
|
||||||
|
// Moving hidden volumes to the "volumes" directory
|
||||||
|
if (File(context.filesDir, SavedVolume.VOLUMES_DIRECTORY).mkdir()) {
|
||||||
|
val cursor = db.query(
|
||||||
|
TABLE_NAME,
|
||||||
|
arrayOf(COLUMN_NAME),
|
||||||
|
"$COLUMN_HIDDEN=?",
|
||||||
|
arrayOf("1"),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
val volumeName = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME))
|
||||||
|
File(
|
||||||
|
PathUtils.pathJoin(
|
||||||
|
context.filesDir.path,
|
||||||
|
volumeName
|
||||||
|
)
|
||||||
|
).renameTo(
|
||||||
|
File(
|
||||||
|
SavedVolume(
|
||||||
|
volumeName,
|
||||||
|
true,
|
||||||
|
EncryptedVolume.GOCRYPTFS_VOLUME_TYPE
|
||||||
|
).getFullPath(context.filesDir.path)
|
||||||
|
).canonicalFile
|
||||||
|
)
|
||||||
|
}
|
||||||
|
cursor.close()
|
||||||
|
} else {
|
||||||
|
Log.e("VolumeDatabase", "Volumes directory creation failed while upgrading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun isVolumeSaved(volumeName: String, isHidden: Boolean): Boolean {
|
fun isVolumeSaved(volumeName: String, isHidden: Boolean): Boolean {
|
||||||
val cursor = readableDatabase.query(TABLE_NAME,
|
val cursor = readableDatabase.query(TABLE_NAME,
|
||||||
@ -42,23 +93,24 @@ class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveVolume(volume: Volume): Boolean {
|
fun saveVolume(volume: SavedVolume): Boolean {
|
||||||
if (!isVolumeSaved(volume.name, volume.isHidden)) {
|
if (!isVolumeSaved(volume.name, volume.isHidden)) {
|
||||||
return (writableDatabase.insert(TABLE_NAME, null, contentValuesFromVolume(volume)) == 0.toLong())
|
return (writableDatabase.insert(TABLE_NAME, null, contentValuesFromVolume(volume)) == 0.toLong())
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getVolumes(): List<Volume> {
|
fun getVolumes(): List<SavedVolume> {
|
||||||
val list: MutableList<Volume> = ArrayList()
|
val list: MutableList<SavedVolume> = ArrayList()
|
||||||
val cursor = readableDatabase.rawQuery("SELECT * FROM $TABLE_NAME", null)
|
val cursor = readableDatabase.rawQuery("SELECT * FROM $TABLE_NAME", null)
|
||||||
while (cursor.moveToNext()){
|
while (cursor.moveToNext()){
|
||||||
list.add(
|
list.add(
|
||||||
Volume(
|
SavedVolume(
|
||||||
cursor.getString(cursor.getColumnIndex(COLUMN_NAME)),
|
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME)),
|
||||||
cursor.getShort(cursor.getColumnIndex(COLUMN_HIDDEN)) == 1.toShort(),
|
cursor.getShort(cursor.getColumnIndexOrThrow(COLUMN_HIDDEN)) == 1.toShort(),
|
||||||
cursor.getBlob(cursor.getColumnIndex(COLUMN_HASH)),
|
cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_TYPE))[0],
|
||||||
cursor.getBlob(cursor.getColumnIndex(COLUMN_IV))
|
cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_HASH)),
|
||||||
|
cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_IV))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -70,7 +122,7 @@ class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
|
|||||||
val cursor = readableDatabase.query(TABLE_NAME, arrayOf(COLUMN_NAME, COLUMN_HASH), "$COLUMN_NAME=?", arrayOf(volumeName), null, null, null)
|
val cursor = readableDatabase.query(TABLE_NAME, arrayOf(COLUMN_NAME, COLUMN_HASH), "$COLUMN_NAME=?", arrayOf(volumeName), null, null, null)
|
||||||
var isHashSaved = false
|
var isHashSaved = false
|
||||||
if (cursor.moveToNext()) {
|
if (cursor.moveToNext()) {
|
||||||
if (cursor.getBlob(cursor.getColumnIndex(COLUMN_HASH)) != null) {
|
if (cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_HASH)) != null) {
|
||||||
isHashSaved = true
|
isHashSaved = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,16 +130,17 @@ class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
|
|||||||
return isHashSaved
|
return isHashSaved
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addHash(volume: Volume): Boolean {
|
fun addHash(volume: SavedVolume): Boolean {
|
||||||
return writableDatabase.update(TABLE_NAME, contentValuesFromVolume(volume), "$COLUMN_NAME=?", arrayOf(volume.name)) > 0
|
return writableDatabase.update(TABLE_NAME, contentValuesFromVolume(volume), "$COLUMN_NAME=?", arrayOf(volume.name)) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeHash(volume: Volume): Boolean {
|
fun removeHash(volume: SavedVolume): Boolean {
|
||||||
return writableDatabase.update(
|
return writableDatabase.update(
|
||||||
TABLE_NAME, contentValuesFromVolume(
|
TABLE_NAME, contentValuesFromVolume(
|
||||||
Volume(
|
SavedVolume(
|
||||||
volume.name,
|
volume.name,
|
||||||
volume.isHidden,
|
volume.isHidden,
|
||||||
|
volume.type,
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
@ -17,16 +17,17 @@ import com.bumptech.glide.request.target.DrawableImageViewTarget
|
|||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import sushi.hardcore.droidfs.ConstValues
|
import sushi.hardcore.droidfs.ConstValues
|
||||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||||
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
|
import sushi.hardcore.droidfs.filesystems.Stat
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class ExplorerElementAdapter(
|
class ExplorerElementAdapter(
|
||||||
val activity: AppCompatActivity,
|
val activity: AppCompatActivity,
|
||||||
val gocryptfsVolume: GocryptfsVolume?,
|
val encryptedVolume: EncryptedVolume?,
|
||||||
private val listener: Listener,
|
private val listener: Listener,
|
||||||
val thumbnailMaxSize: Long,
|
val thumbnailMaxSize: Long,
|
||||||
) : SelectableAdapter<ExplorerElement>(listener::onSelectionChanged) {
|
) : SelectableAdapter<ExplorerElement>(listener::onSelectionChanged) {
|
||||||
@ -42,7 +43,7 @@ class ExplorerElementAdapter(
|
|||||||
private var thumbnailsCache: LruCache<String, Bitmap>? = null
|
private var thumbnailsCache: LruCache<String, Bitmap>? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (gocryptfsVolume != null) {
|
if (encryptedVolume != null) {
|
||||||
thumbnailsCache = LruCache((Runtime.getRuntime().maxMemory() / 1024 / 8).toInt())
|
thumbnailsCache = LruCache((Runtime.getRuntime().maxMemory() / 1024 / 8).toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,9 +106,9 @@ class ExplorerElementAdapter(
|
|||||||
open class RegularElementViewHolder(itemView: View) : ExplorerElementViewHolder(itemView) {
|
open class RegularElementViewHolder(itemView: View) : ExplorerElementViewHolder(itemView) {
|
||||||
open fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) {
|
open fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) {
|
||||||
super.bind(explorerElement, position)
|
super.bind(explorerElement, position)
|
||||||
textElementSize.text = PathUtils.formatSize(explorerElement.size)
|
textElementSize.text = PathUtils.formatSize(explorerElement.stat.size)
|
||||||
(bindingAdapter as ExplorerElementAdapter?)?.let {
|
(bindingAdapter as ExplorerElementAdapter?)?.let {
|
||||||
textElementMtime.text = it.dateFormat.format(explorerElement.mTime)
|
textElementMtime.text = it.dateFormat.format(explorerElement.stat.mTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,7 +119,7 @@ class ExplorerElementAdapter(
|
|||||||
private val scope = CoroutineScope(Dispatchers.IO)
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
private fun loadThumbnail(fullPath: String, adapter: ExplorerElementAdapter) {
|
private fun loadThumbnail(fullPath: String, adapter: ExplorerElementAdapter) {
|
||||||
adapter.gocryptfsVolume?.let { volume ->
|
adapter.encryptedVolume?.let { volume ->
|
||||||
job = scope.launch {
|
job = scope.launch {
|
||||||
volume.loadWholeFile(fullPath, maxSize = adapter.thumbnailMaxSize).first?.let {
|
volume.loadWholeFile(fullPath, maxSize = adapter.thumbnailMaxSize).first?.let {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
@ -220,9 +221,9 @@ class ExplorerElementAdapter(
|
|||||||
}, parent, false
|
}, parent, false
|
||||||
)
|
)
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
ExplorerElement.REGULAR_FILE_TYPE -> FileViewHolder(view)
|
Stat.S_IFREG -> FileViewHolder(view)
|
||||||
ExplorerElement.DIRECTORY_TYPE -> DirectoryViewHolder(view)
|
Stat.S_IFDIR -> DirectoryViewHolder(view)
|
||||||
ExplorerElement.PARENT_FOLDER_TYPE -> ParentFolderViewHolder(view)
|
Stat.PARENT_FOLDER_TYPE -> ParentFolderViewHolder(view)
|
||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,6 +238,6 @@ class ExplorerElementAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return explorerElements[position].elementType.toInt()
|
return explorerElements[position].stat.type
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,7 +10,7 @@ import android.widget.LinearLayout
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.Volume
|
import sushi.hardcore.droidfs.SavedVolume
|
||||||
import sushi.hardcore.droidfs.VolumeDatabase
|
import sushi.hardcore.droidfs.VolumeDatabase
|
||||||
|
|
||||||
class VolumeAdapter(
|
class VolumeAdapter(
|
||||||
@ -19,9 +19,9 @@ class VolumeAdapter(
|
|||||||
private val allowSelection: Boolean,
|
private val allowSelection: Boolean,
|
||||||
private val showReadOnly: Boolean,
|
private val showReadOnly: Boolean,
|
||||||
private val listener: Listener,
|
private val listener: Listener,
|
||||||
) : SelectableAdapter<Volume>(listener::onSelectionChanged) {
|
) : SelectableAdapter<SavedVolume>(listener::onSelectionChanged) {
|
||||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
lateinit var volumes: List<Volume>
|
lateinit var volumes: List<SavedVolume>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
reloadVolumes()
|
reloadVolumes()
|
||||||
@ -29,11 +29,11 @@ class VolumeAdapter(
|
|||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
fun onSelectionChanged(size: Int)
|
fun onSelectionChanged(size: Int)
|
||||||
fun onVolumeItemClick(volume: Volume, position: Int)
|
fun onVolumeItemClick(volume: SavedVolume, position: Int)
|
||||||
fun onVolumeItemLongClick()
|
fun onVolumeItemLongClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItems(): List<Volume> {
|
override fun getItems(): List<SavedVolume> {
|
||||||
return volumes
|
return volumes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,9 +15,14 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import sushi.hardcore.droidfs.*
|
import sushi.hardcore.droidfs.*
|
||||||
import sushi.hardcore.droidfs.databinding.FragmentCreateVolumeBinding
|
import sushi.hardcore.droidfs.databinding.FragmentCreateVolumeBinding
|
||||||
|
import sushi.hardcore.droidfs.filesystems.CryfsVolume
|
||||||
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
|
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume
|
||||||
|
import sushi.hardcore.droidfs.util.WidgetUtil
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class CreateVolumeFragment: Fragment() {
|
class CreateVolumeFragment: Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
@ -48,6 +53,7 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
|
|
||||||
private lateinit var binding: FragmentCreateVolumeBinding
|
private lateinit var binding: FragmentCreateVolumeBinding
|
||||||
private var themeValue = ConstValues.DEFAULT_THEME_VALUE
|
private var themeValue = ConstValues.DEFAULT_THEME_VALUE
|
||||||
|
private val volumeTypes = ArrayList<String>(2)
|
||||||
private lateinit var volumePath: String
|
private lateinit var volumePath: String
|
||||||
private var isHiddenVolume: Boolean = false
|
private var isHiddenVolume: Boolean = false
|
||||||
private var usfFingerprint: Boolean = false
|
private var usfFingerprint: Boolean = false
|
||||||
@ -79,6 +85,45 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
if (!usfFingerprint || fingerprintProtector == null) {
|
if (!usfFingerprint || fingerprintProtector == null) {
|
||||||
binding.checkboxSavePassword.visibility = View.GONE
|
binding.checkboxSavePassword.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
if (!BuildConfig.GOCRYPTFS_DISABLED) {
|
||||||
|
volumeTypes.add(resources.getString(R.string.gocryptfs))
|
||||||
|
}
|
||||||
|
if (!BuildConfig.CRYFS_DISABLED) {
|
||||||
|
volumeTypes.add(resources.getString(R.string.cryfs))
|
||||||
|
}
|
||||||
|
binding.spinnerVolumeType.adapter = ArrayAdapter(
|
||||||
|
requireContext(),
|
||||||
|
android.R.layout.simple_spinner_item,
|
||||||
|
volumeTypes
|
||||||
|
).apply {
|
||||||
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
}
|
||||||
|
val encryptionCipherAdapter = ArrayAdapter(
|
||||||
|
requireContext(),
|
||||||
|
android.R.layout.simple_spinner_item,
|
||||||
|
resources.getStringArray(R.array.gocryptfs_encryption_ciphers).toMutableList()
|
||||||
|
).apply {
|
||||||
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
}
|
||||||
|
binding.spinnerVolumeType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
val ciphersArray = if (volumeTypes[position] == resources.getString(R.string.gocryptfs)) {
|
||||||
|
if (usfFingerprint && fingerprintProtector != null) {
|
||||||
|
binding.checkboxSavePassword.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
R.array.gocryptfs_encryption_ciphers
|
||||||
|
} else {
|
||||||
|
binding.checkboxSavePassword.visibility = View.GONE
|
||||||
|
R.array.cryfs_encryption_ciphers
|
||||||
|
}
|
||||||
|
with(encryptionCipherAdapter) {
|
||||||
|
clear()
|
||||||
|
addAll(resources.getStringArray(ciphersArray).asList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||||
|
}
|
||||||
|
binding.spinnerCipher.adapter = encryptionCipherAdapter
|
||||||
if (pinPasswords) {
|
if (pinPasswords) {
|
||||||
arrayOf(binding.editPassword, binding.editPasswordConfirm).forEach {
|
arrayOf(binding.editPassword, binding.editPasswordConfirm).forEach {
|
||||||
it.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
|
it.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
|
||||||
@ -88,24 +133,6 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
createVolume()
|
createVolume()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
binding.spinnerXchacha.adapter = ArrayAdapter(
|
|
||||||
requireContext(),
|
|
||||||
android.R.layout.simple_spinner_item,
|
|
||||||
resources.getStringArray(R.array.encryption_cipher)
|
|
||||||
).apply {
|
|
||||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
|
||||||
}
|
|
||||||
binding.spinnerXchacha.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
|
||||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
|
||||||
if (position == 1)
|
|
||||||
CustomAlertDialogBuilder(requireContext(), themeValue)
|
|
||||||
.setTitle(R.string.warning)
|
|
||||||
.setMessage(R.string.xchacha_warning)
|
|
||||||
.setPositiveButton(R.string.ok, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
|
||||||
}
|
|
||||||
binding.buttonCreate.setOnClickListener {
|
binding.buttonCreate.setOnClickListener {
|
||||||
createVolume()
|
createVolume()
|
||||||
}
|
}
|
||||||
@ -116,42 +143,10 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
(activity as AddVolumeActivity).onFragmentLoaded(false)
|
(activity as AddVolumeActivity).onFragmentLoaded(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createVolume() {
|
private fun saveVolume(success: Boolean, volumeType: Byte): SavedVolume? {
|
||||||
val password = CharArray(binding.editPassword.text.length)
|
return if (success) {
|
||||||
binding.editPassword.text.getChars(0, password.size, password, 0)
|
|
||||||
val passwordConfirm = CharArray(binding.editPasswordConfirm.text.length)
|
|
||||||
binding.editPasswordConfirm.text.getChars(0, passwordConfirm.size, passwordConfirm, 0)
|
|
||||||
if (!password.contentEquals(passwordConfirm)) {
|
|
||||||
Toast.makeText(requireContext(), R.string.passwords_mismatch, Toast.LENGTH_SHORT).show()
|
|
||||||
Arrays.fill(password, 0.toChar())
|
|
||||||
Arrays.fill(passwordConfirm, 0.toChar())
|
|
||||||
} else {
|
|
||||||
Arrays.fill(passwordConfirm, 0.toChar())
|
|
||||||
var returnedHash: ByteArray? = null
|
|
||||||
if (binding.checkboxSavePassword.isChecked)
|
|
||||||
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
|
|
||||||
object: LoadingTask<Volume?>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) {
|
|
||||||
override suspend fun doTask(): Volume? {
|
|
||||||
val xchacha = when (binding.spinnerXchacha.selectedItemPosition) {
|
|
||||||
0 -> 0
|
|
||||||
1 -> 1
|
|
||||||
else -> -1
|
|
||||||
}
|
|
||||||
val volumeFile = File(volumePath)
|
|
||||||
if (!volumeFile.exists())
|
|
||||||
volumeFile.mkdirs()
|
|
||||||
val volume = if (GocryptfsVolume.createVolume(
|
|
||||||
volumePath,
|
|
||||||
password,
|
|
||||||
false,
|
|
||||||
xchacha,
|
|
||||||
GocryptfsVolume.ScryptDefaultLogN,
|
|
||||||
ConstValues.CREATOR,
|
|
||||||
returnedHash
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
val volumeName = if (isHiddenVolume) File(volumePath).name else volumePath
|
val volumeName = if (isHiddenVolume) File(volumePath).name else volumePath
|
||||||
val volume = Volume(volumeName, isHiddenVolume)
|
val volume = SavedVolume(volumeName, isHiddenVolume, volumeType)
|
||||||
volumeDatabase.apply {
|
volumeDatabase.apply {
|
||||||
if (isVolumeSaved(volumeName, isHiddenVolume)) // cleaning old saved path
|
if (isVolumeSaved(volumeName, isHiddenVolume)) // cleaning old saved path
|
||||||
removeVolume(volumeName)
|
removeVolume(volumeName)
|
||||||
@ -161,7 +156,49 @@ class CreateVolumeFragment: Fragment() {
|
|||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
Arrays.fill(password, 0.toChar())
|
}
|
||||||
|
|
||||||
|
private fun createVolume() {
|
||||||
|
val password = WidgetUtil.encodeEditTextContent(binding.editPassword)
|
||||||
|
val passwordConfirm = WidgetUtil.encodeEditTextContent(binding.editPasswordConfirm)
|
||||||
|
if (!password.contentEquals(passwordConfirm)) {
|
||||||
|
Toast.makeText(requireContext(), R.string.passwords_mismatch, Toast.LENGTH_SHORT).show()
|
||||||
|
Arrays.fill(password, 0)
|
||||||
|
Arrays.fill(passwordConfirm, 0)
|
||||||
|
} else {
|
||||||
|
Arrays.fill(passwordConfirm, 0)
|
||||||
|
var returnedHash: ByteArray? = null
|
||||||
|
if (binding.checkboxSavePassword.isChecked)
|
||||||
|
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
|
||||||
|
object: LoadingTask<SavedVolume?>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) {
|
||||||
|
override suspend fun doTask(): SavedVolume? {
|
||||||
|
val volumeFile = File(volumePath)
|
||||||
|
if (!volumeFile.exists())
|
||||||
|
volumeFile.mkdirs()
|
||||||
|
val volume = if (volumeTypes[binding.spinnerVolumeType.selectedItemPosition] == resources.getString(R.string.gocryptfs)) {
|
||||||
|
val xchacha = when (binding.spinnerCipher.selectedItemPosition) {
|
||||||
|
0 -> 0
|
||||||
|
1 -> 1
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
saveVolume(GocryptfsVolume.createVolume(
|
||||||
|
volumePath,
|
||||||
|
password,
|
||||||
|
false,
|
||||||
|
xchacha,
|
||||||
|
GocryptfsVolume.ScryptDefaultLogN,
|
||||||
|
ConstValues.CREATOR,
|
||||||
|
returnedHash
|
||||||
|
), EncryptedVolume.GOCRYPTFS_VOLUME_TYPE)
|
||||||
|
} else {
|
||||||
|
saveVolume(CryfsVolume.create(
|
||||||
|
volumePath,
|
||||||
|
CryfsVolume.getLocalStateDir(activity.filesDir.path),
|
||||||
|
password,
|
||||||
|
resources.getStringArray(R.array.cryfs_encryption_ciphers)[binding.spinnerCipher.selectedItemPosition]
|
||||||
|
), EncryptedVolume.CRYFS_VOLUME_TYPE)
|
||||||
|
}
|
||||||
|
Arrays.fill(password, 0)
|
||||||
return volume
|
return volume
|
||||||
}
|
}
|
||||||
}.startTask(lifecycleScope) { volume ->
|
}.startTask(lifecycleScope) { volume ->
|
||||||
|
@ -11,7 +11,6 @@ import android.text.TextWatcher
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@ -19,6 +18,7 @@ import androidx.fragment.app.Fragment
|
|||||||
import sushi.hardcore.droidfs.*
|
import sushi.hardcore.droidfs.*
|
||||||
import sushi.hardcore.droidfs.databinding.DialogSdcardErrorBinding
|
import sushi.hardcore.droidfs.databinding.DialogSdcardErrorBinding
|
||||||
import sushi.hardcore.droidfs.databinding.FragmentSelectPathBinding
|
import sushi.hardcore.droidfs.databinding.FragmentSelectPathBinding
|
||||||
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -158,7 +158,7 @@ class SelectPathFragment: Fragment() {
|
|||||||
|
|
||||||
private fun getCurrentVolumePath(): String {
|
private fun getCurrentVolumePath(): String {
|
||||||
return if (binding.switchHiddenVolume.isChecked)
|
return if (binding.switchHiddenVolume.isChecked)
|
||||||
PathUtils.pathJoin(requireContext().filesDir.path, binding.editVolumeName.text.toString())
|
SavedVolume.getHiddenVolumeFullPath(requireContext().filesDir.path, binding.editVolumeName.text.toString())
|
||||||
else
|
else
|
||||||
binding.editVolumeName.text.toString()
|
binding.editVolumeName.text.toString()
|
||||||
}
|
}
|
||||||
@ -190,7 +190,7 @@ class SelectPathFragment: Fragment() {
|
|||||||
if (isHidden) R.string.enter_volume_name else R.string.enter_volume_path,
|
if (isHidden) R.string.enter_volume_name else R.string.enter_volume_path,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
} else if (isHidden && currentVolumeValue.contains("/")) {
|
} else if (isHidden && currentVolumeValue.contains(PathUtils.SEPARATOR)) {
|
||||||
Toast.makeText(requireContext(), R.string.error_slash_in_name, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), R.string.error_slash_in_name, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
val volumePath = getCurrentVolumePath()
|
val volumePath = getCurrentVolumePath()
|
||||||
@ -222,7 +222,8 @@ class SelectPathFragment: Fragment() {
|
|||||||
(activity as AddVolumeActivity).createVolume(volumePath, isHidden)
|
(activity as AddVolumeActivity).createVolume(volumePath, isHidden)
|
||||||
}
|
}
|
||||||
Action.ADD -> {
|
Action.ADD -> {
|
||||||
if (!GocryptfsVolume.isGocryptfsVolume(File(volumePath))) {
|
val volumeType = EncryptedVolume.getVolumeType(volumePath)
|
||||||
|
if (volumeType < 0) {
|
||||||
CustomAlertDialogBuilder(requireContext(), themeValue)
|
CustomAlertDialogBuilder(requireContext(), themeValue)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(R.string.error_not_a_volume)
|
.setMessage(R.string.error_not_a_volume)
|
||||||
@ -232,7 +233,7 @@ class SelectPathFragment: Fragment() {
|
|||||||
val dialog = CustomAlertDialogBuilder(requireContext(), themeValue)
|
val dialog = CustomAlertDialogBuilder(requireContext(), themeValue)
|
||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(R.string.ok) { _, _ -> addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden) }
|
.setPositiveButton(R.string.ok) { _, _ -> addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden, volumeType) }
|
||||||
if (PathUtils.isPathOnExternalStorage(volumePath, requireContext()))
|
if (PathUtils.isPathOnExternalStorage(volumePath, requireContext()))
|
||||||
dialog.setView(
|
dialog.setView(
|
||||||
DialogSdcardErrorBinding.inflate(layoutInflater).apply {
|
DialogSdcardErrorBinding.inflate(layoutInflater).apply {
|
||||||
@ -244,7 +245,7 @@ class SelectPathFragment: Fragment() {
|
|||||||
dialog.setMessage(R.string.add_cant_write_warning)
|
dialog.setMessage(R.string.add_cant_write_warning)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
} else {
|
} else {
|
||||||
addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden)
|
addVolume(if (isHidden) currentVolumeValue else volumePath, isHidden, volumeType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,8 +269,8 @@ class SelectPathFragment: Fragment() {
|
|||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addVolume(volumeName: String, isHidden: Boolean) {
|
private fun addVolume(volumeName: String, isHidden: Boolean, volumeType: Byte) {
|
||||||
volumeDatabase.saveVolume(Volume(volumeName, isHidden))
|
volumeDatabase.saveVolume(SavedVolume(volumeName, isHidden, volumeType))
|
||||||
(activity as AddVolumeActivity).onVolumeAdded(false)
|
(activity as AddVolumeActivity).onVolumeAdded(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,9 +9,9 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
|
||||||
import sushi.hardcore.droidfs.LoadingTask
|
import sushi.hardcore.droidfs.LoadingTask
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@ -33,25 +33,25 @@ object ExternalProvider {
|
|||||||
return previous_content_type
|
return previous_content_type
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exportFile(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String, previous_content_type: String?): Pair<Uri?, String?> {
|
private fun exportFile(context: Context, encryptedVolume: EncryptedVolume, file_path: String, previous_content_type: String?): Pair<Uri?, String?> {
|
||||||
val fileName = File(file_path).name
|
val fileName = File(file_path).name
|
||||||
val tmpFileUri = RestrictedFileProvider.newFile(fileName)
|
val tmpFileUri = RestrictedFileProvider.newFile(fileName)
|
||||||
if (tmpFileUri != null){
|
if (tmpFileUri != null){
|
||||||
storedFiles.add(tmpFileUri)
|
storedFiles.add(tmpFileUri)
|
||||||
if (gocryptfsVolume.exportFile(context, file_path, tmpFileUri)) {
|
if (encryptedVolume.exportFile(context, file_path, tmpFileUri)) {
|
||||||
return Pair(tmpFileUri, getContentType(fileName, previous_content_type))
|
return Pair(tmpFileUri, getContentType(fileName, previous_content_type))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Pair(null, null)
|
return Pair(null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun share(activity: AppCompatActivity, themeValue: String, gocryptfsVolume: GocryptfsVolume, file_paths: List<String>) {
|
fun share(activity: AppCompatActivity, themeValue: String, encryptedVolume: EncryptedVolume, file_paths: List<String>) {
|
||||||
var contentType: String? = null
|
var contentType: String? = null
|
||||||
val uris = ArrayList<Uri>(file_paths.size)
|
val uris = ArrayList<Uri>(file_paths.size)
|
||||||
object : LoadingTask<String?>(activity, themeValue, R.string.loading_msg_export) {
|
object : LoadingTask<String?>(activity, themeValue, R.string.loading_msg_export) {
|
||||||
override suspend fun doTask(): String? {
|
override suspend fun doTask(): String? {
|
||||||
for (path in file_paths) {
|
for (path in file_paths) {
|
||||||
val result = exportFile(activity, gocryptfsVolume, path, contentType)
|
val result = exportFile(activity, encryptedVolume, path, contentType)
|
||||||
contentType = if (result.first != null) {
|
contentType = if (result.first != null) {
|
||||||
uris.add(result.first!!)
|
uris.add(result.first!!)
|
||||||
result.second
|
result.second
|
||||||
@ -83,10 +83,10 @@ object ExternalProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun open(activity: AppCompatActivity, themeValue: String, gocryptfsVolume: GocryptfsVolume, file_path: String) {
|
fun open(activity: AppCompatActivity, themeValue: String, encryptedVolume: EncryptedVolume, file_path: String) {
|
||||||
object : LoadingTask<Intent?>(activity, themeValue, R.string.loading_msg_export) {
|
object : LoadingTask<Intent?>(activity, themeValue, R.string.loading_msg_export) {
|
||||||
override suspend fun doTask(): Intent? {
|
override suspend fun doTask(): Intent? {
|
||||||
val result = exportFile(activity, gocryptfsVolume, file_path, null)
|
val result = exportFile(activity, encryptedVolume, file_path, null)
|
||||||
return if (result.first != null) {
|
return if (result.first != null) {
|
||||||
Intent(Intent.ACTION_VIEW).apply {
|
Intent(Intent.ACTION_VIEW).apply {
|
||||||
setDataAndType(result.first, result.second)
|
setDataAndType(result.first, result.second)
|
||||||
|
@ -31,7 +31,6 @@ import sushi.hardcore.droidfs.ConstValues.isImage
|
|||||||
import sushi.hardcore.droidfs.ConstValues.isPDF
|
import sushi.hardcore.droidfs.ConstValues.isPDF
|
||||||
import sushi.hardcore.droidfs.ConstValues.isText
|
import sushi.hardcore.droidfs.ConstValues.isText
|
||||||
import sushi.hardcore.droidfs.ConstValues.isVideo
|
import sushi.hardcore.droidfs.ConstValues.isVideo
|
||||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.adapters.ExplorerElementAdapter
|
import sushi.hardcore.droidfs.adapters.ExplorerElementAdapter
|
||||||
import sushi.hardcore.droidfs.adapters.OpenAsDialogAdapter
|
import sushi.hardcore.droidfs.adapters.OpenAsDialogAdapter
|
||||||
@ -40,6 +39,8 @@ import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
|||||||
import sushi.hardcore.droidfs.file_operations.FileOperationService
|
import sushi.hardcore.droidfs.file_operations.FileOperationService
|
||||||
import sushi.hardcore.droidfs.file_operations.OperationFile
|
import sushi.hardcore.droidfs.file_operations.OperationFile
|
||||||
import sushi.hardcore.droidfs.file_viewers.*
|
import sushi.hardcore.droidfs.file_viewers.*
|
||||||
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
|
import sushi.hardcore.droidfs.filesystems.Stat
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
||||||
@ -50,7 +51,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
private var foldersFirst = true
|
private var foldersFirst = true
|
||||||
private var mapFolders = true
|
private var mapFolders = true
|
||||||
private var currentSortOrderIndex = 0
|
private var currentSortOrderIndex = 0
|
||||||
protected lateinit var gocryptfsVolume: GocryptfsVolume
|
protected lateinit var encryptedVolume: EncryptedVolume
|
||||||
private lateinit var volumeName: String
|
private lateinit var volumeName: String
|
||||||
private lateinit var explorerViewModel: ExplorerViewModel
|
private lateinit var explorerViewModel: ExplorerViewModel
|
||||||
protected var currentDirectoryPath: String = ""
|
protected var currentDirectoryPath: String = ""
|
||||||
@ -82,8 +83,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
usf_open = sharedPrefs.getBoolean("usf_open", false)
|
usf_open = sharedPrefs.getBoolean("usf_open", false)
|
||||||
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
|
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
|
||||||
volumeName = intent.getStringExtra("volume_name") ?: ""
|
volumeName = intent.getStringExtra("volume_name") ?: ""
|
||||||
val sessionID = intent.getIntExtra("sessionID", -1)
|
encryptedVolume = intent.getParcelableExtra("volume")!!
|
||||||
gocryptfsVolume = GocryptfsVolume(applicationContext, sessionID)
|
|
||||||
sortOrderEntries = resources.getStringArray(R.array.sort_orders_entries)
|
sortOrderEntries = resources.getStringArray(R.array.sort_orders_entries)
|
||||||
sortOrderValues = resources.getStringArray(R.array.sort_orders_values)
|
sortOrderValues = resources.getStringArray(R.array.sort_orders_values)
|
||||||
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
|
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
|
||||||
@ -107,7 +107,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
explorerAdapter = ExplorerElementAdapter(
|
explorerAdapter = ExplorerElementAdapter(
|
||||||
this,
|
this,
|
||||||
if (sharedPrefs.getBoolean("thumbnails", true)) {
|
if (sharedPrefs.getBoolean("thumbnails", true)) {
|
||||||
gocryptfsVolume
|
encryptedVolume
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
},
|
},
|
||||||
@ -139,7 +139,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ExplorerViewModel: ViewModel() {
|
class ExplorerViewModel: ViewModel() {
|
||||||
var currentDirectoryPath = ""
|
var currentDirectoryPath = "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setRecyclerViewLayout() {
|
private fun setRecyclerViewLayout() {
|
||||||
@ -166,7 +166,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||||
val binder = service as FileOperationService.LocalBinder
|
val binder = service as FileOperationService.LocalBinder
|
||||||
fileOperationService = binder.getService()
|
fileOperationService = binder.getService()
|
||||||
binder.setGocryptfsVolume(gocryptfsVolume)
|
binder.setEncryptedVolume(encryptedVolume)
|
||||||
}
|
}
|
||||||
override fun onServiceDisconnected(arg0: ComponentName) {
|
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
private fun startFileViewer(cls: Class<*>, filePath: String){
|
private fun startFileViewer(cls: Class<*>, filePath: String){
|
||||||
val intent = Intent(this, cls).apply {
|
val intent = Intent(this, cls).apply {
|
||||||
putExtra("path", filePath)
|
putExtra("path", filePath)
|
||||||
putExtra("sessionID", gocryptfsVolume.sessionID)
|
putExtra("volume", encryptedVolume)
|
||||||
putExtra("sortOrder", sortOrderValues[currentSortOrderIndex])
|
putExtra("sortOrder", sortOrderValues[currentSortOrderIndex])
|
||||||
}
|
}
|
||||||
isStartingActivity = true
|
isStartingActivity = true
|
||||||
@ -187,7 +187,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
|
|
||||||
private fun openWithExternalApp(fullPath: String){
|
private fun openWithExternalApp(fullPath: String){
|
||||||
isStartingActivity = true
|
isStartingActivity = true
|
||||||
ExternalProvider.open(this, themeValue, gocryptfsVolume, fullPath)
|
ExternalProvider.open(this, themeValue, encryptedVolume, fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showOpenAsDialog(path: String) {
|
private fun showOpenAsDialog(path: String) {
|
||||||
@ -276,11 +276,11 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun recursiveSetSize(directory: ExplorerElement) {
|
private fun recursiveSetSize(directory: ExplorerElement) {
|
||||||
for (child in gocryptfsVolume.listDir(directory.fullPath)) {
|
for (child in encryptedVolume.readDir(directory.fullPath) ?: return) {
|
||||||
if (child.isDirectory) {
|
if (child.isDirectory) {
|
||||||
recursiveSetSize(child)
|
recursiveSetSize(child)
|
||||||
}
|
}
|
||||||
directory.size += child.size
|
directory.stat.size += child.stat.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,11 +301,11 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
|
|
||||||
protected fun setCurrentPath(path: String, onDisplayed: (() -> Unit)? = null) {
|
protected fun setCurrentPath(path: String, onDisplayed: (() -> Unit)? = null) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
explorerElements = gocryptfsVolume.listDir(path)
|
explorerElements = encryptedVolume.readDir(path) ?: return
|
||||||
if (path.isNotEmpty()) { //not root
|
if (path != "/") {
|
||||||
explorerElements.add(
|
explorerElements.add(
|
||||||
0,
|
0,
|
||||||
ExplorerElement("..", (-1).toShort(), parentPath = currentDirectoryPath)
|
ExplorerElement("..", Stat.parentFolderStat(), parentPath = currentDirectoryPath)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,7 +323,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
if (element.isDirectory) {
|
if (element.isDirectory) {
|
||||||
recursiveSetSize(element)
|
recursiveSetSize(element)
|
||||||
}
|
}
|
||||||
totalSize += element.size
|
totalSize += element.stat.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -331,7 +331,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
onDisplayed?.invoke()
|
onDisplayed?.invoke()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
displayExplorerElements(explorerElements.filter { !it.isParentFolder }.sumOf { it.size })
|
displayExplorerElements(explorerElements.filter { !it.isParentFolder }.sumOf { it.stat.size })
|
||||||
onDisplayed?.invoke()
|
onDisplayed?.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,7 +362,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
if (folderName.isEmpty()) {
|
if (folderName.isEmpty()) {
|
||||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
if (!gocryptfsVolume.mkdir(PathUtils.pathJoin(currentDirectoryPath, folderName))) {
|
if (!encryptedVolume.mkdir(PathUtils.pathJoin(currentDirectoryPath, folderName))) {
|
||||||
CustomAlertDialogBuilder(this, themeValue)
|
CustomAlertDialogBuilder(this, themeValue)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(R.string.error_mkdir)
|
.setMessage(R.string.error_mkdir)
|
||||||
@ -382,27 +382,33 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected fun checkPathOverwrite(items: ArrayList<OperationFile>, dstDirectoryPath: String, callback: (ArrayList<OperationFile>?) -> Unit) {
|
protected fun checkPathOverwrite(items: ArrayList<OperationFile>, dstDirectoryPath: String, callback: (ArrayList<OperationFile>?) -> Unit) {
|
||||||
val srcDirectoryPath = items[0].explorerElement.parentPath
|
val srcDirectoryPath = items[0].parentPath
|
||||||
var ready = true
|
var ready = true
|
||||||
for (i in 0 until items.size) {
|
for (i in 0 until items.size) {
|
||||||
val testDstPath: String
|
val testDstPath: String
|
||||||
if (items[i].dstPath == null){
|
if (items[i].dstPath == null){
|
||||||
testDstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].explorerElement.fullPath))
|
testDstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].srcPath))
|
||||||
if (gocryptfsVolume.pathExists(testDstPath)){
|
if (encryptedVolume.pathExists(testDstPath)) {
|
||||||
ready = false
|
ready = false
|
||||||
} else {
|
} else {
|
||||||
items[i].dstPath = testDstPath
|
items[i].dstPath = testDstPath
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
testDstPath = items[i].dstPath!!
|
testDstPath = items[i].dstPath!!
|
||||||
if (gocryptfsVolume.pathExists(testDstPath) && !items[i].overwriteConfirmed){
|
if (encryptedVolume.pathExists(testDstPath) && !items[i].overwriteConfirmed) {
|
||||||
ready = false
|
ready = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ready){
|
if (!ready){
|
||||||
CustomAlertDialogBuilder(this, themeValue)
|
CustomAlertDialogBuilder(this, themeValue)
|
||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setMessage(getString(if (items[i].explorerElement.isDirectory){R.string.dir_overwrite_question} else {R.string.file_overwrite_question}, testDstPath))
|
.setMessage(getString(
|
||||||
|
if (items[i].isDirectory) {
|
||||||
|
R.string.dir_overwrite_question
|
||||||
|
} else {
|
||||||
|
R.string.file_overwrite_question
|
||||||
|
}, testDstPath
|
||||||
|
))
|
||||||
.setPositiveButton(R.string.yes) {_, _ ->
|
.setPositiveButton(R.string.yes) {_, _ ->
|
||||||
items[i].dstPath = testDstPath
|
items[i].dstPath = testDstPath
|
||||||
items[i].overwriteConfirmed = true
|
items[i].overwriteConfirmed = true
|
||||||
@ -410,17 +416,17 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
}
|
}
|
||||||
.setNegativeButton(R.string.no) { _, _ ->
|
.setNegativeButton(R.string.no) { _, _ ->
|
||||||
with(EditTextDialog(this, R.string.enter_new_name) {
|
with(EditTextDialog(this, R.string.enter_new_name) {
|
||||||
items[i].dstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].explorerElement.parentPath), it)
|
items[i].dstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].parentPath), it)
|
||||||
if (items[i].explorerElement.isDirectory){
|
if (items[i].isDirectory) {
|
||||||
for (j in 0 until items.size){
|
for (j in 0 until items.size){
|
||||||
if (PathUtils.isChildOf(items[j].explorerElement.fullPath, items[i].explorerElement.fullPath)){
|
if (PathUtils.isChildOf(items[j].srcPath, items[i].srcPath)) {
|
||||||
items[j].dstPath = PathUtils.pathJoin(items[i].dstPath!!, PathUtils.getRelativePath(items[i].explorerElement.fullPath, items[j].explorerElement.fullPath))
|
items[j].dstPath = PathUtils.pathJoin(items[i].dstPath!!, PathUtils.getRelativePath(items[i].srcPath, items[j].srcPath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkPathOverwrite(items, dstDirectoryPath, callback)
|
checkPathOverwrite(items, dstDirectoryPath, callback)
|
||||||
}) {
|
}) {
|
||||||
setSelectedText(items[i].explorerElement.name)
|
setSelectedText(items[i].name)
|
||||||
setOnCancelListener{
|
setOnCancelListener{
|
||||||
callback(null)
|
callback(null)
|
||||||
}
|
}
|
||||||
@ -452,7 +458,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
items.clear()
|
items.clear()
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
items.add(OperationFile.fromExplorerElement(ExplorerElement(fileName, 1, parentPath = currentDirectoryPath)))
|
items.add(OperationFile(PathUtils.pathJoin(currentDirectoryPath, fileName), Stat.S_IFREG))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (items.size > 0) {
|
if (items.size > 0) {
|
||||||
@ -475,7 +481,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
if (new_name.isEmpty()) {
|
if (new_name.isEmpty()) {
|
||||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
if (!gocryptfsVolume.rename(PathUtils.pathJoin(currentDirectoryPath, old_name), PathUtils.pathJoin(currentDirectoryPath, new_name))) {
|
if (!encryptedVolume.rename(PathUtils.pathJoin(currentDirectoryPath, old_name), PathUtils.pathJoin(currentDirectoryPath, new_name))) {
|
||||||
CustomAlertDialogBuilder(this, themeValue)
|
CustomAlertDialogBuilder(this, themeValue)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(getString(R.string.rename_failed, old_name))
|
.setMessage(getString(R.string.rename_failed, old_name))
|
||||||
@ -587,8 +593,8 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun closeVolumeOnDestroy() {
|
protected open fun closeVolumeOnDestroy() {
|
||||||
if (!gocryptfsVolume.isClosed()){
|
if (!encryptedVolume.isClosed()) {
|
||||||
gocryptfsVolume.close()
|
encryptedVolume.close()
|
||||||
}
|
}
|
||||||
RestrictedFileProvider.wipeAll(this) //additional security
|
RestrictedFileProvider.wipeAll(this) //additional security
|
||||||
}
|
}
|
||||||
@ -616,7 +622,7 @@ open class BaseExplorerActivity : BaseActivity(), ExplorerElementAdapter.Listene
|
|||||||
if (isCreating){
|
if (isCreating){
|
||||||
isCreating = false
|
isCreating = false
|
||||||
} else {
|
} else {
|
||||||
if (gocryptfsVolume.isClosed()){
|
if (encryptedVolume.isClosed()) {
|
||||||
finish()
|
finish()
|
||||||
} else {
|
} else {
|
||||||
isStartingActivity = false
|
isStartingActivity = false
|
||||||
|
@ -11,17 +11,17 @@ import androidx.documentfile.provider.DocumentFile
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import sushi.hardcore.droidfs.CameraActivity
|
import sushi.hardcore.droidfs.CameraActivity
|
||||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
|
||||||
import sushi.hardcore.droidfs.MainActivity
|
import sushi.hardcore.droidfs.MainActivity
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.adapters.IconTextDialogAdapter
|
import sushi.hardcore.droidfs.adapters.IconTextDialogAdapter
|
||||||
import sushi.hardcore.droidfs.content_providers.ExternalProvider
|
import sushi.hardcore.droidfs.content_providers.ExternalProvider
|
||||||
import sushi.hardcore.droidfs.databinding.ActivityExplorerBinding
|
import sushi.hardcore.droidfs.databinding.ActivityExplorerBinding
|
||||||
import sushi.hardcore.droidfs.file_operations.OperationFile
|
import sushi.hardcore.droidfs.file_operations.OperationFile
|
||||||
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
|
import sushi.hardcore.droidfs.filesystems.Stat
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
import sushi.hardcore.droidfs.widgets.EditTextDialog
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class ExplorerActivity : BaseExplorerActivity() {
|
class ExplorerActivity : BaseExplorerActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
@ -36,8 +36,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
private val pickFromOtherVolumes = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private val pickFromOtherVolumes = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
result.data?.let { resultIntent ->
|
result.data?.let { resultIntent ->
|
||||||
val remoteSessionID = resultIntent.getIntExtra("sessionID", -1)
|
val remoteEncryptedVolume = resultIntent.getParcelableExtra<EncryptedVolume>("volume")!!
|
||||||
val remoteGocryptfsVolume = GocryptfsVolume(applicationContext, remoteSessionID)
|
|
||||||
val path = resultIntent.getStringExtra("path")
|
val path = resultIntent.getStringExtra("path")
|
||||||
val operationFiles = ArrayList<OperationFile>()
|
val operationFiles = ArrayList<OperationFile>()
|
||||||
if (path == null){ //multiples elements
|
if (path == null){ //multiples elements
|
||||||
@ -46,12 +45,10 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
if (types != null && paths != null){
|
if (types != null && paths != null){
|
||||||
for (i in paths.indices) {
|
for (i in paths.indices) {
|
||||||
operationFiles.add(
|
operationFiles.add(
|
||||||
OperationFile.fromExplorerElement(
|
OperationFile(paths[i], types[i])
|
||||||
ExplorerElement(File(paths[i]).name, types[i].toShort(), parentPath = PathUtils.getParentPath(paths[i]))
|
|
||||||
)
|
)
|
||||||
)
|
if (types[i] == Stat.S_IFDIR) {
|
||||||
if (types[i] == 0){ //directory
|
remoteEncryptedVolume.recursiveMapFiles(paths[i])?.forEach {
|
||||||
remoteGocryptfsVolume.recursiveMapFiles(paths[i]).forEach {
|
|
||||||
operationFiles.add(OperationFile.fromExplorerElement(it))
|
operationFiles.add(OperationFile.fromExplorerElement(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,18 +56,16 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
operationFiles.add(
|
operationFiles.add(
|
||||||
OperationFile.fromExplorerElement(
|
OperationFile(path, Stat.S_IFREG)
|
||||||
ExplorerElement(File(path).name, 1, parentPath = PathUtils.getParentPath(path))
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (operationFiles.size > 0){
|
if (operationFiles.size > 0){
|
||||||
checkPathOverwrite(operationFiles, currentDirectoryPath) { items ->
|
checkPathOverwrite(operationFiles, currentDirectoryPath) { items ->
|
||||||
if (items == null) {
|
if (items == null) {
|
||||||
remoteGocryptfsVolume.close()
|
remoteEncryptedVolume.close()
|
||||||
} else {
|
} else {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val failedItem = fileOperationService.copyElements(items, remoteGocryptfsVolume)
|
val failedItem = fileOperationService.copyElements(items, remoteEncryptedVolume)
|
||||||
if (failedItem == null) {
|
if (failedItem == null) {
|
||||||
Toast.makeText(this@ExplorerActivity, R.string.success_import, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@ExplorerActivity, R.string.success_import, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
@ -81,12 +76,12 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
setCurrentPath(currentDirectoryPath)
|
setCurrentPath(currentDirectoryPath)
|
||||||
remoteGocryptfsVolume.close()
|
remoteEncryptedVolume.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
remoteGocryptfsVolume.close()
|
remoteEncryptedVolume.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,7 +115,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
private val pickImportDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { rootUri ->
|
private val pickImportDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { rootUri ->
|
||||||
rootUri?.let {
|
rootUri?.let {
|
||||||
val tree = DocumentFile.fromTreeUri(this, it)!! //non-null after Lollipop
|
val tree = DocumentFile.fromTreeUri(this, it)!! //non-null after Lollipop
|
||||||
val operation = OperationFile.fromExplorerElement(ExplorerElement(tree.name!!, 0, parentPath = currentDirectoryPath))
|
val operation = OperationFile(PathUtils.pathJoin(currentDirectoryPath, tree.name!!), Stat.S_IFDIR)
|
||||||
checkPathOverwrite(arrayListOf(operation), currentDirectoryPath) { checkedOperation ->
|
checkPathOverwrite(arrayListOf(operation), currentDirectoryPath) { checkedOperation ->
|
||||||
checkedOperation?.let {
|
checkedOperation?.let {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
@ -192,7 +187,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
"importFromOtherVolumes" -> {
|
"importFromOtherVolumes" -> {
|
||||||
val intent = Intent(this, MainActivity::class.java)
|
val intent = Intent(this, MainActivity::class.java)
|
||||||
intent.action = "pick"
|
intent.action = "pick"
|
||||||
intent.putExtra("sessionID", gocryptfsVolume.sessionID)
|
intent.putExtra("volume", encryptedVolume)
|
||||||
isStartingActivity = true
|
isStartingActivity = true
|
||||||
pickFromOtherVolumes.launch(intent)
|
pickFromOtherVolumes.launch(intent)
|
||||||
}
|
}
|
||||||
@ -215,7 +210,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
"camera" -> {
|
"camera" -> {
|
||||||
val intent = Intent(this, CameraActivity::class.java)
|
val intent = Intent(this, CameraActivity::class.java)
|
||||||
intent.putExtra("path", currentDirectoryPath)
|
intent.putExtra("path", currentDirectoryPath)
|
||||||
intent.putExtra("sessionID", gocryptfsVolume.sessionID)
|
intent.putExtra("volume", encryptedVolume)
|
||||||
isStartingActivity = true
|
isStartingActivity = true
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
@ -241,15 +236,15 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
val filePath = PathUtils.pathJoin(currentDirectoryPath, fileName)
|
val filePath = PathUtils.pathJoin(currentDirectoryPath, fileName)
|
||||||
val handleID = gocryptfsVolume.openWriteMode(filePath) //don't check overwrite because openWriteMode open in read-write (doesn't erase content)
|
val handleID = encryptedVolume.openFile(filePath)
|
||||||
if (handleID == -1) {
|
if (handleID == -1L) {
|
||||||
CustomAlertDialogBuilder(this, themeValue)
|
CustomAlertDialogBuilder(this, themeValue)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(R.string.file_creation_failed)
|
.setMessage(R.string.file_creation_failed)
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
} else {
|
} else {
|
||||||
gocryptfsVolume.closeFile(handleID)
|
encryptedVolume.closeFile(handleID)
|
||||||
setCurrentPath(currentDirectoryPath)
|
setCurrentPath(currentDirectoryPath)
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
@ -312,7 +307,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
for (i in explorerAdapter.selectedItems){
|
for (i in explorerAdapter.selectedItems){
|
||||||
itemsToProcess.add(OperationFile.fromExplorerElement(explorerElements[i]))
|
itemsToProcess.add(OperationFile.fromExplorerElement(explorerElements[i]))
|
||||||
if (explorerElements[i].isDirectory){
|
if (explorerElements[i].isDirectory){
|
||||||
gocryptfsVolume.recursiveMapFiles(explorerElements[i].fullPath).forEach {
|
encryptedVolume.recursiveMapFiles(explorerElements[i].fullPath)?.forEach {
|
||||||
itemsToProcess.add(OperationFile.fromExplorerElement(it))
|
itemsToProcess.add(OperationFile.fromExplorerElement(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -346,11 +341,11 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
}
|
}
|
||||||
} else if (currentItemAction == ItemsActions.MOVE){
|
} else if (currentItemAction == ItemsActions.MOVE){
|
||||||
itemsToProcess.forEach {
|
itemsToProcess.forEach {
|
||||||
it.dstPath = PathUtils.pathJoin(currentDirectoryPath, it.explorerElement.name)
|
it.dstPath = PathUtils.pathJoin(currentDirectoryPath, it.name)
|
||||||
it.overwriteConfirmed = false // reset the field in case of a previous cancelled move
|
it.overwriteConfirmed = false // reset the field in case of a previous cancelled move
|
||||||
}
|
}
|
||||||
val toMove = ArrayList<OperationFile>(itemsToProcess.size)
|
val toMove = ArrayList<OperationFile>(itemsToProcess.size)
|
||||||
val toClean = ArrayList<ExplorerElement>()
|
val toClean = ArrayList<String>()
|
||||||
prepareFilesForMove(
|
prepareFilesForMove(
|
||||||
itemsToProcess,
|
itemsToProcess,
|
||||||
toMove,
|
toMove,
|
||||||
@ -398,7 +393,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
paths.add(explorerElements[i].fullPath)
|
paths.add(explorerElements[i].fullPath)
|
||||||
}
|
}
|
||||||
isStartingActivity = true
|
isStartingActivity = true
|
||||||
ExternalProvider.share(this, themeValue, gocryptfsVolume, paths)
|
ExternalProvider.share(this, themeValue, encryptedVolume, paths)
|
||||||
unselectAll()
|
unselectAll()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -418,12 +413,12 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
*/
|
*/
|
||||||
private fun checkMoveOverwrite(items: List<OperationFile>, callback: (List<OperationFile>?) -> Unit) {
|
private fun checkMoveOverwrite(items: List<OperationFile>, callback: (List<OperationFile>?) -> Unit) {
|
||||||
for (item in items) {
|
for (item in items) {
|
||||||
if (gocryptfsVolume.pathExists(item.dstPath!!) && !item.overwriteConfirmed) {
|
if (encryptedVolume.pathExists(item.dstPath!!) && !item.overwriteConfirmed) {
|
||||||
CustomAlertDialogBuilder(this, themeValue)
|
CustomAlertDialogBuilder(this, themeValue)
|
||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setMessage(
|
.setMessage(
|
||||||
getString(
|
getString(
|
||||||
if (item.explorerElement.isDirectory) {
|
if (item.isDirectory) {
|
||||||
R.string.dir_overwrite_question
|
R.string.dir_overwrite_question
|
||||||
} else {
|
} else {
|
||||||
R.string.file_overwrite_question
|
R.string.file_overwrite_question
|
||||||
@ -440,7 +435,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
item.dstPath = PathUtils.pathJoin(PathUtils.getParentPath(item.dstPath!!), it)
|
item.dstPath = PathUtils.pathJoin(PathUtils.getParentPath(item.dstPath!!), it)
|
||||||
checkMoveOverwrite(items, callback)
|
checkMoveOverwrite(items, callback)
|
||||||
}) {
|
}) {
|
||||||
setSelectedText(item.explorerElement.name)
|
setSelectedText(item.name)
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -463,24 +458,24 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
private fun prepareFilesForMove(
|
private fun prepareFilesForMove(
|
||||||
items: List<OperationFile>,
|
items: List<OperationFile>,
|
||||||
toMove: ArrayList<OperationFile>,
|
toMove: ArrayList<OperationFile>,
|
||||||
toClean: ArrayList<ExplorerElement>,
|
toClean: ArrayList<String>,
|
||||||
onReady: () -> Unit
|
onReady: () -> Unit
|
||||||
) {
|
) {
|
||||||
checkMoveOverwrite(items) { checkedItems ->
|
checkMoveOverwrite(items) { checkedItems ->
|
||||||
checkedItems?.let {
|
checkedItems?.let {
|
||||||
for (item in checkedItems) {
|
for (item in checkedItems) {
|
||||||
if (!item.overwriteConfirmed || !item.explorerElement.isDirectory) {
|
if (!item.overwriteConfirmed || !item.isDirectory) {
|
||||||
toMove.add(item)
|
toMove.add(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val toCheck = mutableListOf<OperationFile>()
|
val toCheck = mutableListOf<OperationFile>()
|
||||||
for (item in checkedItems) {
|
for (item in checkedItems) {
|
||||||
if (item.overwriteConfirmed && item.explorerElement.isDirectory) {
|
if (item.overwriteConfirmed && item.isDirectory) {
|
||||||
val children = gocryptfsVolume.listDir(item.explorerElement.fullPath)
|
val children = encryptedVolume.readDir(item.srcPath)
|
||||||
toCheck.addAll(children.map {
|
children?.map {
|
||||||
OperationFile(it, PathUtils.pathJoin(item.dstPath!!, it.name))
|
OperationFile(it.fullPath, it.stat.type, PathUtils.pathJoin(item.dstPath!!, it.name))
|
||||||
})
|
}?.let { toCheck.addAll(it) }
|
||||||
toClean.add(item.explorerElement)
|
toClean.add(item.srcPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (toCheck.isEmpty()) {
|
if (toCheck.isEmpty()) {
|
||||||
@ -514,10 +509,10 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
val element = explorerAdapter.explorerElements[i]
|
val element = explorerAdapter.explorerElements[i]
|
||||||
val fullPath = PathUtils.pathJoin(currentDirectoryPath, element.name)
|
val fullPath = PathUtils.pathJoin(currentDirectoryPath, element.name)
|
||||||
if (element.isDirectory) {
|
if (element.isDirectory) {
|
||||||
val result = gocryptfsVolume.recursiveRemoveDirectory(fullPath)
|
val result = encryptedVolume.recursiveRemoveDirectory(fullPath)
|
||||||
result?.let{ failedItem = it }
|
result?.let{ failedItem = it }
|
||||||
} else {
|
} else {
|
||||||
if (!gocryptfsVolume.removeFile(fullPath)) {
|
if (!encryptedVolume.deleteFile(fullPath)) {
|
||||||
failedItem = fullPath
|
failedItem = fullPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,16 +5,15 @@ import android.content.Intent
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class ExplorerActivityPick : BaseExplorerActivity() {
|
class ExplorerActivityPick : BaseExplorerActivity() {
|
||||||
private var resultIntent = Intent()
|
private var resultIntent = Intent()
|
||||||
private var isFinishingIntentionally = false
|
private var isFinishingIntentionally = false
|
||||||
override fun init() {
|
override fun init() {
|
||||||
super.init()
|
super.init()
|
||||||
resultIntent.putExtra("sessionID", gocryptfsVolume.sessionID)
|
resultIntent.putExtra("volume", encryptedVolume)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindFileOperationService() {
|
override fun bindFileOperationService() {
|
||||||
@ -65,7 +64,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
|||||||
for (i in explorerAdapter.selectedItems) {
|
for (i in explorerAdapter.selectedItems) {
|
||||||
val e = explorerElements[i]
|
val e = explorerElements[i]
|
||||||
paths.add(PathUtils.pathJoin(currentDirectoryPath, e.name))
|
paths.add(PathUtils.pathJoin(currentDirectoryPath, e.name))
|
||||||
types.add(e.elementType.toInt())
|
types.add(e.stat.type)
|
||||||
}
|
}
|
||||||
resultIntent.putStringArrayListExtra("paths", paths)
|
resultIntent.putStringArrayListExtra("paths", paths)
|
||||||
resultIntent.putIntegerArrayListExtra("types", types)
|
resultIntent.putIntegerArrayListExtra("types", types)
|
||||||
@ -84,10 +83,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
|||||||
|
|
||||||
override fun closeVolumeOnDestroy() {
|
override fun closeVolumeOnDestroy() {
|
||||||
if (!isFinishingIntentionally && !usf_keep_open){
|
if (!isFinishingIntentionally && !usf_keep_open){
|
||||||
val sessionID = intent.getIntExtra("originalSessionID", -1)
|
intent.getParcelableExtra<EncryptedVolume>("destinationVolume")?.let { it.close() }
|
||||||
if (sessionID != -1){
|
|
||||||
GocryptfsVolume(applicationContext, sessionID).close()
|
|
||||||
}
|
|
||||||
super.closeVolumeOnDestroy()
|
super.closeVolumeOnDestroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,31 @@
|
|||||||
package sushi.hardcore.droidfs.explorers
|
package sushi.hardcore.droidfs.explorers
|
||||||
|
|
||||||
import sushi.hardcore.droidfs.collation.getCollationKeyForFileName
|
import sushi.hardcore.droidfs.collation.getCollationKeyForFileName
|
||||||
|
import sushi.hardcore.droidfs.filesystems.Stat
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class ExplorerElement(val name: String, val elementType: Short, var size: Long = -1, mTime: Long = -1, val parentPath: String) {
|
class ExplorerElement(val name: String, val stat: Stat, val parentPath: String) {
|
||||||
val mTime = Date((mTime * 1000).toString().toLong())
|
|
||||||
val fullPath: String = PathUtils.pathJoin(parentPath, name)
|
val fullPath: String = PathUtils.pathJoin(parentPath, name)
|
||||||
val collationKey = Collator.getInstance().getCollationKeyForFileName(fullPath)
|
val collationKey = Collator.getInstance().getCollationKeyForFileName(fullPath)
|
||||||
|
|
||||||
val isDirectory: Boolean
|
val isDirectory: Boolean
|
||||||
get() = elementType.toInt() == DIRECTORY_TYPE
|
get() = stat.type == Stat.S_IFDIR
|
||||||
|
|
||||||
val isParentFolder: Boolean
|
|
||||||
get() = elementType.toInt() == PARENT_FOLDER_TYPE
|
|
||||||
|
|
||||||
val isRegularFile: Boolean
|
val isRegularFile: Boolean
|
||||||
get() = elementType.toInt() == REGULAR_FILE_TYPE
|
get() = stat.type == Stat.S_IFREG
|
||||||
|
|
||||||
|
val isSymlink: Boolean
|
||||||
|
get() = stat.type == Stat.S_IFLNK
|
||||||
|
|
||||||
|
val isParentFolder: Boolean
|
||||||
|
get() = stat.type == Stat.PARENT_FOLDER_TYPE
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val DIRECTORY_TYPE = 0
|
|
||||||
const val PARENT_FOLDER_TYPE = -1
|
|
||||||
const val REGULAR_FILE_TYPE = 1
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
//this function is needed because I had some problems calling the constructor from JNI, probably due to arguments with default values
|
//this function is needed because I had some problems calling the constructor from JNI, probably due to arguments with default values
|
||||||
fun new(name: String, elementType: Short, size: Long, mTime: Long, parentPath: String): ExplorerElement {
|
fun new(name: String, elementType: Int, size: Long, mTime: Long, parentPath: String): ExplorerElement {
|
||||||
return ExplorerElement(name, elementType, size, mTime, parentPath)
|
return ExplorerElement(name, Stat(elementType, size, mTime*1000), parentPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun foldersFirst(a: ExplorerElement, b: ExplorerElement, default: () -> Int): Int {
|
private fun foldersFirst(a: ExplorerElement, b: ExplorerElement, default: () -> Int): Int {
|
||||||
@ -61,12 +59,12 @@ class ExplorerElement(val name: String, val elementType: Short, var size: Long =
|
|||||||
}
|
}
|
||||||
"size" -> {
|
"size" -> {
|
||||||
explorerElements.sortWith { a, b ->
|
explorerElements.sortWith { a, b ->
|
||||||
doSort(a, b, foldersFirst) { (a.size - b.size).toInt() }
|
doSort(a, b, foldersFirst) { (a.stat.size - b.stat.size).toInt() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"date" -> {
|
"date" -> {
|
||||||
explorerElements.sortWith { a, b ->
|
explorerElements.sortWith { a, b ->
|
||||||
doSort(a, b, foldersFirst) { a.mTime.compareTo(b.mTime) }
|
doSort(a, b, foldersFirst) { a.stat.mTime.compareTo(b.stat.mTime) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"name_desc" -> {
|
"name_desc" -> {
|
||||||
@ -76,12 +74,12 @@ class ExplorerElement(val name: String, val elementType: Short, var size: Long =
|
|||||||
}
|
}
|
||||||
"size_desc" -> {
|
"size_desc" -> {
|
||||||
explorerElements.sortWith { a, b ->
|
explorerElements.sortWith { a, b ->
|
||||||
doSort(a, b, foldersFirst) { (b.size - a.size).toInt() }
|
doSort(a, b, foldersFirst) { (b.stat.size - a.stat.size).toInt() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"date_desc" -> {
|
"date_desc" -> {
|
||||||
explorerElements.sortWith { a, b ->
|
explorerElements.sortWith { a, b ->
|
||||||
doSort(a, b, foldersFirst) { b.mTime.compareTo(a.mTime) }
|
doSort(a, b, foldersFirst) { b.stat.mTime.compareTo(a.stat.mTime) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,10 @@ import androidx.core.app.NotificationCompat
|
|||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
import sushi.hardcore.droidfs.ConstValues
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||||
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.util.Wiper
|
import sushi.hardcore.droidfs.util.Wiper
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -29,15 +30,15 @@ class FileOperationService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val binder = LocalBinder()
|
private val binder = LocalBinder()
|
||||||
private lateinit var gocryptfsVolume: GocryptfsVolume
|
private lateinit var encryptedVolume: EncryptedVolume
|
||||||
private lateinit var notificationManager: NotificationManagerCompat
|
private lateinit var notificationManager: NotificationManagerCompat
|
||||||
private val tasks = HashMap<Int, Job>()
|
private val tasks = HashMap<Int, Job>()
|
||||||
private var lastNotificationId = 0
|
private var lastNotificationId = 0
|
||||||
|
|
||||||
inner class LocalBinder : Binder() {
|
inner class LocalBinder : Binder() {
|
||||||
fun getService(): FileOperationService = this@FileOperationService
|
fun getService(): FileOperationService = this@FileOperationService
|
||||||
fun setGocryptfsVolume(g: GocryptfsVolume) {
|
fun setEncryptedVolume(volume: EncryptedVolume) {
|
||||||
gocryptfsVolume = g
|
encryptedVolume = volume
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,17 +122,17 @@ class FileOperationService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyFile(srcPath: String, dstPath: String, remoteGocryptfsVolume: GocryptfsVolume = gocryptfsVolume): Boolean {
|
private fun copyFile(srcPath: String, dstPath: String, remoteEncryptedVolume: EncryptedVolume = encryptedVolume): Boolean {
|
||||||
var success = true
|
var success = true
|
||||||
val srcHandleId = remoteGocryptfsVolume.openReadMode(srcPath)
|
val srcFileHandle = remoteEncryptedVolume.openFile(srcPath)
|
||||||
if (srcHandleId != -1){
|
if (srcFileHandle != -1L) {
|
||||||
val dstHandleId = gocryptfsVolume.openWriteMode(dstPath)
|
val dstFileHandle = encryptedVolume.openFile(dstPath)
|
||||||
if (dstHandleId != -1){
|
if (dstFileHandle != -1L) {
|
||||||
var offset: Long = 0
|
var offset: Long = 0
|
||||||
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
|
val ioBuffer = ByteArray(ConstValues.IO_BUFF_SIZE)
|
||||||
var length: Int
|
var length: Int
|
||||||
while (remoteGocryptfsVolume.readFile(srcHandleId, offset, ioBuffer).also { length = it } > 0) {
|
while (remoteEncryptedVolume.read(srcFileHandle, ioBuffer, offset).also { length = it } > 0) {
|
||||||
val written = gocryptfsVolume.writeFile(dstHandleId, offset, ioBuffer, length).toLong()
|
val written = encryptedVolume.write(dstFileHandle, offset, ioBuffer, length).toLong()
|
||||||
if (written == length.toLong()) {
|
if (written == length.toLong()) {
|
||||||
offset += written
|
offset += written
|
||||||
} else {
|
} else {
|
||||||
@ -139,11 +140,11 @@ class FileOperationService : Service() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gocryptfsVolume.closeFile(dstHandleId)
|
encryptedVolume.closeFile(dstFileHandle)
|
||||||
} else {
|
} else {
|
||||||
success = false
|
success = false
|
||||||
}
|
}
|
||||||
remoteGocryptfsVolume.closeFile(srcHandleId)
|
remoteEncryptedVolume.closeFile(srcFileHandle)
|
||||||
} else {
|
} else {
|
||||||
success = false
|
success = false
|
||||||
}
|
}
|
||||||
@ -152,22 +153,22 @@ class FileOperationService : Service() {
|
|||||||
|
|
||||||
suspend fun copyElements(
|
suspend fun copyElements(
|
||||||
items: ArrayList<OperationFile>,
|
items: ArrayList<OperationFile>,
|
||||||
remoteGocryptfsVolume: GocryptfsVolume = gocryptfsVolume
|
remoteEncryptedVolume: EncryptedVolume = encryptedVolume
|
||||||
): String? = coroutineScope {
|
): String? = coroutineScope {
|
||||||
val notification = showNotification(R.string.file_op_copy_msg, items.size)
|
val notification = showNotification(R.string.file_op_copy_msg, items.size)
|
||||||
val task = async {
|
val task = async {
|
||||||
var failedItem: String? = null
|
var failedItem: String? = null
|
||||||
for (i in 0 until items.size) {
|
for (i in 0 until items.size) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
if (items[i].explorerElement.isDirectory) {
|
if (items[i].isDirectory) {
|
||||||
if (!gocryptfsVolume.pathExists(items[i].dstPath!!)) {
|
if (!encryptedVolume.pathExists(items[i].dstPath!!)) {
|
||||||
if (!gocryptfsVolume.mkdir(items[i].dstPath!!)) {
|
if (!encryptedVolume.mkdir(items[i].dstPath!!)) {
|
||||||
failedItem = items[i].explorerElement.fullPath
|
failedItem = items[i].srcPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!copyFile(items[i].explorerElement.fullPath, items[i].dstPath!!, remoteGocryptfsVolume)) {
|
if (!copyFile(items[i].srcPath, items[i].dstPath!!, remoteEncryptedVolume)) {
|
||||||
failedItem = items[i].explorerElement.fullPath
|
failedItem = items[i].srcPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,23 +184,23 @@ class FileOperationService : Service() {
|
|||||||
waitForTask(notification, task).failedItem
|
waitForTask(notification, task).failedItem
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun moveElements(toMove: List<OperationFile>, toClean: List<ExplorerElement>): String? = coroutineScope {
|
suspend fun moveElements(toMove: List<OperationFile>, toClean: List<String>): String? = coroutineScope {
|
||||||
val notification = showNotification(R.string.file_op_move_msg, toMove.size)
|
val notification = showNotification(R.string.file_op_move_msg, toMove.size)
|
||||||
val task = async(Dispatchers.IO) {
|
val task = async(Dispatchers.IO) {
|
||||||
val total = toMove.size+toClean.size
|
val total = toMove.size+toClean.size
|
||||||
var failedItem: String? = null
|
var failedItem: String? = null
|
||||||
for ((i, item) in toMove.withIndex()) {
|
for ((i, item) in toMove.withIndex()) {
|
||||||
if (!gocryptfsVolume.rename(item.explorerElement.fullPath, item.dstPath!!)) {
|
if (!encryptedVolume.rename(item.srcPath, item.dstPath!!)) {
|
||||||
failedItem = item.explorerElement.fullPath
|
failedItem = item.srcPath
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
updateNotificationProgress(notification, i+1, total)
|
updateNotificationProgress(notification, i+1, total)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (failedItem == null) {
|
if (failedItem == null) {
|
||||||
for ((i, folder) in toClean.asReversed().withIndex()) {
|
for ((i, folderPath) in toClean.asReversed().withIndex()) {
|
||||||
if (!gocryptfsVolume.rmdir(folder.fullPath)) {
|
if (!encryptedVolume.rmdir(folderPath)) {
|
||||||
failedItem = folder.fullPath
|
failedItem = folderPath
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
updateNotificationProgress(notification, toMove.size+i+1, total)
|
updateNotificationProgress(notification, toMove.size+i+1, total)
|
||||||
@ -221,7 +222,7 @@ class FileOperationService : Service() {
|
|||||||
for (i in dstPaths.indices) {
|
for (i in dstPaths.indices) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
if (!gocryptfsVolume.importFile(this@FileOperationService, uris[i], dstPaths[i])) {
|
if (!encryptedVolume.importFile(this@FileOperationService, uris[i], dstPaths[i])) {
|
||||||
failedIndex = i
|
failedIndex = i
|
||||||
}
|
}
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
@ -301,7 +302,7 @@ class FileOperationService : Service() {
|
|||||||
|
|
||||||
// create destination folders so the new files can use them
|
// create destination folders so the new files can use them
|
||||||
for (dir in dstDirs) {
|
for (dir in dstDirs) {
|
||||||
if (!gocryptfsVolume.mkdir(dir)) {
|
if (!encryptedVolume.mkdir(dir)) {
|
||||||
failedItem = dir
|
failedItem = dir
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -345,7 +346,7 @@ class FileOperationService : Service() {
|
|||||||
return if (outputStream == null) {
|
return if (outputStream == null) {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
gocryptfsVolume.exportFile(srcPath, outputStream)
|
encryptedVolume.exportFile(srcPath, outputStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,7 +356,7 @@ class FileOperationService : Service() {
|
|||||||
scope: CoroutineScope
|
scope: CoroutineScope
|
||||||
): String? {
|
): String? {
|
||||||
treeDocumentFile.createDirectory(File(plain_directory_path).name)?.let { childTree ->
|
treeDocumentFile.createDirectory(File(plain_directory_path).name)?.let { childTree ->
|
||||||
val explorerElements = gocryptfsVolume.listDir(plain_directory_path)
|
val explorerElements = encryptedVolume.readDir(plain_directory_path) ?: return null
|
||||||
for (e in explorerElements) {
|
for (e in explorerElements) {
|
||||||
if (!scope.isActive) {
|
if (!scope.isActive) {
|
||||||
return null
|
return null
|
||||||
|
@ -1,11 +1,22 @@
|
|||||||
package sushi.hardcore.droidfs.file_operations
|
package sushi.hardcore.droidfs.file_operations
|
||||||
|
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||||
|
import sushi.hardcore.droidfs.filesystems.Stat
|
||||||
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class OperationFile(val srcPath: String, val type: Int, var dstPath: String? = null, var overwriteConfirmed: Boolean = false) {
|
||||||
|
val isDirectory = type == Stat.S_IFDIR
|
||||||
|
val name: String by lazy {
|
||||||
|
File(srcPath).name
|
||||||
|
}
|
||||||
|
val parentPath by lazy {
|
||||||
|
PathUtils.getParentPath(srcPath)
|
||||||
|
}
|
||||||
|
|
||||||
class OperationFile(val explorerElement: ExplorerElement, var dstPath: String? = null, var overwriteConfirmed: Boolean = false) {
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromExplorerElement(e: ExplorerElement): OperationFile {
|
fun fromExplorerElement(e: ExplorerElement): OperationFile {
|
||||||
return OperationFile(e, null)
|
return OperationFile(e.fullPath, e.stat.type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,18 +5,18 @@ import com.google.android.exoplayer2.upstream.DataSource
|
|||||||
import com.google.android.exoplayer2.upstream.DataSpec
|
import com.google.android.exoplayer2.upstream.DataSpec
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener
|
import com.google.android.exoplayer2.upstream.TransferListener
|
||||||
import sushi.hardcore.droidfs.ConstValues
|
import sushi.hardcore.droidfs.ConstValues
|
||||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private val filePath: String): DataSource {
|
class EncryptedVolumeDataSource(private val encryptedVolume: EncryptedVolume, private val filePath: String): DataSource {
|
||||||
private var handleID = -1
|
private var fileHandle = -1L
|
||||||
private var fileSize: Long = -1
|
private var fileSize: Long = -1
|
||||||
private var fileOffset: Long = 0
|
private var fileOffset: Long = 0
|
||||||
override fun open(dataSpec: DataSpec): Long {
|
override fun open(dataSpec: DataSpec): Long {
|
||||||
fileOffset = dataSpec.position
|
fileOffset = dataSpec.position
|
||||||
handleID = gocryptfsVolume.openReadMode(filePath)
|
fileHandle = encryptedVolume.openFile(filePath)
|
||||||
fileSize = gocryptfsVolume.getSize(filePath)
|
fileSize = encryptedVolume.getAttr(filePath)!!.size
|
||||||
return fileSize
|
return fileSize
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
gocryptfsVolume.closeFile(handleID)
|
encryptedVolume.closeFile(fileHandle)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addTransferListener(transferListener: TransferListener) {
|
override fun addTransferListener(transferListener: TransferListener) {
|
||||||
@ -44,7 +44,7 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
|
|||||||
} else {
|
} else {
|
||||||
ByteArray(tmpReadLength)
|
ByteArray(tmpReadLength)
|
||||||
}
|
}
|
||||||
val read = gocryptfsVolume.readFile(handleID, fileOffset, tmpBuff)
|
val read = encryptedVolume.read(fileHandle, tmpBuff, fileOffset)
|
||||||
System.arraycopy(tmpBuff, 0, buffer, offset+totalRead, read)
|
System.arraycopy(tmpBuff, 0, buffer, offset+totalRead, read)
|
||||||
fileOffset += read
|
fileOffset += read
|
||||||
totalRead += read
|
totalRead += read
|
||||||
@ -52,9 +52,9 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
|
|||||||
return totalRead
|
return totalRead
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory(private val gocryptfsVolume: GocryptfsVolume, private val filePath: String): DataSource.Factory {
|
class Factory(private val encryptedVolume: EncryptedVolume, private val filePath: String): DataSource.Factory {
|
||||||
override fun createDataSource(): DataSource {
|
override fun createDataSource(): DataSource {
|
||||||
return GocryptfsDataSource(gocryptfsVolume, filePath)
|
return EncryptedVolumeDataSource(encryptedVolume, filePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,15 +6,15 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import sushi.hardcore.droidfs.BaseActivity
|
import sushi.hardcore.droidfs.BaseActivity
|
||||||
import sushi.hardcore.droidfs.ConstValues
|
import sushi.hardcore.droidfs.ConstValues
|
||||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||||
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
||||||
import sushi.hardcore.droidfs.util.PathUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
|
|
||||||
abstract class FileViewerActivity: BaseActivity() {
|
abstract class FileViewerActivity: BaseActivity() {
|
||||||
protected lateinit var gocryptfsVolume: GocryptfsVolume
|
protected lateinit var encryptedVolume: EncryptedVolume
|
||||||
protected lateinit var filePath: String
|
protected lateinit var filePath: String
|
||||||
private lateinit var originalParentPath: String
|
private lateinit var originalParentPath: String
|
||||||
private lateinit var windowInsetsController: WindowInsetsControllerCompat
|
private lateinit var windowInsetsController: WindowInsetsControllerCompat
|
||||||
@ -33,8 +33,7 @@ abstract class FileViewerActivity: BaseActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
filePath = intent.getStringExtra("path")!!
|
filePath = intent.getStringExtra("path")!!
|
||||||
originalParentPath = PathUtils.getParentPath(filePath)
|
originalParentPath = PathUtils.getParentPath(filePath)
|
||||||
val sessionID = intent.getIntExtra("sessionID", -1)
|
encryptedVolume = intent.getParcelableExtra("volume")!!
|
||||||
gocryptfsVolume = GocryptfsVolume(applicationContext, sessionID)
|
|
||||||
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
|
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
|
||||||
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
|
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
|
||||||
windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
|
windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
|
||||||
@ -67,7 +66,7 @@ abstract class FileViewerActivity: BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected fun loadWholeFile(path: String, fileSize: Long? = null): ByteArray? {
|
protected fun loadWholeFile(path: String, fileSize: Long? = null): ByteArray? {
|
||||||
val result = gocryptfsVolume.loadWholeFile(path, size = fileSize)
|
val result = encryptedVolume.loadWholeFile(path, size = fileSize)
|
||||||
if (result.second != 0) {
|
if (result.second != 0) {
|
||||||
val dialog = CustomAlertDialogBuilder(this, themeValue)
|
val dialog = CustomAlertDialogBuilder(this, themeValue)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
@ -85,13 +84,15 @@ abstract class FileViewerActivity: BaseActivity() {
|
|||||||
|
|
||||||
protected fun createPlaylist() {
|
protected fun createPlaylist() {
|
||||||
if (!wasMapped){
|
if (!wasMapped){
|
||||||
for (e in gocryptfsVolume.recursiveMapFiles(originalParentPath)) {
|
encryptedVolume.recursiveMapFiles(originalParentPath)?.let { elements ->
|
||||||
|
for (e in elements) {
|
||||||
if (e.isRegularFile) {
|
if (e.isRegularFile) {
|
||||||
if (ConstValues.isExtensionType(getFileType(), e.name) || filePath == e.fullPath) {
|
if (ConstValues.isExtensionType(getFileType(), e.name) || filePath == e.fullPath) {
|
||||||
mappedPlaylist.add(e)
|
mappedPlaylist.add(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
val sortOrder = intent.getStringExtra("sortOrder") ?: "name"
|
val sortOrder = intent.getStringExtra("sortOrder") ?: "name"
|
||||||
ExplorerElement.sortBy(sortOrder, foldersFirst, mappedPlaylist)
|
ExplorerElement.sortBy(sortOrder, foldersFirst, mappedPlaylist)
|
||||||
//find current index
|
//find current index
|
||||||
@ -133,7 +134,7 @@ abstract class FileViewerActivity: BaseActivity() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
if (!isFinishingIntentionally) {
|
if (!isFinishingIntentionally) {
|
||||||
gocryptfsVolume.close()
|
encryptedVolume.close()
|
||||||
RestrictedFileProvider.wipeAll(this)
|
RestrictedFileProvider.wipeAll(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ class ImageViewer: FileViewerActivity() {
|
|||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
createPlaylist() //be sure the playlist is created before deleting if there is only one image
|
createPlaylist() //be sure the playlist is created before deleting if there is only one image
|
||||||
if (gocryptfsVolume.removeFile(filePath)) {
|
if (encryptedVolume.deleteFile(filePath)) {
|
||||||
playlistNext(true)
|
playlistNext(true)
|
||||||
refreshPlaylist()
|
refreshPlaylist()
|
||||||
if (mappedPlaylist.size == 0) { //deleted all images of the playlist
|
if (mappedPlaylist.size == 0) { //deleted all images of the playlist
|
||||||
@ -275,7 +275,7 @@ class ImageViewer: FileViewerActivity() {
|
|||||||
Bitmap.CompressFormat.JPEG
|
Bitmap.CompressFormat.JPEG
|
||||||
}, 100, outputStream) == true
|
}, 100, outputStream) == true
|
||||||
){
|
){
|
||||||
if (gocryptfsVolume.importFile(ByteArrayInputStream(outputStream.toByteArray()), filePath)){
|
if (encryptedVolume.importFile(ByteArrayInputStream(outputStream.toByteArray()), filePath)) {
|
||||||
Toast.makeText(this, R.string.image_saved_successfully, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.image_saved_successfully, Toast.LENGTH_SHORT).show()
|
||||||
callback()
|
callback()
|
||||||
} else {
|
} else {
|
||||||
|
@ -25,7 +25,7 @@ abstract class MediaPlayer: FileViewerActivity() {
|
|||||||
protected open fun onVideoSizeChanged(width: Int, height: Int) {}
|
protected open fun onVideoSizeChanged(width: Int, height: Int) {}
|
||||||
|
|
||||||
private fun createMediaSource(filePath: String): MediaSource {
|
private fun createMediaSource(filePath: String): MediaSource {
|
||||||
val dataSourceFactory = GocryptfsDataSource.Factory(gocryptfsVolume, filePath)
|
val dataSourceFactory = EncryptedVolumeDataSource.Factory(encryptedVolume, filePath)
|
||||||
return ProgressiveMediaSource.Factory(dataSourceFactory, DefaultExtractorsFactory())
|
return ProgressiveMediaSource.Factory(dataSourceFactory, DefaultExtractorsFactory())
|
||||||
.createMediaSource(MediaItem.fromUri(ConstValues.FAKE_URI))
|
.createMediaSource(MediaItem.fromUri(ConstValues.FAKE_URI))
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ class PdfViewer: FileViewerActivity() {
|
|||||||
pdfViewer = PdfViewer(this)
|
pdfViewer = PdfViewer(this)
|
||||||
val fileName = File(filePath).name
|
val fileName = File(filePath).name
|
||||||
title = fileName
|
title = fileName
|
||||||
val fileSize = gocryptfsVolume.getSize(filePath)
|
val fileSize = encryptedVolume.getAttr(filePath)?.size
|
||||||
loadWholeFile(filePath, fileSize)?.let {
|
loadWholeFile(filePath, fileSize)?.let {
|
||||||
pdfViewer.loadPdf(ByteArrayInputStream(it), fileName, fileSize)
|
pdfViewer.loadPdf(ByteArrayInputStream(it), fileName, fileSize)
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import android.view.Menu
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
import sushi.hardcore.droidfs.ConstValues
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
@ -68,14 +68,14 @@ class TextEditor: FileViewerActivity() {
|
|||||||
private fun save(): Boolean{
|
private fun save(): Boolean{
|
||||||
var success = false
|
var success = false
|
||||||
val content = editor.text.toString().toByteArray()
|
val content = editor.text.toString().toByteArray()
|
||||||
val handleID = gocryptfsVolume.openWriteMode(filePath)
|
val fileHandle = encryptedVolume.openFile(filePath)
|
||||||
if (handleID != -1){
|
if (fileHandle != -1L) {
|
||||||
val buff = ByteArrayInputStream(content)
|
val buff = ByteArrayInputStream(content)
|
||||||
var offset: Long = 0
|
var offset: Long = 0
|
||||||
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
|
val ioBuffer = ByteArray(ConstValues.IO_BUFF_SIZE)
|
||||||
var length: Int
|
var length: Int
|
||||||
while (buff.read(ioBuffer).also { length = it } > 0) {
|
while (buff.read(ioBuffer).also { length = it } > 0) {
|
||||||
val written = gocryptfsVolume.writeFile(handleID, offset, ioBuffer, length).toLong()
|
val written = encryptedVolume.write(fileHandle, offset, ioBuffer, length).toLong()
|
||||||
if (written == length.toLong()) {
|
if (written == length.toLong()) {
|
||||||
offset += written
|
offset += written
|
||||||
} else {
|
} else {
|
||||||
@ -83,9 +83,9 @@ class TextEditor: FileViewerActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (offset == content.size.toLong()){
|
if (offset == content.size.toLong()){
|
||||||
success = gocryptfsVolume.truncate(handleID, offset)
|
success = encryptedVolume.truncate(filePath, offset)
|
||||||
}
|
}
|
||||||
gocryptfsVolume.closeFile(handleID)
|
encryptedVolume.closeFile(fileHandle)
|
||||||
buff.close()
|
buff.close()
|
||||||
}
|
}
|
||||||
if (success){
|
if (success){
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
package sushi.hardcore.droidfs.filesystems
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import sushi.hardcore.droidfs.ConstValues
|
||||||
|
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||||
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
|
|
||||||
|
class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
|
||||||
|
companion object {
|
||||||
|
init {
|
||||||
|
System.loadLibrary("cryfs_jni")
|
||||||
|
}
|
||||||
|
|
||||||
|
const val CONFIG_FILE_NAME = "cryfs.config"
|
||||||
|
|
||||||
|
private external fun nativeInit(
|
||||||
|
baseDir: String,
|
||||||
|
localStateDir: String,
|
||||||
|
password: ByteArray,
|
||||||
|
createBaseDir: Boolean,
|
||||||
|
cipher: String?
|
||||||
|
): Long
|
||||||
|
private external fun nativeCreate(fusePtr: Long, path: String, mode: Int): Long
|
||||||
|
private external fun nativeOpen(fusePtr: Long, path: String, flags: Int): Long
|
||||||
|
private external fun nativeRead(fusePtr: Long, fileHandle: Long, buffer: ByteArray, offset: Long): Int
|
||||||
|
private external fun nativeWrite(fusePtr: Long, fileHandle: Long, offset: Long, buffer: ByteArray, size: Int): Int
|
||||||
|
private external fun nativeTruncate(fusePtr: Long, path: String, size: Long): Boolean
|
||||||
|
private external fun nativeDeleteFile(fusePtr: Long, path: String): Boolean
|
||||||
|
private external fun nativeCloseFile(fusePtr: Long, fileHandle: Long): Boolean
|
||||||
|
private external fun nativeReadDir(fusePtr: Long, path: String): MutableList<ExplorerElement>?
|
||||||
|
private external fun nativeMkdir(fusePtr: Long, path: String, mode: Int): Boolean
|
||||||
|
private external fun nativeRmdir(fusePtr: Long, path: String): Boolean
|
||||||
|
private external fun nativeGetAttr(fusePtr: Long, path: String): Stat?
|
||||||
|
private external fun nativeRename(fusePtr: Long, srcPath: String, dstPath: String): Boolean
|
||||||
|
private external fun nativeClose(fusePtr: Long)
|
||||||
|
private external fun nativeIsClosed(fusePtr: Long): Boolean
|
||||||
|
|
||||||
|
fun getLocalStateDir(filesDir: String): String {
|
||||||
|
return PathUtils.pathJoin(filesDir, ConstValues.CRYFS_LOCAL_STATE_DIR)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init(baseDir: String, localStateDir: String, password: ByteArray, createBaseDir: Boolean, cipher: String?): CryfsVolume? {
|
||||||
|
val fusePtr = nativeInit(baseDir, localStateDir, password, createBaseDir, cipher)
|
||||||
|
return if (fusePtr == 0L) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
CryfsVolume(fusePtr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(baseDir: String, localStateDir: String, password: ByteArray, cipher: String?): Boolean {
|
||||||
|
return init(baseDir, localStateDir, password, true, cipher)?.also { it.close() } != null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(baseDir: String, localStateDir: String, password: ByteArray): CryfsVolume? {
|
||||||
|
return init(baseDir, localStateDir, password, false, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(parcel: Parcel) : this(parcel.readLong())
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) = with(parcel) {
|
||||||
|
writeByte(CRYFS_VOLUME_TYPE)
|
||||||
|
writeLong(fusePtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openFile(path: String): Long {
|
||||||
|
val fileHandle = nativeOpen(fusePtr, path, 0)
|
||||||
|
return if (fileHandle == -1L) {
|
||||||
|
nativeCreate(fusePtr, path, 0)
|
||||||
|
} else {
|
||||||
|
fileHandle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(fileHandle: Long, buffer: ByteArray, offset: Long): Int {
|
||||||
|
return nativeRead(fusePtr, fileHandle, buffer, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(fileHandle: Long, offset: Long, buffer: ByteArray, size: Int): Int {
|
||||||
|
return nativeWrite(fusePtr, fileHandle, offset, buffer, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun truncate(path: String, size: Long): Boolean {
|
||||||
|
return nativeTruncate(fusePtr, path, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun closeFile(fileHandle: Long): Boolean {
|
||||||
|
return nativeCloseFile(fusePtr, fileHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteFile(path: String): Boolean {
|
||||||
|
return nativeDeleteFile(fusePtr, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readDir(path: String): MutableList<ExplorerElement>? {
|
||||||
|
return nativeReadDir(fusePtr, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mkdir(path: String): Boolean {
|
||||||
|
return nativeMkdir(fusePtr, path, Stat.S_IFDIR)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rmdir(path: String): Boolean {
|
||||||
|
return nativeRmdir(fusePtr, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAttr(path: String): Stat? {
|
||||||
|
return nativeGetAttr(fusePtr, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rename(srcPath: String, dstPath: String): Boolean {
|
||||||
|
return nativeRename(fusePtr, srcPath, dstPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
return nativeClose(fusePtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isClosed(): Boolean {
|
||||||
|
return nativeIsClosed(fusePtr)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,214 @@
|
|||||||
|
package sushi.hardcore.droidfs.filesystems
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import sushi.hardcore.droidfs.ConstValues
|
||||||
|
import sushi.hardcore.droidfs.SavedVolume
|
||||||
|
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||||
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
abstract class EncryptedVolume: Parcelable {
|
||||||
|
companion object {
|
||||||
|
const val GOCRYPTFS_VOLUME_TYPE: Byte = 0
|
||||||
|
const val CRYFS_VOLUME_TYPE: Byte = 1
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val CREATOR = object : Parcelable.Creator<EncryptedVolume> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): EncryptedVolume {
|
||||||
|
return when (parcel.readByte()) {
|
||||||
|
GOCRYPTFS_VOLUME_TYPE -> GocryptfsVolume(parcel)
|
||||||
|
CRYFS_VOLUME_TYPE -> CryfsVolume(parcel)
|
||||||
|
else -> throw invalidVolumeType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun newArray(size: Int) = arrayOfNulls<EncryptedVolume>(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVolumeType(path: String): Byte {
|
||||||
|
return if (File(path, GocryptfsVolume.CONFIG_FILE_NAME).isFile) {
|
||||||
|
GOCRYPTFS_VOLUME_TYPE
|
||||||
|
} else if (File(path, CryfsVolume.CONFIG_FILE_NAME).isFile) {
|
||||||
|
CRYFS_VOLUME_TYPE
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(volume: SavedVolume, filesDir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): EncryptedVolume? {
|
||||||
|
return when (volume.type) {
|
||||||
|
GOCRYPTFS_VOLUME_TYPE -> {
|
||||||
|
GocryptfsVolume.init(volume.getFullPath(filesDir), password, givenHash, returnedHash)
|
||||||
|
}
|
||||||
|
CRYFS_VOLUME_TYPE -> {
|
||||||
|
CryfsVolume.init(volume.getFullPath(filesDir), CryfsVolume.getLocalStateDir(filesDir), password!!)
|
||||||
|
}
|
||||||
|
else -> throw invalidVolumeType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun invalidVolumeType(): java.lang.RuntimeException {
|
||||||
|
return RuntimeException("Invalid volume type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents() = 0
|
||||||
|
|
||||||
|
abstract fun openFile(path: String): Long
|
||||||
|
abstract fun read(fileHandle: Long, buffer: ByteArray, offset: Long): Int
|
||||||
|
abstract fun write(fileHandle: Long, offset: Long, buffer: ByteArray, size: Int): Int
|
||||||
|
abstract fun closeFile(fileHandle: Long): Boolean
|
||||||
|
abstract fun truncate(path: String, size: Long): Boolean
|
||||||
|
abstract fun deleteFile(path: String): Boolean
|
||||||
|
abstract fun readDir(path: String): MutableList<ExplorerElement>?
|
||||||
|
abstract fun mkdir(path: String): Boolean
|
||||||
|
abstract fun rmdir(path: String): Boolean
|
||||||
|
abstract fun getAttr(path: String): Stat?
|
||||||
|
abstract fun rename(srcPath: String, dstPath: String): Boolean
|
||||||
|
abstract fun close()
|
||||||
|
abstract fun isClosed(): Boolean
|
||||||
|
|
||||||
|
fun pathExists(path: String): Boolean {
|
||||||
|
return getAttr(path) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exportFile(fileHandle: Long, os: OutputStream): Boolean {
|
||||||
|
var offset: Long = 0
|
||||||
|
val ioBuffer = ByteArray(ConstValues.IO_BUFF_SIZE)
|
||||||
|
var length: Int
|
||||||
|
while (read(fileHandle, ioBuffer, offset).also { length = it } > 0) {
|
||||||
|
os.write(ioBuffer, 0, length)
|
||||||
|
offset += length.toLong()
|
||||||
|
}
|
||||||
|
os.close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exportFile(src_path: String, os: OutputStream): Boolean {
|
||||||
|
var success = false
|
||||||
|
val srcfileHandle = openFile(src_path)
|
||||||
|
if (srcfileHandle != -1L) {
|
||||||
|
success = exportFile(srcfileHandle, os)
|
||||||
|
closeFile(srcfileHandle)
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exportFile(src_path: String, dst_path: String): Boolean {
|
||||||
|
return exportFile(src_path, FileOutputStream(dst_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exportFile(context: Context, src_path: String, output_path: Uri): Boolean {
|
||||||
|
val os = context.contentResolver.openOutputStream(output_path)
|
||||||
|
if (os != null) {
|
||||||
|
return exportFile(src_path, os)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun importFile(inputStream: InputStream, dst_path: String): Boolean {
|
||||||
|
val dstfileHandle = openFile(dst_path)
|
||||||
|
if (dstfileHandle != -1L) {
|
||||||
|
var success = true
|
||||||
|
var offset: Long = 0
|
||||||
|
val ioBuffer = ByteArray(ConstValues.IO_BUFF_SIZE)
|
||||||
|
var length: Int
|
||||||
|
while (inputStream.read(ioBuffer).also { length = it } > 0) {
|
||||||
|
val written = write(dstfileHandle, offset, ioBuffer, length).toLong()
|
||||||
|
if (written == length.toLong()) {
|
||||||
|
offset += written
|
||||||
|
} else {
|
||||||
|
inputStream.close()
|
||||||
|
success = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closeFile(dstfileHandle)
|
||||||
|
inputStream.close()
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun importFile(context: Context, src_uri: Uri, dst_path: String): Boolean {
|
||||||
|
val inputStream = context.contentResolver.openInputStream(src_uri)
|
||||||
|
if (inputStream != null) {
|
||||||
|
return importFile(inputStream, dst_path)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadWholeFile(fullPath: String, size: Long? = null, maxSize: Long? = null): Pair<ByteArray?, Int> {
|
||||||
|
val fileSize = size ?: getAttr(fullPath)?.size ?: -1
|
||||||
|
return if (fileSize >= 0) {
|
||||||
|
maxSize?.let {
|
||||||
|
if (fileSize > it) {
|
||||||
|
return Pair(null, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val fileBuff = ByteArray(fileSize.toInt())
|
||||||
|
val fileHandle = openFile(fullPath)
|
||||||
|
if (fileHandle == -1L) {
|
||||||
|
Pair(null, 3)
|
||||||
|
} else {
|
||||||
|
var offset: Long = 0
|
||||||
|
val ioBuffer = ByteArray(ConstValues.IO_BUFF_SIZE)
|
||||||
|
var length: Int
|
||||||
|
while (read(fileHandle, ioBuffer, offset).also { length = it } > 0) {
|
||||||
|
System.arraycopy(ioBuffer, 0, fileBuff, offset.toInt(), length)
|
||||||
|
offset += length.toLong()
|
||||||
|
}
|
||||||
|
closeFile(fileHandle)
|
||||||
|
if (offset == fileBuff.size.toLong()) {
|
||||||
|
Pair(fileBuff, 0)
|
||||||
|
} else {
|
||||||
|
Pair(null, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: OutOfMemoryError) {
|
||||||
|
Pair(null, 2)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Pair(null, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun recursiveMapFiles(rootPath: String): MutableList<ExplorerElement>? {
|
||||||
|
val result = mutableListOf<ExplorerElement>()
|
||||||
|
val explorerElements = readDir(rootPath) ?: return null
|
||||||
|
result.addAll(explorerElements)
|
||||||
|
for (e in explorerElements) {
|
||||||
|
if (e.isDirectory) {
|
||||||
|
result.addAll(recursiveMapFiles(e.fullPath) ?: return null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun recursiveRemoveDirectory(path: String): String? {
|
||||||
|
readDir(path)?.let { elements ->
|
||||||
|
for (e in elements) {
|
||||||
|
val fullPath = PathUtils.pathJoin(path, e.name)
|
||||||
|
if (e.isDirectory) {
|
||||||
|
val result = recursiveRemoveDirectory(fullPath)
|
||||||
|
result?.let { return it }
|
||||||
|
} else {
|
||||||
|
if (!deleteFile(fullPath)) {
|
||||||
|
return fullPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if (!rmdir(path)) {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package sushi.hardcore.droidfs.filesystems
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||||
|
|
||||||
|
class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() {
|
||||||
|
private external fun native_close(sessionID: Int)
|
||||||
|
private external fun native_is_closed(sessionID: Int): Boolean
|
||||||
|
private external fun native_list_dir(sessionID: Int, dir_path: String): MutableList<ExplorerElement>?
|
||||||
|
private external fun native_open_read_mode(sessionID: Int, file_path: String): Int
|
||||||
|
private external fun native_open_write_mode(sessionID: Int, file_path: String, mode: Int): Int
|
||||||
|
private external fun native_read_file(sessionID: Int, handleID: Int, offset: Long, buff: ByteArray): Int
|
||||||
|
private external fun native_write_file(sessionID: Int, handleID: Int, offset: Long, buff: ByteArray, buff_size: Int): Int
|
||||||
|
private external fun native_truncate(sessionID: Int, path: String, offset: Long): Boolean
|
||||||
|
private external fun native_close_file(sessionID: Int, handleID: Int)
|
||||||
|
private external fun native_remove_file(sessionID: Int, file_path: String): Boolean
|
||||||
|
private external fun native_mkdir(sessionID: Int, dir_path: String, mode: Int): Boolean
|
||||||
|
private external fun native_rmdir(sessionID: Int, dir_path: String): Boolean
|
||||||
|
private external fun native_get_attr(sessionID: Int, file_path: String): Stat?
|
||||||
|
private external fun native_rename(sessionID: Int, old_path: String, new_path: String): Boolean
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KeyLen = 32
|
||||||
|
const val ScryptDefaultLogN = 16
|
||||||
|
const val CONFIG_FILE_NAME = "gocryptfs.conf"
|
||||||
|
external fun createVolume(root_cipher_dir: String, password: ByteArray, plainTextNames: Boolean, xchacha: Int, logN: Int, creator: String, returnedHash: ByteArray?): Boolean
|
||||||
|
private external fun nativeInit(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int
|
||||||
|
external fun changePassword(root_cipher_dir: String, old_password: CharArray?, givenHash: ByteArray?, new_password: CharArray, returnedHash: ByteArray?): Boolean
|
||||||
|
|
||||||
|
fun init(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): GocryptfsVolume? {
|
||||||
|
val sessionId = nativeInit(root_cipher_dir, password, givenHash, returnedHash)
|
||||||
|
return if (sessionId == -1) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
GocryptfsVolume(sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
System.loadLibrary("gocryptfs_jni")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(parcel: Parcel) : this(parcel.readInt())
|
||||||
|
|
||||||
|
override fun openFile(path: String): Long {
|
||||||
|
return native_open_write_mode(sessionID, path, 0).toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(fileHandle: Long, buffer: ByteArray, offset: Long): Int {
|
||||||
|
return native_read_file(sessionID, fileHandle.toInt(), offset, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readDir(path: String): MutableList<ExplorerElement>? {
|
||||||
|
return native_list_dir(sessionID, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAttr(path: String): Stat? {
|
||||||
|
return native_get_attr(sessionID, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) = with(parcel) {
|
||||||
|
writeByte(GOCRYPTFS_VOLUME_TYPE)
|
||||||
|
writeInt(sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
native_close(sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isClosed(): Boolean {
|
||||||
|
return native_is_closed(sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mkdir(path: String): Boolean {
|
||||||
|
return native_mkdir(sessionID, path, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rmdir(path: String): Boolean {
|
||||||
|
return native_rmdir(sessionID, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun closeFile(fileHandle: Long): Boolean {
|
||||||
|
native_close_file(sessionID, fileHandle.toInt())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(fileHandle: Long, offset: Long, buffer: ByteArray, size: Int): Int {
|
||||||
|
return native_write_file(sessionID, fileHandle.toInt(), offset, buffer, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun truncate(path: String, size: Long): Boolean {
|
||||||
|
return native_truncate(sessionID, path, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteFile(path: String): Boolean {
|
||||||
|
return native_remove_file(sessionID, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rename(srcPath: String, dstPath: String): Boolean {
|
||||||
|
return native_rename(sessionID, srcPath, dstPath)
|
||||||
|
}
|
||||||
|
}
|
14
app/src/main/java/sushi/hardcore/droidfs/filesystems/Stat.kt
Normal file
14
app/src/main/java/sushi/hardcore/droidfs/filesystems/Stat.kt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package sushi.hardcore.droidfs.filesystems
|
||||||
|
|
||||||
|
class Stat(val type: Int, var size: Long, val mTime: Long) {
|
||||||
|
companion object {
|
||||||
|
const val S_IFDIR = 0x4000
|
||||||
|
const val S_IFREG = 0x8000
|
||||||
|
const val S_IFLNK = 0xA000
|
||||||
|
const val PARENT_FOLDER_TYPE = -1
|
||||||
|
|
||||||
|
fun parentFolderStat(): Stat {
|
||||||
|
return Stat(PARENT_FOLDER_TYPE, -1, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,23 +13,22 @@ import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import kotlin.math.log10
|
import kotlin.math.log10
|
||||||
|
import kotlin.math.max
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
object PathUtils {
|
object PathUtils {
|
||||||
|
const val SEPARATOR = '/'
|
||||||
|
|
||||||
fun getParentPath(path: String): String {
|
fun getParentPath(path: String): String {
|
||||||
return if (path.endsWith("/")) {
|
val strippedPath = if (path.endsWith(SEPARATOR)) {
|
||||||
val a = path.substring(0, path.length - 2)
|
path.substring(0, max(1, path.length - 1))
|
||||||
if (a.contains("/")) {
|
|
||||||
a.substring(0, a.lastIndexOf("/"))
|
|
||||||
} else {
|
} else {
|
||||||
""
|
path
|
||||||
}
|
}
|
||||||
|
return if (strippedPath.count { it == SEPARATOR } <= 1) {
|
||||||
|
SEPARATOR.toString()
|
||||||
} else {
|
} else {
|
||||||
if (path.contains("/")) {
|
strippedPath.substring(0, strippedPath.lastIndexOf(SEPARATOR))
|
||||||
path.substring(0, path.lastIndexOf("/"))
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,27 +36,21 @@ object PathUtils {
|
|||||||
val result = StringBuilder()
|
val result = StringBuilder()
|
||||||
for (element in strings) {
|
for (element in strings) {
|
||||||
if (element.isNotEmpty()) {
|
if (element.isNotEmpty()) {
|
||||||
|
if (!element.startsWith(SEPARATOR) && result.last() != SEPARATOR) {
|
||||||
|
result.append(SEPARATOR)
|
||||||
|
}
|
||||||
result.append(element)
|
result.append(element)
|
||||||
if (!element.endsWith("/")) {
|
|
||||||
result.append("/")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return result.toString()
|
||||||
return result.substring(0, result.length - 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRelativePath(parentPath: String, childPath: String): String {
|
fun getRelativePath(parentPath: String, childPath: String): String {
|
||||||
return when {
|
return childPath.substring(parentPath.length + if (parentPath.endsWith(SEPARATOR) || childPath.length == parentPath.length) {
|
||||||
parentPath.isEmpty() -> {
|
0
|
||||||
childPath
|
} else {
|
||||||
}
|
1
|
||||||
parentPath.length == childPath.length -> {
|
})
|
||||||
""
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
childPath.substring(parentPath.length + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isChildOf(childPath: String, parentPath: String): Boolean {
|
fun isChildOf(childPath: String, parentPath: String): Boolean {
|
||||||
@ -79,7 +72,7 @@ object PathUtils {
|
|||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = uri.path
|
result = uri.path
|
||||||
result?.let {
|
result?.let {
|
||||||
val cut = it.lastIndexOf('/')
|
val cut = it.lastIndexOf(SEPARATOR)
|
||||||
if (cut != -1) {
|
if (cut != -1) {
|
||||||
result = it.substring(cut + 1)
|
result = it.substring(cut + 1)
|
||||||
}
|
}
|
||||||
|
18
app/src/main/java/sushi/hardcore/droidfs/util/WidgetUtil.kt
Normal file
18
app/src/main/java/sushi/hardcore/droidfs/util/WidgetUtil.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package sushi.hardcore.droidfs.util
|
||||||
|
|
||||||
|
import android.widget.EditText
|
||||||
|
import java.nio.CharBuffer
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object WidgetUtil {
|
||||||
|
fun encodeEditTextContent(editText: EditText): ByteArray {
|
||||||
|
val charArray = CharArray(editText.text.length)
|
||||||
|
editText.text.getChars(0, editText.text.length, charArray, 0)
|
||||||
|
val byteArray = StandardCharsets.UTF_8.encode(
|
||||||
|
CharBuffer.wrap(charArray)
|
||||||
|
).array()
|
||||||
|
Arrays.fill(charArray, Char.MIN_VALUE)
|
||||||
|
return byteArray
|
||||||
|
}
|
||||||
|
}
|
@ -30,8 +30,9 @@ void jbyteArray_to_unsignedCharArray(const jbyte* src, unsigned char* dst, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *env, jclass clazz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_createVolume(JNIEnv *env, jclass clazz,
|
||||||
jstring jroot_cipher_dir, jcharArray jpassword,
|
jstring jroot_cipher_dir,
|
||||||
|
jbyteArray jpassword,
|
||||||
jboolean plainTextNames,
|
jboolean plainTextNames,
|
||||||
jint xchacha,
|
jint xchacha,
|
||||||
jint logN,
|
jint logN,
|
||||||
@ -42,9 +43,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *
|
|||||||
GoString gofilename = {root_cipher_dir, strlen(root_cipher_dir)}, gocreator = {creator, strlen(creator)};
|
GoString gofilename = {root_cipher_dir, strlen(root_cipher_dir)}, gocreator = {creator, strlen(creator)};
|
||||||
|
|
||||||
const size_t password_len = (*env)->GetArrayLength(env, jpassword);
|
const size_t password_len = (*env)->GetArrayLength(env, jpassword);
|
||||||
jchar* jchar_password = (*env)->GetCharArrayElements(env, jpassword, NULL);
|
char* password = (char*)(*env)->GetByteArrayElements(env, jpassword, NULL);
|
||||||
char password[password_len];
|
|
||||||
jcharArray_to_charArray(jchar_password, password, password_len);
|
|
||||||
GoSlice go_password = {password, password_len, password_len};
|
GoSlice go_password = {password, password_len, password_len};
|
||||||
|
|
||||||
size_t returned_hash_len;
|
size_t returned_hash_len;
|
||||||
@ -65,7 +64,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *
|
|||||||
(*env)->ReleaseStringUTFChars(env, jroot_cipher_dir, root_cipher_dir);
|
(*env)->ReleaseStringUTFChars(env, jroot_cipher_dir, root_cipher_dir);
|
||||||
(*env)->ReleaseStringUTFChars(env, jcreator, creator);
|
(*env)->ReleaseStringUTFChars(env, jcreator, creator);
|
||||||
wipe(password, password_len);
|
wipe(password, password_len);
|
||||||
(*env)->ReleaseCharArrayElements(env, jpassword, jchar_password, 0);
|
(*env)->ReleaseByteArrayElements(env, jpassword, (jbyte*)password, 0);
|
||||||
|
|
||||||
if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) {
|
if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) {
|
||||||
unsignedCharArray_to_jbyteArray(returned_hash, jbyte_returned_hash, returned_hash_len);
|
unsignedCharArray_to_jbyteArray(returned_hash, jbyte_returned_hash, returned_hash_len);
|
||||||
@ -78,16 +77,15 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_createVolume(JNIEnv *
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_init(JNIEnv *env, jobject clazz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_nativeInit(JNIEnv *env, jobject clazz,
|
||||||
jstring jroot_cipher_dir,
|
jstring jroot_cipher_dir,
|
||||||
jcharArray jpassword,
|
jbyteArray jpassword,
|
||||||
jbyteArray jgiven_hash,
|
jbyteArray jgiven_hash,
|
||||||
jbyteArray jreturned_hash) {
|
jbyteArray jreturned_hash) {
|
||||||
const char* root_cipher_dir = (*env)->GetStringUTFChars(env, jroot_cipher_dir, NULL);
|
const char* root_cipher_dir = (*env)->GetStringUTFChars(env, jroot_cipher_dir, NULL);
|
||||||
GoString go_root_cipher_dir = {root_cipher_dir, strlen(root_cipher_dir)};
|
GoString go_root_cipher_dir = {root_cipher_dir, strlen(root_cipher_dir)};
|
||||||
|
|
||||||
size_t password_len;
|
size_t password_len;
|
||||||
jchar* jchar_password;
|
|
||||||
char* password;
|
char* password;
|
||||||
GoSlice go_password = {NULL, 0, 0};
|
GoSlice go_password = {NULL, 0, 0};
|
||||||
size_t given_hash_len;
|
size_t given_hash_len;
|
||||||
@ -96,9 +94,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_init(JNIEnv *env, job
|
|||||||
GoSlice go_given_hash = {NULL, 0, 0};
|
GoSlice go_given_hash = {NULL, 0, 0};
|
||||||
if ((*env)->IsSameObject(env, jgiven_hash, NULL)){
|
if ((*env)->IsSameObject(env, jgiven_hash, NULL)){
|
||||||
password_len = (*env)->GetArrayLength(env, jpassword);
|
password_len = (*env)->GetArrayLength(env, jpassword);
|
||||||
jchar_password = (*env)->GetCharArrayElements(env, jpassword, NULL);
|
password = (char*)(*env)->GetByteArrayElements(env, jpassword, NULL);
|
||||||
password = malloc(password_len);
|
|
||||||
jcharArray_to_charArray(jchar_password, password, password_len);
|
|
||||||
go_password.data = password;
|
go_password.data = password;
|
||||||
go_password.len = password_len;
|
go_password.len = password_len;
|
||||||
go_password.cap = password_len;
|
go_password.cap = password_len;
|
||||||
@ -131,8 +127,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_init(JNIEnv *env, job
|
|||||||
|
|
||||||
if ((*env)->IsSameObject(env, jgiven_hash, NULL)){
|
if ((*env)->IsSameObject(env, jgiven_hash, NULL)){
|
||||||
wipe(password, password_len);
|
wipe(password, password_len);
|
||||||
free(password);
|
(*env)->ReleaseByteArrayElements(env, jpassword, (jbyte*)password, 0);
|
||||||
(*env)->ReleaseCharArrayElements(env, jpassword, jchar_password, 0);
|
|
||||||
} else {
|
} else {
|
||||||
wipe(given_hash, given_hash_len);
|
wipe(given_hash, given_hash_len);
|
||||||
free(given_hash);
|
free(given_hash);
|
||||||
@ -150,13 +145,13 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_init(JNIEnv *env, job
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1is_1closed(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1is_1closed(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID) {
|
jint sessionID) {
|
||||||
return gcf_is_closed(sessionID);
|
return gcf_is_closed(sessionID);
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_changePassword(JNIEnv *env, jclass clazz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_00024Companion_changePassword(JNIEnv *env, jclass clazz,
|
||||||
jstring jroot_cipher_dir,
|
jstring jroot_cipher_dir,
|
||||||
jcharArray jold_password,
|
jcharArray jold_password,
|
||||||
jbyteArray jgiven_hash,
|
jbyteArray jgiven_hash,
|
||||||
@ -225,6 +220,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_changePassword(JNIEnv
|
|||||||
}
|
}
|
||||||
|
|
||||||
wipe(new_password, new_password_len);
|
wipe(new_password, new_password_len);
|
||||||
|
free(new_password);
|
||||||
(*env)->ReleaseCharArrayElements(env, jnew_password, jchar_new_password, 0);
|
(*env)->ReleaseCharArrayElements(env, jnew_password, jchar_new_password, 0);
|
||||||
|
|
||||||
if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) {
|
if (!(*env)->IsSameObject(env, jreturned_hash, NULL)) {
|
||||||
@ -238,12 +234,12 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_00024Companion_changePassword(JNIEnv
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1close(JNIEnv *env, jobject thiz, jint sessionID) {
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1close(JNIEnv *env, jobject thiz, jint sessionID) {
|
||||||
gcf_close(sessionID);
|
gcf_close(sessionID);
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jobject JNICALL
|
JNIEXPORT jobject JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1list_1dir(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1list_1dir(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID, jstring jplain_dir) {
|
jint sessionID, jstring jplain_dir) {
|
||||||
const char* plain_dir = (*env)->GetStringUTFChars(env, jplain_dir, NULL);
|
const char* plain_dir = (*env)->GetStringUTFChars(env, jplain_dir, NULL);
|
||||||
const size_t plain_dir_len = strlen(plain_dir);
|
const size_t plain_dir_len = strlen(plain_dir);
|
||||||
@ -251,63 +247,59 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1list_1dir(JNIEnv *env, jobje
|
|||||||
|
|
||||||
struct gcf_list_dir_return elements = gcf_list_dir(sessionID, go_plain_dir);
|
struct gcf_list_dir_return elements = gcf_list_dir(sessionID, go_plain_dir);
|
||||||
|
|
||||||
jclass java_ArrayList = (*env)->NewLocalRef(env, (*env)->FindClass(env, "java/util/ArrayList"));
|
jobject elementList = NULL;
|
||||||
jmethodID java_ArrayList_init = (*env)->GetMethodID(env, java_ArrayList, "<init>", "(I)V");
|
if (elements.r0 != NULL) {
|
||||||
jmethodID java_ArrayList_add = (*env)->GetMethodID(env, java_ArrayList, "add", "(Ljava/lang/Object;)Z");
|
jclass arrayList = (*env)->NewLocalRef(env, (*env)->FindClass(env, "java/util/ArrayList"));
|
||||||
|
jmethodID arrayListInit = (*env)->GetMethodID(env, arrayList, "<init>", "(I)V");
|
||||||
|
jmethodID arrayListAdd = (*env)->GetMethodID(env, arrayList, "add", "(Ljava/lang/Object;)Z");
|
||||||
jclass classExplorerElement = (*env)->NewLocalRef(env, (*env)->FindClass(env, "sushi/hardcore/droidfs/explorers/ExplorerElement"));
|
jclass classExplorerElement = (*env)->NewLocalRef(env, (*env)->FindClass(env, "sushi/hardcore/droidfs/explorers/ExplorerElement"));
|
||||||
jmethodID explorerElement_new = (*env)->GetStaticMethodID(env, classExplorerElement, "new", "(Ljava/lang/String;SJJLjava/lang/String;)Lsushi/hardcore/droidfs/explorers/ExplorerElement;");
|
jmethodID explorerElementNew = (*env)->GetStaticMethodID(env, classExplorerElement, "new", "(Ljava/lang/String;IJJLjava/lang/String;)Lsushi/hardcore/droidfs/explorers/ExplorerElement;");
|
||||||
jobject element_list = (*env)->NewObject(env, java_ArrayList, java_ArrayList_init, elements.r2);
|
elementList = (*env)->NewObject(env, arrayList, arrayListInit, elements.r2);
|
||||||
|
|
||||||
unsigned int c = 0;
|
unsigned int c = 0;
|
||||||
for (unsigned int i=0; i<elements.r2; ++i){
|
for (unsigned int i=0; i<elements.r2; ++i) {
|
||||||
const char* name = &(elements.r0[c]);
|
const char* name = &(elements.r0[c]);
|
||||||
size_t name_len = strlen(name);
|
size_t nameLen = strlen(name);
|
||||||
|
|
||||||
const char gcf_full_path[plain_dir_len+name_len+2];
|
char* fullPath = malloc(sizeof(char) * (plain_dir_len + nameLen + 2));
|
||||||
if (plain_dir_len > 0){
|
strcpy(fullPath, plain_dir);
|
||||||
strcpy(gcf_full_path, plain_dir);
|
|
||||||
if (plain_dir[-2] != '/') {
|
if (plain_dir[-2] != '/') {
|
||||||
strcat(gcf_full_path, "/");
|
strcat(fullPath, "/");
|
||||||
}
|
|
||||||
strcat(gcf_full_path, name);
|
|
||||||
} else {
|
|
||||||
strcpy(gcf_full_path, name);
|
|
||||||
}
|
}
|
||||||
|
strcat(fullPath, name);
|
||||||
|
|
||||||
GoString go_name = {gcf_full_path, strlen(gcf_full_path)};
|
GoString go_name = {fullPath, strlen(fullPath)};
|
||||||
struct gcf_get_attrs_return attrs = gcf_get_attrs(sessionID, go_name);
|
struct gcf_get_attrs_return attrs = gcf_get_attrs(sessionID, go_name);
|
||||||
|
free(fullPath);
|
||||||
|
|
||||||
short type = 0; //directory
|
|
||||||
if (S_ISREG(elements.r1[i])){
|
|
||||||
type = 1; //regular file
|
|
||||||
}
|
|
||||||
jstring jname = (*env)->NewStringUTF(env, name);
|
jstring jname = (*env)->NewStringUTF(env, name);
|
||||||
jobject explorerElement = (*env)->CallStaticObjectMethod(
|
jobject explorerElement = (*env)->CallStaticObjectMethod(
|
||||||
env,
|
env,
|
||||||
classExplorerElement,
|
classExplorerElement,
|
||||||
explorerElement_new,
|
explorerElementNew,
|
||||||
jname,
|
jname,
|
||||||
type,
|
elements.r1[i],
|
||||||
(long long) attrs.r0,
|
(long long) attrs.r1,
|
||||||
attrs.r1,
|
(long long) attrs.r2,
|
||||||
jplain_dir
|
jplain_dir
|
||||||
);
|
);
|
||||||
(*env)->CallBooleanMethod(env, element_list, java_ArrayList_add, explorerElement);
|
(*env)->CallBooleanMethod(env, elementList, arrayListAdd, explorerElement);
|
||||||
(*env)->DeleteLocalRef(env, explorerElement);
|
(*env)->DeleteLocalRef(env, explorerElement);
|
||||||
(*env)->DeleteLocalRef(env, jname);
|
(*env)->DeleteLocalRef(env, jname);
|
||||||
c += name_len+1;
|
c += nameLen + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(elements.r0);
|
free(elements.r0);
|
||||||
free(elements.r1);
|
free(elements.r1);
|
||||||
|
}
|
||||||
|
|
||||||
(*env)->ReleaseStringUTFChars(env, jplain_dir, plain_dir);
|
(*env)->ReleaseStringUTFChars(env, jplain_dir, plain_dir);
|
||||||
|
|
||||||
return element_list;
|
return elementList;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jobject JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1get_1size(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1get_1attr(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID, jstring jfile_path) {
|
jint sessionID, jstring jfile_path) {
|
||||||
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
||||||
GoString go_file_path = {file_path, strlen(file_path)};
|
GoString go_file_path = {file_path, strlen(file_path)};
|
||||||
@ -316,25 +308,22 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1get_1size(JNIEnv *env, jobje
|
|||||||
|
|
||||||
(*env)->ReleaseStringUTFChars(env, jfile_path, file_path);
|
(*env)->ReleaseStringUTFChars(env, jfile_path, file_path);
|
||||||
|
|
||||||
return attrs.r0;
|
if (attrs.r3 == 1) {
|
||||||
}
|
jclass stat = (*env)->FindClass(env, "sushi/hardcore/droidfs/filesystems/Stat");
|
||||||
|
jmethodID statInit = (*env)->GetMethodID(env, stat, "<init>", "(IJJ)V");
|
||||||
JNIEXPORT jboolean JNICALL
|
return (*env)->NewObject(
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1path_1exists(JNIEnv *env, jobject thiz,
|
env, stat, statInit,
|
||||||
jint sessionID,
|
(jint)attrs.r0,
|
||||||
jstring jfile_path) {
|
(jlong)attrs.r1,
|
||||||
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
(jlong)attrs.r2
|
||||||
GoString go_file_path = {file_path, strlen(file_path)};
|
);
|
||||||
|
} else {
|
||||||
struct gcf_get_attrs_return attrs = gcf_get_attrs(sessionID, go_file_path);
|
return NULL;
|
||||||
|
}
|
||||||
(*env)->ReleaseStringUTFChars(env, jfile_path, file_path);
|
|
||||||
|
|
||||||
return attrs.r1 != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1open_1read_1mode(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1open_1read_1mode(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID,
|
jint sessionID,
|
||||||
jstring jfile_path) {
|
jstring jfile_path) {
|
||||||
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
||||||
@ -348,7 +337,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1open_1read_1mode(JNIEnv *env
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1open_1write_1mode(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1open_1write_1mode(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID,
|
jint sessionID,
|
||||||
jstring jfile_path,
|
jstring jfile_path,
|
||||||
jint mode) {
|
jint mode) {
|
||||||
@ -363,7 +352,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1open_1write_1mode(JNIEnv *en
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1write_1file(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1write_1file(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID, jint handleID, jlong offset,
|
jint sessionID, jint handleID, jlong offset,
|
||||||
jbyteArray jbuff, jint buff_size) {
|
jbyteArray jbuff, jint buff_size) {
|
||||||
jbyte* buff = (*env)->GetByteArrayElements(env, jbuff, NULL);
|
jbyte* buff = (*env)->GetByteArrayElements(env, jbuff, NULL);
|
||||||
@ -377,38 +366,45 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1write_1file(JNIEnv *env, job
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1read_1file(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1read_1file(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID, jint handleID, jlong offset,
|
jint sessionID, jint handleID, jlong offset,
|
||||||
jbyteArray jbuff) {
|
jbyteArray jbuff) {
|
||||||
const size_t buff_size = (*env)->GetArrayLength(env, jbuff);
|
const size_t buff_size = (*env)->GetArrayLength(env, jbuff);
|
||||||
unsigned char buff[buff_size];
|
unsigned char* buff = malloc(sizeof(char)*buff_size);
|
||||||
GoSlice go_buff = {buff, buff_size, buff_size};
|
GoSlice go_buff = {buff, buff_size, buff_size};
|
||||||
|
|
||||||
int read = gcf_read_file(sessionID, handleID, offset, go_buff);
|
int read = gcf_read_file(sessionID, handleID, offset, go_buff);
|
||||||
|
|
||||||
if (read > 0){
|
if (read > 0){
|
||||||
(*env)->SetByteArrayRegion(env, jbuff, 0, read, buff);
|
(*env)->SetByteArrayRegion(env, jbuff, 0, read, (const jbyte*)buff);
|
||||||
}
|
}
|
||||||
|
free(buff);
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1truncate(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1truncate(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID,
|
jint sessionID,
|
||||||
jint handleID, jlong offset) {
|
jstring jpath,
|
||||||
return gcf_truncate(sessionID, handleID, offset);
|
jlong offset) {
|
||||||
|
const char* path = (*env)->GetStringUTFChars(env, jpath, NULL);
|
||||||
|
GoString go_path = {path, strlen(path)};
|
||||||
|
|
||||||
|
GoUint8 result = gcf_truncate(sessionID, go_path, offset);
|
||||||
|
|
||||||
|
(*env)->ReleaseStringUTFChars(env, jpath, path);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1close_1file(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1close_1file(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID,
|
jint sessionID,
|
||||||
jint handleID) {
|
jint handleID) {
|
||||||
gcf_close_file(sessionID, handleID);
|
gcf_close_file(sessionID, handleID);
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1remove_1file(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1remove_1file(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID, jstring jfile_path) {
|
jint sessionID, jstring jfile_path) {
|
||||||
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
const char* file_path = (*env)->GetStringUTFChars(env, jfile_path, NULL);
|
||||||
GoString go_file_path = {file_path, strlen(file_path)};
|
GoString go_file_path = {file_path, strlen(file_path)};
|
||||||
@ -421,7 +417,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1remove_1file(JNIEnv *env, jo
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1mkdir(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1mkdir(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID, jstring jdir_path, jint mode) {
|
jint sessionID, jstring jdir_path, jint mode) {
|
||||||
const char* dir_path = (*env)->GetStringUTFChars(env, jdir_path, NULL);
|
const char* dir_path = (*env)->GetStringUTFChars(env, jdir_path, NULL);
|
||||||
GoString go_dir_path = {dir_path, strlen(dir_path)};
|
GoString go_dir_path = {dir_path, strlen(dir_path)};
|
||||||
@ -434,7 +430,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1mkdir(JNIEnv *env, jobject t
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1rmdir(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1rmdir(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID, jstring jdir_path) {
|
jint sessionID, jstring jdir_path) {
|
||||||
const char* dir_path = (*env)->GetStringUTFChars(env, jdir_path, NULL);
|
const char* dir_path = (*env)->GetStringUTFChars(env, jdir_path, NULL);
|
||||||
GoString go_dir_path = {dir_path, strlen(dir_path)};
|
GoString go_dir_path = {dir_path, strlen(dir_path)};
|
||||||
@ -447,7 +443,7 @@ Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1rmdir(JNIEnv *env, jobject t
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_sushi_hardcore_droidfs_GocryptfsVolume_native_1rename(JNIEnv *env, jobject thiz,
|
Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1rename(JNIEnv *env, jobject thiz,
|
||||||
jint sessionID, jstring jold_path,
|
jint sessionID, jstring jold_path,
|
||||||
jstring jnew_path) {
|
jstring jnew_path) {
|
||||||
const char* old_path = (*env)->GetStringUTFChars(env, jold_path, NULL);
|
const char* old_path = (*env)->GetStringUTFChars(env, jold_path, NULL);
|
||||||
|
184
app/src/main/native/libcryfs.c
Normal file
184
app/src/main/native/libcryfs.c
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
#include <sys/stat.h>
|
||||||
|
#include <jni.h>
|
||||||
|
#include <libcryfs-jni.h>
|
||||||
|
|
||||||
|
JNIEXPORT jlong JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeInit(JNIEnv *env, jobject thiz,
|
||||||
|
jstring base_dir, jstring jlocalStateDir,
|
||||||
|
jbyteArray password, jboolean createBaseDir,
|
||||||
|
jstring cipher) {
|
||||||
|
return cryfs_init(env, base_dir, jlocalStateDir, password, createBaseDir, cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jlong JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeCreate(JNIEnv *env, jobject thiz,
|
||||||
|
jlong fuse_ptr, jstring path,
|
||||||
|
jint mode) {
|
||||||
|
return cryfs_create(env, fuse_ptr, path, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jlong JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeOpen(JNIEnv *env, jobject thiz,
|
||||||
|
jlong fuse_ptr, jstring path,
|
||||||
|
jint flags) {
|
||||||
|
return cryfs_open(env, fuse_ptr, path, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeRead(JNIEnv *env, jobject thiz,
|
||||||
|
jlong fuse_ptr, jlong file_handle,
|
||||||
|
jbyteArray buffer, jlong offset) {
|
||||||
|
return cryfs_read(env, fuse_ptr, file_handle, buffer, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeWrite(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jlong fuse_ptr,
|
||||||
|
jlong file_handle,
|
||||||
|
jlong offset,
|
||||||
|
jbyteArray buffer,
|
||||||
|
jint size) {
|
||||||
|
return cryfs_write(env, fuse_ptr, file_handle, offset, buffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeTruncate(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jlong fuse_ptr,
|
||||||
|
jstring path,
|
||||||
|
jlong size) {
|
||||||
|
return cryfs_truncate(env, fuse_ptr, path, size) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeDeleteFile(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jlong fuse_ptr,
|
||||||
|
jstring path) {
|
||||||
|
return cryfs_unlink(env, fuse_ptr, path) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeCloseFile(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jlong fuse_ptr,
|
||||||
|
jlong file_handle) {
|
||||||
|
return cryfs_release(fuse_ptr, file_handle) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct readDirHelper {
|
||||||
|
JNIEnv* env;
|
||||||
|
jclass explorerElement;
|
||||||
|
jmethodID explorerElementNew;
|
||||||
|
jmethodID arrayListAdd;
|
||||||
|
jobject elementList;
|
||||||
|
jstring jparentPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
int readDir(void* data, const char* name, const struct stat* stat) {
|
||||||
|
struct readDirHelper* helper = (struct readDirHelper*)data;
|
||||||
|
jstring jname = (*helper->env)->NewStringUTF(helper->env, name);
|
||||||
|
jobject explorerElement = (*helper->env)->CallStaticObjectMethod(
|
||||||
|
helper->env,
|
||||||
|
helper->explorerElement,
|
||||||
|
helper->explorerElementNew,
|
||||||
|
jname,
|
||||||
|
stat->st_mode,
|
||||||
|
stat->st_size,
|
||||||
|
stat->st_mtim.tv_sec,
|
||||||
|
helper->jparentPath
|
||||||
|
);
|
||||||
|
(*helper->env)->CallBooleanMethod(helper->env, helper->elementList, helper->arrayListAdd, explorerElement);
|
||||||
|
(*helper->env)->DeleteLocalRef(helper->env, explorerElement);
|
||||||
|
(*helper->env)->DeleteLocalRef(helper->env, jname);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jobject JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeReadDir(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jlong fuse_ptr,
|
||||||
|
jstring path) {
|
||||||
|
jclass arrayList = (*env)->NewLocalRef(env, (*env)->FindClass(env, "java/util/ArrayList"));
|
||||||
|
jmethodID arrayListInit = (*env)->GetMethodID(env, arrayList, "<init>", "()V");
|
||||||
|
struct readDirHelper helper;
|
||||||
|
helper.env = env;
|
||||||
|
helper.explorerElement = (*env)->NewLocalRef(env, (*env)->FindClass(env, "sushi/hardcore/droidfs/explorers/ExplorerElement"));
|
||||||
|
helper.explorerElementNew = (*env)->GetStaticMethodID(
|
||||||
|
env, helper.explorerElement, "new",
|
||||||
|
"(Ljava/lang/String;IJJLjava/lang/String;)Lsushi/hardcore/droidfs/explorers/ExplorerElement;"
|
||||||
|
);
|
||||||
|
helper.arrayListAdd = (*env)->GetMethodID(env, arrayList, "add", "(Ljava/lang/Object;)Z");
|
||||||
|
helper.elementList = (*env)->NewObject(env, arrayList, arrayListInit);
|
||||||
|
helper.jparentPath = path;
|
||||||
|
|
||||||
|
int result = cryfs_readdir(env, fuse_ptr, path, &helper, readDir);
|
||||||
|
|
||||||
|
if (result == 0) {
|
||||||
|
return helper.elementList;
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeMkdir(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jlong fuse_ptr,
|
||||||
|
jstring path,
|
||||||
|
jint mode) {
|
||||||
|
return cryfs_mkdir(env, fuse_ptr, path, mode) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeRmdir(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jlong fuse_ptr,
|
||||||
|
jstring path) {
|
||||||
|
return cryfs_rmdir(env, fuse_ptr, path) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jobject JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeGetAttr(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jlong fuse_ptr,
|
||||||
|
jstring path) {
|
||||||
|
struct stat stbuf;
|
||||||
|
int result = cryfs_getattr(env, fuse_ptr, path, &stbuf);
|
||||||
|
if (result == 0) {
|
||||||
|
jclass stat = (*env)->FindClass(env, "sushi/hardcore/droidfs/filesystems/Stat");
|
||||||
|
jmethodID statInit = (*env)->GetMethodID(env, stat, "<init>", "(IJJ)V");
|
||||||
|
return (*env)->NewObject(
|
||||||
|
env, stat, statInit,
|
||||||
|
(jint)stbuf.st_mode,
|
||||||
|
(jlong)stbuf.st_size,
|
||||||
|
(jlong)stbuf.st_mtim.tv_sec
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeRename(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jlong fuse_ptr,
|
||||||
|
jstring src_path,
|
||||||
|
jstring dst_path) {
|
||||||
|
return cryfs_rename(env, fuse_ptr, src_path, dst_path) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeClose(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jlong fuse_ptr) {
|
||||||
|
cryfs_destroy(fuse_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeIsClosed(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jlong fuse_ptr) {
|
||||||
|
return cryfs_is_closed(fuse_ptr);
|
||||||
|
}
|
@ -6,6 +6,18 @@
|
|||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:layout_marginHorizontal="@dimen/volume_operation_horizontal_gap">
|
android:layout_marginHorizontal="@dimen/volume_operation_horizontal_gap">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/volume_type_label"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinner_volume_type"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginVertical="@dimen/volume_operation_vertical_gap"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -44,11 +56,11 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/encryption_cipher_label"
|
android:text="@string/encryption_cipher_label"
|
||||||
android:layout_toStartOf="@id/spinner_xchacha"
|
android:layout_toStartOf="@id/spinner_cipher"
|
||||||
android:layout_alignParentStart="true"/>
|
android:layout_alignParentStart="true"/>
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/spinner_xchacha"
|
android:id="@+id/spinner_cipher"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true" />
|
android:layout_alignParentEnd="true" />
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string-array name="encryption_cipher">
|
|
||||||
<item>AES-GCM</item>
|
|
||||||
<item>XChaCha20-Poly1305</item>
|
|
||||||
<item>@string/auto</item>
|
|
||||||
</string-array>
|
|
||||||
|
|
||||||
<string-array name="sort_orders_entries">
|
<string-array name="sort_orders_entries">
|
||||||
<item>اسم</item>
|
<item>اسم</item>
|
||||||
<item>حجم</item>
|
<item>حجم</item>
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
<string name="external_open">فتح بتطبيق خارجي</string>
|
<string name="external_open">فتح بتطبيق خارجي</string>
|
||||||
<string name="single_delete_confirm">هل أنت متأكد من حذف %s ?</string>
|
<string name="single_delete_confirm">هل أنت متأكد من حذف %s ?</string>
|
||||||
<string name="multiple_delete_confirm">هل أنت متأكد من حذف هذه %s العناصر ?</string>
|
<string name="multiple_delete_confirm">هل أنت متأكد من حذف هذه %s العناصر ?</string>
|
||||||
<string name="location">موقع: /%s</string>
|
<string name="location">موقع: %s</string>
|
||||||
<string name="total_size">الحجم الكلي: %s</string>
|
<string name="total_size">الحجم الكلي: %s</string>
|
||||||
<string name="import_from_other_volume">إستيراد من مجلد مشفر آخر</string>
|
<string name="import_from_other_volume">إستيراد من مجلد مشفر آخر</string>
|
||||||
<string name="read_file_failed">لقد فشل فتح هذا الملف.</string>
|
<string name="read_file_failed">لقد فشل فتح هذا الملف.</string>
|
||||||
@ -138,7 +138,6 @@
|
|||||||
<string name="image_saved_successfully">تم حفظ تغييرات الصورة بنجاح.</string>
|
<string name="image_saved_successfully">تم حفظ تغييرات الصورة بنجاح.</string>
|
||||||
<string name="bitmap_compress_failed">فشل ضغط الصورة النقطية.</string>
|
<string name="bitmap_compress_failed">فشل ضغط الصورة النقطية.</string>
|
||||||
<string name="file_write_failed">فشل كتابة الملف.</string>
|
<string name="file_write_failed">فشل كتابة الملف.</string>
|
||||||
<string name="error_not_a_volume">لم يتم التعرف على مجلد التشفير Gocryptfs. يرجى التحقق من المسار المحدد.</string>
|
|
||||||
<string name="version">إصدار</string>
|
<string name="version">إصدار</string>
|
||||||
<string name="error_cipher_null">تشفير الخطأ فارغ</string>
|
<string name="error_cipher_null">تشفير الخطأ فارغ</string>
|
||||||
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
|
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
|
||||||
@ -173,7 +172,6 @@
|
|||||||
<string name="maximize_quality">دقة أعلى</string>
|
<string name="maximize_quality">دقة أعلى</string>
|
||||||
<string name="minimize_latency">أداء أفضل</string>
|
<string name="minimize_latency">أداء أفضل</string>
|
||||||
<string name="auto">تلقائي</string>
|
<string name="auto">تلقائي</string>
|
||||||
<string name="xchacha_warning">XChaCha20-Poly1305 مدعوم فقط منذ إصدار gocryptfs v2.2.0. لن تتمكن الإصدارات الأقدم من فتح وحدة تخزين بناءً على هذا التشفير.</string>
|
|
||||||
<string name="encryption_cipher_label">خوارزمية التشفير:</string>
|
<string name="encryption_cipher_label">خوارزمية التشفير:</string>
|
||||||
<string name="theme">سمة</string>
|
<string name="theme">سمة</string>
|
||||||
<string name="theme_summary">تخصيص سمة التطبيق</string>
|
<string name="theme_summary">تخصيص سمة التطبيق</string>
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
<string name="external_open">Abrir con una aplicación externa</string>
|
<string name="external_open">Abrir con una aplicación externa</string>
|
||||||
<string name="single_delete_confirm">¿Estás seguro de que quieres borrar %s ?</string>
|
<string name="single_delete_confirm">¿Estás seguro de que quieres borrar %s ?</string>
|
||||||
<string name="multiple_delete_confirm">¿Estás seguro de que quiere borrar %s objetos?</string>
|
<string name="multiple_delete_confirm">¿Estás seguro de que quiere borrar %s objetos?</string>
|
||||||
<string name="location">Ubicación: /%s</string>
|
<string name="location">Ubicación: %s</string>
|
||||||
<string name="total_size">Tamaño total: %s</string>
|
<string name="total_size">Tamaño total: %s</string>
|
||||||
<string name="import_from_other_volume">Importar desde otro volumen</string>
|
<string name="import_from_other_volume">Importar desde otro volumen</string>
|
||||||
<string name="read_file_failed">No se ha podido abrir este archivo.</string>
|
<string name="read_file_failed">No se ha podido abrir este archivo.</string>
|
||||||
@ -138,7 +138,6 @@
|
|||||||
<string name="image_saved_successfully">Cambios de la imagen guardados con éxito.</string>
|
<string name="image_saved_successfully">Cambios de la imagen guardados con éxito.</string>
|
||||||
<string name="bitmap_compress_failed">Fallo al comprimir el mapa de bits.</string>
|
<string name="bitmap_compress_failed">Fallo al comprimir el mapa de bits.</string>
|
||||||
<string name="file_write_failed">No se ha podido escribir el archivo.</string>
|
<string name="file_write_failed">No se ha podido escribir el archivo.</string>
|
||||||
<string name="error_not_a_volume">No se reconoce el volumen Gocryptfs. Por favor, comprueba la ruta seleccionada.</string>
|
|
||||||
<string name="version">Versión</string>
|
<string name="version">Versión</string>
|
||||||
<string name="error_cipher_null">Error de cifrado núlo</string>
|
<string name="error_cipher_null">Error de cifrado núlo</string>
|
||||||
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
|
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
|
||||||
@ -173,7 +172,6 @@
|
|||||||
<string name="maximize_quality">Maximizar la calidad</string>
|
<string name="maximize_quality">Maximizar la calidad</string>
|
||||||
<string name="minimize_latency">Minimizar la latencia</string>
|
<string name="minimize_latency">Minimizar la latencia</string>
|
||||||
<string name="auto">Automático</string>
|
<string name="auto">Automático</string>
|
||||||
<string name="xchacha_warning">XChaCha20-Poly1305 sólo está soportado desde gocryptfs v2.2.0. Las versiones anteriores no podrán abrir un volumen basado en este cifrado.</string>
|
|
||||||
<string name="encryption_cipher_label">Cifrado de encriptación:</string>
|
<string name="encryption_cipher_label">Cifrado de encriptación:</string>
|
||||||
<string name="theme">Tema</string>
|
<string name="theme">Tema</string>
|
||||||
<string name="theme_summary">Personalizar el tema de la aplicación</string>
|
<string name="theme_summary">Personalizar el tema de la aplicación</string>
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
<string name="external_open">Abrir com app externo</string>
|
<string name="external_open">Abrir com app externo</string>
|
||||||
<string name="single_delete_confirm">Você tem certeza que quer excluir %s?</string>
|
<string name="single_delete_confirm">Você tem certeza que quer excluir %s?</string>
|
||||||
<string name="multiple_delete_confirm">Você realmente deseja excluir estos %s itens?</string>
|
<string name="multiple_delete_confirm">Você realmente deseja excluir estos %s itens?</string>
|
||||||
<string name="location">Localização: /%s</string>
|
<string name="location">Localização: %s</string>
|
||||||
<string name="total_size">Tamanho total: %s</string>
|
<string name="total_size">Tamanho total: %s</string>
|
||||||
<string name="import_from_other_volume">Importar do outro volume</string>
|
<string name="import_from_other_volume">Importar do outro volume</string>
|
||||||
<string name="read_file_failed">Falha ao abrir este arquivo.</string>
|
<string name="read_file_failed">Falha ao abrir este arquivo.</string>
|
||||||
@ -135,7 +135,6 @@
|
|||||||
<string name="image_saved_successfully">Mudanças na imagem salva com sucesso.</string>
|
<string name="image_saved_successfully">Mudanças na imagem salva com sucesso.</string>
|
||||||
<string name="bitmap_compress_failed">Falha ao compactar o bitmap.</string>
|
<string name="bitmap_compress_failed">Falha ao compactar o bitmap.</string>
|
||||||
<string name="file_write_failed">Falha ao salvar o arquivo.</string>
|
<string name="file_write_failed">Falha ao salvar o arquivo.</string>
|
||||||
<string name="error_not_a_volume">O volume do Gocryptfs não foi reconhecido. Por favor, verifique a localização selecionada.</string>
|
|
||||||
<string name="version">Versão</string>
|
<string name="version">Versão</string>
|
||||||
<string name="error_cipher_null">Erro, o cipher é nulo</string>
|
<string name="error_cipher_null">Erro, o cipher é nulo</string>
|
||||||
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
|
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
|
||||||
@ -170,7 +169,6 @@
|
|||||||
<string name="maximize_quality">Maximizar a qualidade</string>
|
<string name="maximize_quality">Maximizar a qualidade</string>
|
||||||
<string name="minimize_latency">Minimizar a latência</string>
|
<string name="minimize_latency">Minimizar a latência</string>
|
||||||
<string name="auto">Autom</string>
|
<string name="auto">Autom</string>
|
||||||
<string name="xchacha_warning">XChaCha20-Poly1305 só tem suporte desde gocryptfs v2.2.0. Versões mais antigas não podem abrir um volume baseado neste cipher.</string>
|
|
||||||
<string name="encryption_cipher_label">Cipher de criptografia:</string>
|
<string name="encryption_cipher_label">Cipher de criptografia:</string>
|
||||||
<string name="theme">Tema</string>
|
<string name="theme">Tema</string>
|
||||||
<string name="theme_summary">Personalizar o tema do app</string>
|
<string name="theme_summary">Personalizar o tema do app</string>
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
<string name="external_open">Открыть внешним приложением</string>
|
<string name="external_open">Открыть внешним приложением</string>
|
||||||
<string name="single_delete_confirm">Удалить %s?</string>
|
<string name="single_delete_confirm">Удалить %s?</string>
|
||||||
<string name="multiple_delete_confirm">Удалить %s элементов?</string>
|
<string name="multiple_delete_confirm">Удалить %s элементов?</string>
|
||||||
<string name="location">Путь: /%s</string>
|
<string name="location">Путь: %s</string>
|
||||||
<string name="total_size">Общий размер: %s</string>
|
<string name="total_size">Общий размер: %s</string>
|
||||||
<string name="import_from_other_volume">Импорт из другого тома</string>
|
<string name="import_from_other_volume">Импорт из другого тома</string>
|
||||||
<string name="read_file_failed">Невозможно открыть файл.</string>
|
<string name="read_file_failed">Невозможно открыть файл.</string>
|
||||||
@ -133,7 +133,6 @@
|
|||||||
<string name="image_saved_successfully">Изменения изображения успешно сохранены.</string>
|
<string name="image_saved_successfully">Изменения изображения успешно сохранены.</string>
|
||||||
<string name="bitmap_compress_failed">Невозможно сжать растровое изображение.</string>
|
<string name="bitmap_compress_failed">Невозможно сжать растровое изображение.</string>
|
||||||
<string name="file_write_failed">Невозможно записать файл.</string>
|
<string name="file_write_failed">Невозможно записать файл.</string>
|
||||||
<string name="error_not_a_volume">Том GocryptFS не распознан. Проверьте выбранный путь.</string>
|
|
||||||
<string name="version">Версия</string>
|
<string name="version">Версия</string>
|
||||||
<string name="error_cipher_null">Шифр ошибки нулевой</string>
|
<string name="error_cipher_null">Шифр ошибки нулевой</string>
|
||||||
<string name="key_permanently_invalidated_exception_msg">Похоже, вы добавили новый отпечаток пальца. Сохранённый хеш паролей стал непригодным для использования.</string>
|
<string name="key_permanently_invalidated_exception_msg">Похоже, вы добавили новый отпечаток пальца. Сохранённый хеш паролей стал непригодным для использования.</string>
|
||||||
@ -167,7 +166,6 @@
|
|||||||
<string name="maximize_quality">Максимальное качество</string>
|
<string name="maximize_quality">Максимальное качество</string>
|
||||||
<string name="minimize_latency">Минимальная задержка</string>
|
<string name="minimize_latency">Минимальная задержка</string>
|
||||||
<string name="auto">Авто</string>
|
<string name="auto">Авто</string>
|
||||||
<string name="xchacha_warning">Шифр XChaCha20-Poly1305 поддерживается только с версии GocryptFS 2.2.0. Более старые версии не смогут открыть том, использующий данный шифр.</string>
|
|
||||||
<string name="encryption_cipher_label">Шифр:</string>
|
<string name="encryption_cipher_label">Шифр:</string>
|
||||||
<string name="theme">Тема</string>
|
<string name="theme">Тема</string>
|
||||||
<string name="theme_summary">Настройка темы приложения</string>
|
<string name="theme_summary">Настройка темы приложения</string>
|
||||||
|
@ -1,10 +1,24 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string-array name="encryption_cipher">
|
<string-array name="gocryptfs_encryption_ciphers">
|
||||||
<item>AES-GCM</item>
|
<item>AES-GCM</item>
|
||||||
<item>XChaCha20-Poly1305</item>
|
<item>XChaCha20-Poly1305</item>
|
||||||
<item>@string/auto</item>
|
<item>@string/auto</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="cryfs_encryption_ciphers">
|
||||||
|
<item>xchacha20-poly1305</item>
|
||||||
|
<item>aes-256-gcm</item>
|
||||||
|
<item>aes-128-gcm</item>
|
||||||
|
<item>twofish-256-gcm</item>
|
||||||
|
<item>twofish-128-gcm</item>
|
||||||
|
<item>serpent-256-gcm</item>
|
||||||
|
<item>serpent-128-gcm</item>
|
||||||
|
<item>cast-256-gcm</item>
|
||||||
|
<item>mars-448-gcm</item>
|
||||||
|
<item>mars-256-gcm</item>
|
||||||
|
<item>mars-128-gcm</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<string-array name="sort_orders_entries">
|
<string-array name="sort_orders_entries">
|
||||||
<item>Name</item>
|
<item>Name</item>
|
||||||
<item>Size</item>
|
<item>Size</item>
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
<string name="external_open">Open with external app</string>
|
<string name="external_open">Open with external app</string>
|
||||||
<string name="single_delete_confirm">Are you sure you want to delete %s ?</string>
|
<string name="single_delete_confirm">Are you sure you want to delete %s ?</string>
|
||||||
<string name="multiple_delete_confirm">Are you sure you want to delete these %s items ?</string>
|
<string name="multiple_delete_confirm">Are you sure you want to delete these %s items ?</string>
|
||||||
<string name="location">Location: /%s</string>
|
<string name="location">Location: %s</string>
|
||||||
<string name="total_size">Total size: %s</string>
|
<string name="total_size">Total size: %s</string>
|
||||||
<string name="import_from_other_volume">Import from another volume</string>
|
<string name="import_from_other_volume">Import from another volume</string>
|
||||||
<string name="read_file_failed">Failed to open this file.</string>
|
<string name="read_file_failed">Failed to open this file.</string>
|
||||||
@ -138,7 +138,7 @@
|
|||||||
<string name="image_saved_successfully">Image changes successfully saved.</string>
|
<string name="image_saved_successfully">Image changes successfully saved.</string>
|
||||||
<string name="bitmap_compress_failed">Failed to compress the bitmap.</string>
|
<string name="bitmap_compress_failed">Failed to compress the bitmap.</string>
|
||||||
<string name="file_write_failed">Failed to write the file.</string>
|
<string name="file_write_failed">Failed to write the file.</string>
|
||||||
<string name="error_not_a_volume">Gocryptfs volume not recognized. Please check the selected path.</string>
|
<string name="error_not_a_volume">Encrypted volume not recognized. Please check the selected path.</string>
|
||||||
<string name="version">Version</string>
|
<string name="version">Version</string>
|
||||||
<string name="error_cipher_null">Error cipher is null</string>
|
<string name="error_cipher_null">Error cipher is null</string>
|
||||||
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
|
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
|
||||||
@ -173,7 +173,6 @@
|
|||||||
<string name="maximize_quality">Maximize quality</string>
|
<string name="maximize_quality">Maximize quality</string>
|
||||||
<string name="minimize_latency">Minimize latency</string>
|
<string name="minimize_latency">Minimize latency</string>
|
||||||
<string name="auto">Auto</string>
|
<string name="auto">Auto</string>
|
||||||
<string name="xchacha_warning">XChaCha20-Poly1305 is only supported since gocryptfs v2.2.0. Older versions won\'t be able to open a volume based on this cipher.</string>
|
|
||||||
<string name="encryption_cipher_label">Encryption cipher:</string>
|
<string name="encryption_cipher_label">Encryption cipher:</string>
|
||||||
<string name="theme">Theme</string>
|
<string name="theme">Theme</string>
|
||||||
<string name="theme_summary">Customize app theme</string>
|
<string name="theme_summary">Customize app theme</string>
|
||||||
@ -242,4 +241,9 @@
|
|||||||
<string name="elements_selected">%d/%d selected</string>
|
<string name="elements_selected">%d/%d selected</string>
|
||||||
<string name="pin_passwords_title">Numeric keypad layout</string>
|
<string name="pin_passwords_title">Numeric keypad layout</string>
|
||||||
<string name="pin_passwords_summary">Use a numeric keypad layout when entering volume passwords</string>
|
<string name="pin_passwords_summary">Use a numeric keypad layout when entering volume passwords</string>
|
||||||
|
<string name="volume_type_label">Volume type:</string>
|
||||||
|
<string name="gocryptfs">Gocryptfs</string>
|
||||||
|
<string name="cryfs">CryFS</string>
|
||||||
|
<string name="gocryptfs_disabled">Gocryptfs support has been disabled</string>
|
||||||
|
<string name="cryfs_disabled">CryFS support has been disabled</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -19,3 +19,5 @@ android.useAndroidX=true
|
|||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
# Kotlin code style for this project: "official" or "obsolete":
|
# Kotlin code style for this project: "official" or "obsolete":
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
|
||||||
|
android.native.buildOutput=verbose
|
Loading…
Reference in New Issue
Block a user