Refactor unlock and cloud authentication from UseCase to Activity
This commit is contained in:
parent
c8e9616894
commit
36fd5b2a8a
@ -90,11 +90,14 @@ public class CryptoCloudFactory {
|
||||
throw new CancellationException();
|
||||
}
|
||||
|
||||
cryptoCloudContentRepositoryFactory.registerCryptor(impl.getVault(), cryptor);
|
||||
|
||||
return aCopyOf(token.getVault()) //
|
||||
Vault vault = aCopyOf(token.getVault()) //
|
||||
.withVersion(impl.getKeyFile().getVersion()) //
|
||||
.withUnlocked(true) //
|
||||
.build();
|
||||
|
||||
cryptoCloudContentRepositoryFactory.registerCryptor(vault, cryptor);
|
||||
|
||||
return vault;
|
||||
}
|
||||
|
||||
public UnlockTokenImpl createUnlockToken(Vault vault) throws BackendException {
|
||||
|
@ -1,13 +0,0 @@
|
||||
package org.cryptomator.domain.repository;
|
||||
|
||||
import org.cryptomator.domain.Cloud;
|
||||
import org.cryptomator.domain.exception.BackendException;
|
||||
|
||||
public interface CloudAuthenticationService {
|
||||
|
||||
boolean isAuthenticated(Cloud cloud) throws BackendException;
|
||||
|
||||
boolean canAuthenticate(Cloud cloud);
|
||||
|
||||
Cloud updateAuthenticatedCloud(Cloud cloud);
|
||||
}
|
@ -97,10 +97,11 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
<activity android:name=".ui.activity.UnlockVaultActivity"
|
||||
android:theme="@style/TransparentAlertDialogCustom"
|
||||
android:label=""/>
|
||||
<activity android:name=".ui.activity.EmptyDirIdFileInfoActivity" />
|
||||
|
||||
|
||||
<!-- Settings -->
|
||||
<activity android:name=".ui.activity.BiometricAuthSettingsActivity" />
|
||||
<activity android:name=".ui.activity.CloudConnectionListActivity" />
|
||||
|
@ -21,6 +21,7 @@ import org.cryptomator.presentation.ui.activity.SettingsActivity;
|
||||
import org.cryptomator.presentation.ui.activity.SharedFilesActivity;
|
||||
import org.cryptomator.presentation.ui.activity.SplashActivity;
|
||||
import org.cryptomator.presentation.ui.activity.TextEditorActivity;
|
||||
import org.cryptomator.presentation.ui.activity.UnlockVaultActivity;
|
||||
import org.cryptomator.presentation.ui.activity.VaultListActivity;
|
||||
import org.cryptomator.presentation.ui.activity.WebDavAddOrChangeActivity;
|
||||
import org.cryptomator.presentation.ui.fragment.AutoUploadChooseVaultFragment;
|
||||
@ -34,6 +35,7 @@ import org.cryptomator.presentation.ui.fragment.ImagePreviewFragment;
|
||||
import org.cryptomator.presentation.ui.fragment.SetPasswordFragment;
|
||||
import org.cryptomator.presentation.ui.fragment.SharedFilesFragment;
|
||||
import org.cryptomator.presentation.ui.fragment.TextEditorFragment;
|
||||
import org.cryptomator.presentation.ui.fragment.UnlockVaultFragment;
|
||||
import org.cryptomator.presentation.ui.fragment.VaultListFragment;
|
||||
import org.cryptomator.presentation.ui.fragment.WebDavAddOrChangeFragment;
|
||||
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow;
|
||||
@ -114,4 +116,8 @@ public interface ActivityComponent {
|
||||
void inject(AutoUploadChooseVaultFragment autoUploadChooseVaultFragment);
|
||||
|
||||
void inject(LicenseCheckActivity licenseCheckActivity);
|
||||
|
||||
void inject(UnlockVaultActivity unlockVaultActivity);
|
||||
|
||||
void inject(UnlockVaultFragment unlockVaultFragment);
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package org.cryptomator.presentation.intent;
|
||||
|
||||
import org.cryptomator.generator.Intent;
|
||||
import org.cryptomator.presentation.model.VaultModel;
|
||||
import org.cryptomator.presentation.ui.activity.UnlockVaultActivity;
|
||||
|
||||
@Intent(UnlockVaultActivity.class)
|
||||
public interface UnlockVaultIntent {
|
||||
|
||||
VaultModel vaultModel();
|
||||
|
||||
VaultAction vaultAction();
|
||||
|
||||
enum VaultAction {
|
||||
UNLOCK,
|
||||
UNLOCK_FOR_BIOMETRIC_AUTH,
|
||||
ENCRYPT_PASSWORD,
|
||||
CHANGE_PASSWORD
|
||||
}
|
||||
|
||||
}
|
@ -15,6 +15,8 @@ class VaultModel(private val vault: Vault) : Serializable {
|
||||
get() = !vault.isUnlocked
|
||||
val position: Int
|
||||
get() = vault.position
|
||||
val version: Int
|
||||
get() = vault.version
|
||||
|
||||
fun toVault(): Vault {
|
||||
return vault
|
||||
|
@ -7,17 +7,14 @@ import org.cryptomator.domain.di.PerView
|
||||
import org.cryptomator.domain.usecases.GetDecryptedCloudForVaultUseCase
|
||||
import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase
|
||||
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
|
||||
import org.cryptomator.domain.usecases.vault.RemoveStoredVaultPasswordsUseCase
|
||||
import org.cryptomator.domain.usecases.vault.UnlockVaultUseCase
|
||||
import org.cryptomator.domain.usecases.vault.VaultOrUnlockToken
|
||||
import org.cryptomator.generator.Callback
|
||||
import org.cryptomator.presentation.R
|
||||
import org.cryptomator.presentation.exception.ExceptionHandlers
|
||||
import org.cryptomator.presentation.intent.ChooseCloudNodeSettings
|
||||
import org.cryptomator.presentation.intent.Intents
|
||||
import org.cryptomator.presentation.intent.UnlockVaultIntent
|
||||
import org.cryptomator.presentation.model.CloudFolderModel
|
||||
import org.cryptomator.presentation.model.CloudModel
|
||||
import org.cryptomator.presentation.model.ProgressModel
|
||||
import org.cryptomator.presentation.model.VaultModel
|
||||
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper
|
||||
import org.cryptomator.presentation.ui.activity.view.AutoUploadChooseVaultView
|
||||
@ -32,8 +29,6 @@ class AutoUploadChooseVaultPresenter @Inject constructor( //
|
||||
private val getVaultListUseCase: GetVaultListUseCase, //
|
||||
private val getRootFolderUseCase: GetRootFolderUseCase, //
|
||||
private val getDecryptedCloudForVaultUseCase: GetDecryptedCloudForVaultUseCase, //
|
||||
private val unlockVaultUseCase: UnlockVaultUseCase, //
|
||||
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
|
||||
private val cloudFolderModelMapper: CloudFolderModelMapper, //
|
||||
private val sharedPreferencesHandler: SharedPreferencesHandler, //
|
||||
private val authenticationExceptionHandler: AuthenticationExceptionHandler, //
|
||||
@ -83,11 +78,24 @@ class AutoUploadChooseVaultPresenter @Inject constructor( //
|
||||
decryptedCloudFor(authenticatedVault)
|
||||
} else {
|
||||
if (!isPaused) {
|
||||
view?.showEnterPasswordDialog(VaultModel(authenticatedVault))
|
||||
requestActivityResult( //
|
||||
ActivityResultCallbacks.vaultUnlockedAutoUpload(), //
|
||||
Intents.unlockVaultIntent().withVaultModel(VaultModel(authenticatedVault)).withVaultAction(UnlockVaultIntent.VaultAction.UNLOCK))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Callback
|
||||
fun vaultUnlockedAutoUpload(result: ActivityResult) {
|
||||
val cloud = result.intent().getSerializableExtra(SINGLE_RESULT) as Cloud
|
||||
when {
|
||||
result.isResultOk -> rootFolderFor(cloud)
|
||||
else -> TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun decryptedCloudFor(vault: Vault) {
|
||||
getDecryptedCloudForVaultUseCase //
|
||||
.withVault(vault) //
|
||||
@ -151,49 +159,11 @@ class AutoUploadChooseVaultPresenter @Inject constructor( //
|
||||
location?.let { view?.showChosenLocation(it) }
|
||||
}
|
||||
|
||||
fun onUnlockCanceled() {
|
||||
unlockVaultUseCase.cancel()
|
||||
}
|
||||
|
||||
fun onUnlockPressed(vaultModel: VaultModel, password: String?) {
|
||||
view?.showProgress(ProgressModel.GENERIC)
|
||||
unlockVaultUseCase //
|
||||
.withVaultOrUnlockToken(VaultOrUnlockToken.from(vaultModel.toVault())) //
|
||||
.andPassword(password) //
|
||||
.run(object : DefaultResultHandler<Cloud>() {
|
||||
override fun onSuccess(cloud: Cloud) {
|
||||
view?.showProgress(ProgressModel.COMPLETED)
|
||||
rootFolderFor(cloud)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
if (!authenticationExceptionHandler.handleAuthenticationException( //
|
||||
this@AutoUploadChooseVaultPresenter, //
|
||||
e, //
|
||||
ActivityResultCallbacks.unlockVaultAfterAuth(vaultModel.toVault(), password))) {
|
||||
showError(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun onBiometricKeyInvalidated(vaultModel: VaultModel?) {
|
||||
removeStoredVaultPasswordsUseCase.run(object : DefaultResultHandler<Void?>() {
|
||||
override fun onSuccess(void: Void?) {
|
||||
view?.showBiometricAuthKeyInvalidatedDialog()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun useConfirmationInFaceUnlockBiometricAuthentication(): Boolean {
|
||||
return sharedPreferencesHandler.useConfirmationInFaceUnlockBiometricAuthentication()
|
||||
}
|
||||
|
||||
enum class AuthenticationState {
|
||||
CHOOSE_LOCATION, INIT_ROOT
|
||||
}
|
||||
|
||||
init {
|
||||
unsubscribeOnDestroy(getVaultListUseCase)
|
||||
unsubscribeOnDestroy(getVaultListUseCase, getRootFolderUseCase, getDecryptedCloudForVaultUseCase)
|
||||
}
|
||||
}
|
||||
|
@ -2,21 +2,21 @@ package org.cryptomator.presentation.presenter
|
||||
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException
|
||||
import org.cryptomator.domain.Cloud
|
||||
import org.cryptomator.domain.Vault
|
||||
import org.cryptomator.domain.di.PerView
|
||||
import org.cryptomator.domain.usecases.vault.*
|
||||
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
|
||||
import org.cryptomator.domain.usecases.vault.LockVaultUseCase
|
||||
import org.cryptomator.domain.usecases.vault.SaveVaultUseCase
|
||||
import org.cryptomator.generator.Callback
|
||||
import org.cryptomator.presentation.exception.ExceptionHandlers
|
||||
import org.cryptomator.presentation.model.CloudModel
|
||||
import org.cryptomator.presentation.model.ProgressModel
|
||||
import org.cryptomator.presentation.intent.Intents
|
||||
import org.cryptomator.presentation.intent.UnlockVaultIntent
|
||||
import org.cryptomator.presentation.model.VaultModel
|
||||
import org.cryptomator.presentation.ui.activity.view.BiometricAuthSettingsView
|
||||
import org.cryptomator.presentation.workflow.ActivityResult
|
||||
import org.cryptomator.presentation.workflow.AuthenticationExceptionHandler
|
||||
import org.cryptomator.util.SharedPreferencesHandler
|
||||
import java.util.*
|
||||
import java.util.ArrayList
|
||||
import javax.inject.Inject
|
||||
import timber.log.Timber
|
||||
|
||||
@ -24,13 +24,9 @@ import timber.log.Timber
|
||||
class BiometricAuthSettingsPresenter @Inject constructor( //
|
||||
private val getVaultListUseCase: GetVaultListUseCase, //
|
||||
private val saveVaultUseCase: SaveVaultUseCase, //
|
||||
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
|
||||
private val checkVaultPasswordUseCase: CheckVaultPasswordUseCase, //
|
||||
private val unlockVaultUseCase: UnlockVaultUseCase, //
|
||||
private val lockVaultUseCase: LockVaultUseCase, //
|
||||
exceptionMappings: ExceptionHandlers, //
|
||||
private val sharedPreferencesHandler: SharedPreferencesHandler, //
|
||||
private val authenticationExceptionHandler: AuthenticationExceptionHandler) : Presenter<BiometricAuthSettingsView>(exceptionMappings) {
|
||||
private val sharedPreferencesHandler: SharedPreferencesHandler) : Presenter<BiometricAuthSettingsView>(exceptionMappings) {
|
||||
|
||||
fun loadVaultList() {
|
||||
updateVaultListView()
|
||||
@ -49,92 +45,56 @@ class BiometricAuthSettingsPresenter @Inject constructor( //
|
||||
|
||||
fun updateVaultEntityWithChangedBiometricAuthSettings(vaultModel: VaultModel, useBiometricAuth: Boolean) {
|
||||
if (useBiometricAuth) {
|
||||
view?.showEnterPasswordDialog(VaultModel(vaultModel.toVault()))
|
||||
verifyPassword(vaultModel)
|
||||
} else {
|
||||
removePasswordAndSave(vaultModel.toVault())
|
||||
}
|
||||
}
|
||||
|
||||
fun verifyPassword(vaultModel: VaultModel) {
|
||||
private fun verifyPassword(vaultModel: VaultModel) {
|
||||
Timber.tag("BiomtricAuthSettngsPres").i("Checking entered vault password")
|
||||
if (vaultModel.isLocked) {
|
||||
unlockVault(vaultModel)
|
||||
requestActivityResult( //
|
||||
ActivityResultCallbacks.vaultUnlockedBiometricAuthPres(vaultModel), //
|
||||
Intents.unlockVaultIntent().withVaultModel(vaultModel).withVaultAction(UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH))
|
||||
} else {
|
||||
checkPassword(vaultModel)
|
||||
lockVaultUseCase
|
||||
.withVault(vaultModel.toVault())
|
||||
.run(object : DefaultResultHandler<Vault>() {
|
||||
override fun onSuccess(vault: Vault) {
|
||||
super.onSuccess(vault)
|
||||
requestActivityResult( //
|
||||
ActivityResultCallbacks.vaultUnlockedBiometricAuthPres(vaultModel), //
|
||||
Intents.unlockVaultIntent().withVaultModel(vaultModel).withVaultAction(UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPassword(vaultModel: VaultModel) {
|
||||
view?.showProgress(ProgressModel.GENERIC)
|
||||
checkVaultPasswordUseCase //
|
||||
.withVault(vaultModel.toVault()) //
|
||||
.andPassword(vaultModel.password) //
|
||||
.run(object : DefaultResultHandler<Boolean>() {
|
||||
override fun onSuccess(passwordCorrect: Boolean) {
|
||||
if (passwordCorrect) {
|
||||
Timber.tag("BiomtricAuthSettngsPres").i("Password is correct")
|
||||
onPasswordCheckSucceeded(vaultModel)
|
||||
} else {
|
||||
Timber.tag("BiomtricAuthSettngsPres").i("Password is wrong")
|
||||
showError(InvalidPassphraseException())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
super.onError(e)
|
||||
Timber.tag("BiomtricAuthSettngsPres").e(e, "Password check failed")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun unlockVault(vaultModel: VaultModel) {
|
||||
view?.showProgress(ProgressModel.GENERIC)
|
||||
unlockVaultUseCase //
|
||||
.withVaultOrUnlockToken(VaultOrUnlockToken.from(vaultModel.toVault())) //
|
||||
.andPassword(vaultModel.password) //
|
||||
.run(object : DefaultResultHandler<Cloud>() {
|
||||
override fun onSuccess(cloud: Cloud) {
|
||||
Timber.tag("BiomtricAuthSettngsPres").i("Password is correct")
|
||||
onUnlockSucceeded(vaultModel)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
if (!authenticationExceptionHandler.handleAuthenticationException(this@BiometricAuthSettingsPresenter, e, ActivityResultCallbacks.unlockVaultAfterAuth(vaultModel.toVault()))) {
|
||||
showError(e)
|
||||
Timber.tag("BiomtricAuthSettngsPres").e(e, "Password check failed")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun onUnlockSucceeded(vaultModel: VaultModel) {
|
||||
lockVaultUseCase
|
||||
.withVault(vaultModel.toVault())
|
||||
.run(object : DefaultResultHandler<Vault>() {
|
||||
override fun onSuccess(vault: Vault) {
|
||||
super.onSuccess(vault)
|
||||
onPasswordCheckSucceeded(vaultModel)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Timber.tag("BiomtricAuthSettngsPres").e(e, "Locking vault after unlocking failed but continue to save changes")
|
||||
onPasswordCheckSucceeded(vaultModel)
|
||||
}
|
||||
})
|
||||
@Callback
|
||||
fun vaultUnlockedBiometricAuthPres(result: ActivityResult, vaultModel: VaultModel) {
|
||||
val cloud = result.intent().getSerializableExtra(SINGLE_RESULT) as Cloud
|
||||
val password = result.intent().getStringExtra(UnlockVaultPresenter.PASSWORD)
|
||||
val vault = Vault.aCopyOf(vaultModel.toVault()).withCloud(cloud).withSavedPassword(password).build()
|
||||
when {
|
||||
result.isResultOk -> requestActivityResult( //
|
||||
ActivityResultCallbacks.encryptVaultPassword(vaultModel), //
|
||||
Intents.unlockVaultIntent().withVaultModel(VaultModel(vault)).withVaultAction(UnlockVaultIntent.VaultAction.ENCRYPT_PASSWORD))
|
||||
else -> TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
@Callback
|
||||
fun unlockVaultAfterAuth(result: ActivityResult, vault: Vault?) {
|
||||
val cloud = result.getSingleResult(CloudModel::class.java).toCloud()
|
||||
val vaultWithUpdatedCloud = Vault.aCopyOf(vault).withCloud(cloud).build()
|
||||
unlockVault(VaultModel(vaultWithUpdatedCloud))
|
||||
fun encryptVaultPassword(result: ActivityResult, vaultModel: VaultModel) {
|
||||
val tmpVault = result.intent().getSerializableExtra(SINGLE_RESULT) as VaultModel
|
||||
val vault = Vault.aCopyOf(vaultModel.toVault()).withSavedPassword(tmpVault.password).build()
|
||||
when {
|
||||
result.isResultOk -> saveVault(vault)
|
||||
else -> TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
private fun onPasswordCheckSucceeded(vaultModel: VaultModel) {
|
||||
view?.showBiometricAuthenticationDialog(vaultModel)
|
||||
}
|
||||
|
||||
fun saveVault(vault: Vault?) {
|
||||
private fun saveVault(vault: Vault?) {
|
||||
saveVaultUseCase //
|
||||
.withVault(vault) //
|
||||
.run(object : ProgressCompletingResultHandler<Vault>() {
|
||||
@ -145,8 +105,7 @@ class BiometricAuthSettingsPresenter @Inject constructor( //
|
||||
}
|
||||
|
||||
fun switchedGeneralBiometricAuthSettings(isChecked: Boolean) {
|
||||
sharedPreferencesHandler //
|
||||
.changeUseBiometricAuthentication(isChecked)
|
||||
sharedPreferencesHandler.changeUseBiometricAuthentication(isChecked)
|
||||
if (isChecked) {
|
||||
loadVaultList()
|
||||
} else {
|
||||
@ -173,32 +132,15 @@ class BiometricAuthSettingsPresenter @Inject constructor( //
|
||||
|
||||
fun onSetupBiometricAuthInSystemClicked() {
|
||||
val openSecuritySettings = Intent(Settings.ACTION_SECURITY_SETTINGS)
|
||||
requestActivityResult(ActivityResultCallbacks.onSetupFingerCompleted(), openSecuritySettings)
|
||||
requestActivityResult(ActivityResultCallbacks.onSetupBiometricAuthInSystemCompleted(), openSecuritySettings)
|
||||
}
|
||||
|
||||
@Callback
|
||||
fun onSetupFingerCompleted(result: ActivityResult?) {
|
||||
fun onSetupBiometricAuthInSystemCompleted(result: ActivityResult?) {
|
||||
view?.showSetupBiometricAuthDialog()
|
||||
}
|
||||
|
||||
fun onBiometricAuthKeyInvalidated(vaultModel: VaultModel?) {
|
||||
removeStoredVaultPasswordsUseCase.run(object : DefaultResultHandler<Void?>() {
|
||||
override fun onSuccess(void: Void?) {
|
||||
view?.showBiometricAuthKeyInvalidatedDialog()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun onUnlockCanceled() {
|
||||
unlockVaultUseCase.cancel()
|
||||
loadVaultList()
|
||||
}
|
||||
|
||||
fun useConfirmationInFaceUnlockBiometricAuthentication(): Boolean {
|
||||
return sharedPreferencesHandler.useConfirmationInFaceUnlockBiometricAuthentication()
|
||||
}
|
||||
|
||||
init {
|
||||
unsubscribeOnDestroy(getVaultListUseCase, saveVaultUseCase, checkVaultPasswordUseCase, removeStoredVaultPasswordsUseCase, unlockVaultUseCase)
|
||||
unsubscribeOnDestroy(getVaultListUseCase, saveVaultUseCase, lockVaultUseCase)
|
||||
}
|
||||
}
|
||||
|
@ -8,15 +8,13 @@ import org.cryptomator.domain.di.PerView
|
||||
import org.cryptomator.domain.usecases.GetDecryptedCloudForVaultUseCase
|
||||
import org.cryptomator.domain.usecases.cloud.*
|
||||
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
|
||||
import org.cryptomator.domain.usecases.vault.RemoveStoredVaultPasswordsUseCase
|
||||
import org.cryptomator.domain.usecases.vault.UnlockVaultUseCase
|
||||
import org.cryptomator.domain.usecases.vault.VaultOrUnlockToken
|
||||
import org.cryptomator.generator.Callback
|
||||
import org.cryptomator.generator.InstanceState
|
||||
import org.cryptomator.presentation.R
|
||||
import org.cryptomator.presentation.exception.ExceptionHandlers
|
||||
import org.cryptomator.presentation.intent.ChooseCloudNodeSettings
|
||||
import org.cryptomator.presentation.intent.Intents
|
||||
import org.cryptomator.presentation.intent.UnlockVaultIntent
|
||||
import org.cryptomator.presentation.model.*
|
||||
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper
|
||||
import org.cryptomator.presentation.model.mappers.ProgressModelMapper
|
||||
@ -27,7 +25,6 @@ import org.cryptomator.presentation.workflow.ActivityResult
|
||||
import org.cryptomator.presentation.workflow.AuthenticationExceptionHandler
|
||||
import org.cryptomator.presentation.workflow.PermissionsResult
|
||||
import org.cryptomator.util.Optional
|
||||
import org.cryptomator.util.SharedPreferencesHandler
|
||||
import org.cryptomator.util.file.FileCacheUtils
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
@ -36,14 +33,11 @@ import timber.log.Timber
|
||||
@PerView
|
||||
class SharedFilesPresenter @Inject constructor( //
|
||||
private val getVaultListUseCase: GetVaultListUseCase, //
|
||||
private val unlockVaultUseCase: UnlockVaultUseCase, //
|
||||
private val getRootFolderUseCase: GetRootFolderUseCase, //
|
||||
private val getDecryptedCloudForVaultUseCase: GetDecryptedCloudForVaultUseCase, //
|
||||
private val uploadFilesUseCase: UploadFilesUseCase, //
|
||||
private val getCloudListUseCase: GetCloudListUseCase, //
|
||||
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
|
||||
private val contentResolverUtil: ContentResolverUtil, //
|
||||
private val sharedPreferencesHandler: SharedPreferencesHandler, //
|
||||
private val fileCacheUtils: FileCacheUtils, //
|
||||
private val authenticationExceptionHandler: AuthenticationExceptionHandler, //
|
||||
private val cloudFolderModelMapper: CloudFolderModelMapper, //
|
||||
@ -128,6 +122,28 @@ class SharedFilesPresenter @Inject constructor( //
|
||||
vaultModel?.let { onCloudOfVaultAuthenticated(it.toVault()) }
|
||||
}
|
||||
|
||||
private fun onCloudOfVaultAuthenticated(authenticatedVault: Vault) {
|
||||
if (authenticatedVault.isUnlocked) {
|
||||
decryptedCloudFor(authenticatedVault)
|
||||
} else {
|
||||
if (!isPaused) {
|
||||
requestActivityResult( //
|
||||
ActivityResultCallbacks.vaultUnlockedSharedFiles(), //
|
||||
Intents.unlockVaultIntent().withVaultModel(VaultModel(authenticatedVault)).withVaultAction(UnlockVaultIntent.VaultAction.UNLOCK))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Callback
|
||||
fun vaultUnlockedSharedFiles(result: ActivityResult) {
|
||||
val cloud = result.intent().getSerializableExtra(SINGLE_RESULT) as Cloud
|
||||
when {
|
||||
result.isResultOk -> rootFolderFor(cloud)
|
||||
else -> TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
private fun decryptedCloudFor(vault: Vault) {
|
||||
getDecryptedCloudForVaultUseCase //
|
||||
.withVault(vault) //
|
||||
@ -172,32 +188,6 @@ class SharedFilesPresenter @Inject constructor( //
|
||||
}
|
||||
}
|
||||
|
||||
fun onUnlockPressed(vaultModel: VaultModel, password: String?) {
|
||||
view?.showProgress(ProgressModel.GENERIC)
|
||||
unlockVaultUseCase //
|
||||
.withVaultOrUnlockToken(VaultOrUnlockToken.from(vaultModel.toVault())) //
|
||||
.andPassword(password) //
|
||||
.run(object : DefaultResultHandler<Cloud>() {
|
||||
override fun onSuccess(cloud: Cloud) {
|
||||
view?.showProgress(ProgressModel.COMPLETED)
|
||||
rootFolderFor(cloud)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
if (!authenticationExceptionHandler.handleAuthenticationException(this@SharedFilesPresenter, e, ActivityResultCallbacks.unlockVaultAfterAuth(vaultModel.toVault(), password))) {
|
||||
showError(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Callback
|
||||
fun unlockVaultAfterAuth(result: ActivityResult, vault: Vault?, password: String?) {
|
||||
val cloud = result.getSingleResult(CloudModel::class.java).toCloud()
|
||||
val vaultWithUpdatedCloud = Vault.aCopyOf(vault).withCloud(cloud).build()
|
||||
onUnlockPressed(VaultModel(vaultWithUpdatedCloud), password)
|
||||
}
|
||||
|
||||
private fun setLocation(location: CloudFolderModel) {
|
||||
this.location = location
|
||||
}
|
||||
@ -372,16 +362,6 @@ class SharedFilesPresenter @Inject constructor( //
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCloudOfVaultAuthenticated(authenticatedVault: Vault) {
|
||||
if (authenticatedVault.isUnlocked) {
|
||||
decryptedCloudFor(authenticatedVault)
|
||||
} else {
|
||||
if (!isPaused) {
|
||||
view?.showEnterPasswordDialog(VaultModel(authenticatedVault))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onChooseLocationPressed() {
|
||||
authenticate(selectedVault)
|
||||
}
|
||||
@ -410,25 +390,6 @@ class SharedFilesPresenter @Inject constructor( //
|
||||
view?.closeDialog()
|
||||
}
|
||||
|
||||
fun onBiometricAuthKeyInvalidated() {
|
||||
removeStoredVaultPasswordsUseCase.run(object : DefaultResultHandler<Void?>() {
|
||||
override fun onFinished() {
|
||||
view?.showBiometricAuthKeyInvalidatedDialog()
|
||||
}
|
||||
})
|
||||
selectedVault?.let {
|
||||
selectedVault = VaultModel(Vault.aCopyOf(it.toVault()).withSavedPassword(null).build())
|
||||
}
|
||||
}
|
||||
|
||||
fun onUnlockCanceled() {
|
||||
unlockVaultUseCase.cancel()
|
||||
}
|
||||
|
||||
fun useConfirmationInFaceUnlockBiometricAuthentication(): Boolean {
|
||||
return sharedPreferencesHandler.useConfirmationInFaceUnlockBiometricAuthentication()
|
||||
}
|
||||
|
||||
private enum class AuthenticationState {
|
||||
CHOOSE_LOCATION, INIT_ROOT
|
||||
}
|
||||
@ -444,11 +405,9 @@ class SharedFilesPresenter @Inject constructor( //
|
||||
init {
|
||||
unsubscribeOnDestroy( //
|
||||
getRootFolderUseCase, //
|
||||
unlockVaultUseCase, //
|
||||
getVaultListUseCase, //
|
||||
getDecryptedCloudForVaultUseCase, //
|
||||
uploadFilesUseCase, //
|
||||
getCloudListUseCase, //
|
||||
removeStoredVaultPasswordsUseCase)
|
||||
getCloudListUseCase)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,357 @@
|
||||
package org.cryptomator.presentation.presenter
|
||||
|
||||
import android.os.Handler
|
||||
import androidx.biometric.BiometricManager
|
||||
import org.cryptomator.domain.Cloud
|
||||
import org.cryptomator.domain.Vault
|
||||
import org.cryptomator.domain.di.PerView
|
||||
import org.cryptomator.domain.exception.NetworkConnectionException
|
||||
import org.cryptomator.domain.exception.authentication.AuthenticationException
|
||||
import org.cryptomator.domain.usecases.vault.ChangePasswordUseCase
|
||||
import org.cryptomator.domain.usecases.vault.LockVaultUseCase
|
||||
import org.cryptomator.domain.usecases.vault.PrepareUnlockUseCase
|
||||
import org.cryptomator.domain.usecases.vault.RemoveStoredVaultPasswordsUseCase
|
||||
import org.cryptomator.domain.usecases.vault.SaveVaultUseCase
|
||||
import org.cryptomator.domain.usecases.vault.UnlockToken
|
||||
import org.cryptomator.domain.usecases.vault.UnlockVaultUseCase
|
||||
import org.cryptomator.domain.usecases.vault.VaultOrUnlockToken
|
||||
import org.cryptomator.generator.Callback
|
||||
import org.cryptomator.generator.InjectIntent
|
||||
import org.cryptomator.presentation.R
|
||||
import org.cryptomator.presentation.exception.ExceptionHandlers
|
||||
import org.cryptomator.presentation.intent.UnlockVaultIntent
|
||||
import org.cryptomator.presentation.model.CloudModel
|
||||
import org.cryptomator.presentation.model.ProgressModel
|
||||
import org.cryptomator.presentation.model.ProgressStateModel
|
||||
import org.cryptomator.presentation.model.VaultModel
|
||||
import org.cryptomator.presentation.ui.activity.view.UnlockVaultView
|
||||
import org.cryptomator.presentation.workflow.ActivityResult
|
||||
import org.cryptomator.presentation.workflow.AuthenticationExceptionHandler
|
||||
import org.cryptomator.util.SharedPreferencesHandler
|
||||
import java.io.Serializable
|
||||
import javax.inject.Inject
|
||||
import timber.log.Timber
|
||||
|
||||
@PerView
|
||||
class UnlockVaultPresenter @Inject constructor(
|
||||
private val changePasswordUseCase: ChangePasswordUseCase,
|
||||
private val lockVaultUseCase: LockVaultUseCase,
|
||||
private val unlockVaultUseCase: UnlockVaultUseCase,
|
||||
private val prepareUnlockUseCase: PrepareUnlockUseCase,
|
||||
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase,
|
||||
private val saveVaultUseCase: SaveVaultUseCase,
|
||||
private val authenticationExceptionHandler: AuthenticationExceptionHandler,
|
||||
private val sharedPreferencesHandler: SharedPreferencesHandler,
|
||||
exceptionMappings: ExceptionHandlers) : Presenter<UnlockVaultView>(exceptionMappings) {
|
||||
|
||||
private var startedUsingPrepareUnlock = false
|
||||
private var retryUnlockHandler: Handler? = null
|
||||
private var pendingUnlock: PendingUnlock? = null
|
||||
|
||||
@InjectIntent
|
||||
lateinit var intent: UnlockVaultIntent
|
||||
|
||||
@Volatile
|
||||
private var running: Boolean = false
|
||||
|
||||
override fun destroyed() {
|
||||
super.destroyed()
|
||||
if (retryUnlockHandler != null) {
|
||||
running = false
|
||||
retryUnlockHandler?.removeCallbacks(null)
|
||||
}
|
||||
}
|
||||
|
||||
fun setup() {
|
||||
when (intent.vaultAction()) {
|
||||
UnlockVaultIntent.VaultAction.ENCRYPT_PASSWORD -> view?.getEncryptedPasswordWithBiometricAuthentication(intent.vaultModel())
|
||||
UnlockVaultIntent.VaultAction.UNLOCK, UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH -> unlockVault(intent.vaultModel())
|
||||
UnlockVaultIntent.VaultAction.CHANGE_PASSWORD -> view?.showChangePasswordDialog(intent.vaultModel())
|
||||
else -> TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
private fun unlockVault(vaultModel: VaultModel) {
|
||||
if (canUseBiometricOn(vaultModel)) {
|
||||
if (startedUsingPrepareUnlock) {
|
||||
startPrepareUnlockUseCase(vaultModel.toVault())
|
||||
}
|
||||
view?.showBiometricDialog(vaultModel)
|
||||
} else {
|
||||
startPrepareUnlockUseCase(vaultModel.toVault())
|
||||
view?.showEnterPasswordDialog(vaultModel)
|
||||
}
|
||||
}
|
||||
|
||||
fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
if (hasFocus) {
|
||||
if (retryUnlockHandler != null) {
|
||||
running = false
|
||||
retryUnlockHandler?.removeCallbacks(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun pendingUnlockFor(vault: Vault): PendingUnlock? {
|
||||
if (pendingUnlock == null) {
|
||||
pendingUnlock = PendingUnlock(vault)
|
||||
}
|
||||
return if (pendingUnlock?.belongsTo(vault) == true) {
|
||||
pendingUnlock
|
||||
} else {
|
||||
PendingUnlock.NO_OP_PENDING_UNLOCK
|
||||
}
|
||||
}
|
||||
|
||||
private fun canUseBiometricOn(vault: VaultModel): Boolean {
|
||||
return vault.password != null && BiometricManager.from(context()).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS
|
||||
}
|
||||
|
||||
fun onUnlockCanceled() {
|
||||
prepareUnlockUseCase.unsubscribe()
|
||||
unlockVaultUseCase.cancel()
|
||||
finish()
|
||||
}
|
||||
|
||||
fun startPrepareUnlockUseCase(vault: Vault) {
|
||||
pendingUnlock = null
|
||||
prepareUnlockUseCase //
|
||||
.withVault(vault) //
|
||||
.run(object : DefaultResultHandler<UnlockToken>() {
|
||||
override fun onSuccess(unlockToken: UnlockToken) {
|
||||
if (!startedUsingPrepareUnlock && vault.password != null) {
|
||||
doUnlock(unlockToken, vault.password)
|
||||
} else {
|
||||
unlockTokenObtained(unlockToken)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
if (e is AuthenticationException) {
|
||||
view?.cancelBasicAuthIfRunning()
|
||||
}
|
||||
if (!authenticationExceptionHandler.handleAuthenticationException(this@UnlockVaultPresenter, e, ActivityResultCallbacks.authenticatedAfterUnlock(vault))) {
|
||||
super.onError(e)
|
||||
if (e is NetworkConnectionException) {
|
||||
running = true
|
||||
retryUnlockHandler = Handler()
|
||||
restartUnlockUseCase(vault)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Callback(dispatchResultOkOnly = false)
|
||||
fun authenticatedAfterUnlock(result: ActivityResult, vault: Vault) {
|
||||
if (result.isResultOk) {
|
||||
val cloud = result.getSingleResult(CloudModel::class.java).toCloud()
|
||||
if (startedUsingPrepareUnlock) {
|
||||
startPrepareUnlockUseCase(Vault.aCopyOf(vault).withCloud(cloud).build())
|
||||
if (view?.stoppedBiometricAuthDuringCloudAuthentication() == true) {
|
||||
view?.showBiometricDialog(VaultModel(vault))
|
||||
}
|
||||
} else {
|
||||
view?.showProgress(ProgressModel.GENERIC)
|
||||
startPrepareUnlockUseCase(vault)
|
||||
}
|
||||
} else {
|
||||
view?.closeDialog()
|
||||
val error = result.getSingleResult(Throwable::class.java)
|
||||
error?.let { showError(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun restartUnlockUseCase(vault: Vault) {
|
||||
retryUnlockHandler?.postDelayed({
|
||||
if (running) {
|
||||
prepareUnlockUseCase //
|
||||
.withVault(vault) //
|
||||
.run(object : DefaultResultHandler<UnlockToken>() {
|
||||
override fun onSuccess(unlockToken: UnlockToken) {
|
||||
if (!startedUsingPrepareUnlock && vault.password != null) {
|
||||
doUnlock(unlockToken, vault.password)
|
||||
} else {
|
||||
unlockTokenObtained(unlockToken)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
if (e is NetworkConnectionException) {
|
||||
restartUnlockUseCase(vault)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
private fun unlockTokenObtained(unlockToken: UnlockToken) {
|
||||
pendingUnlockFor(unlockToken.vault)?.setUnlockToken(unlockToken, this)
|
||||
}
|
||||
|
||||
fun onUnlockClick(vault: VaultModel, password: String?) {
|
||||
view?.showProgress(ProgressModel.GENERIC)
|
||||
pendingUnlockFor(vault.toVault())?.setPassword(password, this)
|
||||
}
|
||||
|
||||
private fun doUnlock(token: UnlockToken, password: String) {
|
||||
unlockVaultUseCase //
|
||||
.withVaultOrUnlockToken(VaultOrUnlockToken.from(token)) //
|
||||
.andPassword(password) //
|
||||
.run(object : DefaultResultHandler<Cloud>() {
|
||||
override fun onSuccess(cloud: Cloud) {
|
||||
when (intent.vaultAction()) {
|
||||
UnlockVaultIntent.VaultAction.ENCRYPT_PASSWORD, UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH -> {
|
||||
handleUnlockVaultSuccess(token.vault, cloud, password)
|
||||
}
|
||||
UnlockVaultIntent.VaultAction.UNLOCK -> finishWithResult(cloud)
|
||||
else -> TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun handleUnlockVaultSuccess(vault: Vault, cloud: Cloud, password: String) {
|
||||
lockVaultUseCase.withVault(vault).run(object : DefaultResultHandler<Vault>() {
|
||||
override fun onFinished() {
|
||||
finishWithResultAndExtra(cloud, PASSWORD, password)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fun startedUsingPrepareUnlock(): Boolean {
|
||||
return startedUsingPrepareUnlock
|
||||
}
|
||||
|
||||
fun useConfirmationInFaceUnlockBiometricAuthentication(): Boolean {
|
||||
return sharedPreferencesHandler.useConfirmationInFaceUnlockBiometricAuthentication()
|
||||
}
|
||||
|
||||
fun onBiometricKeyInvalidated() {
|
||||
removeStoredVaultPasswordsUseCase.run(object : DefaultResultHandler<Void?>() {
|
||||
override fun onSuccess(void: Void?) {
|
||||
view?.showBiometricAuthKeyInvalidatedDialog()
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Timber.tag("VaultListPresenter").e(e, "Error while removing vault passwords")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun onBiometricAuthenticationSucceeded(vaultModel: VaultModel) {
|
||||
when (intent.vaultAction()) {
|
||||
UnlockVaultIntent.VaultAction.ENCRYPT_PASSWORD -> finishWithResult(vaultModel)
|
||||
UnlockVaultIntent.VaultAction.UNLOCK, UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH -> {
|
||||
if (startedUsingPrepareUnlock) {
|
||||
onUnlockClick(vaultModel, vaultModel.password)
|
||||
} else {
|
||||
view?.showProgress(ProgressModel.GENERIC)
|
||||
startPrepareUnlockUseCase(vaultModel.toVault())
|
||||
}
|
||||
}
|
||||
UnlockVaultIntent.VaultAction.CHANGE_PASSWORD -> {
|
||||
saveVaultUseCase //
|
||||
.withVault(vaultModel.toVault()) //
|
||||
.run(object : DefaultResultHandler<Vault>() {
|
||||
override fun onSuccess(vault: Vault) {
|
||||
finishWithResult(vaultModel)
|
||||
}
|
||||
})
|
||||
}
|
||||
else -> TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
fun onChangePasswordClick(vaultModel: VaultModel, oldPassword: String, newPassword: String) {
|
||||
view?.showProgress(ProgressModel(ProgressStateModel.CHANGING_PASSWORD))
|
||||
changePasswordUseCase.withVault(vaultModel.toVault()) //
|
||||
.andOldPassword(oldPassword) //
|
||||
.andNewPassword(newPassword) //
|
||||
.run(object : DefaultResultHandler<Void?>() {
|
||||
override fun onSuccess(void: Void?) {
|
||||
view?.showProgress(ProgressModel.COMPLETED)
|
||||
view?.showMessage(R.string.screen_vault_list_change_password_successful)
|
||||
if (canUseBiometricOn(vaultModel)) {
|
||||
view?.getEncryptedPasswordWithBiometricAuthentication(VaultModel( //
|
||||
Vault.aCopyOf(vaultModel.toVault()) //
|
||||
.withSavedPassword(newPassword) //
|
||||
.build()))
|
||||
} else {
|
||||
finishWithResult(vaultModel)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
if (!authenticationExceptionHandler.handleAuthenticationException( //
|
||||
this@UnlockVaultPresenter, e, //
|
||||
ActivityResultCallbacks.changePasswordAfterAuthentication(vaultModel.toVault(), oldPassword, newPassword))) {
|
||||
showError(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Callback
|
||||
fun changePasswordAfterAuthentication(result: ActivityResult, vault: Vault, oldPassword: String, newPassword: String) {
|
||||
val cloud = result.getSingleResult(CloudModel::class.java).toCloud()
|
||||
val vaultWithUpdatedCloud = Vault.aCopyOf(vault).withCloud(cloud).build()
|
||||
onChangePasswordClick(VaultModel(vaultWithUpdatedCloud), oldPassword, newPassword)
|
||||
}
|
||||
|
||||
fun saveVaultAfterChangePasswordButFailedBiometricAuth(vault: Vault) {
|
||||
Timber.tag("UnlockVaultPresenter").e("Save vault without password because biometric auth failed after changing vault password")
|
||||
saveVaultUseCase //
|
||||
.withVault(vault) //
|
||||
.run(object : DefaultResultHandler<Vault>() {
|
||||
override fun onSuccess(vault: Vault) {
|
||||
finishWithResult(vault)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private open class PendingUnlock(private val vault: Vault?) : Serializable {
|
||||
|
||||
private var unlockToken: UnlockToken? = null
|
||||
private var password: String? = null
|
||||
|
||||
fun setUnlockToken(unlockToken: UnlockToken?, presenter: UnlockVaultPresenter) {
|
||||
this.unlockToken = unlockToken
|
||||
continueIfComplete(presenter)
|
||||
}
|
||||
|
||||
fun setPassword(password: String?, presenter: UnlockVaultPresenter) {
|
||||
this.password = password
|
||||
continueIfComplete(presenter)
|
||||
}
|
||||
|
||||
open fun continueIfComplete(presenter: UnlockVaultPresenter) {
|
||||
unlockToken?.let { token -> password?.let { password -> presenter.doUnlock(token, password) } }
|
||||
}
|
||||
|
||||
fun belongsTo(vault: Vault): Boolean {
|
||||
return vault == this.vault
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val NO_OP_PENDING_UNLOCK: PendingUnlock = object : PendingUnlock(null) {
|
||||
override fun continueIfComplete(presenter: UnlockVaultPresenter) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val PASSWORD = "password"
|
||||
}
|
||||
|
||||
init {
|
||||
unsubscribeOnDestroy(changePasswordUseCase, lockVaultUseCase, unlockVaultUseCase, prepareUnlockUseCase, removeStoredVaultPasswordsUseCase, saveVaultUseCase)
|
||||
}
|
||||
|
||||
}
|
@ -6,16 +6,12 @@ import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import androidx.biometric.BiometricManager
|
||||
import org.cryptomator.data.cloud.crypto.CryptoCloud
|
||||
import org.cryptomator.data.util.NetworkConnectionCheck
|
||||
import org.cryptomator.domain.Cloud
|
||||
import org.cryptomator.domain.CloudFolder
|
||||
import org.cryptomator.domain.Vault
|
||||
import org.cryptomator.domain.di.PerView
|
||||
import org.cryptomator.domain.exception.NetworkConnectionException
|
||||
import org.cryptomator.domain.exception.authentication.AuthenticationException
|
||||
import org.cryptomator.domain.exception.license.LicenseNotValidException
|
||||
import org.cryptomator.domain.exception.update.SSLHandshakePreAndroid5UpdateCheckException
|
||||
import org.cryptomator.domain.usecases.DoLicenseCheckUseCase
|
||||
@ -26,27 +22,21 @@ import org.cryptomator.domain.usecases.LicenseCheck
|
||||
import org.cryptomator.domain.usecases.NoOpResultHandler
|
||||
import org.cryptomator.domain.usecases.UpdateCheck
|
||||
import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase
|
||||
import org.cryptomator.domain.usecases.vault.ChangePasswordUseCase
|
||||
import org.cryptomator.domain.usecases.vault.DeleteVaultUseCase
|
||||
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
|
||||
import org.cryptomator.domain.usecases.vault.LockVaultUseCase
|
||||
import org.cryptomator.domain.usecases.vault.MoveVaultPositionUseCase
|
||||
import org.cryptomator.domain.usecases.vault.PrepareUnlockUseCase
|
||||
import org.cryptomator.domain.usecases.vault.RemoveStoredVaultPasswordsUseCase
|
||||
import org.cryptomator.domain.usecases.vault.RenameVaultUseCase
|
||||
import org.cryptomator.domain.usecases.vault.SaveVaultUseCase
|
||||
import org.cryptomator.domain.usecases.vault.UnlockToken
|
||||
import org.cryptomator.domain.usecases.vault.UnlockVaultUseCase
|
||||
import org.cryptomator.domain.usecases.vault.VaultOrUnlockToken
|
||||
import org.cryptomator.generator.Callback
|
||||
import org.cryptomator.presentation.BuildConfig
|
||||
import org.cryptomator.presentation.R
|
||||
import org.cryptomator.presentation.exception.ExceptionHandlers
|
||||
import org.cryptomator.presentation.intent.Intents
|
||||
import org.cryptomator.presentation.intent.UnlockVaultIntent
|
||||
import org.cryptomator.presentation.model.CloudModel
|
||||
import org.cryptomator.presentation.model.CloudTypeModel
|
||||
import org.cryptomator.presentation.model.ProgressModel
|
||||
import org.cryptomator.presentation.model.ProgressStateModel
|
||||
import org.cryptomator.presentation.model.VaultModel
|
||||
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper
|
||||
import org.cryptomator.presentation.service.AutoUploadService
|
||||
@ -65,7 +55,6 @@ import org.cryptomator.presentation.workflow.CreateNewVaultWorkflow
|
||||
import org.cryptomator.presentation.workflow.Workflow
|
||||
import org.cryptomator.util.Optional
|
||||
import org.cryptomator.util.SharedPreferencesHandler
|
||||
import java.io.Serializable
|
||||
import javax.inject.Inject
|
||||
import timber.log.Timber
|
||||
|
||||
@ -76,15 +65,11 @@ class VaultListPresenter @Inject constructor( //
|
||||
private val renameVaultUseCase: RenameVaultUseCase, //
|
||||
private val lockVaultUseCase: LockVaultUseCase, //
|
||||
private val getDecryptedCloudForVaultUseCase: GetDecryptedCloudForVaultUseCase, //
|
||||
private val prepareUnlockUseCase: PrepareUnlockUseCase, //
|
||||
private val unlockVaultUseCase: UnlockVaultUseCase, //
|
||||
private val getRootFolderUseCase: GetRootFolderUseCase, //
|
||||
private val addExistingVaultWorkflow: AddExistingVaultWorkflow, //
|
||||
private val createNewVaultWorkflow: CreateNewVaultWorkflow, //
|
||||
private val saveVaultUseCase: SaveVaultUseCase, //
|
||||
private val moveVaultPositionUseCase: MoveVaultPositionUseCase, //
|
||||
private val changePasswordUseCase: ChangePasswordUseCase, //
|
||||
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
|
||||
private val licenseCheckUseCase: DoLicenseCheckUseCase, //
|
||||
private val updateCheckUseCase: DoUpdateCheckUseCase, //
|
||||
private val updateUseCase: DoUpdateUseCase, //
|
||||
@ -96,31 +81,14 @@ class VaultListPresenter @Inject constructor( //
|
||||
exceptionMappings: ExceptionHandlers) : Presenter<VaultListView>(exceptionMappings) {
|
||||
|
||||
private var vaultAction: VaultAction? = null
|
||||
private var changedVaultPassword = false
|
||||
private var startedUsingPrepareUnlock = false
|
||||
private var retryUnlockHandler: Handler? = null
|
||||
|
||||
@Volatile
|
||||
private var running = false
|
||||
override fun workflows(): Iterable<Workflow<*>> {
|
||||
return listOf(addExistingVaultWorkflow, createNewVaultWorkflow)
|
||||
}
|
||||
|
||||
override fun destroyed() {
|
||||
super.destroyed()
|
||||
if (retryUnlockHandler != null) {
|
||||
running = false
|
||||
retryUnlockHandler?.removeCallbacks(null)
|
||||
}
|
||||
}
|
||||
|
||||
fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
if (hasFocus) {
|
||||
loadVaultList()
|
||||
if (retryUnlockHandler != null) {
|
||||
running = false
|
||||
retryUnlockHandler?.removeCallbacks(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,11 +225,6 @@ class VaultListPresenter @Inject constructor( //
|
||||
renameVault(VaultModel(vaultWithUpdatedCloud), newVaultName)
|
||||
}
|
||||
|
||||
fun onUnlockCanceled() {
|
||||
prepareUnlockUseCase.unsubscribe()
|
||||
unlockVaultUseCase.cancel()
|
||||
}
|
||||
|
||||
private fun browseFilesOf(vault: VaultModel) {
|
||||
getDecryptedCloudForVaultUseCase //
|
||||
.withVault(vault.toVault()) //
|
||||
@ -318,7 +281,6 @@ class VaultListPresenter @Inject constructor( //
|
||||
}
|
||||
|
||||
fun onVaultClicked(vault: VaultModel) {
|
||||
startedUsingPrepareUnlock = sharedPreferencesHandler.backgroundUnlockPreparation()
|
||||
startVaultAction(vault, VaultAction.UNLOCK)
|
||||
}
|
||||
|
||||
@ -378,7 +340,6 @@ class VaultListPresenter @Inject constructor( //
|
||||
when (vaultAction) {
|
||||
VaultAction.UNLOCK -> requireUserAuthentication(authenticatedVaultModel)
|
||||
VaultAction.RENAME -> view?.showRenameDialog(authenticatedVaultModel)
|
||||
VaultAction.CHANGE_PASSWORD -> view?.showChangePasswordDialog(authenticatedVaultModel)
|
||||
}
|
||||
vaultAction = null
|
||||
}
|
||||
@ -387,83 +348,22 @@ class VaultListPresenter @Inject constructor( //
|
||||
view?.addOrUpdateVault(authenticatedVault)
|
||||
if (authenticatedVault.isLocked) {
|
||||
if (!isPaused) {
|
||||
if (canUseBiometricOn(authenticatedVault)) {
|
||||
if (startedUsingPrepareUnlock) {
|
||||
startPrepareUnlockUseCase(authenticatedVault.toVault())
|
||||
}
|
||||
view?.showBiometricDialog(authenticatedVault)
|
||||
} else {
|
||||
startPrepareUnlockUseCase(authenticatedVault.toVault())
|
||||
view?.showEnterPasswordDialog(authenticatedVault)
|
||||
}
|
||||
requestActivityResult( //
|
||||
ActivityResultCallbacks.vaultUnlockedVaultList(), //
|
||||
Intents.unlockVaultIntent().withVaultModel(authenticatedVault).withVaultAction(UnlockVaultIntent.VaultAction.UNLOCK))
|
||||
}
|
||||
} else {
|
||||
browseFilesOf(authenticatedVault)
|
||||
}
|
||||
}
|
||||
|
||||
fun startPrepareUnlockUseCase(vault: Vault) {
|
||||
pendingUnlock = null
|
||||
prepareUnlockUseCase //
|
||||
.withVault(vault) //
|
||||
.run(object : DefaultResultHandler<UnlockToken>() {
|
||||
override fun onSuccess(unlockToken: UnlockToken) {
|
||||
if (!startedUsingPrepareUnlock && vault.password != null) {
|
||||
doUnlock(unlockToken, vault.password)
|
||||
} else {
|
||||
unlockTokenObtained(unlockToken)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
if (e is AuthenticationException) {
|
||||
view?.cancelBasicAuthIfRunning()
|
||||
}
|
||||
if (!authenticationExceptionHandler.handleAuthenticationException(this@VaultListPresenter, e, ActivityResultCallbacks.authenticatedAfterUnlock(vault))) {
|
||||
super.onError(e)
|
||||
if (e is NetworkConnectionException) {
|
||||
running = true
|
||||
retryUnlockHandler = Handler()
|
||||
restartUnlockUseCase(vault)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun restartUnlockUseCase(vault: Vault) {
|
||||
retryUnlockHandler?.postDelayed({
|
||||
if (running) {
|
||||
prepareUnlockUseCase //
|
||||
.withVault(vault) //
|
||||
.run(object : DefaultResultHandler<UnlockToken>() {
|
||||
override fun onSuccess(unlockToken: UnlockToken) {
|
||||
if (!startedUsingPrepareUnlock && vault.password != null) {
|
||||
doUnlock(unlockToken, vault.password)
|
||||
} else {
|
||||
unlockTokenObtained(unlockToken)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
if (e is NetworkConnectionException) {
|
||||
restartUnlockUseCase(vault)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
private fun doUnlock(token: UnlockToken, password: String) {
|
||||
unlockVaultUseCase //
|
||||
.withVaultOrUnlockToken(VaultOrUnlockToken.from(token)) //
|
||||
.andPassword(password) //
|
||||
.run(object : DefaultResultHandler<Cloud>() {
|
||||
override fun onSuccess(cloud: Cloud) {
|
||||
navigateToVaultContent(cloud)
|
||||
}
|
||||
})
|
||||
@Callback
|
||||
fun vaultUnlockedVaultList(result: ActivityResult) {
|
||||
val cloud = result.intent().getSerializableExtra(SINGLE_RESULT) as Cloud
|
||||
when {
|
||||
result.isResultOk -> navigateToVaultContent(cloud)
|
||||
else -> TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToVaultContent(cloud: Cloud) {
|
||||
@ -488,51 +388,6 @@ class VaultListPresenter @Inject constructor( //
|
||||
} else false
|
||||
}
|
||||
|
||||
private fun unlockTokenObtained(unlockToken: UnlockToken) {
|
||||
pendingUnlockFor(unlockToken.vault)?.setUnlockToken(unlockToken, this)
|
||||
}
|
||||
|
||||
fun onUnlockClick(vault: VaultModel, password: String?) {
|
||||
view?.showProgress(ProgressModel.GENERIC)
|
||||
pendingUnlockFor(vault.toVault())?.setPassword(password, this)
|
||||
}
|
||||
|
||||
private var pendingUnlock: PendingUnlock? = null
|
||||
private fun pendingUnlockFor(vault: Vault): PendingUnlock? {
|
||||
if (pendingUnlock == null) {
|
||||
pendingUnlock = PendingUnlock(vault)
|
||||
}
|
||||
return if (pendingUnlock?.belongsTo(vault) == true) {
|
||||
pendingUnlock
|
||||
} else {
|
||||
PendingUnlock.NO_OP_PENDING_UNLOCK
|
||||
}
|
||||
}
|
||||
|
||||
@Callback(dispatchResultOkOnly = false)
|
||||
fun authenticatedAfterUnlock(result: ActivityResult, vault: Vault) {
|
||||
if (result.isResultOk) {
|
||||
val cloud = result.getSingleResult(CloudModel::class.java).toCloud()
|
||||
if (startedUsingPrepareUnlock) {
|
||||
startPrepareUnlockUseCase(Vault.aCopyOf(vault).withCloud(cloud).build())
|
||||
if (view?.stoppedBiometricAuthDuringCloudAuthentication() == true) {
|
||||
view?.showBiometricDialog(VaultModel(vault))
|
||||
}
|
||||
} else {
|
||||
view?.showProgress(ProgressModel.GENERIC)
|
||||
startPrepareUnlockUseCase(vault)
|
||||
}
|
||||
} else {
|
||||
view?.closeDialog()
|
||||
val error = result.getSingleResult(Throwable::class.java)
|
||||
error?.let { showError(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun canUseBiometricOn(vault: VaultModel): Boolean {
|
||||
return vault.password != null && BiometricManager.from(context()).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS
|
||||
}
|
||||
|
||||
fun onAddExistingVault() {
|
||||
addExistingVaultWorkflow.start()
|
||||
}
|
||||
@ -548,60 +403,11 @@ class VaultListPresenter @Inject constructor( //
|
||||
}
|
||||
|
||||
fun onChangePasswordClicked(vaultModel: VaultModel) {
|
||||
startVaultAction(vaultModel, VaultAction.CHANGE_PASSWORD)
|
||||
}
|
||||
|
||||
fun onChangePasswordClicked(vaultModel: VaultModel, oldPassword: String?, newPassword: String?) {
|
||||
view?.showProgress(ProgressModel(ProgressStateModel.CHANGING_PASSWORD))
|
||||
changePasswordUseCase.withVault(vaultModel.toVault()) //
|
||||
.andOldPassword(oldPassword) //
|
||||
.andNewPassword(newPassword) //
|
||||
.run(object : DefaultResultHandler<Void?>() {
|
||||
override fun onSuccess(void: Void?) {
|
||||
view?.showProgress(ProgressModel.COMPLETED)
|
||||
view?.showMessage(R.string.screen_vault_list_change_password_successful)
|
||||
if (canUseBiometricOn(vaultModel)) {
|
||||
changedVaultPassword = true
|
||||
view?.getEncryptedPasswordWithBiometricAuthentication(VaultModel( //
|
||||
Vault.aCopyOf(vaultModel.toVault()) //
|
||||
.withSavedPassword(newPassword) //
|
||||
.build()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
if (!authenticationExceptionHandler.handleAuthenticationException( //
|
||||
this@VaultListPresenter, e, //
|
||||
ActivityResultCallbacks.changePasswordAfterAuthentication(vaultModel.toVault(), oldPassword, newPassword))) {
|
||||
showError(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Callback
|
||||
fun changePasswordAfterAuthentication(result: ActivityResult, vault: Vault?, oldPassword: String?, newPassword: String?) {
|
||||
val cloud = result.getSingleResult(CloudModel::class.java).toCloud()
|
||||
val vaultWithUpdatedCloud = Vault.aCopyOf(vault).withCloud(cloud).build()
|
||||
onChangePasswordClicked(VaultModel(vaultWithUpdatedCloud), oldPassword, newPassword)
|
||||
}
|
||||
|
||||
private fun save(vaultModel: VaultModel) {
|
||||
saveVaultUseCase //
|
||||
.withVault(vaultModel.toVault()) //
|
||||
.run(DefaultResultHandler())
|
||||
}
|
||||
|
||||
fun onBiometricKeyInvalidated() {
|
||||
removeStoredVaultPasswordsUseCase.run(object : DefaultResultHandler<Void?>() {
|
||||
override fun onSuccess(void: Void?) {
|
||||
view?.showBiometricAuthKeyInvalidatedDialog()
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Timber.tag("VaultListPresenter").e(e, "Error while removing vault passwords")
|
||||
}
|
||||
})
|
||||
Intents
|
||||
.unlockVaultIntent()
|
||||
.withVaultModel(vaultModel)
|
||||
.withVaultAction(UnlockVaultIntent.VaultAction.CHANGE_PASSWORD)
|
||||
.startActivity(this)
|
||||
}
|
||||
|
||||
fun onVaultSettingsClicked(vaultModel: VaultModel) {
|
||||
@ -654,26 +460,8 @@ class VaultListPresenter @Inject constructor( //
|
||||
})
|
||||
}
|
||||
|
||||
fun onBiometricAuthenticationSucceeded(vaultModel: VaultModel) {
|
||||
if (changedVaultPassword) {
|
||||
changedVaultPassword = false
|
||||
save(vaultModel)
|
||||
} else {
|
||||
if (startedUsingPrepareUnlock) {
|
||||
onUnlockClick(vaultModel, vaultModel.password)
|
||||
} else {
|
||||
view?.showProgress(ProgressModel.GENERIC)
|
||||
startPrepareUnlockUseCase(vaultModel.toVault())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun useConfirmationInFaceUnlockBiometricAuthentication(): Boolean {
|
||||
return sharedPreferencesHandler.useConfirmationInFaceUnlockBiometricAuthentication()
|
||||
}
|
||||
|
||||
private enum class VaultAction {
|
||||
UNLOCK, RENAME, CHANGE_PASSWORD
|
||||
UNLOCK, RENAME
|
||||
}
|
||||
|
||||
fun installUpdate() {
|
||||
@ -698,43 +486,6 @@ class VaultListPresenter @Inject constructor( //
|
||||
})
|
||||
}
|
||||
|
||||
fun startedUsingPrepareUnlock(): Boolean {
|
||||
return startedUsingPrepareUnlock
|
||||
}
|
||||
|
||||
private open class PendingUnlock(private val vault: Vault?) : Serializable {
|
||||
|
||||
private var unlockToken: UnlockToken? = null
|
||||
private var password: String? = null
|
||||
|
||||
fun setUnlockToken(unlockToken: UnlockToken?, presenter: VaultListPresenter) {
|
||||
this.unlockToken = unlockToken
|
||||
continueIfComplete(presenter)
|
||||
}
|
||||
|
||||
fun setPassword(password: String?, presenter: VaultListPresenter) {
|
||||
this.password = password
|
||||
continueIfComplete(presenter)
|
||||
}
|
||||
|
||||
open fun continueIfComplete(presenter: VaultListPresenter) {
|
||||
unlockToken?.let { token -> password?.let { password -> presenter.doUnlock(token, password) } }
|
||||
}
|
||||
|
||||
fun belongsTo(vault: Vault): Boolean {
|
||||
return vault == this.vault
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val NO_OP_PENDING_UNLOCK: PendingUnlock = object : PendingUnlock(null) {
|
||||
override fun continueIfComplete(presenter: VaultListPresenter) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
unsubscribeOnDestroy( //
|
||||
deleteVaultUseCase, //
|
||||
@ -743,9 +494,6 @@ class VaultListPresenter @Inject constructor( //
|
||||
getVaultListUseCase, //
|
||||
saveVaultUseCase, //
|
||||
moveVaultPositionUseCase, //
|
||||
removeStoredVaultPasswordsUseCase, //
|
||||
unlockVaultUseCase, //
|
||||
prepareUnlockUseCase, //
|
||||
licenseCheckUseCase, //
|
||||
updateCheckUseCase, //
|
||||
updateUseCase)
|
||||
|
@ -1,7 +1,5 @@
|
||||
package org.cryptomator.presentation.ui.activity
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.cryptomator.generator.Activity
|
||||
import org.cryptomator.presentation.R
|
||||
@ -9,20 +7,15 @@ import org.cryptomator.presentation.model.CloudFolderModel
|
||||
import org.cryptomator.presentation.model.VaultModel
|
||||
import org.cryptomator.presentation.presenter.AutoUploadChooseVaultPresenter
|
||||
import org.cryptomator.presentation.ui.activity.view.AutoUploadChooseVaultView
|
||||
import org.cryptomator.presentation.ui.dialog.BiometricAuthKeyInvalidatedDialog
|
||||
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
|
||||
import org.cryptomator.presentation.ui.dialog.NotEnoughVaultsDialog
|
||||
import org.cryptomator.presentation.ui.fragment.AutoUploadChooseVaultFragment
|
||||
import org.cryptomator.presentation.util.BiometricAuthentication
|
||||
import javax.inject.Inject
|
||||
import kotlinx.android.synthetic.main.toolbar_layout.toolbar
|
||||
|
||||
@Activity
|
||||
class AutoUploadChooseVaultActivity : BaseActivity(), //
|
||||
AutoUploadChooseVaultView, //
|
||||
NotEnoughVaultsDialog.Callback, //
|
||||
EnterPasswordDialog.Callback,
|
||||
BiometricAuthentication.Callback {
|
||||
NotEnoughVaultsDialog.Callback {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AutoUploadChooseVaultPresenter
|
||||
@ -40,7 +33,7 @@ class AutoUploadChooseVaultActivity : BaseActivity(), //
|
||||
}
|
||||
}
|
||||
|
||||
override fun createFragment(): Fragment? = AutoUploadChooseVaultFragment()
|
||||
override fun createFragment(): Fragment = AutoUploadChooseVaultFragment()
|
||||
|
||||
|
||||
override fun displayVaults(vaults: List<VaultModel>) {
|
||||
@ -69,41 +62,5 @@ class AutoUploadChooseVaultActivity : BaseActivity(), //
|
||||
autoUploadChooseVaultFragment().showChosenLocation(location)
|
||||
}
|
||||
|
||||
override fun onUnlockCanceled() {
|
||||
presenter.onUnlockCanceled()
|
||||
}
|
||||
|
||||
override fun onUnlockClick(vaultModel: VaultModel, password: String) {
|
||||
presenter.onUnlockPressed(vaultModel, password)
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
override fun showEnterPasswordDialog(vaultModel: VaultModel) {
|
||||
if (vaultWithBiometricAuthEnabled(vaultModel)) {
|
||||
BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.DECRYPT, presenter.useConfirmationInFaceUnlockBiometricAuthentication())
|
||||
.startListening(autoUploadChooseVaultFragment(), vaultModel)
|
||||
} else {
|
||||
showDialog(EnterPasswordDialog.newInstance(vaultModel))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBiometricAuthenticated(vault: VaultModel) {
|
||||
presenter.onUnlockPressed(vault, vault.password)
|
||||
}
|
||||
|
||||
override fun onBiometricAuthenticationFailed(vault: VaultModel) {
|
||||
showDialog(EnterPasswordDialog.newInstance(vault))
|
||||
}
|
||||
|
||||
override fun onBiometricKeyInvalidated(vault: VaultModel) {
|
||||
presenter.onBiometricKeyInvalidated(vault)
|
||||
}
|
||||
|
||||
override fun showBiometricAuthKeyInvalidatedDialog() {
|
||||
showDialog(BiometricAuthKeyInvalidatedDialog.newInstance())
|
||||
}
|
||||
|
||||
private fun vaultWithBiometricAuthEnabled(vault: VaultModel): Boolean = vault.password != null
|
||||
|
||||
private fun autoUploadChooseVaultFragment(): AutoUploadChooseVaultFragment = getCurrentFragment(R.id.fragmentContainer) as AutoUploadChooseVaultFragment
|
||||
}
|
||||
|
@ -1,28 +1,20 @@
|
||||
package org.cryptomator.presentation.ui.activity
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.cryptomator.domain.Vault
|
||||
import org.cryptomator.generator.Activity
|
||||
import org.cryptomator.presentation.R
|
||||
import org.cryptomator.presentation.model.VaultModel
|
||||
import org.cryptomator.presentation.presenter.BiometricAuthSettingsPresenter
|
||||
import org.cryptomator.presentation.ui.activity.view.BiometricAuthSettingsView
|
||||
import org.cryptomator.presentation.ui.dialog.BiometricAuthKeyInvalidatedDialog
|
||||
import org.cryptomator.presentation.ui.dialog.EnrollSystemBiometricDialog
|
||||
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
|
||||
import org.cryptomator.presentation.ui.fragment.BiometricAuthSettingsFragment
|
||||
import org.cryptomator.presentation.util.BiometricAuthentication
|
||||
import javax.inject.Inject
|
||||
import kotlinx.android.synthetic.main.toolbar_layout.toolbar
|
||||
|
||||
@Activity
|
||||
class BiometricAuthSettingsActivity : BaseActivity(), //
|
||||
EnterPasswordDialog.Callback, //
|
||||
BiometricAuthSettingsView, //
|
||||
BiometricAuthentication.Callback, //
|
||||
EnrollSystemBiometricDialog.Callback {
|
||||
|
||||
@Inject
|
||||
@ -42,10 +34,6 @@ class BiometricAuthSettingsActivity : BaseActivity(), //
|
||||
}
|
||||
}
|
||||
|
||||
override fun showBiometricAuthKeyInvalidatedDialog() {
|
||||
showDialog(BiometricAuthKeyInvalidatedDialog.newInstance())
|
||||
}
|
||||
|
||||
override fun createFragment(): Fragment? = BiometricAuthSettingsFragment()
|
||||
|
||||
override fun renderVaultList(vaultModelCollection: List<VaultModel>) {
|
||||
@ -56,30 +44,6 @@ class BiometricAuthSettingsActivity : BaseActivity(), //
|
||||
biometricAuthSettingsFragment().clearVaultList()
|
||||
}
|
||||
|
||||
override fun showEnterPasswordDialog(vaultModel: VaultModel) {
|
||||
showDialog(EnterPasswordDialog.newInstance(vaultModel))
|
||||
}
|
||||
|
||||
override fun onUnlockClick(vaultModel: VaultModel, password: String) {
|
||||
val vaultModelWithSavedPassword = VaultModel( //
|
||||
Vault //
|
||||
.aCopyOf(vaultModel.toVault()) //
|
||||
.withSavedPassword(password) //
|
||||
.build())
|
||||
|
||||
presenter.verifyPassword(vaultModelWithSavedPassword)
|
||||
}
|
||||
|
||||
override fun onUnlockCanceled() {
|
||||
presenter.onUnlockCanceled()
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
override fun showBiometricAuthenticationDialog(vaultModel: VaultModel) {
|
||||
BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.ENCRYPT, presenter.useConfirmationInFaceUnlockBiometricAuthentication())
|
||||
.startListening(biometricAuthSettingsFragment(), vaultModel)
|
||||
}
|
||||
|
||||
private fun biometricAuthSettingsFragment(): BiometricAuthSettingsFragment = getCurrentFragment(R.id.fragmentContainer) as BiometricAuthSettingsFragment
|
||||
|
||||
override fun onSetupBiometricAuthInSystemClicked() {
|
||||
@ -89,17 +53,4 @@ class BiometricAuthSettingsActivity : BaseActivity(), //
|
||||
override fun onCancelSetupBiometricAuthInSystemClicked() {
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onBiometricAuthenticated(vault: VaultModel) {
|
||||
presenter.saveVault(vault.toVault())
|
||||
}
|
||||
|
||||
override fun onBiometricAuthenticationFailed(vault: VaultModel) {
|
||||
showError(getString(R.string.error_biometric_auth_aborted))
|
||||
biometricAuthSettingsFragment().addOrUpdateVault(vault)
|
||||
}
|
||||
|
||||
override fun onBiometricKeyInvalidated(vault: VaultModel) {
|
||||
presenter.onBiometricAuthKeyInvalidated(vault)
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ import android.content.Intent
|
||||
import android.content.Intent.ACTION_SEND
|
||||
import android.content.Intent.ACTION_SEND_MULTIPLE
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.cryptomator.generator.Activity
|
||||
import org.cryptomator.presentation.R
|
||||
@ -16,13 +14,10 @@ import org.cryptomator.presentation.model.SharedFileModel
|
||||
import org.cryptomator.presentation.model.VaultModel
|
||||
import org.cryptomator.presentation.presenter.SharedFilesPresenter
|
||||
import org.cryptomator.presentation.ui.activity.view.SharedFilesView
|
||||
import org.cryptomator.presentation.ui.dialog.BiometricAuthKeyInvalidatedDialog
|
||||
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
|
||||
import org.cryptomator.presentation.ui.dialog.NotEnoughVaultsDialog
|
||||
import org.cryptomator.presentation.ui.dialog.ReplaceDialog
|
||||
import org.cryptomator.presentation.ui.dialog.UploadCloudFileDialog
|
||||
import org.cryptomator.presentation.ui.fragment.SharedFilesFragment
|
||||
import org.cryptomator.presentation.util.BiometricAuthentication
|
||||
import java.lang.String.format
|
||||
import java.util.ArrayList
|
||||
import javax.inject.Inject
|
||||
@ -32,8 +27,6 @@ import timber.log.Timber
|
||||
@Activity
|
||||
class SharedFilesActivity : BaseActivity(), //
|
||||
SharedFilesView, //
|
||||
EnterPasswordDialog.Callback, //
|
||||
BiometricAuthentication.Callback, //
|
||||
ReplaceDialog.Callback, //
|
||||
NotEnoughVaultsDialog.Callback, //
|
||||
UploadCloudFileDialog.Callback {
|
||||
@ -126,7 +119,7 @@ class SharedFilesActivity : BaseActivity(), //
|
||||
}
|
||||
}
|
||||
|
||||
override fun createFragment(): Fragment? = SharedFilesFragment()
|
||||
override fun createFragment(): Fragment = SharedFilesFragment()
|
||||
|
||||
public override fun onMenuItemSelected(itemId: Int): Boolean = when (itemId) {
|
||||
android.R.id.home -> {
|
||||
@ -150,16 +143,6 @@ class SharedFilesActivity : BaseActivity(), //
|
||||
|
||||
private fun sharedFilesFragment(): SharedFilesFragment = getCurrentFragment(R.id.fragmentContainer) as SharedFilesFragment
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
override fun showEnterPasswordDialog(vault: VaultModel) {
|
||||
if (vaultWithBiometricAuthEnabled(vault)) {
|
||||
BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.DECRYPT, presenter.useConfirmationInFaceUnlockBiometricAuthentication())
|
||||
.startListening(sharedFilesFragment(), vault)
|
||||
} else {
|
||||
showDialog(EnterPasswordDialog.newInstance(vault))
|
||||
}
|
||||
}
|
||||
|
||||
override fun showReplaceDialog(existingFiles: List<String>, size: Int) {
|
||||
ReplaceDialog.withContext(this).show(existingFiles, size)
|
||||
}
|
||||
@ -168,22 +151,10 @@ class SharedFilesActivity : BaseActivity(), //
|
||||
sharedFilesFragment().showChosenLocation(folder)
|
||||
}
|
||||
|
||||
override fun showBiometricAuthKeyInvalidatedDialog() {
|
||||
showDialog(BiometricAuthKeyInvalidatedDialog.newInstance())
|
||||
}
|
||||
|
||||
override fun showUploadDialog(uploadingFiles: Int) {
|
||||
showDialog(UploadCloudFileDialog.newInstance(uploadingFiles))
|
||||
}
|
||||
|
||||
override fun onUnlockClick(vaultModel: VaultModel, password: String) {
|
||||
presenter.onUnlockPressed(vaultModel, password)
|
||||
}
|
||||
|
||||
override fun onUnlockCanceled() {
|
||||
presenter.onUnlockCanceled()
|
||||
}
|
||||
|
||||
override fun onReplacePositiveClicked() {
|
||||
presenter.onReplaceExistingFilesPressed()
|
||||
}
|
||||
@ -209,20 +180,6 @@ class SharedFilesActivity : BaseActivity(), //
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onBiometricAuthenticated(vault: VaultModel) {
|
||||
presenter.onUnlockPressed(vault, vault.password)
|
||||
}
|
||||
|
||||
override fun onBiometricAuthenticationFailed(vault: VaultModel) {
|
||||
showDialog(EnterPasswordDialog.newInstance(vault))
|
||||
}
|
||||
|
||||
override fun onBiometricKeyInvalidated(vault: VaultModel) {
|
||||
presenter.onBiometricAuthKeyInvalidated()
|
||||
}
|
||||
|
||||
private fun vaultWithBiometricAuthEnabled(vault: VaultModel): Boolean = vault.password != null
|
||||
|
||||
override fun onUploadCanceled() {
|
||||
presenter.onUploadCanceled()
|
||||
}
|
||||
|
@ -0,0 +1,109 @@
|
||||
package org.cryptomator.presentation.ui.activity
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import org.cryptomator.domain.Vault
|
||||
import org.cryptomator.generator.Activity
|
||||
import org.cryptomator.generator.InjectIntent
|
||||
import org.cryptomator.presentation.R
|
||||
import org.cryptomator.presentation.intent.UnlockVaultIntent
|
||||
import org.cryptomator.presentation.model.VaultModel
|
||||
import org.cryptomator.presentation.presenter.UnlockVaultPresenter
|
||||
import org.cryptomator.presentation.ui.activity.view.UnlockVaultView
|
||||
import org.cryptomator.presentation.ui.dialog.BiometricAuthKeyInvalidatedDialog
|
||||
import org.cryptomator.presentation.ui.dialog.ChangePasswordDialog
|
||||
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
|
||||
import org.cryptomator.presentation.ui.fragment.UnlockVaultFragment
|
||||
import org.cryptomator.presentation.util.BiometricAuthentication
|
||||
import javax.inject.Inject
|
||||
|
||||
@Activity(layout = R.layout.activity_unlock_vault)
|
||||
class UnlockVaultActivity : BaseActivity(), //
|
||||
UnlockVaultView, //
|
||||
BiometricAuthentication.Callback,
|
||||
ChangePasswordDialog.Callback {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: UnlockVaultPresenter
|
||||
|
||||
@InjectIntent
|
||||
lateinit var unlockVaultIntent: UnlockVaultIntent
|
||||
|
||||
|
||||
private lateinit var biometricAuthentication: BiometricAuthentication
|
||||
|
||||
override fun finish() {
|
||||
super.finish()
|
||||
overridePendingTransition(0, 0)
|
||||
}
|
||||
|
||||
override fun showEnterPasswordDialog(vault: VaultModel) {
|
||||
showDialog(EnterPasswordDialog.newInstance(vault))
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
override fun showBiometricDialog(vault: VaultModel) {
|
||||
biometricAuthentication = BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.DECRYPT, presenter.useConfirmationInFaceUnlockBiometricAuthentication())
|
||||
biometricAuthentication.startListening(unlockVaultFragment(), vault)
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
override fun getEncryptedPasswordWithBiometricAuthentication(vaultModel: VaultModel) {
|
||||
biometricAuthentication = BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.ENCRYPT, presenter.useConfirmationInFaceUnlockBiometricAuthentication())
|
||||
biometricAuthentication.startListening(unlockVaultFragment(), vaultModel)
|
||||
}
|
||||
|
||||
override fun showBiometricAuthKeyInvalidatedDialog() {
|
||||
showDialog(BiometricAuthKeyInvalidatedDialog.newInstance())
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
override fun cancelBasicAuthIfRunning() {
|
||||
biometricAuthentication.stopListening()
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
override fun stoppedBiometricAuthDuringCloudAuthentication(): Boolean {
|
||||
return biometricAuthentication.stoppedBiometricAuthDuringCloudAuthentication()
|
||||
}
|
||||
|
||||
override fun onUnlockClick(vaultModel: VaultModel, password: String) {
|
||||
presenter.onUnlockClick(vaultModel, password)
|
||||
}
|
||||
|
||||
override fun onUnlockCanceled() {
|
||||
presenter.onUnlockCanceled()
|
||||
}
|
||||
|
||||
override fun onBiometricAuthenticated(vault: VaultModel) {
|
||||
presenter.onBiometricAuthenticationSucceeded(vault)
|
||||
}
|
||||
|
||||
override fun onBiometricAuthenticationFailed(vault: VaultModel) {
|
||||
val vaultWithoutPassword = Vault.aCopyOf(vault.toVault()).withSavedPassword(null).build()
|
||||
when(unlockVaultIntent.vaultAction()) {
|
||||
UnlockVaultIntent.VaultAction.CHANGE_PASSWORD -> presenter.saveVaultAfterChangePasswordButFailedBiometricAuth(vaultWithoutPassword)
|
||||
else -> {
|
||||
if (!presenter.startedUsingPrepareUnlock()) {
|
||||
presenter.startPrepareUnlockUseCase(vaultWithoutPassword)
|
||||
}
|
||||
showEnterPasswordDialog(VaultModel(vaultWithoutPassword))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBiometricKeyInvalidated(vault: VaultModel) {
|
||||
presenter.onBiometricKeyInvalidated()
|
||||
}
|
||||
|
||||
private fun unlockVaultFragment(): UnlockVaultFragment = //
|
||||
getCurrentFragment(R.id.fragmentContainer) as UnlockVaultFragment
|
||||
|
||||
override fun showChangePasswordDialog(vaultModel: VaultModel) {
|
||||
showDialog(ChangePasswordDialog.newInstance(vaultModel))
|
||||
}
|
||||
|
||||
override fun onChangePasswordClick(vaultModel: VaultModel, oldPassword: String, newPassword: String) {
|
||||
presenter.onChangePasswordClick(vaultModel, oldPassword, newPassword)
|
||||
}
|
||||
}
|
@ -2,9 +2,7 @@ package org.cryptomator.presentation.ui.activity
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.cryptomator.domain.Vault
|
||||
import org.cryptomator.generator.Activity
|
||||
@ -25,9 +23,6 @@ import org.cryptomator.presentation.ui.bottomsheet.SettingsVaultBottomSheet
|
||||
import org.cryptomator.presentation.ui.callback.VaultListCallback
|
||||
import org.cryptomator.presentation.ui.dialog.AskForLockScreenDialog
|
||||
import org.cryptomator.presentation.ui.dialog.BetaConfirmationDialog
|
||||
import org.cryptomator.presentation.ui.dialog.BiometricAuthKeyInvalidatedDialog
|
||||
import org.cryptomator.presentation.ui.dialog.ChangePasswordDialog
|
||||
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
|
||||
import org.cryptomator.presentation.ui.dialog.UpdateAppAvailableDialog
|
||||
import org.cryptomator.presentation.ui.dialog.UpdateAppDialog
|
||||
import org.cryptomator.presentation.ui.dialog.VaultDeleteConfirmationDialog
|
||||
@ -35,7 +30,6 @@ import org.cryptomator.presentation.ui.dialog.VaultNotFoundDialog
|
||||
import org.cryptomator.presentation.ui.dialog.VaultRenameDialog
|
||||
import org.cryptomator.presentation.ui.fragment.VaultListFragment
|
||||
import org.cryptomator.presentation.ui.layout.ObscuredAwareCoordinatorLayout.Listener
|
||||
import org.cryptomator.presentation.util.BiometricAuthentication
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import kotlinx.android.synthetic.main.activity_layout_obscure_aware.activityRootView
|
||||
@ -45,10 +39,8 @@ import kotlinx.android.synthetic.main.toolbar_layout.toolbar
|
||||
class VaultListActivity : BaseActivity(), //
|
||||
VaultListView, //
|
||||
VaultListCallback, //
|
||||
BiometricAuthentication.Callback, //
|
||||
AskForLockScreenDialog.Callback, //
|
||||
ChangePasswordDialog.Callback, //
|
||||
VaultNotFoundDialog.Callback,
|
||||
VaultNotFoundDialog.Callback, //
|
||||
UpdateAppAvailableDialog.Callback, //
|
||||
UpdateAppDialog.Callback, //
|
||||
BetaConfirmationDialog.Callback {
|
||||
@ -59,8 +51,6 @@ class VaultListActivity : BaseActivity(), //
|
||||
@InjectIntent
|
||||
lateinit var vaultListIntent: VaultListIntent
|
||||
|
||||
private var biometricAuthentication: BiometricAuthentication? = null
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
vaultListPresenter.onWindowFocusChanged(hasFocus)
|
||||
@ -125,40 +115,6 @@ class VaultListActivity : BaseActivity(), //
|
||||
showDialog(VaultRenameDialog.newInstance(vaultModel))
|
||||
}
|
||||
|
||||
override fun showEnterPasswordDialog(vault: VaultModel) {
|
||||
showDialog(EnterPasswordDialog.newInstance(vault))
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
override fun showBiometricDialog(vault: VaultModel) {
|
||||
biometricAuthentication = BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.DECRYPT, vaultListPresenter.useConfirmationInFaceUnlockBiometricAuthentication())
|
||||
biometricAuthentication?.startListening(vaultListFragment(), vault)
|
||||
}
|
||||
|
||||
override fun showChangePasswordDialog(vaultModel: VaultModel) {
|
||||
showDialog(ChangePasswordDialog.newInstance(vaultModel))
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
override fun getEncryptedPasswordWithBiometricAuthentication(vaultModel: VaultModel) {
|
||||
biometricAuthentication = BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.ENCRYPT, vaultListPresenter.useConfirmationInFaceUnlockBiometricAuthentication())
|
||||
biometricAuthentication?.startListening(vaultListFragment(), vaultModel)
|
||||
}
|
||||
|
||||
override fun showBiometricAuthKeyInvalidatedDialog() {
|
||||
showDialog(BiometricAuthKeyInvalidatedDialog.newInstance())
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
override fun cancelBasicAuthIfRunning() {
|
||||
biometricAuthentication?.stopListening()
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
override fun stoppedBiometricAuthDuringCloudAuthentication(): Boolean {
|
||||
return biometricAuthentication?.stoppedBiometricAuthDuringCloudAuthentication() == true
|
||||
}
|
||||
|
||||
override fun rowMoved(fromPosition: Int, toPosition: Int) {
|
||||
vaultListFragment().rowMoved(fromPosition, toPosition)
|
||||
}
|
||||
@ -209,14 +165,6 @@ class VaultListActivity : BaseActivity(), //
|
||||
vaultListPresenter.onCreateVault()
|
||||
}
|
||||
|
||||
override fun onUnlockClick(vaultModel: VaultModel, password: String) {
|
||||
vaultListPresenter.onUnlockClick(vaultModel, password)
|
||||
}
|
||||
|
||||
override fun onUnlockCanceled() {
|
||||
vaultListPresenter.onUnlockCanceled()
|
||||
}
|
||||
|
||||
override fun onDeleteVaultClick(vaultModel: VaultModel) {
|
||||
VaultDeleteConfirmationDialog.newInstance(vaultModel) //
|
||||
.show(supportFragmentManager, "VaultDeleteConfirmationDialog")
|
||||
@ -249,10 +197,6 @@ class VaultListActivity : BaseActivity(), //
|
||||
private fun vaultListFragment(): VaultListFragment = //
|
||||
getCurrentFragment(R.id.fragmentContainer) as VaultListFragment
|
||||
|
||||
override fun onChangePasswordClick(vaultModel: VaultModel, oldPassword: String, newPassword: String) {
|
||||
vaultListPresenter.onChangePasswordClicked(vaultModel, oldPassword, newPassword)
|
||||
}
|
||||
|
||||
override fun onDeleteMissingVaultClicked(vault: Vault) {
|
||||
vaultListPresenter.onDeleteMissingVaultClicked(vault)
|
||||
}
|
||||
@ -279,21 +223,4 @@ class VaultListActivity : BaseActivity(), //
|
||||
override fun onAskForBetaConfirmationFinished() {
|
||||
sharedPreferencesHandler.setBetaScreenDialogAlreadyShown()
|
||||
}
|
||||
|
||||
override fun onBiometricAuthenticated(vault: VaultModel) {
|
||||
vaultListPresenter.onBiometricAuthenticationSucceeded(vault)
|
||||
}
|
||||
|
||||
override fun onBiometricAuthenticationFailed(vault: VaultModel) {
|
||||
val vaultWithoutPassword = Vault.aCopyOf(vault.toVault()).withSavedPassword(null).build()
|
||||
if (!vaultListPresenter.startedUsingPrepareUnlock()) {
|
||||
vaultListPresenter.startPrepareUnlockUseCase(vaultWithoutPassword)
|
||||
}
|
||||
showEnterPasswordDialog(VaultModel(vaultWithoutPassword))
|
||||
}
|
||||
|
||||
override fun onBiometricKeyInvalidated(vault: VaultModel) {
|
||||
vaultListPresenter.onBiometricKeyInvalidated()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,5 @@ interface AutoUploadChooseVaultView : View {
|
||||
fun displayDialogUnableToUploadFiles()
|
||||
fun displayVaults(vaults: List<VaultModel>)
|
||||
fun showChosenLocation(location: CloudFolderModel)
|
||||
fun showEnterPasswordDialog(vaultModel: VaultModel)
|
||||
fun showBiometricAuthKeyInvalidatedDialog()
|
||||
|
||||
}
|
||||
|
@ -6,9 +6,6 @@ interface BiometricAuthSettingsView : View {
|
||||
|
||||
fun renderVaultList(vaultModelCollection: List<VaultModel>)
|
||||
fun clearVaultList()
|
||||
fun showBiometricAuthenticationDialog(vaultModel: VaultModel)
|
||||
fun showEnterPasswordDialog(vaultModel: VaultModel)
|
||||
fun showSetupBiometricAuthDialog()
|
||||
fun showBiometricAuthKeyInvalidatedDialog()
|
||||
|
||||
}
|
||||
|
@ -11,10 +11,8 @@ interface SharedFilesView : View {
|
||||
fun displayVaults(vaults: List<VaultModel>)
|
||||
fun displayFilesToUpload(sharedFiles: List<SharedFileModel>)
|
||||
fun displayDialogUnableToUploadFiles()
|
||||
fun showEnterPasswordDialog(vault: VaultModel)
|
||||
fun showReplaceDialog(existingFiles: List<String>, size: Int)
|
||||
fun showChosenLocation(folder: CloudFolderModel)
|
||||
fun showBiometricAuthKeyInvalidatedDialog()
|
||||
fun showUploadDialog(uploadingFiles: Int)
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
package org.cryptomator.presentation.ui.activity.view
|
||||
|
||||
import org.cryptomator.presentation.model.VaultModel
|
||||
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
|
||||
|
||||
interface UnlockVaultView : View, EnterPasswordDialog.Callback {
|
||||
|
||||
fun showEnterPasswordDialog(vault: VaultModel)
|
||||
fun showBiometricDialog(vault: VaultModel)
|
||||
fun getEncryptedPasswordWithBiometricAuthentication(vaultModel: VaultModel)
|
||||
fun showBiometricAuthKeyInvalidatedDialog()
|
||||
fun cancelBasicAuthIfRunning()
|
||||
fun stoppedBiometricAuthDuringCloudAuthentication(): Boolean
|
||||
fun showChangePasswordDialog(vaultModel: VaultModel)
|
||||
|
||||
}
|
@ -12,17 +12,10 @@ interface VaultListView : View {
|
||||
fun addOrUpdateVault(vault: VaultModel)
|
||||
fun renameVault(vaultModel: VaultModel)
|
||||
fun navigateToVaultContent(vault: VaultModel, decryptedRoot: CloudFolderModel)
|
||||
fun showEnterPasswordDialog(vault: VaultModel)
|
||||
fun showBiometricDialog(vault: VaultModel)
|
||||
fun showChangePasswordDialog(vaultModel: VaultModel)
|
||||
fun getEncryptedPasswordWithBiometricAuthentication(vaultModel: VaultModel)
|
||||
fun showVaultSettingsDialog(vaultModel: VaultModel)
|
||||
fun showAddVaultBottomSheet()
|
||||
fun showRenameDialog(vaultModel: VaultModel)
|
||||
fun showBiometricAuthKeyInvalidatedDialog()
|
||||
fun isVaultLocked(vaultModel: VaultModel): Boolean
|
||||
fun cancelBasicAuthIfRunning()
|
||||
fun stoppedBiometricAuthDuringCloudAuthentication(): Boolean
|
||||
fun rowMoved(fromPosition: Int, toPosition: Int)
|
||||
fun vaultMoved(vaults: List<VaultModel>)
|
||||
|
||||
|
@ -2,8 +2,11 @@ package org.cryptomator.presentation.ui.callback
|
||||
|
||||
import org.cryptomator.presentation.ui.bottomsheet.AddVaultBottomSheet
|
||||
import org.cryptomator.presentation.ui.bottomsheet.SettingsVaultBottomSheet
|
||||
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
|
||||
import org.cryptomator.presentation.ui.dialog.VaultDeleteConfirmationDialog
|
||||
import org.cryptomator.presentation.ui.dialog.VaultRenameDialog
|
||||
|
||||
interface VaultListCallback : AddVaultBottomSheet.Callback, EnterPasswordDialog.Callback, SettingsVaultBottomSheet.Callback, VaultDeleteConfirmationDialog.Callback, VaultRenameDialog.Callback
|
||||
// FIXME delete this file and add this interfaces to VaultListView.kt
|
||||
interface VaultListCallback : AddVaultBottomSheet.Callback, //
|
||||
SettingsVaultBottomSheet.Callback, //
|
||||
VaultDeleteConfirmationDialog.Callback, //
|
||||
VaultRenameDialog.Callback
|
||||
|
@ -0,0 +1,17 @@
|
||||
package org.cryptomator.presentation.ui.fragment
|
||||
|
||||
import org.cryptomator.generator.Fragment
|
||||
import org.cryptomator.presentation.R
|
||||
import org.cryptomator.presentation.presenter.UnlockVaultPresenter
|
||||
import javax.inject.Inject
|
||||
|
||||
@Fragment(R.layout.fragment_unlock_vault)
|
||||
class UnlockVaultFragment : BaseFragment() {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: UnlockVaultPresenter
|
||||
|
||||
override fun setupView() {
|
||||
presenter.setup()
|
||||
}
|
||||
}
|
15
presentation/src/main/res/layout/activity_unlock_vault.xml
Normal file
15
presentation/src/main/res/layout/activity_unlock_vault.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/activityRootView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragmentContainer"
|
||||
android:name="org.cryptomator.presentation.ui.fragment.UnlockVaultFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:tag="UnlockVaultFragment" />
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
@ -29,6 +29,10 @@
|
||||
<item name="colorAccent">@color/colorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="TransparentAlertDialogCustom" parent="AlertDialogCustom">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="Toolbar.Theme" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||
<item name="colorAccent">@color/textColorWhite</item>
|
||||
</style>
|
||||
|
@ -13,17 +13,13 @@ import org.cryptomator.domain.usecases.DoUpdateUseCase;
|
||||
import org.cryptomator.domain.usecases.GetDecryptedCloudForVaultUseCase;
|
||||
import org.cryptomator.domain.usecases.ResultHandler;
|
||||
import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase;
|
||||
import org.cryptomator.domain.usecases.vault.ChangePasswordUseCase;
|
||||
import org.cryptomator.domain.usecases.vault.DeleteVaultUseCase;
|
||||
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase;
|
||||
import org.cryptomator.domain.usecases.vault.LockVaultUseCase;
|
||||
import org.cryptomator.domain.usecases.vault.MoveVaultPositionUseCase;
|
||||
import org.cryptomator.domain.usecases.vault.PrepareUnlockUseCase;
|
||||
import org.cryptomator.domain.usecases.vault.RemoveStoredVaultPasswordsUseCase;
|
||||
import org.cryptomator.domain.usecases.vault.RenameVaultUseCase;
|
||||
import org.cryptomator.domain.usecases.vault.SaveVaultUseCase;
|
||||
import org.cryptomator.domain.usecases.vault.UnlockToken;
|
||||
import org.cryptomator.domain.usecases.vault.UnlockVaultUseCase;
|
||||
import org.cryptomator.presentation.exception.ExceptionHandlers;
|
||||
import org.cryptomator.presentation.model.VaultModel;
|
||||
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper;
|
||||
@ -98,17 +94,12 @@ public class VaultListPresenterTest {
|
||||
private LockVaultUseCase lockVaultUseCase = Mockito.mock(LockVaultUseCase.class);
|
||||
private LockVaultUseCase.Launcher lockVaultUseCaseLauncher = Mockito.mock(LockVaultUseCase.Launcher.class);
|
||||
private GetDecryptedCloudForVaultUseCase getDecryptedCloudForVaultUseCase = Mockito.mock(GetDecryptedCloudForVaultUseCase.class);
|
||||
private PrepareUnlockUseCase prepareUnlockUseCase = Mockito.mock(PrepareUnlockUseCase.class);
|
||||
private PrepareUnlockUseCase.Launcher prepareUnlockUseCaseLauncher = Mockito.mock(PrepareUnlockUseCase.Launcher.class);
|
||||
private UnlockToken unlockToken = Mockito.mock(UnlockToken.class);
|
||||
private UnlockVaultUseCase unlockVaultUseCase = Mockito.mock(UnlockVaultUseCase.class);
|
||||
private GetRootFolderUseCase getRootFolderUseCase = Mockito.mock(GetRootFolderUseCase.class);
|
||||
private AddExistingVaultWorkflow addExistingVaultWorkflow = Mockito.mock(AddExistingVaultWorkflow.class);
|
||||
private CreateNewVaultWorkflow createNewVaultWorkflow = Mockito.mock(CreateNewVaultWorkflow.class);
|
||||
private SaveVaultUseCase saveVaultUseCase = Mockito.mock(SaveVaultUseCase.class);
|
||||
private MoveVaultPositionUseCase moveVaultPositionUseCase = Mockito.mock(MoveVaultPositionUseCase.class);
|
||||
private ChangePasswordUseCase changePasswordUseCase = Mockito.mock(ChangePasswordUseCase.class);
|
||||
private RemoveStoredVaultPasswordsUseCase removeStoredVaultPasswordsUseCase = Mockito.mock(RemoveStoredVaultPasswordsUseCase.class);
|
||||
private DoLicenseCheckUseCase doLicenceCheckUsecase = Mockito.mock(DoLicenseCheckUseCase.class);
|
||||
private DoUpdateCheckUseCase updateCheckUseCase = Mockito.mock(DoUpdateCheckUseCase.class);
|
||||
private DoUpdateUseCase updateUseCase = Mockito.mock(DoUpdateUseCase.class);
|
||||
@ -126,15 +117,11 @@ public class VaultListPresenterTest {
|
||||
renameVaultUseCase, //
|
||||
lockVaultUseCase, //
|
||||
getDecryptedCloudForVaultUseCase, //
|
||||
prepareUnlockUseCase, //
|
||||
unlockVaultUseCase, //
|
||||
getRootFolderUseCase, //
|
||||
addExistingVaultWorkflow, //
|
||||
createNewVaultWorkflow, //
|
||||
saveVaultUseCase, //
|
||||
moveVaultPositionUseCase, //
|
||||
changePasswordUseCase, //
|
||||
removeStoredVaultPasswordsUseCase, //
|
||||
doLicenceCheckUsecase, //
|
||||
updateCheckUseCase, //
|
||||
updateUseCase, //
|
||||
@ -241,43 +228,4 @@ public class VaultListPresenterTest {
|
||||
Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnUnlockCanceled() {
|
||||
inTest.onUnlockCanceled();
|
||||
|
||||
verify(prepareUnlockUseCase).unsubscribe();
|
||||
verify(unlockVaultUseCase).cancel();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnVaultLockedClicked() {
|
||||
ArgumentCaptor<ResultHandler<Vault>> captor = ArgumentCaptor.forClass(ResultHandler.class);
|
||||
|
||||
when(lockVaultUseCase.withVault(AN_UNLOCKED_VAULT_MODEL.toVault())).thenReturn(lockVaultUseCaseLauncher);
|
||||
|
||||
inTest.onVaultLockClicked(AN_UNLOCKED_VAULT_MODEL);
|
||||
|
||||
verify(lockVaultUseCaseLauncher).run(captor.capture());
|
||||
captor.getValue().onSuccess(AN_UNLOCKED_VAULT_MODEL.toVault());
|
||||
verify(vaultListView).addOrUpdateVault(AN_UNLOCKED_VAULT_MODEL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onVaultClickedWithCloudAndLocked() {
|
||||
ArgumentCaptor<ResultHandler<UnlockToken>> captor = ArgumentCaptor.forClass(ResultHandler.class);
|
||||
|
||||
when(prepareUnlockUseCase.withVault(ANOTHER_VAULT_MODEL_WITH_CLOUD.toVault())) //
|
||||
.thenReturn(prepareUnlockUseCaseLauncher);
|
||||
when(unlockToken.getVault()) //
|
||||
.thenReturn(ANOTHER_VAULT_MODEL_WITH_CLOUD.toVault());
|
||||
|
||||
inTest.onVaultClicked(ANOTHER_VAULT_MODEL_WITH_CLOUD);
|
||||
|
||||
verify(prepareUnlockUseCaseLauncher).run(captor.capture());
|
||||
captor.getValue().onSuccess(unlockToken);
|
||||
|
||||
verify(vaultListView).addOrUpdateVault(ANOTHER_VAULT_MODEL_WITH_CLOUD);
|
||||
verify(vaultListView).showEnterPasswordDialog(ANOTHER_VAULT_MODEL_WITH_CLOUD);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user