diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 4c853160..3d4338a5 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -8,8 +8,8 @@ allprojects { ext { androidBuildToolsVersion = "30.0.2" androidMinSdkVersion = 24 - androidTargetSdkVersion = 29 - androidCompileSdkVersion = 29 + androidTargetSdkVersion = 30 + androidCompileSdkVersion = 30 // android and java libs androidVersion = '4.1.1.4' diff --git a/data/build.gradle b/data/build.gradle index 211a64e4..4d2111a2 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -82,7 +82,7 @@ android { } greendao { - schemaVersion 9 + schemaVersion 10 } configurations.all { diff --git a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt index 6e637898..1e0ad4c0 100644 --- a/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt +++ b/data/src/androidTest/java/org/cryptomator/data/db/UpgradeDatabaseTest.kt @@ -50,6 +50,7 @@ class UpgradeDatabaseTest { Upgrade6To7().applyTo(db, 6) Upgrade7To8().applyTo(db, 7) Upgrade8To9(sharedPreferencesHandler).applyTo(db, 8) + Upgrade9To10(sharedPreferencesHandler).applyTo(db, 9) CloudEntityDao(DaoConfig(db, CloudEntityDao::class.java)).loadAll() VaultEntityDao(DaoConfig(db, VaultEntityDao::class.java)).loadAll() @@ -407,4 +408,66 @@ class UpgradeDatabaseTest { 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")))) + } + } diff --git a/data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoCloud.java b/data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoCloud.java index ca6dd52e..eeadb135 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoCloud.java +++ b/data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoCloud.java @@ -31,11 +31,6 @@ public class CryptoCloud implements Cloud { return vault.equals(cloud.vault); } - @Override - public boolean predefined() { - return false; - } - @Override public boolean persistent() { return false; diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/DocumentIdCache.kt b/data/src/main/java/org/cryptomator/data/cloud/local/DocumentIdCache.kt similarity index 93% rename from data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/DocumentIdCache.kt rename to data/src/main/java/org/cryptomator/data/cloud/local/DocumentIdCache.kt index b4c9698d..db3abc87 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/DocumentIdCache.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/local/DocumentIdCache.kt @@ -1,4 +1,4 @@ -package org.cryptomator.data.cloud.local.storageaccessframework +package org.cryptomator.data.cloud.local import android.util.LruCache import org.cryptomator.domain.CloudFolder diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFile.kt b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFile.kt similarity index 93% rename from data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFile.kt rename to data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFile.kt index cffbb02c..c6bc7971 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFile.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFile.kt @@ -1,4 +1,4 @@ -package org.cryptomator.data.cloud.local.storageaccessframework +package org.cryptomator.data.cloud.local import android.net.Uri import org.cryptomator.domain.Cloud diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFolder.kt b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFolder.kt similarity index 94% rename from data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFolder.kt rename to data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFolder.kt index af3d8027..d147d6b0 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFolder.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFolder.kt @@ -1,4 +1,4 @@ -package org.cryptomator.data.cloud.local.storageaccessframework +package org.cryptomator.data.cloud.local import android.net.Uri import org.cryptomator.domain.Cloud diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFrameworkContentRepository.kt b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFrameworkContentRepository.kt similarity index 98% rename from data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFrameworkContentRepository.kt rename to data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFrameworkContentRepository.kt index 0f21ca53..76dc5456 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFrameworkContentRepository.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFrameworkContentRepository.kt @@ -1,4 +1,4 @@ -package org.cryptomator.data.cloud.local.storageaccessframework +package org.cryptomator.data.cloud.local import android.content.Context import org.cryptomator.domain.LocalStorageCloud diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFrameworkImpl.kt b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFrameworkImpl.kt similarity index 94% rename from data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFrameworkImpl.kt rename to data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFrameworkImpl.kt index bf93abcd..762018bf 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFrameworkImpl.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFrameworkImpl.kt @@ -1,4 +1,4 @@ -package org.cryptomator.data.cloud.local.storageaccessframework +package org.cryptomator.data.cloud.local import android.content.ContentResolver import android.content.Context @@ -7,10 +7,10 @@ import android.net.Uri import android.os.Build import android.provider.DocumentsContract import androidx.documentfile.provider.DocumentFile -import org.cryptomator.data.cloud.local.storageaccessframework.LocalStorageAccessFrameworkNodeFactory.file -import org.cryptomator.data.cloud.local.storageaccessframework.LocalStorageAccessFrameworkNodeFactory.folder -import org.cryptomator.data.cloud.local.storageaccessframework.LocalStorageAccessFrameworkNodeFactory.from -import org.cryptomator.data.cloud.local.storageaccessframework.LocalStorageAccessFrameworkNodeFactory.getNodePath +import org.cryptomator.data.cloud.local.LocalStorageAccessFrameworkNodeFactory.file +import org.cryptomator.data.cloud.local.LocalStorageAccessFrameworkNodeFactory.folder +import org.cryptomator.data.cloud.local.LocalStorageAccessFrameworkNodeFactory.from +import org.cryptomator.data.cloud.local.LocalStorageAccessFrameworkNodeFactory.getNodePath import org.cryptomator.data.util.CopyStream import org.cryptomator.data.util.TransferredBytesAwareInputStream 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 { source.parent?.let { parent -> var newUri = try { - DocumentsContract.renameDocument(contentResolver(), source.uri, name) + requireNotNull(source.uri) + DocumentsContract.renameDocument(contentResolver(), source.uri!!, name) } catch (e: FileNotFoundException) { /* Bug in Android 9 see #460 TLDR; In this renameDocument-method, Android 9 throws 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 { return Supplier { - val mimeType = if (mimeTypes.fromFilename(file.name) == null) MimeType.APPLICATION_OCTET_STREAM else mimeTypes.fromFilename(file.name) - try { - DocumentsContract.createDocument(contentResolver(), file.parent.uri, mimeType.toString(), file.name) // FIXME - } catch (e: FileNotFoundException) { - null + file.parent.uri?.let { + val mimeType = if (mimeTypes.fromFilename(file.name) == null) MimeType.APPLICATION_OCTET_STREAM else mimeTypes.fromFilename(file.name) + try { + DocumentsContract.createDocument(contentResolver(), it, mimeType.toString(), file.name) + } catch (e: FileNotFoundException) { + null + } } } } @@ -372,7 +375,7 @@ internal class LocalStorageAccessFrameworkImpl(context: Context, private val mim fun delete(node: LocalStorageAccessNode) { requireNotNull(node.uri) try { - DocumentsContract.deleteDocument(contentResolver(), node.uri) + DocumentsContract.deleteDocument(contentResolver(), node.uri!!) } catch (e: FileNotFoundException) { throw NoSuchCloudFileException(node.name) } diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFrameworkNodeFactory.kt b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFrameworkNodeFactory.kt similarity index 98% rename from data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFrameworkNodeFactory.kt rename to data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFrameworkNodeFactory.kt index f352c4bb..d65b3761 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessFrameworkNodeFactory.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessFrameworkNodeFactory.kt @@ -1,4 +1,4 @@ -package org.cryptomator.data.cloud.local.storageaccessframework +package org.cryptomator.data.cloud.local import android.database.Cursor import android.provider.DocumentsContract diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessNode.kt b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessNode.kt similarity index 76% rename from data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessNode.kt rename to data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessNode.kt index 12ffd6df..aa6fa800 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/LocalStorageAccessNode.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageAccessNode.kt @@ -1,4 +1,4 @@ -package org.cryptomator.data.cloud.local.storageaccessframework +package org.cryptomator.data.cloud.local import android.net.Uri import org.cryptomator.domain.CloudNode diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageContentRepositoryFactory.java b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageContentRepositoryFactory.java index c3ea26b0..b07bfebb 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageContentRepositoryFactory.java +++ b/data/src/main/java/org/cryptomator/data/cloud/local/LocalStorageContentRepositoryFactory.java @@ -1,9 +1,10 @@ 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.domain.Cloud; 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.util.file.MimeTypes; +import java.util.List; + import javax.inject.Inject; 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 public class LocalStorageContentRepositoryFactory implements CloudContentRepositoryFactory { @@ -39,23 +36,14 @@ public class LocalStorageContentRepositoryFactory implements CloudContentReposit @Override public CloudContentRepository cloudContentRepositoryFor(Cloud cloud) { - if (!hasPermissions(WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE)) { - throw new NoAuthenticationProvidedException(cloud); - } - if (((LocalStorageCloud) cloud).rootUri() != null) { - 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; + List permissions = context.getContentResolver().getPersistedUriPermissions(); + for (UriPermission permission : permissions) { + if(permission.getUri().toString().equals(((LocalStorageCloud) cloud).rootUri())) { + return new LocalStorageAccessFrameworkContentRepository(context, mimeTypes, (LocalStorageCloud) cloud); } } - return true; + + throw new NoAuthenticationProvidedException(cloud); } } diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/RootLocalStorageAccessFolder.kt b/data/src/main/java/org/cryptomator/data/cloud/local/RootLocalStorageAccessFolder.kt similarity index 92% rename from data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/RootLocalStorageAccessFolder.kt rename to data/src/main/java/org/cryptomator/data/cloud/local/RootLocalStorageAccessFolder.kt index bc59d39b..9268ac45 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/local/storageaccessframework/RootLocalStorageAccessFolder.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/local/RootLocalStorageAccessFolder.kt @@ -1,4 +1,4 @@ -package org.cryptomator.data.cloud.local.storageaccessframework +package org.cryptomator.data.cloud.local import android.net.Uri import android.provider.DocumentsContract diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalFile.kt b/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalFile.kt deleted file mode 100644 index 43eb8ad6..00000000 --- a/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalFile.kt +++ /dev/null @@ -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 -} diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalFolder.kt b/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalFolder.kt deleted file mode 100644 index 40d60d43..00000000 --- a/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalFolder.kt +++ /dev/null @@ -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) - } -} diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalNode.kt b/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalNode.kt deleted file mode 100644 index 313b8859..00000000 --- a/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalNode.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.cryptomator.data.cloud.local.file - -import org.cryptomator.domain.CloudNode - -interface LocalNode : CloudNode { - - override val parent: LocalFolder? - -} diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalStorageContentRepository.kt b/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalStorageContentRepository.kt deleted file mode 100644 index 46c225f1..00000000 --- a/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalStorageContentRepository.kt +++ /dev/null @@ -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 { - - 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 { - 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, 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) { - 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 - } - -} diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalStorageImpl.kt b/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalStorageImpl.kt deleted file mode 100644 index 18547d8e..00000000 --- a/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalStorageImpl.kt +++ /dev/null @@ -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 { - 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, 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) { - 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))) - } - -} diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalStorageNodeFactory.kt b/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalStorageNodeFactory.kt deleted file mode 100644 index e6788644..00000000 --- a/data/src/main/java/org/cryptomator/data/cloud/local/file/LocalStorageNodeFactory.kt +++ /dev/null @@ -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) - } -} diff --git a/data/src/main/java/org/cryptomator/data/cloud/local/file/RootLocalFolder.kt b/data/src/main/java/org/cryptomator/data/cloud/local/file/RootLocalFolder.kt deleted file mode 100644 index fde9d439..00000000 --- a/data/src/main/java/org/cryptomator/data/cloud/local/file/RootLocalFolder.kt +++ /dev/null @@ -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) - } -} diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java b/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java index 3b6ef827..638c5c76 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java @@ -27,7 +27,8 @@ class DatabaseUpgrades { Upgrade5To6 upgrade5To6, // Upgrade6To7 upgrade6To7, // Upgrade7To8 upgrade7To8, // - Upgrade8To9 upgrade8To9) { + Upgrade8To9 upgrade8To9, // + Upgrade9To10 upgrade9To10) { availableUpgrades = defineUpgrades( // upgrade0To1, // @@ -38,7 +39,8 @@ class DatabaseUpgrades { upgrade5To6, // upgrade6To7, // upgrade7To8, // - upgrade8To9); + upgrade8To9, // + upgrade9To10); } private static Comparator reverseOrder() { diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt new file mode 100644 index 00000000..01cf79fa --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade9To10.kt @@ -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() + 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() + } + } +} diff --git a/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java b/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java index bdb422c3..61f1307c 100644 --- a/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java +++ b/data/src/main/java/org/cryptomator/data/repository/CloudRepositoryImpl.java @@ -76,9 +76,6 @@ class CloudRepositoryImpl implements CloudRepository { @Override public void delete(Cloud cloud) { - if (cloud.predefined()) { - throw new IllegalArgumentException("Can not delete predefined cloud"); - } if (!cloud.persistent()) { throw new IllegalArgumentException("Can not delete non persistent cloud"); } diff --git a/data/src/main/java/org/cryptomator/data/util/NetworkConnectionCheck.kt b/data/src/main/java/org/cryptomator/data/util/NetworkConnectionCheck.kt index 14332c8d..9ad87420 100644 --- a/data/src/main/java/org/cryptomator/data/util/NetworkConnectionCheck.kt +++ b/data/src/main/java/org/cryptomator/data/util/NetworkConnectionCheck.kt @@ -28,6 +28,6 @@ class NetworkConnectionCheck @Inject internal constructor(private val context: C fun checkWifiOnAndConnected(): Boolean { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val activeNetwork = connectivityManager.activeNetwork - return connectivityManager.getNetworkCapabilities(activeNetwork).hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + return connectivityManager.getNetworkCapabilities(activeNetwork)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true } } diff --git a/domain/src/main/java/org/cryptomator/domain/Cloud.kt b/domain/src/main/java/org/cryptomator/domain/Cloud.kt index ee1aba13..6c1f1e22 100644 --- a/domain/src/main/java/org/cryptomator/domain/Cloud.kt +++ b/domain/src/main/java/org/cryptomator/domain/Cloud.kt @@ -7,7 +7,6 @@ interface Cloud : Serializable { fun id(): Long? fun type(): CloudType? fun configurationMatches(cloud: Cloud?): Boolean - fun predefined(): Boolean fun persistent(): Boolean fun requiresNetwork(): Boolean } diff --git a/domain/src/main/java/org/cryptomator/domain/DropboxCloud.java b/domain/src/main/java/org/cryptomator/domain/DropboxCloud.java index b8f4fc8a..2178e3c7 100644 --- a/domain/src/main/java/org/cryptomator/domain/DropboxCloud.java +++ b/domain/src/main/java/org/cryptomator/domain/DropboxCloud.java @@ -48,11 +48,6 @@ public class DropboxCloud implements Cloud { return true; } - @Override - public boolean predefined() { - return true; - } - @Override public boolean persistent() { return true; diff --git a/domain/src/main/java/org/cryptomator/domain/GoogleDriveCloud.java b/domain/src/main/java/org/cryptomator/domain/GoogleDriveCloud.java index e8b27ce9..8ebd70f0 100644 --- a/domain/src/main/java/org/cryptomator/domain/GoogleDriveCloud.java +++ b/domain/src/main/java/org/cryptomator/domain/GoogleDriveCloud.java @@ -48,11 +48,6 @@ public class GoogleDriveCloud implements Cloud { return true; } - @Override - public boolean predefined() { - return true; - } - @Override public boolean persistent() { return true; diff --git a/domain/src/main/java/org/cryptomator/domain/LocalStorageCloud.java b/domain/src/main/java/org/cryptomator/domain/LocalStorageCloud.java index ee895359..fb96449c 100644 --- a/domain/src/main/java/org/cryptomator/domain/LocalStorageCloud.java +++ b/domain/src/main/java/org/cryptomator/domain/LocalStorageCloud.java @@ -1,6 +1,5 @@ package org.cryptomator.domain; -import android.os.Build; import android.text.TextUtils; 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 public boolean persistent() { return true; diff --git a/domain/src/main/java/org/cryptomator/domain/OnedriveCloud.java b/domain/src/main/java/org/cryptomator/domain/OnedriveCloud.java index 5b082012..a56accf0 100644 --- a/domain/src/main/java/org/cryptomator/domain/OnedriveCloud.java +++ b/domain/src/main/java/org/cryptomator/domain/OnedriveCloud.java @@ -43,11 +43,6 @@ public class OnedriveCloud implements Cloud { return CloudType.ONEDRIVE; } - @Override - public boolean predefined() { - return true; - } - @Override public boolean persistent() { return true; diff --git a/domain/src/main/java/org/cryptomator/domain/PCloud.java b/domain/src/main/java/org/cryptomator/domain/PCloud.java index 95164c65..89b01617 100644 --- a/domain/src/main/java/org/cryptomator/domain/PCloud.java +++ b/domain/src/main/java/org/cryptomator/domain/PCloud.java @@ -59,12 +59,6 @@ public class PCloud implements Cloud { return username.equals(cloud.username); } - - @Override - public boolean predefined() { - return false; - } - @Override public boolean persistent() { return true; diff --git a/domain/src/main/java/org/cryptomator/domain/S3Cloud.java b/domain/src/main/java/org/cryptomator/domain/S3Cloud.java index 44268df6..064cc93a 100644 --- a/domain/src/main/java/org/cryptomator/domain/S3Cloud.java +++ b/domain/src/main/java/org/cryptomator/domain/S3Cloud.java @@ -80,12 +80,6 @@ public class S3Cloud implements Cloud { return s3Bucket.equals(cloud.s3Bucket) && s3Endpoint.equals(cloud.s3Endpoint) && s3Region.equals(cloud.s3Region); } - - @Override - public boolean predefined() { - return false; - } - @Override public boolean persistent() { return true; diff --git a/domain/src/main/java/org/cryptomator/domain/WebDavCloud.java b/domain/src/main/java/org/cryptomator/domain/WebDavCloud.java index 2ffba185..ccb21dd6 100644 --- a/domain/src/main/java/org/cryptomator/domain/WebDavCloud.java +++ b/domain/src/main/java/org/cryptomator/domain/WebDavCloud.java @@ -66,11 +66,6 @@ public class WebDavCloud implements Cloud { return certificate; } - @Override - public boolean predefined() { - return false; - } - @Override public boolean persistent() { return true; diff --git a/presentation/src/foss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt b/presentation/src/foss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt index abfe6e74..eac080bf 100644 --- a/presentation/src/foss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt +++ b/presentation/src/foss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt @@ -1,7 +1,9 @@ package org.cryptomator.presentation.presenter -import android.Manifest 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 com.dropbox.core.android.Auth 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.model.CloudModel import org.cryptomator.presentation.model.CloudTypeModel +import org.cryptomator.presentation.model.LocalStorageModel import org.cryptomator.presentation.model.ProgressModel import org.cryptomator.presentation.model.ProgressStateModel 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.AddExistingVaultWorkflow import org.cryptomator.presentation.workflow.CreateNewVaultWorkflow -import org.cryptomator.presentation.workflow.PermissionsResult import org.cryptomator.presentation.workflow.Workflow import org.cryptomator.util.ExceptionUtil import org.cryptomator.util.crypto.CredentialCryptor @@ -433,6 +435,7 @@ class AuthenticateCloudPresenter @Inject constructor( // private inner class LocalStorageAuthStrategy : AuthStrategy { private var authenticationStarted = false + override fun supports(cloud: CloudModel): Boolean { return cloud.cloudType() == CloudTypeModel.LOCAL } @@ -445,22 +448,41 @@ class AuthenticateCloudPresenter @Inject constructor( // private fun startAuthentication(cloud: CloudModel) { authenticationStarted = true - requestPermissions( - PermissionsResultCallbacks.onLocalStorageAuthenticated(cloud), // - R.string.permission_snackbar_auth_local_vault, // - Manifest.permission.READ_EXTERNAL_STORAGE, // - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) + + val uri = (cloud as LocalStorageModel).uri() + + val permissions = context().contentResolver.persistedUriPermissions + 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 - fun onLocalStorageAuthenticated(result: PermissionsResult, cloud: CloudModel) { - if (result.granted()) { - succeedAuthenticationWith(cloud.toCloud()) - } else { - failAuthentication(PermissionNotGrantedException(R.string.permission_snackbar_auth_local_vault)) + fun rePickedLocalStorageLocation(result: ActivityResult, cloud: LocalStorageModel) { + val rootTreeUriOfLocalStorage = result.intent().data + rootTreeUriOfLocalStorage?.let { + context() // + .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 { diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml index 0e2bbcf7..5b155c10 100644 --- a/presentation/src/main/AndroidManifest.xml +++ b/presentation/src/main/AndroidManifest.xml @@ -7,7 +7,6 @@ - @@ -28,7 +27,6 @@ android:allowBackup="false" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:requestLegacyExternalStorage="true" android:supportsRtl="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> diff --git a/presentation/src/main/java/org/cryptomator/presentation/BootAwareReceiver.kt b/presentation/src/main/java/org/cryptomator/presentation/BootAwareReceiver.kt index b0124eb2..f20baf4a 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/BootAwareReceiver.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/BootAwareReceiver.kt @@ -3,7 +3,6 @@ package org.cryptomator.presentation import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.os.Build import org.cryptomator.presentation.service.CryptorsService import org.cryptomator.presentation.service.PhotoContentJob import org.cryptomator.util.SharedPreferencesHandler @@ -18,7 +17,7 @@ class BootAwareReceiver : BroadcastReceiver() { context.stopService(CryptorsService.lockAllIntent(context)) } 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") PhotoContentJob.scheduleJob(context) } diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt index 47e03969..391d30a9 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/BrowseFilesPresenter.kt @@ -4,11 +4,8 @@ import android.Manifest import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri -import android.os.Build -import android.os.Environment import android.provider.DocumentsContract import android.widget.Toast -import androidx.annotation.RequiresApi import org.cryptomator.domain.CloudFile import org.cryptomator.domain.CloudFolder 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.MimeType import org.cryptomator.util.file.MimeTypes -import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException -import java.io.FileOutputStream import java.io.Serializable import java.security.DigestInputStream import java.security.MessageDigest @@ -738,30 +733,6 @@ class BrowseFilesPresenter @Inject constructor( // 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) { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) intent.addCategory(Intent.CATEGORY_OPENABLE) @@ -789,7 +760,6 @@ class BrowseFilesPresenter @Inject constructor( // } @Callback - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) fun pickedLocalStorageLocation( result: ActivityResult, // nodesToExport: ArrayList>, // @@ -809,7 +779,6 @@ class BrowseFilesPresenter @Inject constructor( // disableSelectionMode() } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private fun collectNodesToExport( parentUri: Uri, // exportOperation: ExportOperation, // @@ -827,7 +796,6 @@ class BrowseFilesPresenter @Inject constructor( // collectFolderContentForExport(parentUri, exportOperation, foldersForRecursiveDirListing, filesToExport) } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private fun collectFolderContentForExport( parentUri: Uri, exportOperation: ExportOperation, folders: List, // filesToExport: List @@ -847,7 +815,6 @@ class BrowseFilesPresenter @Inject constructor( // }) } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private fun prepareExportingOf(parentUri: Uri, exportOperation: ExportOperation, filesToExport: List, cloudNodeRecursiveListing: CloudNodeRecursiveListing) { downloadFiles = ArrayList() 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, parentUri: Uri): List { return filesToExport.mapTo(ArrayList()) { createDownloadFile(it, parentUri) } } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private fun prepareFolderContentForExport(cloudFolderRecursiveListing: CloudFolderRecursiveListing, parentUri: Uri) { createFolder(parentUri, cloudFolderRecursiveListing.parent.name)?.let { downloadFiles.addAll(prepareFilesForExport(cloudFolderRecursiveListing.files, it)) @@ -877,7 +842,6 @@ class BrowseFilesPresenter @Inject constructor( // } ?: throw FatalBackendException("Failed to create parent folder for export") } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private fun createFolder(parentUri: Uri, folderName: String): Uri? { return try { DocumentsContract.createDocument( // @@ -892,7 +856,6 @@ class BrowseFilesPresenter @Inject constructor( // } } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private fun createDownloadFile(file: CloudFile, documentUri: Uri): DownloadFile { return try { DownloadFile.Builder() // @@ -918,7 +881,6 @@ class BrowseFilesPresenter @Inject constructor( // } } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Throws(IllegalFileNameException::class, NoSuchCloudFileException::class) private fun createNewDocumentUri(parentUri: Uri, fileName: String): Uri { val mimeType = mimeTypes.fromFilename(fileName) ?: MimeType.APPLICATION_OCTET_STREAM diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt index ff98ce85..0a5bbc51 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -3,9 +3,7 @@ package org.cryptomator.presentation.presenter import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri -import android.os.Build import android.widget.Toast -import androidx.annotation.RequiresApi import org.cryptomator.domain.Cloud import org.cryptomator.domain.LocalStorageCloud import org.cryptomator.domain.PCloud @@ -251,7 +249,6 @@ class CloudConnectionListPresenter @Inject constructor( // } @Callback - @RequiresApi(api = Build.VERSION_CODES.KITKAT) fun pickedLocalStorageLocation(result: ActivityResult) { val rootTreeUriOfLocalStorage = result.intent().data persistUriPermission(rootTreeUriOfLocalStorage) @@ -266,7 +263,6 @@ class CloudConnectionListPresenter @Inject constructor( // }) } - @RequiresApi(api = Build.VERSION_CODES.KITKAT) private fun persistUriPermission(rootTreeUriOfLocalStorage: Uri?) { rootTreeUriOfLocalStorage?.let { context() // @@ -278,7 +274,6 @@ class CloudConnectionListPresenter @Inject constructor( // } } - @RequiresApi(api = Build.VERSION_CODES.KITKAT) private fun releaseUriPermission(uri: String) { context() // .contentResolver // @@ -294,10 +289,6 @@ class CloudConnectionListPresenter @Inject constructor( // } } - fun onDefaultLocalCloudConnectionClicked() { - finishWithResult(SELECTED_CLOUD, defaultLocalStorageCloud) - } - companion object { const val SELECTED_CLOUD = "selectedCloudConnection" diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/ImagePreviewPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/ImagePreviewPresenter.kt index 31b066d8..393aac0a 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/ImagePreviewPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/ImagePreviewPresenter.kt @@ -3,8 +3,6 @@ package org.cryptomator.presentation.presenter import android.Manifest import android.content.Intent import android.net.Uri -import android.os.Build -import android.os.Environment import org.cryptomator.domain.CloudFile import org.cryptomator.domain.CloudNode 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.PermissionsResult import org.cryptomator.util.ExceptionUtil -import java.io.File import java.io.FileNotFoundException -import java.io.FileOutputStream import java.io.IOException import java.io.InputStream import java.io.OutputStream @@ -58,37 +54,33 @@ class ImagePreviewPresenter @Inject constructor( // @InstanceState lateinit var pageIndexes: ArrayList - fun onExportImageClicked(uri: Uri) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - copyFileToDownloadDirectory(uri) - } else { - copyFileToUserSelectedLocation(uri) - } + fun exportImageToUserSelectedLocation(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.exportImageToUserSelectedLocation(uri.toString()), intent) } - private fun copyFileToDownloadDirectory(uri: Uri) { + @Callback + fun exportImageToUserSelectedLocation(result: ActivityResult, sourceUri: String?) { requestPermissions( - PermissionsResultCallbacks.copyFileToDownloadDirectory(uri.toString()), // - R.string.permission_message_export_file, Manifest.permission.WRITE_EXTERNAL_STORAGE + PermissionsResultCallbacks.exportImageToUserSelectedLocation(result.intent()?.dataString, sourceUri), // + R.string.permission_message_export_file, // + Manifest.permission.READ_EXTERNAL_STORAGE ) } @Callback - fun copyFileToDownloadDirectory(result: PermissionsResult, uriString: String?) { + fun exportImageToUserSelectedLocation(result: PermissionsResult, targetUri: String?, sourceUri: String?) { if (result.granted()) { - val uriFile = Uri.parse(uriString) - 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, contentResolverUtil.fileName(uriFile)) - try { - copyFile(contentResolverUtil.openInputStream(uriFile), FileOutputStream(target)) - } catch (e: FileNotFoundException) { - showError(e) - } - } else { - view?.showError(R.string.screen_file_browser_msg_creating_download_dir_failed) + try { + copyFile( + contentResolverUtil.openInputStream(Uri.parse(sourceUri)), // + contentResolverUtil.openOutputStream(Uri.parse(targetUri)) + ) + } catch (e: FileNotFoundException) { + showError(e) } } } @@ -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) { shareFileHelper.shareFile(this, uri) } diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/SettingsPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/SettingsPresenter.kt index f40390d1..43cb4dc4 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/SettingsPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/SettingsPresenter.kt @@ -90,17 +90,14 @@ class SettingsPresenter @Inject internal constructor( requestPermissions( PermissionsResultCallbacks.onLocalStoragePermissionGranted(), // R.string.permission_snackbar_auth_auto_upload, // - Manifest.permission.READ_EXTERNAL_STORAGE, // - Manifest.permission.WRITE_EXTERNAL_STORAGE + Manifest.permission.READ_EXTERNAL_STORAGE ) } @Callback fun onLocalStoragePermissionGranted(result: PermissionsResult) { if (result.granted()) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - scheduleJob(context()) - } + scheduleJob(context()) } else { view?.disableAutoUpload() } diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt index f5b327b2..edea86e7 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt @@ -67,7 +67,7 @@ class UnlockVaultPresenter @Inject constructor( super.destroyed() if (retryUnlockHandler != null) { 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) { if (hasFocus) { if (retryUnlockHandler != null) { running = false - retryUnlockHandler?.removeCallbacks(null) + retryUnlockHandler?.removeCallbacksAndMessages(null) } } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt index 3e4ece77..b5e6d288 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt @@ -12,6 +12,7 @@ import org.cryptomator.data.cloud.crypto.CryptoCloud import org.cryptomator.data.util.NetworkConnectionCheck import org.cryptomator.domain.Cloud import org.cryptomator.domain.CloudFolder +import org.cryptomator.domain.CloudType import org.cryptomator.domain.Vault import org.cryptomator.domain.di.PerView 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.dialog.AppIsObscuredInfoDialog 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.UpdateAppAvailableDialog import org.cryptomator.presentation.ui.dialog.UpdateAppDialog +import org.cryptomator.presentation.ui.dialog.VaultsRemovedDuringMigrationDialog import org.cryptomator.presentation.util.FileUtil import org.cryptomator.presentation.workflow.ActivityResult import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow @@ -104,6 +105,12 @@ class VaultListPresenter @Inject constructor( // 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() } @@ -119,9 +126,10 @@ class VaultListPresenter @Inject constructor( // } override fun onError(e: Throwable) { - var license: String? = "" - if (e is LicenseNotValidException) { - license = e.license + val license = if (e is LicenseNotValidException) { + e.license + } else { + "" } val intent = Intent(context(), LicenseCheckActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK @@ -272,8 +280,8 @@ class VaultListPresenter @Inject constructor( // view?.showVaultCreationHint() } else { view?.hideVaultCreationHint() - view?.renderVaultList(vaultModels) } + view?.renderVaultList(vaultModels) } }) } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt index 209f27ea..901b67cb 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt @@ -66,7 +66,7 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou presenter.onDeleteImageClicked(imagePreviewFiles[imagePreviewSliderAdapter.getIndex(viewPager.currentItem)]) } exportImage.setOnClickListener { - currentImageUri?.let { presenter.onExportImageClicked(it) } + currentImageUri?.let { presenter.exportImageToUserSelectedLocation(it) } } shareImage.setOnClickListener { currentImageUri?.let { presenter.onShareImageClicked(it) } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/FolderSettingsBottomSheet.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/FolderSettingsBottomSheet.kt index 6440caa7..1c3a587f 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/FolderSettingsBottomSheet.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/FolderSettingsBottomSheet.kt @@ -1,6 +1,5 @@ package org.cryptomator.presentation.ui.bottomsheet -import android.os.Build import android.os.Bundle import android.view.View import org.cryptomator.generator.BottomSheet @@ -46,12 +45,10 @@ class FolderSettingsBottomSheet : BaseBottomSheet= Build.VERSION_CODES.LOLLIPOP) { - export_folder.visibility = View.VISIBLE - export_folder.setOnClickListener { - callback?.onExportFolderClicked(cloudFolderModel) - dismiss() - } + export_folder.visibility = View.VISIBLE + export_folder.setOnClickListener { + callback?.onExportFolderClicked(cloudFolderModel) + dismiss() } delete_folder.setOnClickListener { callback?.onDeleteNodeClicked(cloudFolderModel) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AppIsObscuredInfoDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AppIsObscuredInfoDialog.kt index ccf63d03..e01fde74 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AppIsObscuredInfoDialog.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/AppIsObscuredInfoDialog.kt @@ -13,10 +13,10 @@ import kotlinx.android.synthetic.main.dialog_app_is_obscured_info.tv_app_is_obsc class AppIsObscuredInfoDialog : BaseDialog() { public override fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog { - builder // + return builder // .setTitle(R.string.dialog_app_is_obscured_info_title) // - .setNeutralButton(R.string.dialog_app_is_obscured_info_neutral_button) { dialog: DialogInterface, _: Int -> dialog.dismiss() } - return builder.create() + .setNeutralButton(R.string.dialog_app_is_obscured_info_neutral_button) { dialog: DialogInterface, _: Int -> dialog.dismiss() } // + .create() } override fun disableDialogWhenObscured(): Boolean { diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/UpdateAppAvailableDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/UpdateAppAvailableDialog.kt index 7f477e70..074113f5 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/UpdateAppAvailableDialog.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/UpdateAppAvailableDialog.kt @@ -1,7 +1,6 @@ package org.cryptomator.presentation.ui.dialog import android.content.DialogInterface -import android.os.Build import android.os.Bundle import android.text.Html import android.view.View @@ -32,11 +31,7 @@ class UpdateAppAvailableDialog : BaseProgressErrorDialog= Build.VERSION_CODES.N) { - tv_message.text = Html.fromHtml(message, Html.FROM_HTML_MODE_COMPACT) - } else { - tv_message.text = Html.fromHtml(message) - } + tv_message.text = Html.fromHtml(message, Html.FROM_HTML_MODE_COMPACT) } override fun enableViewAfterError(): View { diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/VaultIsRootFolderOfCloudDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/VaultIsRootFolderOfCloudDialog.kt new file mode 100644 index 00000000..19b83692 --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/VaultIsRootFolderOfCloudDialog.kt @@ -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() { + + 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() + } + } +} diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/VaultsRemovedDuringMigrationDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/VaultsRemovedDuringMigrationDialog.kt new file mode 100644 index 00000000..0d8de189 --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/VaultsRemovedDuringMigrationDialog.kt @@ -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() { + + public override fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog { + val vaultsRemovedDuringMigration = requireArguments().getSerializable(VAULTS_REMOVED_ARG) as Pair> + + 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> + + 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>): DialogFragment { + val args = Bundle() + args.putSerializable(VAULTS_REMOVED_ARG, vaultsRemovedDuringMigration) + val fragment = VaultsRemovedDuringMigrationDialog() + fragment.arguments = args + return fragment + } + } +} diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/CloudConnectionListFragment.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/CloudConnectionListFragment.kt index bf974c6f..57d8a0ed 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/CloudConnectionListFragment.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/CloudConnectionListFragment.kt @@ -1,6 +1,5 @@ package org.cryptomator.presentation.ui.fragment -import android.os.Environment import android.util.TypedValue import android.view.View.GONE import android.view.View.VISIBLE @@ -13,10 +12,7 @@ import org.cryptomator.presentation.presenter.CloudConnectionListPresenter import org.cryptomator.presentation.ui.adapter.CloudConnectionListAdapter 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.rv_local_default_cloud 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 @Fragment(R.layout.fragment_browse_cloud_connections) @@ -42,7 +38,6 @@ class CloudConnectionListFragment : BaseFragment() { override fun setupView() { setupRecyclerView() - rv_local_default_cloud.setOnClickListener { cloudConnectionListPresenter.onDefaultLocalCloudConnectionClicked() } floating_action_button.setOnClickListener { cloudConnectionListPresenter.onAddConnectionClicked() } } @@ -71,11 +66,5 @@ class CloudConnectionListFragment : BaseFragment() { fun setSelectedCloudType(selectedCloudType: CloudTypeModel) { 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() - } } } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SettingsFragment.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SettingsFragment.kt index 1022f509..d40769ff 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SettingsFragment.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SettingsFragment.kt @@ -1,6 +1,5 @@ package org.cryptomator.presentation.ui.fragment -import android.os.Build import android.os.Bundle import android.text.SpannableString import android.text.style.ForegroundColorSpan @@ -259,9 +258,7 @@ class SettingsFragment : PreferenceFragmentCompat() { if (enabled) { activity().grantLocalStoragePermissionForAutoUpload() } 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 } diff --git a/presentation/src/main/java/org/cryptomator/presentation/workflow/AddExistingVaultWorkflow.java b/presentation/src/main/java/org/cryptomator/presentation/workflow/AddExistingVaultWorkflow.java index 80714856..29b03ba4 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/workflow/AddExistingVaultWorkflow.java +++ b/presentation/src/main/java/org/cryptomator/presentation/workflow/AddExistingVaultWorkflow.java @@ -18,6 +18,7 @@ import org.cryptomator.presentation.model.ProgressModel; import org.cryptomator.presentation.model.mappers.CloudModelMapper; import org.cryptomator.presentation.presenter.ChooseCloudServicePresenter; import org.cryptomator.presentation.presenter.VaultListPresenter; +import org.cryptomator.presentation.ui.dialog.VaultIsRootFolderOfCloudDialog; import java.io.Serializable; import java.util.Arrays; @@ -116,9 +117,13 @@ public class AddExistingVaultWorkflow extends Workflow result) { CloudFileModel masterkeyFile = result.getResult(); - state().masterkeyFile = masterkeyFile.toCloudNode(); - presenter().getView().showProgress(ProgressModel.GENERIC); - finish(); + if(!masterkeyFile.getPath().equals("/masterkey.cryptomator") && !masterkeyFile.getPath().equals("/vault.cryptomator")) { + state().masterkeyFile = masterkeyFile.toCloudNode(); + presenter().getView().showProgress(ProgressModel.GENERIC); + finish(); + } else { + presenter().getView().showDialog(VaultIsRootFolderOfCloudDialog.Companion.newInstance()); + } } @Override diff --git a/presentation/src/main/res/layout/dialog_vault_is_root_folder_of_cloud.xml b/presentation/src/main/res/layout/dialog_vault_is_root_folder_of_cloud.xml new file mode 100644 index 00000000..eb831c0a --- /dev/null +++ b/presentation/src/main/res/layout/dialog_vault_is_root_folder_of_cloud.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/presentation/src/main/res/layout/dialog_vaults_removed_during_migration.xml b/presentation/src/main/res/layout/dialog_vaults_removed_during_migration.xml new file mode 100644 index 00000000..db8ac720 --- /dev/null +++ b/presentation/src/main/res/layout/dialog_vaults_removed_during_migration.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/presentation/src/main/res/layout/fragment_browse_cloud_connections.xml b/presentation/src/main/res/layout/fragment_browse_cloud_connections.xml index 4da30d58..c0930487 100644 --- a/presentation/src/main/res/layout/fragment_browse_cloud_connections.xml +++ b/presentation/src/main/res/layout/fragment_browse_cloud_connections.xml @@ -9,38 +9,10 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - - diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 8a87b0b0..8ed2c79e 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -61,6 +61,8 @@ Cryptomator needs storage access to upload files Cryptomator needs storage access to share files + Cryptomator has lost permission to access this location. Please select this folder again to restore the permission. + Settings Search Previous @@ -166,8 +168,6 @@ @string/screen_vault_list_vault_action_delete Click here to add locations Server doesn\'t seem to be WebDAV compatible - Custom locations - Default storage No additional locations available. @@ -417,6 +417,14 @@ 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\nHow to enable Cryptomator Close + Please re-add vaults for %1s cloud + 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/dialog_unable_to_share_positive_button + + Vault is root folder of the cloud connection + 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/dialog_unable_to_share_positive_button + 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 aware of the risks. Are you sure you want to remove this cloud connection? diff --git a/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt b/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt index 8f33fa66..7e12859b 100644 --- a/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt +++ b/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt @@ -1,8 +1,10 @@ package org.cryptomator.presentation.presenter -import android.Manifest import android.accounts.AccountManager 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 com.dropbox.core.android.Auth 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.model.CloudModel import org.cryptomator.presentation.model.CloudTypeModel +import org.cryptomator.presentation.model.LocalStorageModel import org.cryptomator.presentation.model.ProgressModel import org.cryptomator.presentation.model.ProgressStateModel 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.AddExistingVaultWorkflow import org.cryptomator.presentation.workflow.CreateNewVaultWorkflow -import org.cryptomator.presentation.workflow.PermissionsResult import org.cryptomator.presentation.workflow.Workflow import org.cryptomator.util.ExceptionUtil import org.cryptomator.util.crypto.CredentialCryptor @@ -479,6 +481,7 @@ class AuthenticateCloudPresenter @Inject constructor( // private inner class LocalStorageAuthStrategy : AuthStrategy { private var authenticationStarted = false + override fun supports(cloud: CloudModel): Boolean { return cloud.cloudType() == CloudTypeModel.LOCAL } @@ -491,22 +494,41 @@ class AuthenticateCloudPresenter @Inject constructor( // private fun startAuthentication(cloud: CloudModel) { authenticationStarted = true - requestPermissions( - PermissionsResultCallbacks.onLocalStorageAuthenticated(cloud), // - R.string.permission_snackbar_auth_local_vault, // - Manifest.permission.READ_EXTERNAL_STORAGE, // - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) + + val uri = (cloud as LocalStorageModel).uri() + + val permissions = context().contentResolver.persistedUriPermissions + 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 - fun onLocalStorageAuthenticated(result: PermissionsResult, cloud: CloudModel) { - if (result.granted()) { - succeedAuthenticationWith(cloud.toCloud()) - } else { - failAuthentication(PermissionNotGrantedException(R.string.permission_snackbar_auth_local_vault)) + fun rePickedLocalStorageLocation(result: ActivityResult, cloud: LocalStorageModel) { + val rootTreeUriOfLocalStorage = result.intent().data + rootTreeUriOfLocalStorage?.let { + context() // + .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 { diff --git a/util/src/main/java/org/cryptomator/util/SharedPreferencesHandler.kt b/util/src/main/java/org/cryptomator/util/SharedPreferencesHandler.kt index eedee837..ac60155f 100644 --- a/util/src/main/java/org/cryptomator/util/SharedPreferencesHandler.kt +++ b/util/src/main/java/org/cryptomator/util/SharedPreferencesHandler.kt @@ -236,6 +236,31 @@ constructor(context: Context) : SharedPreferences.OnSharedPreferenceChangeListen return defaultSharedPreferences.getBoolean(BACKGROUND_UNLOCK_PREPARATION, true) } + fun vaultsRemovedDuringMigration(vaultsToBeRemoved: Pair>?) { + 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>? { + 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 { 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 KEEP_UNLOCKED_WHILE_EDITING = "keepUnlockedWhileEditing" 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 DISABLE_APP_WHEN_OBSCURED = "disableAppWhenObscured" const val SECURE_SCREEN = "secureScreen" diff --git a/util/src/main/java/org/cryptomator/util/crypto/CryptoOperationsImpl.java b/util/src/main/java/org/cryptomator/util/crypto/CryptoOperationsImpl.java index 85b11a79..04acdceb 100644 --- a/util/src/main/java/org/cryptomator/util/crypto/CryptoOperationsImpl.java +++ b/util/src/main/java/org/cryptomator/util/crypto/CryptoOperationsImpl.java @@ -1,7 +1,6 @@ package org.cryptomator.util.crypto; import android.content.Context; -import android.os.Build; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; @@ -43,13 +42,10 @@ class CryptoOperationsImpl implements CryptoOperations { KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec // .Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) // .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.generateKey(); };