From 91d1a65ba721c02aef25ddf001003c7003a7c955 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Fri, 22 Oct 2021 15:52:02 +0200 Subject: [PATCH] Update target API to 30 (Android 11) Write external storage got removed due to forced scoped storage. Local storage cloud is now only available using the storage access framework implementation. Migration still needs to be implemented as well as how to recover when the authentications fails in the LocalStorageAuthStrategy. Fixes #251 --- buildsystem/dependencies.gradle | 4 +- .../data/cloud/crypto/CryptoCloud.java | 5 - .../DocumentIdCache.kt | 2 +- .../LocalStorageAccessFile.kt | 2 +- .../LocalStorageAccessFolder.kt | 2 +- ...StorageAccessFrameworkContentRepository.kt | 2 +- .../LocalStorageAccessFrameworkImpl.kt | 27 +-- .../LocalStorageAccessFrameworkNodeFactory.kt | 2 +- .../LocalStorageAccessNode.kt | 2 +- .../LocalStorageContentRepositoryFactory.java | 36 ++-- .../RootLocalStorageAccessFolder.kt | 2 +- .../data/cloud/local/file/LocalFile.kt | 11 -- .../data/cloud/local/file/LocalFolder.kt | 14 -- .../data/cloud/local/file/LocalNode.kt | 9 - .../file/LocalStorageContentRepository.kt | 111 ------------ .../data/cloud/local/file/LocalStorageImpl.kt | 165 ------------------ .../local/file/LocalStorageNodeFactory.kt | 36 ---- .../data/cloud/local/file/RootLocalFolder.kt | 15 -- .../data/repository/CloudRepositoryImpl.java | 3 - .../data/util/NetworkConnectionCheck.kt | 2 +- .../main/java/org/cryptomator/domain/Cloud.kt | 1 - .../org/cryptomator/domain/DropboxCloud.java | 5 - .../cryptomator/domain/GoogleDriveCloud.java | 5 - .../cryptomator/domain/LocalStorageCloud.java | 6 - .../org/cryptomator/domain/OnedriveCloud.java | 5 - .../java/org/cryptomator/domain/PCloud.java | 6 - .../java/org/cryptomator/domain/S3Cloud.java | 6 - .../org/cryptomator/domain/WebDavCloud.java | 5 - .../presenter/AuthenticateCloudPresenter.kt | 24 +-- presentation/src/main/AndroidManifest.xml | 2 - .../presentation/BootAwareReceiver.kt | 3 +- .../presenter/BrowseFilesPresenter.kt | 38 ---- .../presenter/CloudConnectionListPresenter.kt | 5 - .../presenter/ImagePreviewPresenter.kt | 77 ++------ .../presenter/SettingsPresenter.kt | 7 +- .../presenter/UnlockVaultPresenter.kt | 5 +- .../ui/activity/ImagePreviewActivity.kt | 2 +- .../bottomsheet/FolderSettingsBottomSheet.kt | 11 +- .../ui/dialog/UpdateAppAvailableDialog.kt | 7 +- .../fragment/CloudConnectionListFragment.kt | 11 -- .../ui/fragment/SettingsFragment.kt | 5 +- .../fragment_browse_cloud_connections.xml | 28 --- presentation/src/main/res/values/strings.xml | 1 - .../presenter/AuthenticateCloudPresenter.kt | 24 +-- .../util/crypto/CryptoOperationsImpl.java | 10 +- 45 files changed, 90 insertions(+), 661 deletions(-) rename data/src/main/java/org/cryptomator/data/cloud/local/{storageaccessframework => }/DocumentIdCache.kt (93%) rename data/src/main/java/org/cryptomator/data/cloud/local/{storageaccessframework => }/LocalStorageAccessFile.kt (93%) rename data/src/main/java/org/cryptomator/data/cloud/local/{storageaccessframework => }/LocalStorageAccessFolder.kt (94%) rename data/src/main/java/org/cryptomator/data/cloud/local/{storageaccessframework => }/LocalStorageAccessFrameworkContentRepository.kt (98%) rename data/src/main/java/org/cryptomator/data/cloud/local/{storageaccessframework => }/LocalStorageAccessFrameworkImpl.kt (94%) rename data/src/main/java/org/cryptomator/data/cloud/local/{storageaccessframework => }/LocalStorageAccessFrameworkNodeFactory.kt (98%) rename data/src/main/java/org/cryptomator/data/cloud/local/{storageaccessframework => }/LocalStorageAccessNode.kt (76%) rename data/src/main/java/org/cryptomator/data/cloud/local/{storageaccessframework => }/RootLocalStorageAccessFolder.kt (92%) delete mode 100644 data/src/main/java/org/cryptomator/data/cloud/local/file/LocalFile.kt delete mode 100644 data/src/main/java/org/cryptomator/data/cloud/local/file/LocalFolder.kt delete mode 100644 data/src/main/java/org/cryptomator/data/cloud/local/file/LocalNode.kt delete mode 100644 data/src/main/java/org/cryptomator/data/cloud/local/file/LocalStorageContentRepository.kt delete mode 100644 data/src/main/java/org/cryptomator/data/cloud/local/file/LocalStorageImpl.kt delete mode 100644 data/src/main/java/org/cryptomator/data/cloud/local/file/LocalStorageNodeFactory.kt delete mode 100644 data/src/main/java/org/cryptomator/data/cloud/local/file/RootLocalFolder.kt 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/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..904f8b15 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) // FIXME + } 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/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..07bce89b 100644 --- a/presentation/src/foss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt +++ b/presentation/src/foss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt @@ -1,6 +1,5 @@ package org.cryptomator.presentation.presenter -import android.Manifest import android.accounts.AccountManager import android.widget.Toast import com.dropbox.core.android.Auth @@ -35,6 +34,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 +44,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 @@ -445,20 +444,15 @@ 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 permissions = context().contentResolver.persistedUriPermissions + for (permission in permissions) { + if (permission.uri.toString() == (cloud as LocalStorageModel).uri()) { + succeedAuthenticationWith(cloud.toCloud()) + } + } - @Callback - fun onLocalStorageAuthenticated(result: PermissionsResult, cloud: CloudModel) { - if (result.granted()) { - succeedAuthenticationWith(cloud.toCloud()) - } else { + // FIXME think about how to re-request permission + // FIXME change in the FOSS variant too failAuthentication(PermissionNotGrantedException(R.string.permission_snackbar_auth_local_vault)) } } 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..ba4ab6a3 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 // 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/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/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/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/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..d9312db4 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -167,7 +167,6 @@ Click here to add locations Server doesn\'t seem to be WebDAV compatible Custom locations - Default storage No additional locations available. 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..f542dd4d 100644 --- a/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt +++ b/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt @@ -1,6 +1,5 @@ package org.cryptomator.presentation.presenter -import android.Manifest import android.accounts.AccountManager import android.content.ActivityNotFoundException import android.widget.Toast @@ -38,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 @@ -47,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 @@ -491,20 +490,15 @@ 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 permissions = context().contentResolver.persistedUriPermissions + for (permission in permissions) { + if (permission.uri.toString() == (cloud as LocalStorageModel).uri()) { + succeedAuthenticationWith(cloud.toCloud()) + } + } - @Callback - fun onLocalStorageAuthenticated(result: PermissionsResult, cloud: CloudModel) { - if (result.granted()) { - succeedAuthenticationWith(cloud.toCloud()) - } else { + // FIXME think about how to re-request permission + // FIXME change in the FOSS variant too failAuthentication(PermissionNotGrantedException(R.string.permission_snackbar_auth_local_vault)) } } 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(); };