diff options
Diffstat (limited to 'app/src/main/java')
17 files changed, 109 insertions, 54 deletions
diff --git a/app/src/main/java/app/passwordstore/data/repo/PasswordRepository.kt b/app/src/main/java/app/passwordstore/data/repo/PasswordRepository.kt index 1ddf1c61..454054cf 100644 --- a/app/src/main/java/app/passwordstore/data/repo/PasswordRepository.kt +++ b/app/src/main/java/app/passwordstore/data/repo/PasswordRepository.kt @@ -114,7 +114,11 @@ object PasswordRepository { val dir = getRepositoryDirectory() // Un-initialize the repo if the dir does not exist or is absolutely empty settings.edit { - if (!dir.exists() || !dir.isDirectory || requireNotNull(dir.listFiles()).isEmpty()) { + if ( + !dir.exists() || + !dir.isDirectory || + requireNotNull(dir.listFiles()) { "Failed to list files in ${dir.path}" }.isEmpty() + ) { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) } else { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) diff --git a/app/src/main/java/app/passwordstore/ui/dialogs/PasswordGeneratorDialogFragment.kt b/app/src/main/java/app/passwordstore/ui/dialogs/PasswordGeneratorDialogFragment.kt index da1a060a..1f37e205 100644 --- a/app/src/main/java/app/passwordstore/ui/dialogs/PasswordGeneratorDialogFragment.kt +++ b/app/src/main/java/app/passwordstore/ui/dialogs/PasswordGeneratorDialogFragment.kt @@ -31,9 +31,8 @@ import app.passwordstore.util.settings.PreferenceKeys import com.github.michaelbull.result.getOrElse import com.github.michaelbull.result.runCatching import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import reactivecircus.flowbinding.android.widget.afterTextChanges import reactivecircus.flowbinding.android.widget.checkedChanges @@ -55,17 +54,18 @@ class PasswordGeneratorDialogFragment : DialogFragment() { binding.lengthNumber.setText(prefs.getInt(PreferenceKeys.LENGTH, 20).toString()) binding.passwordText.typeface = Typeface.MONOSPACE - merge( - binding.numerals.checkedChanges().skipInitialValue(), - binding.symbols.checkedChanges().skipInitialValue(), - binding.uppercase.checkedChanges().skipInitialValue(), - binding.lowercase.checkedChanges().skipInitialValue(), - binding.ambiguous.checkedChanges().skipInitialValue(), - binding.pronounceable.checkedChanges().skipInitialValue(), - binding.lengthNumber.afterTextChanges().skipInitialValue(), - ) - .onEach { generate(binding.passwordText) } - .launchIn(lifecycleScope) + lifecycleScope.launch { + merge( + binding.numerals.checkedChanges().skipInitialValue(), + binding.symbols.checkedChanges().skipInitialValue(), + binding.uppercase.checkedChanges().skipInitialValue(), + binding.lowercase.checkedChanges().skipInitialValue(), + binding.ambiguous.checkedChanges().skipInitialValue(), + binding.pronounceable.checkedChanges().skipInitialValue(), + binding.lengthNumber.afterTextChanges().skipInitialValue(), + ) + .collect { generate(binding.passwordText) } + } return builder .run { diff --git a/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt b/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt index a1c33e3f..9a8080a4 100644 --- a/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt +++ b/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt @@ -27,8 +27,7 @@ import com.github.michaelbull.result.runCatching import dagger.hilt.android.AndroidEntryPoint import java.io.File import javax.inject.Inject -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import me.zhanghai.android.fastscroll.FastScrollerBuilder @AndroidEntryPoint @@ -57,12 +56,16 @@ class SelectFolderFragment : Fragment(R.layout.password_recycler_view) { FastScrollerBuilder(binding.passRecycler).build() registerForContextMenu(binding.passRecycler) - val path = requireNotNull(requireArguments().getString(PasswordStore.REQUEST_ARG_PATH)) + val path = + requireNotNull(requireArguments().getString(PasswordStore.REQUEST_ARG_PATH)) { + "Cannot navigate if ${PasswordStore.REQUEST_ARG_PATH} is not provided" + } model.navigateTo(File(path), listMode = ListMode.DirectoriesOnly, pushPreviousLocation = false) - model.searchResult - .flowWithLifecycle(lifecycle) - .onEach { result -> recyclerAdapter.submitList(result.passwordItems) } - .launchIn(lifecycleScope) + lifecycleScope.launch { + model.searchResult.flowWithLifecycle(lifecycle).collect { result -> + recyclerAdapter.submitList(result.passwordItems) + } + } } override fun onAttach(context: Context) { diff --git a/app/src/main/java/app/passwordstore/ui/git/config/GitServerConfigActivity.kt b/app/src/main/java/app/passwordstore/ui/git/config/GitServerConfigActivity.kt index 5e2bafb9..9a3029bc 100644 --- a/app/src/main/java/app/passwordstore/ui/git/config/GitServerConfigActivity.kt +++ b/app/src/main/java/app/passwordstore/ui/git/config/GitServerConfigActivity.kt @@ -221,7 +221,10 @@ class GitServerConfigActivity : BaseGitActivity() { /** Clones the repository, the directory exists, deletes it */ private fun cloneRepository() { - val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory()) + val localDir = + requireNotNull(PasswordRepository.getRepositoryDirectory()) { + "Repository directory must be set before cloning" + } val localDirFiles = localDir.listFiles() ?: emptyArray() // Warn if non-empty folder unless it's a just-initialized store that has just a .git folder if ( diff --git a/app/src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt b/app/src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt index d6bcce11..60580ff5 100644 --- a/app/src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt +++ b/app/src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt @@ -50,8 +50,6 @@ import com.github.michaelbull.result.runCatching import dagger.hilt.android.AndroidEntryPoint import java.io.File import javax.inject.Inject -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import me.zhanghai.android.fastscroll.FastScrollerBuilder @@ -177,11 +175,13 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { recyclerAdapter.makeSelectable(recyclerView) registerForContextMenu(recyclerView) - val path = requireNotNull(requireArguments().getString(PasswordStore.REQUEST_ARG_PATH)) + val path = + requireNotNull(requireArguments().getString(PasswordStore.REQUEST_ARG_PATH)) { + "Cannot navigate if ${PasswordStore.REQUEST_ARG_PATH} is not provided" + } model.navigateTo(File(path), pushPreviousLocation = false) - model.searchResult - .flowWithLifecycle(lifecycle) - .onEach { result -> + lifecycleScope.launch { + model.searchResult.flowWithLifecycle(lifecycle).collect { result -> // Only run animations when the new list is filtered, i.e., the user submitted a search, // and not on folder navigation since the latter leads to too many removal animations. (recyclerView.itemAnimator as OnOffItemAnimator).isEnabled = result.isFiltered @@ -209,7 +209,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { } } } - .launchIn(lifecycleScope) + } } private val actionModeCallback = diff --git a/app/src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt b/app/src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt index 8aeee609..861250c8 100644 --- a/app/src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt +++ b/app/src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt @@ -83,8 +83,16 @@ class PasswordStore : BaseGitActivity() { private val passwordMoveAction = registerForActivityResult(StartActivityForResult()) { result -> val intentData = result.data ?: return@registerForActivityResult - val filesToMove = requireNotNull(intentData.getStringArrayExtra("Files")) - val target = File(requireNotNull(intentData.getStringExtra("SELECTED_FOLDER_PATH"))) + val filesToMove = + requireNotNull(intentData.getStringArrayExtra("Files")) { + "'Files' intent extra must be set" + } + val target = + File( + requireNotNull(intentData.getStringExtra("SELECTED_FOLDER_PATH")) { + "'SELECTED_FOLDER_PATH' intent extra must be set" + } + ) val repositoryPath = PasswordRepository.getRepositoryDirectory().absolutePath if (!target.isDirectory) { logcat(ERROR) { "Tried moving passwords to a non-existing folder." } @@ -103,7 +111,12 @@ class PasswordStore : BaseGitActivity() { } val destinationFile = File(target.absolutePath + "/" + source.name) val basename = source.nameWithoutExtension - val sourceLongName = getLongName(requireNotNull(source.parent), repositoryPath, basename) + val sourceLongName = + getLongName( + requireNotNull(source.parent) { "$file has no parent" }, + repositoryPath, + basename + ) val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) if (destinationFile.exists()) { logcat(ERROR) { "Trying to move a file that already exists." } @@ -132,7 +145,11 @@ class PasswordStore : BaseGitActivity() { val source = File(filesToMove[0]) val basename = source.nameWithoutExtension val sourceLongName = - getLongName(requireNotNull(source.parent), repositoryPath, basename) + getLongName( + requireNotNull(source.parent) { "$basename has no parent" }, + repositoryPath, + basename + ) val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) withContext(dispatcherProvider.main()) { commitChange( diff --git a/app/src/main/java/app/passwordstore/util/autofill/Api26AutofillResponseBuilder.kt b/app/src/main/java/app/passwordstore/util/autofill/Api26AutofillResponseBuilder.kt index 77322116..8b087f99 100644 --- a/app/src/main/java/app/passwordstore/util/autofill/Api26AutofillResponseBuilder.kt +++ b/app/src/main/java/app/passwordstore/util/autofill/Api26AutofillResponseBuilder.kt @@ -125,7 +125,7 @@ private constructor( // https://developer.android.com/reference/android/service/autofill/SaveInfo#FLAG_DELAY_SAVE private fun makeSaveInfo(): SaveInfo? { if (!canBeSaved) return null - check(saveFlags != null) + check(saveFlags != null) { "saveFlags must not be null" } val idsToSave = scenario.fieldsToSave.toTypedArray() if (idsToSave.isEmpty()) return null var saveDataTypes = SaveInfo.SAVE_DATA_TYPE_PASSWORD diff --git a/app/src/main/java/app/passwordstore/util/autofill/Api30AutofillResponseBuilder.kt b/app/src/main/java/app/passwordstore/util/autofill/Api30AutofillResponseBuilder.kt index 49970fa6..ef1c302c 100644 --- a/app/src/main/java/app/passwordstore/util/autofill/Api30AutofillResponseBuilder.kt +++ b/app/src/main/java/app/passwordstore/util/autofill/Api30AutofillResponseBuilder.kt @@ -244,7 +244,7 @@ private constructor( // https://developer.android.com/reference/android/service/autofill/SaveInfo#FLAG_DELAY_SAVE private fun makeSaveInfo(): SaveInfo? { if (!canBeSaved) return null - check(saveFlags != null) + check(saveFlags != null) { "saveFlags must not be null" } val idsToSave = scenario.fieldsToSave.toTypedArray() if (idsToSave.isEmpty()) return null var saveDataTypes = SaveInfo.SAVE_DATA_TYPE_PASSWORD diff --git a/app/src/main/java/app/passwordstore/util/autofill/AutofillMatcher.kt b/app/src/main/java/app/passwordstore/util/autofill/AutofillMatcher.kt index 52c3d778..56171f6b 100644 --- a/app/src/main/java/app/passwordstore/util/autofill/AutofillMatcher.kt +++ b/app/src/main/java/app/passwordstore/util/autofill/AutofillMatcher.kt @@ -40,7 +40,9 @@ class AutofillPublisherChangedException(val formOrigin: FormOrigin) : ) { init { - require(formOrigin is FormOrigin.App) + require(formOrigin is FormOrigin.App) { + "${this::class.java.simpleName} is only applicable for apps" + } } } diff --git a/app/src/main/java/app/passwordstore/util/git/operation/BreakOutOfDetached.kt b/app/src/main/java/app/passwordstore/util/git/operation/BreakOutOfDetached.kt index b6f31b18..77a19da6 100644 --- a/app/src/main/java/app/passwordstore/util/git/operation/BreakOutOfDetached.kt +++ b/app/src/main/java/app/passwordstore/util/git/operation/BreakOutOfDetached.kt @@ -9,6 +9,7 @@ import app.passwordstore.R import app.passwordstore.data.repo.PasswordRepository import app.passwordstore.util.extensions.unsafeLazy import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.eclipse.jgit.api.GitCommand import org.eclipse.jgit.api.RebaseCommand import org.eclipse.jgit.api.ResetCommand import org.eclipse.jgit.lib.RepositoryState @@ -30,7 +31,7 @@ class BreakOutOfDetached(callingActivity: AppCompatActivity) : GitOperation(call git.checkout().setName(localBranch), ) - override val commands by unsafeLazy { + override val commands: Array<GitCommand<out Any>> by unsafeLazy { if (merging) { // We need to run some non-command operations first repository.writeMergeCommitMsg(null) diff --git a/app/src/main/java/app/passwordstore/util/git/operation/CredentialFinder.kt b/app/src/main/java/app/passwordstore/util/git/operation/CredentialFinder.kt index 0210a744..e90e3b23 100644 --- a/app/src/main/java/app/passwordstore/util/git/operation/CredentialFinder.kt +++ b/app/src/main/java/app/passwordstore/util/git/operation/CredentialFinder.kt @@ -15,6 +15,7 @@ import androidx.core.widget.doOnTextChanged import androidx.fragment.app.FragmentActivity import app.passwordstore.R import app.passwordstore.injection.prefs.GitPreferences +import app.passwordstore.util.coroutines.DispatcherProvider import app.passwordstore.util.git.sshj.InteractivePasswordFinder import app.passwordstore.util.settings.AuthMode import app.passwordstore.util.settings.PreferenceKeys @@ -32,7 +33,8 @@ import kotlin.coroutines.resume class CredentialFinder( private val callingActivity: FragmentActivity, private val authMode: AuthMode, -) : InteractivePasswordFinder() { + dispatcherProvider: DispatcherProvider, +) : InteractivePasswordFinder(dispatcherProvider) { private val hiltEntryPoint = EntryPointAccessors.fromApplication( diff --git a/app/src/main/java/app/passwordstore/util/git/operation/GitOperation.kt b/app/src/main/java/app/passwordstore/util/git/operation/GitOperation.kt index 1cc42633..d4ccfaab 100644 --- a/app/src/main/java/app/passwordstore/util/git/operation/GitOperation.kt +++ b/app/src/main/java/app/passwordstore/util/git/operation/GitOperation.kt @@ -121,7 +121,8 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { authMethod: SshAuthMethod, credentialsProvider: CredentialsProvider? = null ) { - sshSessionFactory = SshjSessionFactory(authMethod, hostKeyFile, sshFacade) + sshSessionFactory = + SshjSessionFactory(authMethod, hostKeyFile, sshFacade, hiltEntryPoint.dispatcherProvider()) commands.filterIsInstance<TransportCommand<*, *>>().forEach { command -> command.setTransportConfigCallback { transport: Transport -> (transport as? SshTransport)?.sshSessionFactory = sshSessionFactory @@ -217,7 +218,13 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { } AuthMode.Password -> { val httpsCredentialProvider = - HttpsCredentialsProvider(CredentialFinder(callingActivity, AuthMode.Password)) + HttpsCredentialsProvider( + CredentialFinder( + callingActivity, + AuthMode.Password, + hiltEntryPoint.dispatcherProvider() + ) + ) registerAuthProviders(SshAuthMethod.Password(authActivity), httpsCredentialProvider) } AuthMode.None -> {} diff --git a/app/src/main/java/app/passwordstore/util/git/operation/ResetToRemoteOperation.kt b/app/src/main/java/app/passwordstore/util/git/operation/ResetToRemoteOperation.kt index 08cc195a..f6b4fdcd 100644 --- a/app/src/main/java/app/passwordstore/util/git/operation/ResetToRemoteOperation.kt +++ b/app/src/main/java/app/passwordstore/util/git/operation/ResetToRemoteOperation.kt @@ -6,12 +6,13 @@ package app.passwordstore.util.git.operation import androidx.appcompat.app.AppCompatActivity import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode.TRACK +import org.eclipse.jgit.api.GitCommand import org.eclipse.jgit.api.ResetCommand class ResetToRemoteOperation(callingActivity: AppCompatActivity, remoteBranch: String) : GitOperation(callingActivity) { - override val commands = + override val commands: Array<GitCommand<out Any>> = arrayOf( // Fetch everything from the origin remote git.fetch().setRemote("origin").setRemoveDeletedRefs(true), diff --git a/app/src/main/java/app/passwordstore/util/git/operation/SyncOperation.kt b/app/src/main/java/app/passwordstore/util/git/operation/SyncOperation.kt index e36b01b1..57be9439 100644 --- a/app/src/main/java/app/passwordstore/util/git/operation/SyncOperation.kt +++ b/app/src/main/java/app/passwordstore/util/git/operation/SyncOperation.kt @@ -5,13 +5,14 @@ package app.passwordstore.util.git.operation import androidx.appcompat.app.AppCompatActivity +import org.eclipse.jgit.api.GitCommand class SyncOperation( callingActivity: AppCompatActivity, rebase: Boolean, ) : GitOperation(callingActivity) { - override val commands = + override val commands: Array<GitCommand<out Any>> = arrayOf( // Stage all files git.add().addFilepattern("."), diff --git a/app/src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt b/app/src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt index 54858530..20bb01d6 100644 --- a/app/src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt +++ b/app/src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt @@ -6,6 +6,7 @@ package app.passwordstore.util.git.sshj import android.util.Base64 import androidx.appcompat.app.AppCompatActivity +import app.passwordstore.util.coroutines.DispatcherProvider import app.passwordstore.util.git.operation.CredentialFinder import app.passwordstore.util.settings.AuthMode import app.passwordstore.util.ssh.SSHFacade @@ -20,7 +21,6 @@ import java.util.Collections import java.util.concurrent.TimeUnit import kotlin.coroutines.Continuation import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import logcat.LogPriority.WARN import logcat.logcat @@ -49,15 +49,18 @@ sealed class SshAuthMethod(val activity: AppCompatActivity) { class SshKey(activity: AppCompatActivity) : SshAuthMethod(activity) } -abstract class InteractivePasswordFinder : PasswordFinder { +abstract class InteractivePasswordFinder(private val dispatcherProvider: DispatcherProvider) : + PasswordFinder { private var isRetry = false abstract fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) - final override fun reqPassword(resource: Resource<*>?): CharArray { + override fun reqPassword(resource: Resource<*>?): CharArray { val password = - runBlocking(Dispatchers.Main) { suspendCoroutine { cont -> askForPassword(cont, isRetry) } } + runBlocking(dispatcherProvider.main()) { + suspendCoroutine { cont -> askForPassword(cont, isRetry) } + } isRetry = true return password?.toCharArray() ?: throw SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER) } @@ -69,6 +72,7 @@ class SshjSessionFactory( private val authMethod: SshAuthMethod, private val hostKeyFile: File, private val sshFacade: SSHFacade, + private val dispatcherProvider: DispatcherProvider, ) : SshSessionFactory() { private var currentSession: SshjSession? = null @@ -80,10 +84,12 @@ class SshjSessionFactory( tms: Int ): RemoteSession { return currentSession - ?: SshjSession(uri, uri.user, authMethod, hostKeyFile, sshFacade).connect().also { - logcat { "New SSH connection created" } - currentSession = it - } + ?: SshjSession(uri, uri.user, authMethod, hostKeyFile, sshFacade, dispatcherProvider) + .connect() + .also { + logcat { "New SSH connection created" } + currentSession = it + } } fun close() { @@ -125,6 +131,7 @@ private class SshjSession( private val authMethod: SshAuthMethod, private val hostKeyFile: File, private val sshFacade: SSHFacade, + private val dispatcherProvider: DispatcherProvider, ) : RemoteSession { private lateinit var ssh: SSHClient @@ -151,7 +158,8 @@ private class SshjSession( ssh.addHostKeyVerifier(makeTofuHostKeyVerifier(hostKeyFile)) ssh.connect(uri.host, uri.port.takeUnless { it == -1 } ?: 22) if (!ssh.isConnected) throw IOException() - val passwordAuth = AuthPassword(CredentialFinder(authMethod.activity, AuthMode.Password)) + val passwordAuth = + AuthPassword(CredentialFinder(authMethod.activity, AuthMode.Password, dispatcherProvider)) when (authMethod) { is SshAuthMethod.Password -> { ssh.auth(username, passwordAuth) @@ -159,7 +167,10 @@ private class SshjSession( is SshAuthMethod.SshKey -> { val pubkeyAuth = AuthPublickey( - sshFacade.keyProvider(ssh, CredentialFinder(authMethod.activity, AuthMode.SshKey)) + sshFacade.keyProvider( + ssh, + CredentialFinder(authMethod.activity, AuthMode.SshKey, dispatcherProvider) + ) ) ssh.auth(username, pubkeyAuth, passwordAuth) } diff --git a/app/src/main/java/app/passwordstore/util/services/PasswordExportService.kt b/app/src/main/java/app/passwordstore/util/services/PasswordExportService.kt index 9243f52d..d5032f4f 100644 --- a/app/src/main/java/app/passwordstore/util/services/PasswordExportService.kt +++ b/app/src/main/java/app/passwordstore/util/services/PasswordExportService.kt @@ -57,7 +57,10 @@ class PasswordExportService : Service() { */ private fun exportPasswords(targetDirectory: DocumentFile) { - val repositoryDirectory = requireNotNull(PasswordRepository.getRepositoryDirectory()) + val repositoryDirectory = + requireNotNull(PasswordRepository.getRepositoryDirectory()) { + "Password directory must be set to export them" + } val sourcePassDir = DocumentFile.fromFile(repositoryDirectory) logcat { "Copying ${repositoryDirectory.path} to $targetDirectory" } diff --git a/app/src/main/java/app/passwordstore/util/settings/GitSettings.kt b/app/src/main/java/app/passwordstore/util/settings/GitSettings.kt index 56124104..bfbf9c85 100644 --- a/app/src/main/java/app/passwordstore/util/settings/GitSettings.kt +++ b/app/src/main/java/app/passwordstore/util/settings/GitSettings.kt @@ -68,7 +68,7 @@ constructor( var url get() = settings.getString(PreferenceKeys.GIT_REMOTE_URL) private set(value) { - require(value != null) + require(value != null) { "Cannot set a null URL" } if (value == url) return settings.edit { putString(PreferenceKeys.GIT_REMOTE_URL, value) } if (PasswordRepository.isInitialized) PasswordRepository.addRemote("origin", value, true) |