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
This commit is contained in:
Julian Raufelder 2021-10-22 15:52:02 +02:00
parent 3b641a22d8
commit 91d1a65ba7
No known key found for this signature in database
GPG Key ID: 17EE71F6634E381D
45 changed files with 90 additions and 661 deletions

View File

@ -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'

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<Uri?> {
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)
}

View File

@ -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

View File

@ -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

View File

@ -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<UriPermission> 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);
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -1,9 +0,0 @@
package org.cryptomator.data.cloud.local.file
import org.cryptomator.domain.CloudNode
interface LocalNode : CloudNode {
override val parent: LocalFolder?
}

View File

@ -1,111 +0,0 @@
package org.cryptomator.data.cloud.local.file
import android.content.Context
import org.cryptomator.domain.LocalStorageCloud
import org.cryptomator.domain.exception.BackendException
import org.cryptomator.domain.exception.FatalBackendException
import org.cryptomator.domain.exception.NoSuchCloudFileException
import org.cryptomator.domain.repository.CloudContentRepository
import org.cryptomator.domain.usecases.ProgressAware
import org.cryptomator.domain.usecases.cloud.DataSource
import org.cryptomator.domain.usecases.cloud.DownloadState
import org.cryptomator.domain.usecases.cloud.UploadState
import org.cryptomator.util.ExceptionUtil
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.io.OutputStream
class LocalStorageContentRepository(context: Context, localStorageCloud: LocalStorageCloud) : CloudContentRepository<LocalStorageCloud, LocalNode, LocalFolder, LocalFile> {
private val localStorageImpl: LocalStorageImpl = LocalStorageImpl(context, localStorageCloud)
@Throws(BackendException::class)
override fun root(cloud: LocalStorageCloud): LocalFolder {
return localStorageImpl.root()
}
override fun resolve(cloud: LocalStorageCloud, path: String): LocalFolder {
return localStorageImpl.resolve(path)
}
@Throws(BackendException::class)
override fun file(parent: LocalFolder, name: String): LocalFile {
return localStorageImpl.file(parent, name, null)
}
@Throws(BackendException::class)
override fun file(parent: LocalFolder, name: String, size: Long?): LocalFile {
return localStorageImpl.file(parent, name, size)
}
@Throws(BackendException::class)
override fun folder(parent: LocalFolder, name: String): LocalFolder {
return localStorageImpl.folder(parent, name)
}
@Throws(BackendException::class)
override fun exists(node: LocalNode): Boolean {
return localStorageImpl.exists(node)
}
@Throws(BackendException::class)
override fun list(folder: LocalFolder): List<LocalNode> {
return localStorageImpl.list(folder)
}
@Throws(BackendException::class)
override fun create(folder: LocalFolder): LocalFolder {
return localStorageImpl.create(folder)
}
@Throws(BackendException::class)
override fun move(source: LocalFolder, target: LocalFolder): LocalFolder {
return localStorageImpl.move(source, target) as LocalFolder
}
@Throws(BackendException::class)
override fun move(source: LocalFile, target: LocalFile): LocalFile {
return localStorageImpl.move(source, target) as LocalFile
}
@Throws(BackendException::class)
override fun write(file: LocalFile, data: DataSource, progressAware: ProgressAware<UploadState>, replace: Boolean, size: Long): LocalFile {
return try {
localStorageImpl.write(file, data, progressAware, replace, size)
} catch (e: IOException) {
if (ExceptionUtil.contains(e, FileNotFoundException::class.java)) {
throw NoSuchCloudFileException(file.name)
}
throw FatalBackendException(e)
}
}
@Throws(BackendException::class)
override fun read(file: LocalFile, encryptedTmpFile: File?, data: OutputStream, progressAware: ProgressAware<DownloadState>) {
try {
localStorageImpl.read(file, data, progressAware)
} catch (e: IOException) {
if (ExceptionUtil.contains(e, FileNotFoundException::class.java)) {
throw NoSuchCloudFileException(file.name)
}
throw FatalBackendException(e)
}
}
@Throws(BackendException::class)
override fun delete(node: LocalNode) {
localStorageImpl.delete(node)
}
@Throws(BackendException::class)
override fun checkAuthenticationAndRetrieveCurrentAccount(cloud: LocalStorageCloud): String {
return ""
}
@Throws(BackendException::class)
override fun logout(cloud: LocalStorageCloud) {
// empty
}
}

View File

@ -1,165 +0,0 @@
package org.cryptomator.data.cloud.local.file
import android.content.Context
import org.cryptomator.data.util.CopyStream
import org.cryptomator.data.util.TransferredBytesAwareInputStream
import org.cryptomator.data.util.TransferredBytesAwareOutputStream
import org.cryptomator.domain.CloudNode
import org.cryptomator.domain.LocalStorageCloud
import org.cryptomator.domain.exception.BackendException
import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException
import org.cryptomator.domain.exception.FatalBackendException
import org.cryptomator.domain.exception.NoSuchCloudFileException
import org.cryptomator.domain.exception.ParentFolderIsNullException
import org.cryptomator.domain.usecases.ProgressAware
import org.cryptomator.domain.usecases.cloud.DataSource
import org.cryptomator.domain.usecases.cloud.DownloadState
import org.cryptomator.domain.usecases.cloud.Progress
import org.cryptomator.domain.usecases.cloud.UploadState
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import java.util.Date
internal class LocalStorageImpl(private val context: Context, localStorageCloud: LocalStorageCloud) {
private val root: RootLocalFolder = RootLocalFolder(localStorageCloud)
fun root(): LocalFolder {
return root
}
fun resolve(path: String): LocalFolder {
val names = path.substring(root.path.length + 1).split("/").toTypedArray()
var folder: LocalFolder = root
for (name in names) {
folder = folder(folder, name)
}
return folder
}
fun file(folder: LocalFolder, name: String, size: Long?): LocalFile {
return LocalStorageNodeFactory.file(folder, name, folder.path + '/' + name, size, null)
}
fun folder(folder: LocalFolder, name: String): LocalFolder {
return LocalStorageNodeFactory.folder(folder, name, folder.path + '/' + name)
}
fun exists(node: CloudNode): Boolean {
return File(node.path).exists()
}
@Throws(BackendException::class)
fun list(folder: LocalFolder): List<LocalNode> {
val localDirectory = File(folder.path)
if (!exists(folder)) {
throw NoSuchCloudFileException()
}
return localDirectory.listFiles()?.map { file -> LocalStorageNodeFactory.from(folder, file) }
?: throw FatalBackendException("listFiles() shouldn't return null")
}
@Throws(BackendException::class)
fun create(folder: LocalFolder): LocalFolder {
folder.parent?.let { parentFolder ->
val createFolder = File(folder.path)
if (createFolder.exists()) {
throw CloudNodeAlreadyExistsException(folder.name)
}
if (!createFolder.mkdirs()) {
throw FatalBackendException("Couldn't create a local folder at " + folder.path)
}
return LocalStorageNodeFactory.folder(parentFolder, createFolder)
} ?: throw ParentFolderIsNullException(folder.name)
}
@Throws(BackendException::class)
fun move(source: LocalNode, target: LocalNode): LocalNode {
target.parent?.let {
val sourceFile = File(source.path)
val targetFile = File(target.path)
if (targetFile.exists()) {
throw CloudNodeAlreadyExistsException(target.name)
}
if (!sourceFile.exists()) {
throw NoSuchCloudFileException(source.name)
}
if (!sourceFile.renameTo(targetFile)) {
throw FatalBackendException("Couldn't move " + source.path + " to " + target.path)
}
return LocalStorageNodeFactory.from(it, targetFile)
} ?: throw ParentFolderIsNullException(target.name)
}
fun delete(node: CloudNode) {
val fileOrDirectory = File(node.path)
if (!deleteRecursive(fileOrDirectory)) {
throw FatalBackendException("Couldn't delete local CloudNode $fileOrDirectory")
}
}
private fun deleteRecursive(fileOrDirectory: File): Boolean {
if (fileOrDirectory.isDirectory) {
fileOrDirectory.listFiles()?.forEach {
deleteRecursive(it)
}
}
return fileOrDirectory.delete()
}
@Throws(IOException::class, BackendException::class)
fun write(file: LocalFile, data: DataSource, progressAware: ProgressAware<UploadState>, replace: Boolean, size: Long): LocalFile {
if (!replace && exists(file)) {
throw CloudNodeAlreadyExistsException("CloudNode already exists and replace is false")
}
progressAware.onProgress(Progress.started(UploadState.upload(file)))
val localFile = File(file.path)
FileOutputStream(localFile).use { out ->
data.open(context)?.use { inputStream ->
object : TransferredBytesAwareInputStream(inputStream) {
override fun bytesTransferred(transferred: Long) {
progressAware.onProgress( //
Progress.progress(UploadState.upload(file)) //
.between(0) //
.and(size) //
.withValue(transferred)
)
}
}.use { CopyStream.copyStreamToStream(it, out) }
} ?: throw FatalBackendException("InputStream shouldn't be null")
}
progressAware.onProgress(Progress.completed(UploadState.upload(file)))
return LocalStorageNodeFactory.file( //
file.parent, //
file.name, //
localFile.path, //
localFile.length(), //
Date(localFile.lastModified())
)
}
@Throws(IOException::class)
fun read(file: LocalFile, data: OutputStream, progressAware: ProgressAware<DownloadState>) {
progressAware.onProgress(Progress.started(DownloadState.download(file)))
val localFile = File(file.path)
FileInputStream(localFile).use { inputStream ->
object : TransferredBytesAwareOutputStream(data) {
override fun bytesTransferred(transferred: Long) {
progressAware //
.onProgress(
Progress.progress(DownloadState.download(file)) //
.between(0) //
.and(localFile.length()) //
.withValue(transferred)
)
}
}.use { out -> CopyStream.copyStreamToStream(inputStream, out) }
}
progressAware.onProgress(Progress.completed(DownloadState.download(file)))
}
}

View File

@ -1,36 +0,0 @@
package org.cryptomator.data.cloud.local.file
import java.io.File
import java.util.Date
internal object LocalStorageNodeFactory {
@JvmStatic
fun from(parent: LocalFolder, file: File): LocalNode {
return if (file.isDirectory) {
folder(parent, file)
} else {
file( //
parent, //
file.name, //
file.path, //
file.length(), //
Date(file.lastModified())
)
}
}
fun folder(parent: LocalFolder, file: File): LocalFolder {
return folder(parent, file.name, file.path)
}
@JvmStatic
fun folder(parent: LocalFolder, name: String, path: String): LocalFolder {
return LocalFolder(parent, name, path)
}
@JvmStatic
fun file(folder: LocalFolder, name: String, path: String, size: Long?, modified: Date?): LocalFile {
return LocalFile(folder, name, path, size, modified)
}
}

View File

@ -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)
}
}

View File

@ -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");
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -48,11 +48,6 @@ public class DropboxCloud implements Cloud {
return true;
}
@Override
public boolean predefined() {
return true;
}
@Override
public boolean persistent() {
return true;

View File

@ -48,11 +48,6 @@ public class GoogleDriveCloud implements Cloud {
return true;
}
@Override
public boolean predefined() {
return true;
}
@Override
public boolean persistent() {
return true;

View File

@ -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;

View File

@ -43,11 +43,6 @@ public class OnedriveCloud implements Cloud {
return CloudType.ONEDRIVE;
}
@Override
public boolean predefined() {
return true;
}
@Override
public boolean persistent() {
return true;

View File

@ -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;

View File

@ -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;

View File

@ -66,11 +66,6 @@ public class WebDavCloud implements Cloud {
return certificate;
}
@Override
public boolean predefined() {
return false;
}
@Override
public boolean persistent() {
return true;

View File

@ -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))
}
}

View File

@ -7,7 +7,6 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
@ -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">

View File

@ -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)
}

View File

@ -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<CloudNodeModel<*>>, //
@ -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<CloudFolderModel>, //
filesToExport: List<CloudFileModel>
@ -847,7 +815,6 @@ class BrowseFilesPresenter @Inject constructor( //
})
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private fun prepareExportingOf(parentUri: Uri, exportOperation: ExportOperation, filesToExport: List<CloudFileModel>, cloudNodeRecursiveListing: CloudNodeRecursiveListing) {
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<CloudFile>, parentUri: Uri): List<DownloadFile> {
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

View File

@ -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 //

View File

@ -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<Int>
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)
}

View File

@ -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()
}

View File

@ -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)
}
}
}

View File

@ -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) }

View File

@ -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<FolderSettingsBottomSheet.Call
callback?.onMoveFolderClicked(cloudFolderModel)
dismiss()
}
if (Build.VERSION.SDK_INT >= 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)

View File

@ -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<UpdateAppAvailableDialo
public override fun setupView() {
val message = requireArguments().getSerializable(MESSAGE_ARG) as String
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
tv_message.text = Html.fromHtml(message, Html.FROM_HTML_MODE_COMPACT)
} else {
tv_message.text = Html.fromHtml(message)
}
tv_message.text = Html.fromHtml(message, Html.FROM_HTML_MODE_COMPACT)
}
override fun enableViewAfterError(): View {

View File

@ -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()
}
}
}

View File

@ -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
}

View File

@ -9,38 +9,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/rv_local_default_cloud"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<include
android:id="@+id/default_local_cloud"
layout="@layout/view_default_local_cloud"
android:layout_width="match_parent"
android:layout_height="72dp"
android:clickable="true"
android:clipToPadding="true"
android:focusable="true" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/default_local_cloud"
android:layout_marginStart="16dp"
android:singleLine="true"
android:text="@string/screen_cloud_local_custom_storage_title"
android:textColor="@color/colorPrimary"
android:textSize="16sp" />
</RelativeLayout>
<include
layout="@layout/recycler_view_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/rv_local_default_cloud"
android:clipToPadding="true"
android:paddingBottom="88dp" />

View File

@ -167,7 +167,6 @@
<string name="screen_cloud_connections_no_connections">Click here to add locations</string>
<string name="screen_cloud_error_webdav_not_supported">Server doesn\'t seem to be WebDAV compatible</string>
<string name="screen_cloud_local_custom_storage_title">Custom locations</string>
<string name="screen_cloud_local_default_storage_title">Default storage</string>
<string name="screen_cloud_local_error_no_content_provider">No additional locations available.</string>
<!-- ## screen: webdav settings -->

View File

@ -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))
}
}

View File

@ -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();
};