Merge branch 'feature/251-update-to-API-level-30' into develop
This commit is contained in:
commit
c18632106e
@ -8,8 +8,8 @@ allprojects {
|
|||||||
ext {
|
ext {
|
||||||
androidBuildToolsVersion = "30.0.2"
|
androidBuildToolsVersion = "30.0.2"
|
||||||
androidMinSdkVersion = 24
|
androidMinSdkVersion = 24
|
||||||
androidTargetSdkVersion = 29
|
androidTargetSdkVersion = 30
|
||||||
androidCompileSdkVersion = 29
|
androidCompileSdkVersion = 30
|
||||||
|
|
||||||
// android and java libs
|
// android and java libs
|
||||||
androidVersion = '4.1.1.4'
|
androidVersion = '4.1.1.4'
|
||||||
|
@ -82,7 +82,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
greendao {
|
greendao {
|
||||||
schemaVersion 9
|
schemaVersion 10
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.all {
|
configurations.all {
|
||||||
|
@ -50,6 +50,7 @@ class UpgradeDatabaseTest {
|
|||||||
Upgrade6To7().applyTo(db, 6)
|
Upgrade6To7().applyTo(db, 6)
|
||||||
Upgrade7To8().applyTo(db, 7)
|
Upgrade7To8().applyTo(db, 7)
|
||||||
Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8)
|
Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8)
|
||||||
|
Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9)
|
||||||
|
|
||||||
CloudEntityDao(DaoConfig(db, CloudEntityDao::class.java)).loadAll()
|
CloudEntityDao(DaoConfig(db, CloudEntityDao::class.java)).loadAll()
|
||||||
VaultEntityDao(DaoConfig(db, VaultEntityDao::class.java)).loadAll()
|
VaultEntityDao(DaoConfig(db, VaultEntityDao::class.java)).loadAll()
|
||||||
@ -407,4 +408,66 @@ class UpgradeDatabaseTest {
|
|||||||
|
|
||||||
Assert.assertThat(sharedPreferencesHandler.isBetaModeAlreadyShown(), CoreMatchers.`is`(false))
|
Assert.assertThat(sharedPreferencesHandler.isBetaModeAlreadyShown(), CoreMatchers.`is`(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun upgrade9To10() {
|
||||||
|
Upgrade0To1().applyTo(db, 0)
|
||||||
|
Upgrade1To2().applyTo(db, 1)
|
||||||
|
Upgrade2To3(context).applyTo(db, 2)
|
||||||
|
Upgrade3To4().applyTo(db, 3)
|
||||||
|
Upgrade4To5().applyTo(db, 4)
|
||||||
|
Upgrade5To6().applyTo(db, 5)
|
||||||
|
Upgrade6To7().applyTo(db, 6)
|
||||||
|
Upgrade7To8().applyTo(db, 7)
|
||||||
|
Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8)
|
||||||
|
|
||||||
|
Sql.insertInto("CLOUD_ENTITY") //
|
||||||
|
.integer("_id", 15) //
|
||||||
|
.text("TYPE", CloudType.LOCAL.name) //
|
||||||
|
.text("URL", "url") //
|
||||||
|
.text("USERNAME", "username") //
|
||||||
|
.text("WEBDAV_CERTIFICATE", "certificate") //
|
||||||
|
.text("ACCESS_TOKEN", "accessToken")
|
||||||
|
.text("S3_BUCKET", "s3Bucket") //
|
||||||
|
.text("S3_REGION", "s3Region") //
|
||||||
|
.text("S3_SECRET_KEY", "s3SecretKey") //
|
||||||
|
.executeOn(db)
|
||||||
|
|
||||||
|
Sql.insertInto("VAULT_ENTITY") //
|
||||||
|
.integer("_id", 25) //
|
||||||
|
.integer("FOLDER_CLOUD_ID", 15) //
|
||||||
|
.text("FOLDER_PATH", "path") //
|
||||||
|
.text("FOLDER_NAME", "name") //
|
||||||
|
.text("CLOUD_TYPE", CloudType.LOCAL.name) //
|
||||||
|
.text("PASSWORD", "password") //
|
||||||
|
.integer("POSITION", 10) //
|
||||||
|
.executeOn(db)
|
||||||
|
|
||||||
|
Sql.insertInto("VAULT_ENTITY") //
|
||||||
|
.integer("_id", 26) //
|
||||||
|
.integer("FOLDER_CLOUD_ID", 4) //
|
||||||
|
.text("FOLDER_PATH", "pathOfVault26") //
|
||||||
|
.text("FOLDER_NAME", "name") //
|
||||||
|
.text("CLOUD_TYPE", CloudType.LOCAL.name) //
|
||||||
|
.text("PASSWORD", "password") //
|
||||||
|
.integer("POSITION", 11) //
|
||||||
|
.executeOn(db)
|
||||||
|
|
||||||
|
Sql.query("CLOUD_ENTITY").executeOn(db).use {
|
||||||
|
Assert.assertThat(it.count, CoreMatchers.`is`(5))
|
||||||
|
}
|
||||||
|
|
||||||
|
Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9)
|
||||||
|
|
||||||
|
Sql.query("VAULT_ENTITY").executeOn(db).use {
|
||||||
|
Assert.assertThat(it.count, CoreMatchers.`is`(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
Sql.query("CLOUD_ENTITY").executeOn(db).use {
|
||||||
|
Assert.assertThat(it.count, CoreMatchers.`is`(4))
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertThat(sharedPreferencesHandler.vaultsRemovedDuringMigration(), CoreMatchers.`is`(Pair("LOCAL", arrayListOf("pathOfVault26"))))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,11 +31,6 @@ public class CryptoCloud implements Cloud {
|
|||||||
return vault.equals(cloud.vault);
|
return vault.equals(cloud.vault);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean predefined() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean persistent() {
|
public boolean persistent() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.data.cloud.local.storageaccessframework
|
package org.cryptomator.data.cloud.local
|
||||||
|
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import org.cryptomator.domain.CloudFolder
|
import org.cryptomator.domain.CloudFolder
|
@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.data.cloud.local.storageaccessframework
|
package org.cryptomator.data.cloud.local
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import org.cryptomator.domain.Cloud
|
import org.cryptomator.domain.Cloud
|
@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.data.cloud.local.storageaccessframework
|
package org.cryptomator.data.cloud.local
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import org.cryptomator.domain.Cloud
|
import org.cryptomator.domain.Cloud
|
@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.data.cloud.local.storageaccessframework
|
package org.cryptomator.data.cloud.local
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.cryptomator.domain.LocalStorageCloud
|
import org.cryptomator.domain.LocalStorageCloud
|
@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.data.cloud.local.storageaccessframework
|
package org.cryptomator.data.cloud.local
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@ -7,10 +7,10 @@ import android.net.Uri
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import org.cryptomator.data.cloud.local.storageaccessframework.LocalStorageAccessFrameworkNodeFactory.file
|
import org.cryptomator.data.cloud.local.LocalStorageAccessFrameworkNodeFactory.file
|
||||||
import org.cryptomator.data.cloud.local.storageaccessframework.LocalStorageAccessFrameworkNodeFactory.folder
|
import org.cryptomator.data.cloud.local.LocalStorageAccessFrameworkNodeFactory.folder
|
||||||
import org.cryptomator.data.cloud.local.storageaccessframework.LocalStorageAccessFrameworkNodeFactory.from
|
import org.cryptomator.data.cloud.local.LocalStorageAccessFrameworkNodeFactory.from
|
||||||
import org.cryptomator.data.cloud.local.storageaccessframework.LocalStorageAccessFrameworkNodeFactory.getNodePath
|
import org.cryptomator.data.cloud.local.LocalStorageAccessFrameworkNodeFactory.getNodePath
|
||||||
import org.cryptomator.data.util.CopyStream
|
import org.cryptomator.data.util.CopyStream
|
||||||
import org.cryptomator.data.util.TransferredBytesAwareInputStream
|
import org.cryptomator.data.util.TransferredBytesAwareInputStream
|
||||||
import org.cryptomator.data.util.TransferredBytesAwareOutputStream
|
import org.cryptomator.data.util.TransferredBytesAwareOutputStream
|
||||||
@ -243,7 +243,8 @@ internal class LocalStorageAccessFrameworkImpl(context: Context, private val mim
|
|||||||
private fun rename(source: LocalStorageAccessNode, name: String): LocalStorageAccessNode {
|
private fun rename(source: LocalStorageAccessNode, name: String): LocalStorageAccessNode {
|
||||||
source.parent?.let { parent ->
|
source.parent?.let { parent ->
|
||||||
var newUri = try {
|
var newUri = try {
|
||||||
DocumentsContract.renameDocument(contentResolver(), source.uri, name)
|
requireNotNull(source.uri)
|
||||||
|
DocumentsContract.renameDocument(contentResolver(), source.uri!!, name)
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
/* Bug in Android 9 see #460 TLDR; In this renameDocument-method, Android 9 throws
|
/* Bug in Android 9 see #460 TLDR; In this renameDocument-method, Android 9 throws
|
||||||
a `FileNotFoundException` although the file exists and is also renamed. */
|
a `FileNotFoundException` although the file exists and is also renamed. */
|
||||||
@ -336,11 +337,13 @@ internal class LocalStorageAccessFrameworkImpl(context: Context, private val mim
|
|||||||
|
|
||||||
private fun createNewDocumentSupplier(file: LocalStorageAccessFile): Supplier<Uri?> {
|
private fun createNewDocumentSupplier(file: LocalStorageAccessFile): Supplier<Uri?> {
|
||||||
return Supplier {
|
return Supplier {
|
||||||
val mimeType = if (mimeTypes.fromFilename(file.name) == null) MimeType.APPLICATION_OCTET_STREAM else mimeTypes.fromFilename(file.name)
|
file.parent.uri?.let {
|
||||||
try {
|
val mimeType = if (mimeTypes.fromFilename(file.name) == null) MimeType.APPLICATION_OCTET_STREAM else mimeTypes.fromFilename(file.name)
|
||||||
DocumentsContract.createDocument(contentResolver(), file.parent.uri, mimeType.toString(), file.name) // FIXME
|
try {
|
||||||
} catch (e: FileNotFoundException) {
|
DocumentsContract.createDocument(contentResolver(), it, mimeType.toString(), file.name)
|
||||||
null
|
} catch (e: FileNotFoundException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -372,7 +375,7 @@ internal class LocalStorageAccessFrameworkImpl(context: Context, private val mim
|
|||||||
fun delete(node: LocalStorageAccessNode) {
|
fun delete(node: LocalStorageAccessNode) {
|
||||||
requireNotNull(node.uri)
|
requireNotNull(node.uri)
|
||||||
try {
|
try {
|
||||||
DocumentsContract.deleteDocument(contentResolver(), node.uri)
|
DocumentsContract.deleteDocument(contentResolver(), node.uri!!)
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
throw NoSuchCloudFileException(node.name)
|
throw NoSuchCloudFileException(node.name)
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.data.cloud.local.storageaccessframework
|
package org.cryptomator.data.cloud.local
|
||||||
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.data.cloud.local.storageaccessframework
|
package org.cryptomator.data.cloud.local
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import org.cryptomator.domain.CloudNode
|
import org.cryptomator.domain.CloudNode
|
@ -1,9 +1,10 @@
|
|||||||
package org.cryptomator.data.cloud.local;
|
package org.cryptomator.data.cloud.local;
|
||||||
|
|
||||||
import android.content.Context;
|
import static org.cryptomator.domain.CloudType.LOCAL;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.UriPermission;
|
||||||
|
|
||||||
import org.cryptomator.data.cloud.local.file.LocalStorageContentRepository;
|
|
||||||
import org.cryptomator.data.cloud.local.storageaccessframework.LocalStorageAccessFrameworkContentRepository;
|
|
||||||
import org.cryptomator.data.repository.CloudContentRepositoryFactory;
|
import org.cryptomator.data.repository.CloudContentRepositoryFactory;
|
||||||
import org.cryptomator.domain.Cloud;
|
import org.cryptomator.domain.Cloud;
|
||||||
import org.cryptomator.domain.LocalStorageCloud;
|
import org.cryptomator.domain.LocalStorageCloud;
|
||||||
@ -11,15 +12,11 @@ import org.cryptomator.domain.exception.authentication.NoAuthenticationProvidedE
|
|||||||
import org.cryptomator.domain.repository.CloudContentRepository;
|
import org.cryptomator.domain.repository.CloudContentRepository;
|
||||||
import org.cryptomator.util.file.MimeTypes;
|
import org.cryptomator.util.file.MimeTypes;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
|
||||||
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
|
||||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
|
||||||
import static androidx.core.content.ContextCompat.checkSelfPermission;
|
|
||||||
import static org.cryptomator.domain.CloudType.LOCAL;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class LocalStorageContentRepositoryFactory implements CloudContentRepositoryFactory {
|
public class LocalStorageContentRepositoryFactory implements CloudContentRepositoryFactory {
|
||||||
|
|
||||||
@ -39,23 +36,14 @@ public class LocalStorageContentRepositoryFactory implements CloudContentReposit
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CloudContentRepository cloudContentRepositoryFor(Cloud cloud) {
|
public CloudContentRepository cloudContentRepositoryFor(Cloud cloud) {
|
||||||
if (!hasPermissions(WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE)) {
|
List<UriPermission> permissions = context.getContentResolver().getPersistedUriPermissions();
|
||||||
throw new NoAuthenticationProvidedException(cloud);
|
for (UriPermission permission : permissions) {
|
||||||
}
|
if(permission.getUri().toString().equals(((LocalStorageCloud) cloud).rootUri())) {
|
||||||
if (((LocalStorageCloud) cloud).rootUri() != null) {
|
return new LocalStorageAccessFrameworkContentRepository(context, mimeTypes, (LocalStorageCloud) cloud);
|
||||||
return new LocalStorageAccessFrameworkContentRepository(context, mimeTypes, (LocalStorageCloud) cloud);
|
|
||||||
} else {
|
|
||||||
return new LocalStorageContentRepository(context, (LocalStorageCloud) cloud);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasPermissions(String... permissions) {
|
|
||||||
for (String permission : permissions) {
|
|
||||||
if (checkSelfPermission(context, permission) != PERMISSION_GRANTED) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
throw new NoAuthenticationProvidedException(cloud);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.data.cloud.local.storageaccessframework
|
package org.cryptomator.data.cloud.local
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
@ -1,11 +0,0 @@
|
|||||||
package org.cryptomator.data.cloud.local.file
|
|
||||||
|
|
||||||
import org.cryptomator.domain.Cloud
|
|
||||||
import org.cryptomator.domain.CloudFile
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class LocalFile(override val parent: LocalFolder, override val name: String, override val path: String, override val size: Long?, override val modified: Date?) : CloudFile, LocalNode {
|
|
||||||
|
|
||||||
override val cloud: Cloud?
|
|
||||||
get() = parent.cloud
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package org.cryptomator.data.cloud.local.file
|
|
||||||
|
|
||||||
import org.cryptomator.domain.Cloud
|
|
||||||
import org.cryptomator.domain.CloudFolder
|
|
||||||
|
|
||||||
open class LocalFolder(override val parent: LocalFolder?, override val name: String, override val path: String) : CloudFolder, LocalNode {
|
|
||||||
|
|
||||||
override val cloud: Cloud?
|
|
||||||
get() = parent?.cloud
|
|
||||||
|
|
||||||
override fun withCloud(cloud: Cloud?): LocalFolder? {
|
|
||||||
return LocalFolder(parent?.withCloud(cloud), name, path)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package org.cryptomator.data.cloud.local.file
|
|
||||||
|
|
||||||
import org.cryptomator.domain.CloudNode
|
|
||||||
|
|
||||||
interface LocalNode : CloudNode {
|
|
||||||
|
|
||||||
override val parent: LocalFolder?
|
|
||||||
|
|
||||||
}
|
|
@ -1,111 +0,0 @@
|
|||||||
package org.cryptomator.data.cloud.local.file
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import org.cryptomator.domain.LocalStorageCloud
|
|
||||||
import org.cryptomator.domain.exception.BackendException
|
|
||||||
import org.cryptomator.domain.exception.FatalBackendException
|
|
||||||
import org.cryptomator.domain.exception.NoSuchCloudFileException
|
|
||||||
import org.cryptomator.domain.repository.CloudContentRepository
|
|
||||||
import org.cryptomator.domain.usecases.ProgressAware
|
|
||||||
import org.cryptomator.domain.usecases.cloud.DataSource
|
|
||||||
import org.cryptomator.domain.usecases.cloud.DownloadState
|
|
||||||
import org.cryptomator.domain.usecases.cloud.UploadState
|
|
||||||
import org.cryptomator.util.ExceptionUtil
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
class LocalStorageContentRepository(context: Context, localStorageCloud: LocalStorageCloud) : CloudContentRepository<LocalStorageCloud, LocalNode, LocalFolder, LocalFile> {
|
|
||||||
|
|
||||||
private val localStorageImpl: LocalStorageImpl = LocalStorageImpl(context, localStorageCloud)
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun root(cloud: LocalStorageCloud): LocalFolder {
|
|
||||||
return localStorageImpl.root()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resolve(cloud: LocalStorageCloud, path: String): LocalFolder {
|
|
||||||
return localStorageImpl.resolve(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun file(parent: LocalFolder, name: String): LocalFile {
|
|
||||||
return localStorageImpl.file(parent, name, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun file(parent: LocalFolder, name: String, size: Long?): LocalFile {
|
|
||||||
return localStorageImpl.file(parent, name, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun folder(parent: LocalFolder, name: String): LocalFolder {
|
|
||||||
return localStorageImpl.folder(parent, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun exists(node: LocalNode): Boolean {
|
|
||||||
return localStorageImpl.exists(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun list(folder: LocalFolder): List<LocalNode> {
|
|
||||||
return localStorageImpl.list(folder)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun create(folder: LocalFolder): LocalFolder {
|
|
||||||
return localStorageImpl.create(folder)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun move(source: LocalFolder, target: LocalFolder): LocalFolder {
|
|
||||||
return localStorageImpl.move(source, target) as LocalFolder
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun move(source: LocalFile, target: LocalFile): LocalFile {
|
|
||||||
return localStorageImpl.move(source, target) as LocalFile
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun write(file: LocalFile, data: DataSource, progressAware: ProgressAware<UploadState>, replace: Boolean, size: Long): LocalFile {
|
|
||||||
return try {
|
|
||||||
localStorageImpl.write(file, data, progressAware, replace, size)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
if (ExceptionUtil.contains(e, FileNotFoundException::class.java)) {
|
|
||||||
throw NoSuchCloudFileException(file.name)
|
|
||||||
}
|
|
||||||
throw FatalBackendException(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun read(file: LocalFile, encryptedTmpFile: File?, data: OutputStream, progressAware: ProgressAware<DownloadState>) {
|
|
||||||
try {
|
|
||||||
localStorageImpl.read(file, data, progressAware)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
if (ExceptionUtil.contains(e, FileNotFoundException::class.java)) {
|
|
||||||
throw NoSuchCloudFileException(file.name)
|
|
||||||
}
|
|
||||||
throw FatalBackendException(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun delete(node: LocalNode) {
|
|
||||||
localStorageImpl.delete(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun checkAuthenticationAndRetrieveCurrentAccount(cloud: LocalStorageCloud): String {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
override fun logout(cloud: LocalStorageCloud) {
|
|
||||||
// empty
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,165 +0,0 @@
|
|||||||
package org.cryptomator.data.cloud.local.file
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import org.cryptomator.data.util.CopyStream
|
|
||||||
import org.cryptomator.data.util.TransferredBytesAwareInputStream
|
|
||||||
import org.cryptomator.data.util.TransferredBytesAwareOutputStream
|
|
||||||
import org.cryptomator.domain.CloudNode
|
|
||||||
import org.cryptomator.domain.LocalStorageCloud
|
|
||||||
import org.cryptomator.domain.exception.BackendException
|
|
||||||
import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException
|
|
||||||
import org.cryptomator.domain.exception.FatalBackendException
|
|
||||||
import org.cryptomator.domain.exception.NoSuchCloudFileException
|
|
||||||
import org.cryptomator.domain.exception.ParentFolderIsNullException
|
|
||||||
import org.cryptomator.domain.usecases.ProgressAware
|
|
||||||
import org.cryptomator.domain.usecases.cloud.DataSource
|
|
||||||
import org.cryptomator.domain.usecases.cloud.DownloadState
|
|
||||||
import org.cryptomator.domain.usecases.cloud.Progress
|
|
||||||
import org.cryptomator.domain.usecases.cloud.UploadState
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
internal class LocalStorageImpl(private val context: Context, localStorageCloud: LocalStorageCloud) {
|
|
||||||
|
|
||||||
private val root: RootLocalFolder = RootLocalFolder(localStorageCloud)
|
|
||||||
|
|
||||||
fun root(): LocalFolder {
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
fun resolve(path: String): LocalFolder {
|
|
||||||
val names = path.substring(root.path.length + 1).split("/").toTypedArray()
|
|
||||||
var folder: LocalFolder = root
|
|
||||||
for (name in names) {
|
|
||||||
folder = folder(folder, name)
|
|
||||||
}
|
|
||||||
return folder
|
|
||||||
}
|
|
||||||
|
|
||||||
fun file(folder: LocalFolder, name: String, size: Long?): LocalFile {
|
|
||||||
return LocalStorageNodeFactory.file(folder, name, folder.path + '/' + name, size, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun folder(folder: LocalFolder, name: String): LocalFolder {
|
|
||||||
return LocalStorageNodeFactory.folder(folder, name, folder.path + '/' + name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exists(node: CloudNode): Boolean {
|
|
||||||
return File(node.path).exists()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
fun list(folder: LocalFolder): List<LocalNode> {
|
|
||||||
val localDirectory = File(folder.path)
|
|
||||||
if (!exists(folder)) {
|
|
||||||
throw NoSuchCloudFileException()
|
|
||||||
}
|
|
||||||
return localDirectory.listFiles()?.map { file -> LocalStorageNodeFactory.from(folder, file) }
|
|
||||||
?: throw FatalBackendException("listFiles() shouldn't return null")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
fun create(folder: LocalFolder): LocalFolder {
|
|
||||||
folder.parent?.let { parentFolder ->
|
|
||||||
val createFolder = File(folder.path)
|
|
||||||
if (createFolder.exists()) {
|
|
||||||
throw CloudNodeAlreadyExistsException(folder.name)
|
|
||||||
}
|
|
||||||
if (!createFolder.mkdirs()) {
|
|
||||||
throw FatalBackendException("Couldn't create a local folder at " + folder.path)
|
|
||||||
}
|
|
||||||
return LocalStorageNodeFactory.folder(parentFolder, createFolder)
|
|
||||||
} ?: throw ParentFolderIsNullException(folder.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(BackendException::class)
|
|
||||||
fun move(source: LocalNode, target: LocalNode): LocalNode {
|
|
||||||
target.parent?.let {
|
|
||||||
val sourceFile = File(source.path)
|
|
||||||
val targetFile = File(target.path)
|
|
||||||
if (targetFile.exists()) {
|
|
||||||
throw CloudNodeAlreadyExistsException(target.name)
|
|
||||||
}
|
|
||||||
if (!sourceFile.exists()) {
|
|
||||||
throw NoSuchCloudFileException(source.name)
|
|
||||||
}
|
|
||||||
if (!sourceFile.renameTo(targetFile)) {
|
|
||||||
throw FatalBackendException("Couldn't move " + source.path + " to " + target.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return LocalStorageNodeFactory.from(it, targetFile)
|
|
||||||
} ?: throw ParentFolderIsNullException(target.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun delete(node: CloudNode) {
|
|
||||||
val fileOrDirectory = File(node.path)
|
|
||||||
if (!deleteRecursive(fileOrDirectory)) {
|
|
||||||
throw FatalBackendException("Couldn't delete local CloudNode $fileOrDirectory")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteRecursive(fileOrDirectory: File): Boolean {
|
|
||||||
if (fileOrDirectory.isDirectory) {
|
|
||||||
fileOrDirectory.listFiles()?.forEach {
|
|
||||||
deleteRecursive(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileOrDirectory.delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class, BackendException::class)
|
|
||||||
fun write(file: LocalFile, data: DataSource, progressAware: ProgressAware<UploadState>, replace: Boolean, size: Long): LocalFile {
|
|
||||||
if (!replace && exists(file)) {
|
|
||||||
throw CloudNodeAlreadyExistsException("CloudNode already exists and replace is false")
|
|
||||||
}
|
|
||||||
progressAware.onProgress(Progress.started(UploadState.upload(file)))
|
|
||||||
val localFile = File(file.path)
|
|
||||||
FileOutputStream(localFile).use { out ->
|
|
||||||
data.open(context)?.use { inputStream ->
|
|
||||||
object : TransferredBytesAwareInputStream(inputStream) {
|
|
||||||
override fun bytesTransferred(transferred: Long) {
|
|
||||||
progressAware.onProgress( //
|
|
||||||
Progress.progress(UploadState.upload(file)) //
|
|
||||||
.between(0) //
|
|
||||||
.and(size) //
|
|
||||||
.withValue(transferred)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.use { CopyStream.copyStreamToStream(it, out) }
|
|
||||||
} ?: throw FatalBackendException("InputStream shouldn't be null")
|
|
||||||
}
|
|
||||||
progressAware.onProgress(Progress.completed(UploadState.upload(file)))
|
|
||||||
return LocalStorageNodeFactory.file( //
|
|
||||||
file.parent, //
|
|
||||||
file.name, //
|
|
||||||
localFile.path, //
|
|
||||||
localFile.length(), //
|
|
||||||
Date(localFile.lastModified())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun read(file: LocalFile, data: OutputStream, progressAware: ProgressAware<DownloadState>) {
|
|
||||||
progressAware.onProgress(Progress.started(DownloadState.download(file)))
|
|
||||||
val localFile = File(file.path)
|
|
||||||
FileInputStream(localFile).use { inputStream ->
|
|
||||||
object : TransferredBytesAwareOutputStream(data) {
|
|
||||||
override fun bytesTransferred(transferred: Long) {
|
|
||||||
progressAware //
|
|
||||||
.onProgress(
|
|
||||||
Progress.progress(DownloadState.download(file)) //
|
|
||||||
.between(0) //
|
|
||||||
.and(localFile.length()) //
|
|
||||||
.withValue(transferred)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.use { out -> CopyStream.copyStreamToStream(inputStream, out) }
|
|
||||||
}
|
|
||||||
progressAware.onProgress(Progress.completed(DownloadState.download(file)))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package org.cryptomator.data.cloud.local.file
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
internal object LocalStorageNodeFactory {
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun from(parent: LocalFolder, file: File): LocalNode {
|
|
||||||
return if (file.isDirectory) {
|
|
||||||
folder(parent, file)
|
|
||||||
} else {
|
|
||||||
file( //
|
|
||||||
parent, //
|
|
||||||
file.name, //
|
|
||||||
file.path, //
|
|
||||||
file.length(), //
|
|
||||||
Date(file.lastModified())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun folder(parent: LocalFolder, file: File): LocalFolder {
|
|
||||||
return folder(parent, file.name, file.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun folder(parent: LocalFolder, name: String, path: String): LocalFolder {
|
|
||||||
return LocalFolder(parent, name, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun file(folder: LocalFolder, name: String, path: String, size: Long?, modified: Date?): LocalFile {
|
|
||||||
return LocalFile(folder, name, path, size, modified)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package org.cryptomator.data.cloud.local.file
|
|
||||||
|
|
||||||
import android.os.Environment
|
|
||||||
import org.cryptomator.domain.Cloud
|
|
||||||
import org.cryptomator.domain.LocalStorageCloud
|
|
||||||
|
|
||||||
class RootLocalFolder(private val localStorageCloud: LocalStorageCloud) : LocalFolder(null, "", Environment.getExternalStorageDirectory().path) {
|
|
||||||
|
|
||||||
override val cloud: Cloud
|
|
||||||
get() = localStorageCloud
|
|
||||||
|
|
||||||
override fun withCloud(cloud: Cloud?): RootLocalFolder {
|
|
||||||
return RootLocalFolder(cloud as LocalStorageCloud)
|
|
||||||
}
|
|
||||||
}
|
|
@ -27,7 +27,8 @@ class DatabaseUpgrades {
|
|||||||
Upgrade5To6 upgrade5To6, //
|
Upgrade5To6 upgrade5To6, //
|
||||||
Upgrade6To7 upgrade6To7, //
|
Upgrade6To7 upgrade6To7, //
|
||||||
Upgrade7To8 upgrade7To8, //
|
Upgrade7To8 upgrade7To8, //
|
||||||
Upgrade8To9 upgrade8To9) {
|
Upgrade8To9 upgrade8To9, //
|
||||||
|
Upgrade9To10 upgrade9To10) {
|
||||||
|
|
||||||
availableUpgrades = defineUpgrades( //
|
availableUpgrades = defineUpgrades( //
|
||||||
upgrade0To1, //
|
upgrade0To1, //
|
||||||
@ -38,7 +39,8 @@ class DatabaseUpgrades {
|
|||||||
upgrade5To6, //
|
upgrade5To6, //
|
||||||
upgrade6To7, //
|
upgrade6To7, //
|
||||||
upgrade7To8, //
|
upgrade7To8, //
|
||||||
upgrade8To9);
|
upgrade8To9, //
|
||||||
|
upgrade9To10);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Comparator<DatabaseUpgrade> reverseOrder() {
|
private static Comparator<DatabaseUpgrade> reverseOrder() {
|
||||||
|
48
data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt
Normal file
48
data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package org.cryptomator.data.db
|
||||||
|
|
||||||
|
import org.cryptomator.domain.CloudType
|
||||||
|
import org.cryptomator.util.SharedPreferencesHandler
|
||||||
|
import org.greenrobot.greendao.database.Database
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
internal class Upgrade9To10 @Inject constructor(private val sharedPreferencesHandler: SharedPreferencesHandler) : DatabaseUpgrade(9, 10) {
|
||||||
|
|
||||||
|
private val defaultLocalStorageCloudId = 4L
|
||||||
|
|
||||||
|
override fun internalApplyTo(db: Database, origin: Int) {
|
||||||
|
db.beginTransaction()
|
||||||
|
|
||||||
|
try {
|
||||||
|
Sql.query("VAULT_ENTITY")
|
||||||
|
.columns(listOf("FOLDER_PATH"))
|
||||||
|
.where("FOLDER_CLOUD_ID", Sql.eq(defaultLocalStorageCloudId))
|
||||||
|
.executeOn(db).use {
|
||||||
|
val vaultsToBeRemoved = ArrayList<String>()
|
||||||
|
while (it.moveToNext()) {
|
||||||
|
val folderPath = it.getString(it.getColumnIndex("FOLDER_PATH"))
|
||||||
|
vaultsToBeRemoved.add(folderPath)
|
||||||
|
}
|
||||||
|
if (vaultsToBeRemoved.isNotEmpty()) {
|
||||||
|
sharedPreferencesHandler.vaultsRemovedDuringMigration(Pair(CloudType.LOCAL.name, vaultsToBeRemoved))
|
||||||
|
Timber.tag("Upgrade9To10").i("Added %s to the removeDuringMigrations", vaultsToBeRemoved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sql.deleteFrom("VAULT_ENTITY")
|
||||||
|
.where("FOLDER_CLOUD_ID", Sql.eq(defaultLocalStorageCloudId))
|
||||||
|
.executeOn(db)
|
||||||
|
|
||||||
|
Sql.deleteFrom("CLOUD_ENTITY")
|
||||||
|
.where("_id", Sql.eq(defaultLocalStorageCloudId))
|
||||||
|
.where("TYPE", Sql.eq("LOCAL"))
|
||||||
|
.executeOn(db)
|
||||||
|
|
||||||
|
db.setTransactionSuccessful()
|
||||||
|
} finally {
|
||||||
|
db.endTransaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -76,9 +76,6 @@ class CloudRepositoryImpl implements CloudRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(Cloud cloud) {
|
public void delete(Cloud cloud) {
|
||||||
if (cloud.predefined()) {
|
|
||||||
throw new IllegalArgumentException("Can not delete predefined cloud");
|
|
||||||
}
|
|
||||||
if (!cloud.persistent()) {
|
if (!cloud.persistent()) {
|
||||||
throw new IllegalArgumentException("Can not delete non persistent cloud");
|
throw new IllegalArgumentException("Can not delete non persistent cloud");
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,6 @@ class NetworkConnectionCheck @Inject internal constructor(private val context: C
|
|||||||
fun checkWifiOnAndConnected(): Boolean {
|
fun checkWifiOnAndConnected(): Boolean {
|
||||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
val activeNetwork = connectivityManager.activeNetwork
|
val activeNetwork = connectivityManager.activeNetwork
|
||||||
return connectivityManager.getNetworkCapabilities(activeNetwork).hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
return connectivityManager.getNetworkCapabilities(activeNetwork)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ interface Cloud : Serializable {
|
|||||||
fun id(): Long?
|
fun id(): Long?
|
||||||
fun type(): CloudType?
|
fun type(): CloudType?
|
||||||
fun configurationMatches(cloud: Cloud?): Boolean
|
fun configurationMatches(cloud: Cloud?): Boolean
|
||||||
fun predefined(): Boolean
|
|
||||||
fun persistent(): Boolean
|
fun persistent(): Boolean
|
||||||
fun requiresNetwork(): Boolean
|
fun requiresNetwork(): Boolean
|
||||||
}
|
}
|
||||||
|
@ -48,11 +48,6 @@ public class DropboxCloud implements Cloud {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean predefined() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean persistent() {
|
public boolean persistent() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -48,11 +48,6 @@ public class GoogleDriveCloud implements Cloud {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean predefined() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean persistent() {
|
public boolean persistent() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.domain;
|
package org.cryptomator.domain;
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -48,11 +47,6 @@ public class LocalStorageCloud implements Cloud {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean predefined() {
|
|
||||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean persistent() {
|
public boolean persistent() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -43,11 +43,6 @@ public class OnedriveCloud implements Cloud {
|
|||||||
return CloudType.ONEDRIVE;
|
return CloudType.ONEDRIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean predefined() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean persistent() {
|
public boolean persistent() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -59,12 +59,6 @@ public class PCloud implements Cloud {
|
|||||||
return username.equals(cloud.username);
|
return username.equals(cloud.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean predefined() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean persistent() {
|
public boolean persistent() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -80,12 +80,6 @@ public class S3Cloud implements Cloud {
|
|||||||
return s3Bucket.equals(cloud.s3Bucket) && s3Endpoint.equals(cloud.s3Endpoint) && s3Region.equals(cloud.s3Region);
|
return s3Bucket.equals(cloud.s3Bucket) && s3Endpoint.equals(cloud.s3Endpoint) && s3Region.equals(cloud.s3Region);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean predefined() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean persistent() {
|
public boolean persistent() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -66,11 +66,6 @@ public class WebDavCloud implements Cloud {
|
|||||||
return certificate;
|
return certificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean predefined() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean persistent() {
|
public boolean persistent() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package org.cryptomator.presentation.presenter
|
package org.cryptomator.presentation.presenter
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.accounts.AccountManager
|
import android.accounts.AccountManager
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.Intent.ACTION_OPEN_DOCUMENT_TREE
|
||||||
|
import android.provider.DocumentsContract
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.dropbox.core.android.Auth
|
import com.dropbox.core.android.Auth
|
||||||
import org.cryptomator.data.cloud.onedrive.OnedriveClientFactory
|
import org.cryptomator.data.cloud.onedrive.OnedriveClientFactory
|
||||||
@ -35,6 +37,7 @@ import org.cryptomator.presentation.intent.AuthenticateCloudIntent
|
|||||||
import org.cryptomator.presentation.intent.Intents
|
import org.cryptomator.presentation.intent.Intents
|
||||||
import org.cryptomator.presentation.model.CloudModel
|
import org.cryptomator.presentation.model.CloudModel
|
||||||
import org.cryptomator.presentation.model.CloudTypeModel
|
import org.cryptomator.presentation.model.CloudTypeModel
|
||||||
|
import org.cryptomator.presentation.model.LocalStorageModel
|
||||||
import org.cryptomator.presentation.model.ProgressModel
|
import org.cryptomator.presentation.model.ProgressModel
|
||||||
import org.cryptomator.presentation.model.ProgressStateModel
|
import org.cryptomator.presentation.model.ProgressStateModel
|
||||||
import org.cryptomator.presentation.model.S3CloudModel
|
import org.cryptomator.presentation.model.S3CloudModel
|
||||||
@ -44,7 +47,6 @@ import org.cryptomator.presentation.ui.activity.view.AuthenticateCloudView
|
|||||||
import org.cryptomator.presentation.workflow.ActivityResult
|
import org.cryptomator.presentation.workflow.ActivityResult
|
||||||
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow
|
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow
|
||||||
import org.cryptomator.presentation.workflow.CreateNewVaultWorkflow
|
import org.cryptomator.presentation.workflow.CreateNewVaultWorkflow
|
||||||
import org.cryptomator.presentation.workflow.PermissionsResult
|
|
||||||
import org.cryptomator.presentation.workflow.Workflow
|
import org.cryptomator.presentation.workflow.Workflow
|
||||||
import org.cryptomator.util.ExceptionUtil
|
import org.cryptomator.util.ExceptionUtil
|
||||||
import org.cryptomator.util.crypto.CredentialCryptor
|
import org.cryptomator.util.crypto.CredentialCryptor
|
||||||
@ -433,6 +435,7 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
|||||||
private inner class LocalStorageAuthStrategy : AuthStrategy {
|
private inner class LocalStorageAuthStrategy : AuthStrategy {
|
||||||
|
|
||||||
private var authenticationStarted = false
|
private var authenticationStarted = false
|
||||||
|
|
||||||
override fun supports(cloud: CloudModel): Boolean {
|
override fun supports(cloud: CloudModel): Boolean {
|
||||||
return cloud.cloudType() == CloudTypeModel.LOCAL
|
return cloud.cloudType() == CloudTypeModel.LOCAL
|
||||||
}
|
}
|
||||||
@ -445,22 +448,41 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
|||||||
|
|
||||||
private fun startAuthentication(cloud: CloudModel) {
|
private fun startAuthentication(cloud: CloudModel) {
|
||||||
authenticationStarted = true
|
authenticationStarted = true
|
||||||
requestPermissions(
|
|
||||||
PermissionsResultCallbacks.onLocalStorageAuthenticated(cloud), //
|
val uri = (cloud as LocalStorageModel).uri()
|
||||||
R.string.permission_snackbar_auth_local_vault, //
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE, //
|
val permissions = context().contentResolver.persistedUriPermissions
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
for (permission in permissions) {
|
||||||
)
|
if (permission.uri.toString() == uri) {
|
||||||
|
succeedAuthenticationWith(cloud.toCloud())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.tag("AuthicateCloudPrester").e("Permission revoked, ask to re-pick location")
|
||||||
|
|
||||||
|
Toast.makeText(context(), getString(R.string.permission_revoked_re_request_permission), Toast.LENGTH_LONG).show()
|
||||||
|
|
||||||
|
val openDocumentTree = Intent(ACTION_OPEN_DOCUMENT_TREE).apply {
|
||||||
|
putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
requestActivityResult(ActivityResultCallbacks.rePickedLocalStorageLocation(cloud), openDocumentTree)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Callback
|
@Callback
|
||||||
fun onLocalStorageAuthenticated(result: PermissionsResult, cloud: CloudModel) {
|
fun rePickedLocalStorageLocation(result: ActivityResult, cloud: LocalStorageModel) {
|
||||||
if (result.granted()) {
|
val rootTreeUriOfLocalStorage = result.intent().data
|
||||||
succeedAuthenticationWith(cloud.toCloud())
|
rootTreeUriOfLocalStorage?.let {
|
||||||
} else {
|
context() //
|
||||||
failAuthentication(PermissionNotGrantedException(R.string.permission_snackbar_auth_local_vault))
|
.contentResolver //
|
||||||
|
.takePersistableUriPermission( //
|
||||||
|
it, //
|
||||||
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
Timber.tag("AuthicateCloudPrester").e("Permission granted again")
|
||||||
|
succeedAuthenticationWith(cloud.toCloud())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun encrypt(password: String): String {
|
private fun encrypt(password: String): String {
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
@ -28,7 +27,6 @@
|
|||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:usesCleartextTraffic="true">
|
android:usesCleartextTraffic="true">
|
||||||
|
@ -3,7 +3,6 @@ package org.cryptomator.presentation
|
|||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
|
||||||
import org.cryptomator.presentation.service.CryptorsService
|
import org.cryptomator.presentation.service.CryptorsService
|
||||||
import org.cryptomator.presentation.service.PhotoContentJob
|
import org.cryptomator.presentation.service.PhotoContentJob
|
||||||
import org.cryptomator.util.SharedPreferencesHandler
|
import org.cryptomator.util.SharedPreferencesHandler
|
||||||
@ -18,7 +17,7 @@ class BootAwareReceiver : BroadcastReceiver() {
|
|||||||
context.stopService(CryptorsService.lockAllIntent(context))
|
context.stopService(CryptorsService.lockAllIntent(context))
|
||||||
}
|
}
|
||||||
intent.action.equals(Intent.ACTION_BOOT_COMPLETED, ignoreCase = true) -> {
|
intent.action.equals(Intent.ACTION_BOOT_COMPLETED, ignoreCase = true) -> {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && SharedPreferencesHandler(context).usePhotoUpload()) {
|
if (SharedPreferencesHandler(context).usePhotoUpload()) {
|
||||||
Timber.tag("BootAwareReceiver").i("Starting AutoUploadJobScheduler")
|
Timber.tag("BootAwareReceiver").i("Starting AutoUploadJobScheduler")
|
||||||
PhotoContentJob.scheduleJob(context)
|
PhotoContentJob.scheduleJob(context)
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,8 @@ import android.Manifest
|
|||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.os.Environment
|
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import org.cryptomator.domain.CloudFile
|
import org.cryptomator.domain.CloudFile
|
||||||
import org.cryptomator.domain.CloudFolder
|
import org.cryptomator.domain.CloudFolder
|
||||||
import org.cryptomator.domain.CloudNode
|
import org.cryptomator.domain.CloudNode
|
||||||
@ -83,10 +80,8 @@ import org.cryptomator.util.SharedPreferencesHandler
|
|||||||
import org.cryptomator.util.file.FileCacheUtils
|
import org.cryptomator.util.file.FileCacheUtils
|
||||||
import org.cryptomator.util.file.MimeType
|
import org.cryptomator.util.file.MimeType
|
||||||
import org.cryptomator.util.file.MimeTypes
|
import org.cryptomator.util.file.MimeTypes
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.security.DigestInputStream
|
import java.security.DigestInputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
@ -738,30 +733,6 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
exportNodesToUserSelectedLocation(selectedCloudFiles, trigger)
|
exportNodesToUserSelectedLocation(selectedCloudFiles, trigger)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Callback
|
|
||||||
fun exportFileToDownloadDirectory(result: PermissionsResult, fileToExport: CloudFileModel, exportOperation: ExportOperation) {
|
|
||||||
if (result.granted()) {
|
|
||||||
val downloads = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
|
||||||
val cryptomatorDownloads = File(downloads, context().getString(R.string.download_subdirectory_name))
|
|
||||||
cryptomatorDownloads.mkdirs()
|
|
||||||
if (cryptomatorDownloads.isDirectory) {
|
|
||||||
val target = File(cryptomatorDownloads, fileToExport.name)
|
|
||||||
try {
|
|
||||||
val downloadFile = DownloadFile.Builder() //
|
|
||||||
.setDownloadFile(fileToExport.toCloudNode()) //
|
|
||||||
.setDataSink(FileOutputStream(target)) //
|
|
||||||
.build()
|
|
||||||
exportOperation.export(this, listOf(downloadFile))
|
|
||||||
} catch (e: FileNotFoundException) {
|
|
||||||
showError(e)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
view?.showError(R.string.screen_file_browser_msg_creating_download_dir_failed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.KITKAT)
|
|
||||||
private fun exportFileToUserSelectedLocation(fileToExport: CloudFileModel, exportOperation: ExportOperation) {
|
private fun exportFileToUserSelectedLocation(fileToExport: CloudFileModel, exportOperation: ExportOperation) {
|
||||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
@ -789,7 +760,6 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Callback
|
@Callback
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
fun pickedLocalStorageLocation(
|
fun pickedLocalStorageLocation(
|
||||||
result: ActivityResult, //
|
result: ActivityResult, //
|
||||||
nodesToExport: ArrayList<CloudNodeModel<*>>, //
|
nodesToExport: ArrayList<CloudNodeModel<*>>, //
|
||||||
@ -809,7 +779,6 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
disableSelectionMode()
|
disableSelectionMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private fun collectNodesToExport(
|
private fun collectNodesToExport(
|
||||||
parentUri: Uri, //
|
parentUri: Uri, //
|
||||||
exportOperation: ExportOperation, //
|
exportOperation: ExportOperation, //
|
||||||
@ -827,7 +796,6 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
collectFolderContentForExport(parentUri, exportOperation, foldersForRecursiveDirListing, filesToExport)
|
collectFolderContentForExport(parentUri, exportOperation, foldersForRecursiveDirListing, filesToExport)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private fun collectFolderContentForExport(
|
private fun collectFolderContentForExport(
|
||||||
parentUri: Uri, exportOperation: ExportOperation, folders: List<CloudFolderModel>, //
|
parentUri: Uri, exportOperation: ExportOperation, folders: List<CloudFolderModel>, //
|
||||||
filesToExport: List<CloudFileModel>
|
filesToExport: List<CloudFileModel>
|
||||||
@ -847,7 +815,6 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private fun prepareExportingOf(parentUri: Uri, exportOperation: ExportOperation, filesToExport: List<CloudFileModel>, cloudNodeRecursiveListing: CloudNodeRecursiveListing) {
|
private fun prepareExportingOf(parentUri: Uri, exportOperation: ExportOperation, filesToExport: List<CloudFileModel>, cloudNodeRecursiveListing: CloudNodeRecursiveListing) {
|
||||||
downloadFiles = ArrayList()
|
downloadFiles = ArrayList()
|
||||||
downloadFiles.addAll(prepareFilesForExport(cloudFileModelMapper.fromModels(filesToExport), parentUri))
|
downloadFiles.addAll(prepareFilesForExport(cloudFileModelMapper.fromModels(filesToExport), parentUri))
|
||||||
@ -862,12 +829,10 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private fun prepareFilesForExport(filesToExport: List<CloudFile>, parentUri: Uri): List<DownloadFile> {
|
private fun prepareFilesForExport(filesToExport: List<CloudFile>, parentUri: Uri): List<DownloadFile> {
|
||||||
return filesToExport.mapTo(ArrayList()) { createDownloadFile(it, parentUri) }
|
return filesToExport.mapTo(ArrayList()) { createDownloadFile(it, parentUri) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private fun prepareFolderContentForExport(cloudFolderRecursiveListing: CloudFolderRecursiveListing, parentUri: Uri) {
|
private fun prepareFolderContentForExport(cloudFolderRecursiveListing: CloudFolderRecursiveListing, parentUri: Uri) {
|
||||||
createFolder(parentUri, cloudFolderRecursiveListing.parent.name)?.let {
|
createFolder(parentUri, cloudFolderRecursiveListing.parent.name)?.let {
|
||||||
downloadFiles.addAll(prepareFilesForExport(cloudFolderRecursiveListing.files, it))
|
downloadFiles.addAll(prepareFilesForExport(cloudFolderRecursiveListing.files, it))
|
||||||
@ -877,7 +842,6 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
} ?: throw FatalBackendException("Failed to create parent folder for export")
|
} ?: throw FatalBackendException("Failed to create parent folder for export")
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private fun createFolder(parentUri: Uri, folderName: String): Uri? {
|
private fun createFolder(parentUri: Uri, folderName: String): Uri? {
|
||||||
return try {
|
return try {
|
||||||
DocumentsContract.createDocument( //
|
DocumentsContract.createDocument( //
|
||||||
@ -892,7 +856,6 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private fun createDownloadFile(file: CloudFile, documentUri: Uri): DownloadFile {
|
private fun createDownloadFile(file: CloudFile, documentUri: Uri): DownloadFile {
|
||||||
return try {
|
return try {
|
||||||
DownloadFile.Builder() //
|
DownloadFile.Builder() //
|
||||||
@ -918,7 +881,6 @@ class BrowseFilesPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
@Throws(IllegalFileNameException::class, NoSuchCloudFileException::class)
|
@Throws(IllegalFileNameException::class, NoSuchCloudFileException::class)
|
||||||
private fun createNewDocumentUri(parentUri: Uri, fileName: String): Uri {
|
private fun createNewDocumentUri(parentUri: Uri, fileName: String): Uri {
|
||||||
val mimeType = mimeTypes.fromFilename(fileName) ?: MimeType.APPLICATION_OCTET_STREAM
|
val mimeType = mimeTypes.fromFilename(fileName) ?: MimeType.APPLICATION_OCTET_STREAM
|
||||||
|
@ -3,9 +3,7 @@ package org.cryptomator.presentation.presenter
|
|||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import org.cryptomator.domain.Cloud
|
import org.cryptomator.domain.Cloud
|
||||||
import org.cryptomator.domain.LocalStorageCloud
|
import org.cryptomator.domain.LocalStorageCloud
|
||||||
import org.cryptomator.domain.PCloud
|
import org.cryptomator.domain.PCloud
|
||||||
@ -251,7 +249,6 @@ class CloudConnectionListPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Callback
|
@Callback
|
||||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
|
||||||
fun pickedLocalStorageLocation(result: ActivityResult) {
|
fun pickedLocalStorageLocation(result: ActivityResult) {
|
||||||
val rootTreeUriOfLocalStorage = result.intent().data
|
val rootTreeUriOfLocalStorage = result.intent().data
|
||||||
persistUriPermission(rootTreeUriOfLocalStorage)
|
persistUriPermission(rootTreeUriOfLocalStorage)
|
||||||
@ -266,7 +263,6 @@ class CloudConnectionListPresenter @Inject constructor( //
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
|
||||||
private fun persistUriPermission(rootTreeUriOfLocalStorage: Uri?) {
|
private fun persistUriPermission(rootTreeUriOfLocalStorage: Uri?) {
|
||||||
rootTreeUriOfLocalStorage?.let {
|
rootTreeUriOfLocalStorage?.let {
|
||||||
context() //
|
context() //
|
||||||
@ -278,7 +274,6 @@ class CloudConnectionListPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
|
||||||
private fun releaseUriPermission(uri: String) {
|
private fun releaseUriPermission(uri: String) {
|
||||||
context() //
|
context() //
|
||||||
.contentResolver //
|
.contentResolver //
|
||||||
@ -294,10 +289,6 @@ class CloudConnectionListPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDefaultLocalCloudConnectionClicked() {
|
|
||||||
finishWithResult(SELECTED_CLOUD, defaultLocalStorageCloud)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val SELECTED_CLOUD = "selectedCloudConnection"
|
const val SELECTED_CLOUD = "selectedCloudConnection"
|
||||||
|
@ -3,8 +3,6 @@ package org.cryptomator.presentation.presenter
|
|||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.os.Environment
|
|
||||||
import org.cryptomator.domain.CloudFile
|
import org.cryptomator.domain.CloudFile
|
||||||
import org.cryptomator.domain.CloudNode
|
import org.cryptomator.domain.CloudNode
|
||||||
import org.cryptomator.domain.di.PerView
|
import org.cryptomator.domain.di.PerView
|
||||||
@ -30,9 +28,7 @@ import org.cryptomator.presentation.util.ShareFileHelper
|
|||||||
import org.cryptomator.presentation.workflow.ActivityResult
|
import org.cryptomator.presentation.workflow.ActivityResult
|
||||||
import org.cryptomator.presentation.workflow.PermissionsResult
|
import org.cryptomator.presentation.workflow.PermissionsResult
|
||||||
import org.cryptomator.util.ExceptionUtil
|
import org.cryptomator.util.ExceptionUtil
|
||||||
import java.io.File
|
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@ -58,37 +54,33 @@ class ImagePreviewPresenter @Inject constructor( //
|
|||||||
@InstanceState
|
@InstanceState
|
||||||
lateinit var pageIndexes: ArrayList<Int>
|
lateinit var pageIndexes: ArrayList<Int>
|
||||||
|
|
||||||
fun onExportImageClicked(uri: Uri) {
|
fun exportImageToUserSelectedLocation(uri: Uri) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||||
copyFileToDownloadDirectory(uri)
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
} else {
|
intent.type = "*/*"
|
||||||
copyFileToUserSelectedLocation(uri)
|
intent.putExtra(Intent.EXTRA_TITLE, contentResolverUtil.fileName(uri))
|
||||||
}
|
requestActivityResult(ActivityResultCallbacks.exportImageToUserSelectedLocation(uri.toString()), intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyFileToDownloadDirectory(uri: Uri) {
|
@Callback
|
||||||
|
fun exportImageToUserSelectedLocation(result: ActivityResult, sourceUri: String?) {
|
||||||
requestPermissions(
|
requestPermissions(
|
||||||
PermissionsResultCallbacks.copyFileToDownloadDirectory(uri.toString()), //
|
PermissionsResultCallbacks.exportImageToUserSelectedLocation(result.intent()?.dataString, sourceUri), //
|
||||||
R.string.permission_message_export_file, Manifest.permission.WRITE_EXTERNAL_STORAGE
|
R.string.permission_message_export_file, //
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Callback
|
@Callback
|
||||||
fun copyFileToDownloadDirectory(result: PermissionsResult, uriString: String?) {
|
fun exportImageToUserSelectedLocation(result: PermissionsResult, targetUri: String?, sourceUri: String?) {
|
||||||
if (result.granted()) {
|
if (result.granted()) {
|
||||||
val uriFile = Uri.parse(uriString)
|
try {
|
||||||
val downloads = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
copyFile(
|
||||||
val cryptomatorDownloads = File(downloads, context().getString(R.string.download_subdirectory_name))
|
contentResolverUtil.openInputStream(Uri.parse(sourceUri)), //
|
||||||
cryptomatorDownloads.mkdirs()
|
contentResolverUtil.openOutputStream(Uri.parse(targetUri))
|
||||||
if (cryptomatorDownloads.isDirectory) {
|
)
|
||||||
val target = File(cryptomatorDownloads, contentResolverUtil.fileName(uriFile))
|
} catch (e: FileNotFoundException) {
|
||||||
try {
|
showError(e)
|
||||||
copyFile(contentResolverUtil.openInputStream(uriFile), FileOutputStream(target))
|
|
||||||
} catch (e: FileNotFoundException) {
|
|
||||||
showError(e)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
view?.showError(R.string.screen_file_browser_msg_creating_download_dir_failed)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,37 +99,6 @@ class ImagePreviewPresenter @Inject constructor( //
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyFileToUserSelectedLocation(uri: Uri) {
|
|
||||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
intent.type = "*/*"
|
|
||||||
intent.putExtra(Intent.EXTRA_TITLE, contentResolverUtil.fileName(uri))
|
|
||||||
requestActivityResult(ActivityResultCallbacks.copyFileToUserSelectedLocation(uri.toString()), intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Callback
|
|
||||||
fun copyFileToUserSelectedLocation(result: ActivityResult, sourceUri: String?) {
|
|
||||||
requestPermissions(
|
|
||||||
PermissionsResultCallbacks.copyFileToUserSelectedLocation(result.intent()?.dataString, sourceUri), //
|
|
||||||
R.string.permission_message_export_file, //
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Callback
|
|
||||||
fun copyFileToUserSelectedLocation(result: PermissionsResult, targetUri: String?, sourceUri: String?) {
|
|
||||||
if (result.granted()) {
|
|
||||||
try {
|
|
||||||
copyFile(
|
|
||||||
contentResolverUtil.openInputStream(Uri.parse(sourceUri)), //
|
|
||||||
contentResolverUtil.openOutputStream(Uri.parse(targetUri))
|
|
||||||
)
|
|
||||||
} catch (e: FileNotFoundException) {
|
|
||||||
showError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onShareImageClicked(uri: Uri) {
|
fun onShareImageClicked(uri: Uri) {
|
||||||
shareFileHelper.shareFile(this, uri)
|
shareFileHelper.shareFile(this, uri)
|
||||||
}
|
}
|
||||||
|
@ -90,17 +90,14 @@ class SettingsPresenter @Inject internal constructor(
|
|||||||
requestPermissions(
|
requestPermissions(
|
||||||
PermissionsResultCallbacks.onLocalStoragePermissionGranted(), //
|
PermissionsResultCallbacks.onLocalStoragePermissionGranted(), //
|
||||||
R.string.permission_snackbar_auth_auto_upload, //
|
R.string.permission_snackbar_auth_auto_upload, //
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE, //
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Callback
|
@Callback
|
||||||
fun onLocalStoragePermissionGranted(result: PermissionsResult) {
|
fun onLocalStoragePermissionGranted(result: PermissionsResult) {
|
||||||
if (result.granted()) {
|
if (result.granted()) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
scheduleJob(context())
|
||||||
scheduleJob(context())
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
view?.disableAutoUpload()
|
view?.disableAutoUpload()
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ class UnlockVaultPresenter @Inject constructor(
|
|||||||
super.destroyed()
|
super.destroyed()
|
||||||
if (retryUnlockHandler != null) {
|
if (retryUnlockHandler != null) {
|
||||||
running = false
|
running = false
|
||||||
retryUnlockHandler?.removeCallbacks(null)
|
retryUnlockHandler?.removeCallbacksAndMessages(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,11 +140,12 @@ class UnlockVaultPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME why is this method not used?
|
||||||
fun onWindowFocusChanged(hasFocus: Boolean) {
|
fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
if (retryUnlockHandler != null) {
|
if (retryUnlockHandler != null) {
|
||||||
running = false
|
running = false
|
||||||
retryUnlockHandler?.removeCallbacks(null)
|
retryUnlockHandler?.removeCallbacksAndMessages(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import org.cryptomator.data.cloud.crypto.CryptoCloud
|
|||||||
import org.cryptomator.data.util.NetworkConnectionCheck
|
import org.cryptomator.data.util.NetworkConnectionCheck
|
||||||
import org.cryptomator.domain.Cloud
|
import org.cryptomator.domain.Cloud
|
||||||
import org.cryptomator.domain.CloudFolder
|
import org.cryptomator.domain.CloudFolder
|
||||||
|
import org.cryptomator.domain.CloudType
|
||||||
import org.cryptomator.domain.Vault
|
import org.cryptomator.domain.Vault
|
||||||
import org.cryptomator.domain.di.PerView
|
import org.cryptomator.domain.di.PerView
|
||||||
import org.cryptomator.domain.exception.license.LicenseNotValidException
|
import org.cryptomator.domain.exception.license.LicenseNotValidException
|
||||||
@ -46,10 +47,10 @@ import org.cryptomator.presentation.ui.activity.LicenseCheckActivity
|
|||||||
import org.cryptomator.presentation.ui.activity.view.VaultListView
|
import org.cryptomator.presentation.ui.activity.view.VaultListView
|
||||||
import org.cryptomator.presentation.ui.dialog.AppIsObscuredInfoDialog
|
import org.cryptomator.presentation.ui.dialog.AppIsObscuredInfoDialog
|
||||||
import org.cryptomator.presentation.ui.dialog.AskForLockScreenDialog
|
import org.cryptomator.presentation.ui.dialog.AskForLockScreenDialog
|
||||||
import org.cryptomator.presentation.ui.dialog.BetaConfirmationDialog
|
|
||||||
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
|
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
|
||||||
import org.cryptomator.presentation.ui.dialog.UpdateAppAvailableDialog
|
import org.cryptomator.presentation.ui.dialog.UpdateAppAvailableDialog
|
||||||
import org.cryptomator.presentation.ui.dialog.UpdateAppDialog
|
import org.cryptomator.presentation.ui.dialog.UpdateAppDialog
|
||||||
|
import org.cryptomator.presentation.ui.dialog.VaultsRemovedDuringMigrationDialog
|
||||||
import org.cryptomator.presentation.util.FileUtil
|
import org.cryptomator.presentation.util.FileUtil
|
||||||
import org.cryptomator.presentation.workflow.ActivityResult
|
import org.cryptomator.presentation.workflow.ActivityResult
|
||||||
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow
|
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow
|
||||||
@ -104,6 +105,12 @@ class VaultListPresenter @Inject constructor( //
|
|||||||
sharedPreferencesHandler.setScreenLockDialogAlreadyShown()
|
sharedPreferencesHandler.setScreenLockDialogAlreadyShown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sharedPreferencesHandler.vaultsRemovedDuringMigration()?.let {
|
||||||
|
val cloudNameString = getString(CloudTypeModel.valueOf(CloudType.valueOf(it.first)).displayNameResource)
|
||||||
|
view?.showDialog(VaultsRemovedDuringMigrationDialog.newInstance(Pair(cloudNameString, it.second)))
|
||||||
|
sharedPreferencesHandler.vaultsRemovedDuringMigration(null)
|
||||||
|
}
|
||||||
|
|
||||||
checkLicense()
|
checkLicense()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,9 +126,10 @@ class VaultListPresenter @Inject constructor( //
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(e: Throwable) {
|
override fun onError(e: Throwable) {
|
||||||
var license: String? = ""
|
val license = if (e is LicenseNotValidException) {
|
||||||
if (e is LicenseNotValidException) {
|
e.license
|
||||||
license = e.license
|
} else {
|
||||||
|
""
|
||||||
}
|
}
|
||||||
val intent = Intent(context(), LicenseCheckActivity::class.java)
|
val intent = Intent(context(), LicenseCheckActivity::class.java)
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
@ -272,8 +280,8 @@ class VaultListPresenter @Inject constructor( //
|
|||||||
view?.showVaultCreationHint()
|
view?.showVaultCreationHint()
|
||||||
} else {
|
} else {
|
||||||
view?.hideVaultCreationHint()
|
view?.hideVaultCreationHint()
|
||||||
view?.renderVaultList(vaultModels)
|
|
||||||
}
|
}
|
||||||
|
view?.renderVaultList(vaultModels)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou
|
|||||||
presenter.onDeleteImageClicked(imagePreviewFiles[imagePreviewSliderAdapter.getIndex(viewPager.currentItem)])
|
presenter.onDeleteImageClicked(imagePreviewFiles[imagePreviewSliderAdapter.getIndex(viewPager.currentItem)])
|
||||||
}
|
}
|
||||||
exportImage.setOnClickListener {
|
exportImage.setOnClickListener {
|
||||||
currentImageUri?.let { presenter.onExportImageClicked(it) }
|
currentImageUri?.let { presenter.exportImageToUserSelectedLocation(it) }
|
||||||
}
|
}
|
||||||
shareImage.setOnClickListener {
|
shareImage.setOnClickListener {
|
||||||
currentImageUri?.let { presenter.onShareImageClicked(it) }
|
currentImageUri?.let { presenter.onShareImageClicked(it) }
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.presentation.ui.bottomsheet
|
package org.cryptomator.presentation.ui.bottomsheet
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import org.cryptomator.generator.BottomSheet
|
import org.cryptomator.generator.BottomSheet
|
||||||
@ -46,12 +45,10 @@ class FolderSettingsBottomSheet : BaseBottomSheet<FolderSettingsBottomSheet.Call
|
|||||||
callback?.onMoveFolderClicked(cloudFolderModel)
|
callback?.onMoveFolderClicked(cloudFolderModel)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
export_folder.visibility = View.VISIBLE
|
||||||
export_folder.visibility = View.VISIBLE
|
export_folder.setOnClickListener {
|
||||||
export_folder.setOnClickListener {
|
callback?.onExportFolderClicked(cloudFolderModel)
|
||||||
callback?.onExportFolderClicked(cloudFolderModel)
|
dismiss()
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
delete_folder.setOnClickListener {
|
delete_folder.setOnClickListener {
|
||||||
callback?.onDeleteNodeClicked(cloudFolderModel)
|
callback?.onDeleteNodeClicked(cloudFolderModel)
|
||||||
|
@ -13,10 +13,10 @@ import kotlinx.android.synthetic.main.dialog_app_is_obscured_info.tv_app_is_obsc
|
|||||||
class AppIsObscuredInfoDialog : BaseDialog<Activity>() {
|
class AppIsObscuredInfoDialog : BaseDialog<Activity>() {
|
||||||
|
|
||||||
public override fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog {
|
public override fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog {
|
||||||
builder //
|
return builder //
|
||||||
.setTitle(R.string.dialog_app_is_obscured_info_title) //
|
.setTitle(R.string.dialog_app_is_obscured_info_title) //
|
||||||
.setNeutralButton(R.string.dialog_app_is_obscured_info_neutral_button) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
|
.setNeutralButton(R.string.dialog_app_is_obscured_info_neutral_button) { dialog: DialogInterface, _: Int -> dialog.dismiss() } //
|
||||||
return builder.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disableDialogWhenObscured(): Boolean {
|
override fun disableDialogWhenObscured(): Boolean {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.cryptomator.presentation.ui.dialog
|
package org.cryptomator.presentation.ui.dialog
|
||||||
|
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -32,11 +31,7 @@ class UpdateAppAvailableDialog : BaseProgressErrorDialog<UpdateAppAvailableDialo
|
|||||||
|
|
||||||
public override fun setupView() {
|
public override fun setupView() {
|
||||||
val message = requireArguments().getSerializable(MESSAGE_ARG) as String
|
val message = requireArguments().getSerializable(MESSAGE_ARG) as String
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
tv_message.text = Html.fromHtml(message, Html.FROM_HTML_MODE_COMPACT)
|
||||||
tv_message.text = Html.fromHtml(message, Html.FROM_HTML_MODE_COMPACT)
|
|
||||||
} else {
|
|
||||||
tv_message.text = Html.fromHtml(message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun enableViewAfterError(): View {
|
override fun enableViewAfterError(): View {
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package org.cryptomator.presentation.ui.dialog
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import org.cryptomator.generator.Dialog
|
||||||
|
import org.cryptomator.presentation.R
|
||||||
|
|
||||||
|
@Dialog(R.layout.dialog_vault_is_root_folder_of_cloud)
|
||||||
|
class VaultIsRootFolderOfCloudDialog : BaseDialog<Activity>() {
|
||||||
|
|
||||||
|
public override fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog {
|
||||||
|
return builder //
|
||||||
|
.setTitle(R.string.dialog_vault_is_root_folder_of_cloud_title) //
|
||||||
|
.setNeutralButton(R.string.dialog_vault_is_root_folder_of_cloud_neutral_button) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setupView() {}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(): DialogFragment {
|
||||||
|
return VaultIsRootFolderOfCloudDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package org.cryptomator.presentation.ui.dialog
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import org.cryptomator.generator.Dialog
|
||||||
|
import org.cryptomator.presentation.R
|
||||||
|
import kotlinx.android.synthetic.main.dialog_vaults_removed_during_migration.tv_message
|
||||||
|
|
||||||
|
@Dialog(R.layout.dialog_vaults_removed_during_migration)
|
||||||
|
class VaultsRemovedDuringMigrationDialog : BaseDialog<Activity>() {
|
||||||
|
|
||||||
|
public override fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog {
|
||||||
|
val vaultsRemovedDuringMigration = requireArguments().getSerializable(VAULTS_REMOVED_ARG) as Pair<String, ArrayList<String>>
|
||||||
|
|
||||||
|
return builder //
|
||||||
|
.setTitle(String.format(getString(R.string.dialog_vaults_removed_during_migration_title), vaultsRemovedDuringMigration.first)) //
|
||||||
|
.setNeutralButton(R.string.dialog_vaults_removed_during_migration_neutral_button) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun setupView() {
|
||||||
|
val vaultsRemovedDuringMigration = requireArguments().getSerializable(VAULTS_REMOVED_ARG) as Pair<String, ArrayList<String>>
|
||||||
|
|
||||||
|
val vaultsRemovedDuringMigrationString = vaultsRemovedDuringMigration
|
||||||
|
.second
|
||||||
|
.map { path -> "* $path" }
|
||||||
|
.reduce { acc, s -> "$acc\n$s" }
|
||||||
|
|
||||||
|
tv_message.text = String.format(getString(R.string.dialog_vaults_removed_during_migration_hint), vaultsRemovedDuringMigrationString)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val VAULTS_REMOVED_ARG = "vaultsRemovedArg"
|
||||||
|
|
||||||
|
fun newInstance(vaultsRemovedDuringMigration: Pair<String, List<String>>): DialogFragment {
|
||||||
|
val args = Bundle()
|
||||||
|
args.putSerializable(VAULTS_REMOVED_ARG, vaultsRemovedDuringMigration)
|
||||||
|
val fragment = VaultsRemovedDuringMigrationDialog()
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.presentation.ui.fragment
|
package org.cryptomator.presentation.ui.fragment
|
||||||
|
|
||||||
import android.os.Environment
|
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
@ -13,10 +12,7 @@ import org.cryptomator.presentation.presenter.CloudConnectionListPresenter
|
|||||||
import org.cryptomator.presentation.ui.adapter.CloudConnectionListAdapter
|
import org.cryptomator.presentation.ui.adapter.CloudConnectionListAdapter
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.android.synthetic.main.fragment_browse_cloud_connections.floating_action_button
|
import kotlinx.android.synthetic.main.fragment_browse_cloud_connections.floating_action_button
|
||||||
import kotlinx.android.synthetic.main.fragment_browse_cloud_connections.rv_local_default_cloud
|
|
||||||
import kotlinx.android.synthetic.main.recycler_view_layout.recyclerView
|
import kotlinx.android.synthetic.main.recycler_view_layout.recyclerView
|
||||||
import kotlinx.android.synthetic.main.view_cloud_connection_content.cloudSubText
|
|
||||||
import kotlinx.android.synthetic.main.view_cloud_connection_content.cloudText
|
|
||||||
import kotlinx.android.synthetic.main.view_empty_cloud_connections.rl_creation_hint
|
import kotlinx.android.synthetic.main.view_empty_cloud_connections.rl_creation_hint
|
||||||
|
|
||||||
@Fragment(R.layout.fragment_browse_cloud_connections)
|
@Fragment(R.layout.fragment_browse_cloud_connections)
|
||||||
@ -42,7 +38,6 @@ class CloudConnectionListFragment : BaseFragment() {
|
|||||||
|
|
||||||
override fun setupView() {
|
override fun setupView() {
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
rv_local_default_cloud.setOnClickListener { cloudConnectionListPresenter.onDefaultLocalCloudConnectionClicked() }
|
|
||||||
floating_action_button.setOnClickListener { cloudConnectionListPresenter.onAddConnectionClicked() }
|
floating_action_button.setOnClickListener { cloudConnectionListPresenter.onAddConnectionClicked() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,11 +66,5 @@ class CloudConnectionListFragment : BaseFragment() {
|
|||||||
|
|
||||||
fun setSelectedCloudType(selectedCloudType: CloudTypeModel) {
|
fun setSelectedCloudType(selectedCloudType: CloudTypeModel) {
|
||||||
this.selectedCloudType = selectedCloudType
|
this.selectedCloudType = selectedCloudType
|
||||||
|
|
||||||
if (CloudTypeModel.LOCAL == selectedCloudType) {
|
|
||||||
rv_local_default_cloud.visibility = VISIBLE
|
|
||||||
cloudText.text = getString(R.string.screen_cloud_local_default_storage_title)
|
|
||||||
cloudSubText.text = Environment.getExternalStorageDirectory().toString()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.cryptomator.presentation.ui.fragment
|
package org.cryptomator.presentation.ui.fragment
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
@ -259,9 +258,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||||||
if (enabled) {
|
if (enabled) {
|
||||||
activity().grantLocalStoragePermissionForAutoUpload()
|
activity().grantLocalStoragePermissionForAutoUpload()
|
||||||
} else {
|
} else {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
PhotoContentJob.cancelJob(activity().applicationContext)
|
||||||
PhotoContentJob.cancelJob(activity().applicationContext)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(findPreference(SharedPreferencesHandler.PHOTO_UPLOAD) as SwitchPreferenceCompat?)?.isChecked = enabled
|
(findPreference(SharedPreferencesHandler.PHOTO_UPLOAD) as SwitchPreferenceCompat?)?.isChecked = enabled
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import org.cryptomator.presentation.model.ProgressModel;
|
|||||||
import org.cryptomator.presentation.model.mappers.CloudModelMapper;
|
import org.cryptomator.presentation.model.mappers.CloudModelMapper;
|
||||||
import org.cryptomator.presentation.presenter.ChooseCloudServicePresenter;
|
import org.cryptomator.presentation.presenter.ChooseCloudServicePresenter;
|
||||||
import org.cryptomator.presentation.presenter.VaultListPresenter;
|
import org.cryptomator.presentation.presenter.VaultListPresenter;
|
||||||
|
import org.cryptomator.presentation.ui.dialog.VaultIsRootFolderOfCloudDialog;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -116,9 +117,13 @@ public class AddExistingVaultWorkflow extends Workflow<AddExistingVaultWorkflow.
|
|||||||
@Callback
|
@Callback
|
||||||
void cryptomatorFileChosen(SerializableResult<CloudFileModel> result) {
|
void cryptomatorFileChosen(SerializableResult<CloudFileModel> result) {
|
||||||
CloudFileModel masterkeyFile = result.getResult();
|
CloudFileModel masterkeyFile = result.getResult();
|
||||||
state().masterkeyFile = masterkeyFile.toCloudNode();
|
if(!masterkeyFile.getPath().equals("/masterkey.cryptomator") && !masterkeyFile.getPath().equals("/vault.cryptomator")) {
|
||||||
presenter().getView().showProgress(ProgressModel.GENERIC);
|
state().masterkeyFile = masterkeyFile.toCloudNode();
|
||||||
finish();
|
presenter().getView().showProgress(ProgressModel.GENERIC);
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
presenter().getView().showDialog(VaultIsRootFolderOfCloudDialog.Companion.newInstance());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="@dimen/activity_vertical_margin">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:text="@string/dialog_vault_is_root_folder_of_cloud_hint" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="@dimen/activity_vertical_margin">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:text="@string/dialog_vaults_removed_during_migration_hint" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
@ -9,38 +9,10 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/rv_local_default_cloud"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/default_local_cloud"
|
|
||||||
layout="@layout/view_default_local_cloud"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="72dp"
|
|
||||||
android:clickable="true"
|
|
||||||
android:clipToPadding="true"
|
|
||||||
android:focusable="true" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/default_local_cloud"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:text="@string/screen_cloud_local_custom_storage_title"
|
|
||||||
android:textColor="@color/colorPrimary"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<include
|
<include
|
||||||
layout="@layout/recycler_view_layout"
|
layout="@layout/recycler_view_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@+id/rv_local_default_cloud"
|
|
||||||
android:clipToPadding="true"
|
android:clipToPadding="true"
|
||||||
android:paddingBottom="88dp" />
|
android:paddingBottom="88dp" />
|
||||||
|
|
||||||
|
@ -61,6 +61,8 @@
|
|||||||
<string name="permission_message_upload_file">Cryptomator needs storage access to upload files</string>
|
<string name="permission_message_upload_file">Cryptomator needs storage access to upload files</string>
|
||||||
<string name="permission_message_share_file">Cryptomator needs storage access to share files</string>
|
<string name="permission_message_share_file">Cryptomator needs storage access to share files</string>
|
||||||
|
|
||||||
|
<string name="permission_revoked_re_request_permission">Cryptomator has lost permission to access this location. Please select this folder again to restore the permission.</string>
|
||||||
|
|
||||||
<string name="snack_bar_action_title_settings">Settings</string>
|
<string name="snack_bar_action_title_settings">Settings</string>
|
||||||
<string name="snack_bar_action_title_search">Search</string>
|
<string name="snack_bar_action_title_search">Search</string>
|
||||||
<string name="snack_bar_action_title_search_previous">Previous</string>
|
<string name="snack_bar_action_title_search_previous">Previous</string>
|
||||||
@ -166,8 +168,6 @@
|
|||||||
<string name="screen_cloud_settings_option_delete" translatable="false">@string/screen_vault_list_vault_action_delete</string>
|
<string name="screen_cloud_settings_option_delete" translatable="false">@string/screen_vault_list_vault_action_delete</string>
|
||||||
<string name="screen_cloud_connections_no_connections">Click here to add locations</string>
|
<string name="screen_cloud_connections_no_connections">Click here to add locations</string>
|
||||||
<string name="screen_cloud_error_webdav_not_supported">Server doesn\'t seem to be WebDAV compatible</string>
|
<string name="screen_cloud_error_webdav_not_supported">Server doesn\'t seem to be WebDAV compatible</string>
|
||||||
<string name="screen_cloud_local_custom_storage_title">Custom locations</string>
|
|
||||||
<string name="screen_cloud_local_default_storage_title">Default storage</string>
|
|
||||||
<string name="screen_cloud_local_error_no_content_provider">No additional locations available.</string>
|
<string name="screen_cloud_local_error_no_content_provider">No additional locations available.</string>
|
||||||
|
|
||||||
<!-- ## screen: webdav settings -->
|
<!-- ## screen: webdav settings -->
|
||||||
@ -417,6 +417,14 @@
|
|||||||
<string name="dialog_app_is_obscured_info_hint">Another app is displaying something on top of Cryptomator (e.g., a blue light filter or night mode app). For security reasons, Cryptomator is disabled.\n\n<a href="https://docs.cryptomator.org/en/1.5/android/settings/#block-app-when-obscured">How to enable Cryptomator</a></string>
|
<string name="dialog_app_is_obscured_info_hint">Another app is displaying something on top of Cryptomator (e.g., a blue light filter or night mode app). For security reasons, Cryptomator is disabled.\n\n<a href="https://docs.cryptomator.org/en/1.5/android/settings/#block-app-when-obscured">How to enable Cryptomator</a></string>
|
||||||
<string name="dialog_app_is_obscured_info_neutral_button">Close</string>
|
<string name="dialog_app_is_obscured_info_neutral_button">Close</string>
|
||||||
|
|
||||||
|
<string name="dialog_vaults_removed_during_migration_title">Please re-add vaults for %1s cloud</string>
|
||||||
|
<string name="dialog_vaults_removed_during_migration_hint">While migrating to this app version we need to remove the following vaults from the app:\n%2s \n\nThose vaults aren\'t removed from the cloud but only from this app. Sorry for the inconvenience and please re-add these vaults to continue working with them.</string>
|
||||||
|
<string name="dialog_vaults_removed_during_migration_neutral_button" translatable="false">@string/dialog_unable_to_share_positive_button</string>
|
||||||
|
|
||||||
|
<string name="dialog_vault_is_root_folder_of_cloud_title">Vault is root folder of the cloud connection</string>
|
||||||
|
<string name="dialog_vault_is_root_folder_of_cloud_hint">Create a new cloud connection where you select at least the parent folder of this vault folder as the root directory to add this vault.</string>
|
||||||
|
<string name="dialog_vault_is_root_folder_of_cloud_neutral_button" translatable="false">@string/dialog_unable_to_share_positive_button</string>
|
||||||
|
|
||||||
<string name="dialog_disable_secure_screen_disclaimer_hint">This setting is a security feature and prevents other apps from tricking users into doing things they do not wan\'t to do.\n\nBy disabling, you confirm that you are <a href="https://docs.cryptomator.org/en/1.5/android/settings/#screen-security">aware of the risks</a>.</string>
|
<string name="dialog_disable_secure_screen_disclaimer_hint">This setting is a security feature and prevents other apps from tricking users into doing things they do not wan\'t to do.\n\nBy disabling, you confirm that you are <a href="https://docs.cryptomator.org/en/1.5/android/settings/#screen-security">aware of the risks</a>.</string>
|
||||||
|
|
||||||
<string name="dialog_delete_cloud_connection_with_vaults_message">Are you sure you want to remove this cloud connection?</string>
|
<string name="dialog_delete_cloud_connection_with_vaults_message">Are you sure you want to remove this cloud connection?</string>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package org.cryptomator.presentation.presenter
|
package org.cryptomator.presentation.presenter
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.accounts.AccountManager
|
import android.accounts.AccountManager
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.Intent.ACTION_OPEN_DOCUMENT_TREE
|
||||||
|
import android.provider.DocumentsContract
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.dropbox.core.android.Auth
|
import com.dropbox.core.android.Auth
|
||||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
||||||
@ -38,6 +40,7 @@ import org.cryptomator.presentation.intent.AuthenticateCloudIntent
|
|||||||
import org.cryptomator.presentation.intent.Intents
|
import org.cryptomator.presentation.intent.Intents
|
||||||
import org.cryptomator.presentation.model.CloudModel
|
import org.cryptomator.presentation.model.CloudModel
|
||||||
import org.cryptomator.presentation.model.CloudTypeModel
|
import org.cryptomator.presentation.model.CloudTypeModel
|
||||||
|
import org.cryptomator.presentation.model.LocalStorageModel
|
||||||
import org.cryptomator.presentation.model.ProgressModel
|
import org.cryptomator.presentation.model.ProgressModel
|
||||||
import org.cryptomator.presentation.model.ProgressStateModel
|
import org.cryptomator.presentation.model.ProgressStateModel
|
||||||
import org.cryptomator.presentation.model.S3CloudModel
|
import org.cryptomator.presentation.model.S3CloudModel
|
||||||
@ -47,7 +50,6 @@ import org.cryptomator.presentation.ui.activity.view.AuthenticateCloudView
|
|||||||
import org.cryptomator.presentation.workflow.ActivityResult
|
import org.cryptomator.presentation.workflow.ActivityResult
|
||||||
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow
|
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow
|
||||||
import org.cryptomator.presentation.workflow.CreateNewVaultWorkflow
|
import org.cryptomator.presentation.workflow.CreateNewVaultWorkflow
|
||||||
import org.cryptomator.presentation.workflow.PermissionsResult
|
|
||||||
import org.cryptomator.presentation.workflow.Workflow
|
import org.cryptomator.presentation.workflow.Workflow
|
||||||
import org.cryptomator.util.ExceptionUtil
|
import org.cryptomator.util.ExceptionUtil
|
||||||
import org.cryptomator.util.crypto.CredentialCryptor
|
import org.cryptomator.util.crypto.CredentialCryptor
|
||||||
@ -479,6 +481,7 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
|||||||
private inner class LocalStorageAuthStrategy : AuthStrategy {
|
private inner class LocalStorageAuthStrategy : AuthStrategy {
|
||||||
|
|
||||||
private var authenticationStarted = false
|
private var authenticationStarted = false
|
||||||
|
|
||||||
override fun supports(cloud: CloudModel): Boolean {
|
override fun supports(cloud: CloudModel): Boolean {
|
||||||
return cloud.cloudType() == CloudTypeModel.LOCAL
|
return cloud.cloudType() == CloudTypeModel.LOCAL
|
||||||
}
|
}
|
||||||
@ -491,22 +494,41 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
|||||||
|
|
||||||
private fun startAuthentication(cloud: CloudModel) {
|
private fun startAuthentication(cloud: CloudModel) {
|
||||||
authenticationStarted = true
|
authenticationStarted = true
|
||||||
requestPermissions(
|
|
||||||
PermissionsResultCallbacks.onLocalStorageAuthenticated(cloud), //
|
val uri = (cloud as LocalStorageModel).uri()
|
||||||
R.string.permission_snackbar_auth_local_vault, //
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE, //
|
val permissions = context().contentResolver.persistedUriPermissions
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
for (permission in permissions) {
|
||||||
)
|
if (permission.uri.toString() == uri) {
|
||||||
|
succeedAuthenticationWith(cloud.toCloud())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.tag("AuthicateCloudPrester").e("Permission revoked, ask to re-pick location")
|
||||||
|
|
||||||
|
Toast.makeText(context(), getString(R.string.permission_revoked_re_request_permission), Toast.LENGTH_LONG).show()
|
||||||
|
|
||||||
|
val openDocumentTree = Intent(ACTION_OPEN_DOCUMENT_TREE).apply {
|
||||||
|
putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
requestActivityResult(ActivityResultCallbacks.rePickedLocalStorageLocation(cloud), openDocumentTree)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Callback
|
@Callback
|
||||||
fun onLocalStorageAuthenticated(result: PermissionsResult, cloud: CloudModel) {
|
fun rePickedLocalStorageLocation(result: ActivityResult, cloud: LocalStorageModel) {
|
||||||
if (result.granted()) {
|
val rootTreeUriOfLocalStorage = result.intent().data
|
||||||
succeedAuthenticationWith(cloud.toCloud())
|
rootTreeUriOfLocalStorage?.let {
|
||||||
} else {
|
context() //
|
||||||
failAuthentication(PermissionNotGrantedException(R.string.permission_snackbar_auth_local_vault))
|
.contentResolver //
|
||||||
|
.takePersistableUriPermission( //
|
||||||
|
it, //
|
||||||
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
Timber.tag("AuthicateCloudPrester").e("Permission granted again")
|
||||||
|
succeedAuthenticationWith(cloud.toCloud())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun encrypt(password: String): String {
|
private fun encrypt(password: String): String {
|
||||||
|
@ -236,6 +236,31 @@ constructor(context: Context) : SharedPreferences.OnSharedPreferenceChangeListen
|
|||||||
return defaultSharedPreferences.getBoolean(BACKGROUND_UNLOCK_PREPARATION, true)
|
return defaultSharedPreferences.getBoolean(BACKGROUND_UNLOCK_PREPARATION, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun vaultsRemovedDuringMigration(vaultsToBeRemoved: Pair<String, List<String>>?) {
|
||||||
|
vaultsToBeRemoved?.let {
|
||||||
|
val vaultsToBeRemovedString = if (it.second.isNotEmpty()) {
|
||||||
|
it.second.reduce { acc, s -> "$acc,$s" }
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
defaultSharedPreferences.setValue(VAULTS_REMOVED_DURING_MIGRATION_TYPE, it.first)
|
||||||
|
defaultSharedPreferences.setValue(VAULTS_REMOVED_DURING_MIGRATION, vaultsToBeRemovedString)
|
||||||
|
} ?: run {
|
||||||
|
defaultSharedPreferences.setValue(VAULTS_REMOVED_DURING_MIGRATION_TYPE, null)
|
||||||
|
defaultSharedPreferences.setValue(VAULTS_REMOVED_DURING_MIGRATION, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun vaultsRemovedDuringMigration(): Pair<String, List<String>>? {
|
||||||
|
val vaultsRemovedDuringMigrationType = defaultSharedPreferences.getString(VAULTS_REMOVED_DURING_MIGRATION_TYPE, null)
|
||||||
|
val vaultsRemovedDuringMigration = defaultSharedPreferences.getString(VAULTS_REMOVED_DURING_MIGRATION, null)
|
||||||
|
return if(vaultsRemovedDuringMigrationType != null && vaultsRemovedDuringMigration != null) {
|
||||||
|
Pair(vaultsRemovedDuringMigrationType, ArrayList(vaultsRemovedDuringMigration.split(',')))
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val SCREEN_LOCK_DIALOG_SHOWN = "askForScreenLockDialogShown"
|
private const val SCREEN_LOCK_DIALOG_SHOWN = "askForScreenLockDialogShown"
|
||||||
@ -248,6 +273,8 @@ constructor(context: Context) : SharedPreferences.OnSharedPreferenceChangeListen
|
|||||||
private const val GLOB_SEARCH = "globSearch"
|
private const val GLOB_SEARCH = "globSearch"
|
||||||
private const val KEEP_UNLOCKED_WHILE_EDITING = "keepUnlockedWhileEditing"
|
private const val KEEP_UNLOCKED_WHILE_EDITING = "keepUnlockedWhileEditing"
|
||||||
private const val BACKGROUND_UNLOCK_PREPARATION = "backgroundUnlockPreparation"
|
private const val BACKGROUND_UNLOCK_PREPARATION = "backgroundUnlockPreparation"
|
||||||
|
private const val VAULTS_REMOVED_DURING_MIGRATION = "vaultsRemovedDuringMigration"
|
||||||
|
private const val VAULTS_REMOVED_DURING_MIGRATION_TYPE = "vaultsRemovedDuringMigrationType"
|
||||||
const val DEBUG_MODE = "debugMode"
|
const val DEBUG_MODE = "debugMode"
|
||||||
const val DISABLE_APP_WHEN_OBSCURED = "disableAppWhenObscured"
|
const val DISABLE_APP_WHEN_OBSCURED = "disableAppWhenObscured"
|
||||||
const val SECURE_SCREEN = "secureScreen"
|
const val SECURE_SCREEN = "secureScreen"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.cryptomator.util.crypto;
|
package org.cryptomator.util.crypto;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
|
||||||
import android.security.keystore.KeyGenParameterSpec;
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
import android.security.keystore.KeyProperties;
|
import android.security.keystore.KeyProperties;
|
||||||
|
|
||||||
@ -43,13 +42,10 @@ class CryptoOperationsImpl implements CryptoOperations {
|
|||||||
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec //
|
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec //
|
||||||
.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) //
|
.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) //
|
||||||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC) //
|
.setBlockModes(KeyProperties.BLOCK_MODE_CBC) //
|
||||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) //
|
||||||
|
.setUserAuthenticationRequired(requireUserAuthentication) //
|
||||||
|
.setInvalidatedByBiometricEnrollment(requireUserAuthentication);
|
||||||
|
|
||||||
builder.setUserAuthenticationRequired(requireUserAuthentication);
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
builder.setInvalidatedByBiometricEnrollment(requireUserAuthentication);
|
|
||||||
}
|
|
||||||
generator.init(builder.build());
|
generator.init(builder.build());
|
||||||
generator.generateKey();
|
generator.generateKey();
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user