From 3a4e827f1abcb3d77d43dba4f7cf4d9a9c9655f7 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Thu, 1 Jun 2023 20:50:52 +0530 Subject: fix: clear violations of `RawDispatcherUse` --- app/lint-baseline.xml | 186 +-------------------- .../ui/adapters/PasswordItemRecyclerAdapter.kt | 12 +- .../ui/autofill/AutofillDecryptActivity.kt | 9 +- .../ui/autofill/AutofillFilterView.kt | 47 +++--- .../app/passwordstore/ui/crypto/DecryptActivity.kt | 4 +- .../ui/crypto/PasswordCreationActivity.kt | 7 +- .../DicewarePasswordGeneratorDialogFragment.kt | 16 +- .../ui/folderselect/SelectFolderFragment.kt | 5 +- .../passwordstore/ui/git/base/BaseGitActivity.kt | 5 +- .../ui/git/config/GitServerConfigActivity.kt | 3 +- .../passwordstore/ui/passwords/PasswordFragment.kt | 4 +- .../passwordstore/ui/passwords/PasswordStore.kt | 19 +-- .../ui/sshkeygen/SshKeyGenActivity.kt | 9 +- .../passwordstore/util/git/GitCommandExecutor.kt | 17 +- .../util/git/operation/GitOperation.kt | 10 +- .../util/git/sshj/SshjSessionFactory.kt | 4 +- .../util/services/ClipboardService.kt | 14 +- .../viewmodel/SearchableRepositoryViewModel.kt | 9 +- .../autofill/oreo/ui/AutofillSmsActivity.kt | 13 +- 19 files changed, 122 insertions(+), 271 deletions(-) (limited to 'app') diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index c2f393ad..cbf1295c 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -29,183 +29,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -275,7 +99,7 @@ errorLine2=" ^"> @@ -297,7 +121,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -308,7 +132,7 @@ errorLine2=" ^"> diff --git a/app/src/main/java/app/passwordstore/ui/adapters/PasswordItemRecyclerAdapter.kt b/app/src/main/java/app/passwordstore/ui/adapters/PasswordItemRecyclerAdapter.kt index 35c34690..f7d5cf9a 100644 --- a/app/src/main/java/app/passwordstore/ui/adapters/PasswordItemRecyclerAdapter.kt +++ b/app/src/main/java/app/passwordstore/ui/adapters/PasswordItemRecyclerAdapter.kt @@ -15,17 +15,21 @@ import androidx.recyclerview.selection.Selection import androidx.recyclerview.widget.RecyclerView import app.passwordstore.R import app.passwordstore.data.password.PasswordItem +import app.passwordstore.util.coroutines.DispatcherProvider import app.passwordstore.util.viewmodel.SearchableRepositoryAdapter import app.passwordstore.util.viewmodel.stableId import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -open class PasswordItemRecyclerAdapter(coroutineScope: CoroutineScope) : +open class PasswordItemRecyclerAdapter( + coroutineScope: CoroutineScope, + dispatcherProvider: DispatcherProvider, +) : SearchableRepositoryAdapter( R.layout.password_row_layout, ::PasswordItemViewHolder, coroutineScope, + dispatcherProvider, PasswordItemViewHolder::bind, ) { @@ -52,7 +56,7 @@ open class PasswordItemRecyclerAdapter(coroutineScope: CoroutineScope) : private val folderIndicator: AppCompatImageView = itemView.findViewById(R.id.folder_indicator) var itemDetails: ItemDetailsLookup.ItemDetails? = null - suspend fun bind(item: PasswordItem) { + suspend fun bind(item: PasswordItem, dispatcherProvider: DispatcherProvider) { val parentPath = item.fullPathToParent.replace("(^/)|(/$)".toRegex(), "") val source = if (parentPath.isNotEmpty()) { @@ -66,7 +70,7 @@ open class PasswordItemRecyclerAdapter(coroutineScope: CoroutineScope) : if (item.type == PasswordItem.TYPE_CATEGORY) { folderIndicator.visibility = View.VISIBLE val count = - withContext(Dispatchers.IO) { + withContext(dispatcherProvider.io()) { item.file.listFiles { path -> path.isDirectory || path.extension == "gpg" }?.size ?: 0 } childCount.visibility = if (count > 0) View.VISIBLE else View.GONE diff --git a/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt b/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt index 536e11a9..023822a4 100644 --- a/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt +++ b/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt @@ -33,7 +33,6 @@ import dagger.hilt.android.AndroidEntryPoint import java.io.ByteArrayOutputStream import java.io.File import javax.inject.Inject -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -101,7 +100,7 @@ class AutofillDecryptActivity : BasePgpActivity() { private fun askPassphrase(filePath: String, clientState: Bundle, action: AutofillAction) { val dialog = PasswordDialog() lifecycleScope.launch { - withContext(Dispatchers.Main) { + withContext(dispatcherProvider.main()) { dialog.password.collectLatest { value -> if (value != null) { decrypt(File(filePath), clientState, action, value) @@ -129,14 +128,14 @@ class AutofillDecryptActivity : BasePgpActivity() { clientState, action ) - withContext(Dispatchers.Main) { + withContext(dispatcherProvider.main()) { setResult( RESULT_OK, Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) } ) } } - withContext(Dispatchers.Main) { finish() } + withContext(dispatcherProvider.main()) { finish() } } private suspend fun decryptCredential(file: File, password: String): Credentials? { @@ -148,7 +147,7 @@ class AutofillDecryptActivity : BasePgpActivity() { } .onSuccess { encryptedInput -> runCatching { - withContext(Dispatchers.IO) { + withContext(dispatcherProvider.io()) { val outputStream = ByteArrayOutputStream() repository.decrypt( password, diff --git a/app/src/main/java/app/passwordstore/ui/autofill/AutofillFilterView.kt b/app/src/main/java/app/passwordstore/ui/autofill/AutofillFilterView.kt index 3a5e5d19..f0267f26 100644 --- a/app/src/main/java/app/passwordstore/ui/autofill/AutofillFilterView.kt +++ b/app/src/main/java/app/passwordstore/ui/autofill/AutofillFilterView.kt @@ -20,7 +20,6 @@ import androidx.core.text.bold import androidx.core.text.buildSpannedString import androidx.core.text.underline import androidx.core.widget.addTextChangedListener -import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import app.passwordstore.R @@ -29,6 +28,7 @@ import app.passwordstore.databinding.ActivityOreoAutofillFilterBinding import app.passwordstore.util.autofill.AutofillMatcher import app.passwordstore.util.autofill.AutofillPreferences import app.passwordstore.util.autofill.DirectoryStructure +import app.passwordstore.util.coroutines.DispatcherProvider import app.passwordstore.util.extensions.viewBinding import app.passwordstore.util.viewmodel.FilterMode import app.passwordstore.util.viewmodel.ListMode @@ -37,14 +37,17 @@ import app.passwordstore.util.viewmodel.SearchableRepositoryAdapter import app.passwordstore.util.viewmodel.SearchableRepositoryViewModel import com.github.androidpasswordstore.autofillparser.FormOrigin import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import javax.inject.Inject +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch import logcat.LogPriority.ERROR import logcat.logcat @AndroidEntryPoint class AutofillFilterView : AppCompatActivity() { + @Inject lateinit var dispatcherProvider: DispatcherProvider + companion object { private const val HEIGHT_PERCENTAGE = 0.9 @@ -142,7 +145,8 @@ class AutofillFilterView : AppCompatActivity() { R.layout.oreo_autofill_filter_row, ::PasswordViewHolder, lifecycleScope, - ) { item -> + dispatcherProvider, + ) { item, _ -> val file = item.file.relativeTo(item.rootDir) val pathToIdentifier = directoryStructure.getPathToIdentifierFor(file) val identifier = directoryStructure.getIdentifierFor(file) @@ -191,23 +195,24 @@ class AutofillFilterView : AppCompatActivity() { R.string.oreo_autofill_match_with, formOrigin.getPrettyIdentifier(applicationContext) ) - model.searchResult - .flowWithLifecycle(lifecycle) - .onEach { result -> - val list = result.passwordItems - (rvPassword.adapter as SearchableRepositoryAdapter).submitList(list) { - rvPassword.scrollToPosition(0) - } - // Switch RecyclerView out for a "no results" message if the new list is empty and - // the message is not yet shown (and vice versa). - if ( - (list.isEmpty() && rvPasswordSwitcher.nextView.id == rvPasswordEmpty.id) || - (list.isNotEmpty() && rvPasswordSwitcher.nextView.id == rvPassword.id) - ) { - rvPasswordSwitcher.showNext() - } - } - .launchIn(lifecycleScope) + lifecycleScope.launch { handleSearchResults() } + } + } + + private suspend fun handleSearchResults() { + model.searchResult.collect { result -> + val list = result.passwordItems + (binding.rvPassword.adapter as SearchableRepositoryAdapter).submitList(list) { + binding.rvPassword.scrollToPosition(0) + } + // Switch RecyclerView out for a "no results" message if the new list is empty and + // the message is not yet shown (and vice versa). + if ( + (list.isEmpty() && binding.rvPasswordSwitcher.nextView.id == binding.rvPasswordEmpty.id) || + (list.isNotEmpty() && binding.rvPasswordSwitcher.nextView.id == binding.rvPassword.id) + ) { + binding.rvPasswordSwitcher.showNext() + } } } diff --git a/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt index 4de3c120..56ba382c 100644 --- a/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt +++ b/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt @@ -36,8 +36,6 @@ import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import logcat.LogPriority.ERROR @@ -268,7 +266,7 @@ class DecryptActivity : BasePgpActivity() { binding.recyclerView.itemAnimator = null if (entry.hasTotp()) { - entry.totp.onEach(adapter::updateOTPCode).launchIn(lifecycleScope) + entry.totp.collect(adapter::updateOTPCode) } } diff --git a/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt index 8d5cdb11..f724ec3c 100644 --- a/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt @@ -56,7 +56,6 @@ import java.io.ByteArrayOutputStream import java.io.File import java.io.IOException import javax.inject.Inject -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import logcat.LogPriority.ERROR @@ -355,10 +354,10 @@ class PasswordCreationActivity : BasePgpActivity() { else -> "$fullPath/$editName.gpg" } - lifecycleScope.launch(Dispatchers.Main) { + lifecycleScope.launch(dispatcherProvider.main()) { runCatching { val result = - withContext(Dispatchers.IO) { + withContext(dispatcherProvider.io()) { val outputStream = ByteArrayOutputStream() repository.encrypt(gpgIdentifiers, content.byteInputStream(), outputStream) outputStream @@ -380,7 +379,7 @@ class PasswordCreationActivity : BasePgpActivity() { return@runCatching } - withContext(Dispatchers.IO) { file.writeBytes(result.toByteArray()) } + withContext(dispatcherProvider.io()) { file.writeBytes(result.toByteArray()) } // associate the new password name with the last name's timestamp in // history diff --git a/app/src/main/java/app/passwordstore/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt b/app/src/main/java/app/passwordstore/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt index bb3b6f6f..e158a4c1 100644 --- a/app/src/main/java/app/passwordstore/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt +++ b/app/src/main/java/app/passwordstore/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt @@ -26,9 +26,8 @@ import app.passwordstore.util.settings.PreferenceKeys.DICEWARE_SEPARATOR import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject -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 @AndroidEntryPoint @@ -47,12 +46,13 @@ class DicewarePasswordGeneratorDialogFragment : DialogFragment() { binding.passwordLengthText.setText(prefs.getInt(DICEWARE_LENGTH, 5).toString()) binding.passwordText.typeface = Typeface.MONOSPACE - merge( - binding.passwordLengthText.afterTextChanges(), - binding.passwordSeparatorText.afterTextChanges(), - ) - .onEach { generatePassword(binding) } - .launchIn(lifecycleScope) + lifecycleScope.launch { + merge( + binding.passwordLengthText.afterTextChanges(), + binding.passwordSeparatorText.afterTextChanges(), + ) + .collect { _ -> generatePassword(binding) } + } return builder .run { setTitle(R.string.pwgen_title) 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 a7827e93..a1c33e3f 100644 --- a/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt +++ b/app/src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt @@ -18,6 +18,7 @@ import app.passwordstore.data.password.PasswordItem import app.passwordstore.databinding.PasswordRecyclerViewBinding import app.passwordstore.ui.adapters.PasswordItemRecyclerAdapter import app.passwordstore.ui.passwords.PasswordStore +import app.passwordstore.util.coroutines.DispatcherProvider import app.passwordstore.util.extensions.viewBinding import app.passwordstore.util.viewmodel.ListMode import app.passwordstore.util.viewmodel.SearchableRepositoryViewModel @@ -25,6 +26,7 @@ import com.github.michaelbull.result.onFailure 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 me.zhanghai.android.fastscroll.FastScrollerBuilder @@ -32,6 +34,7 @@ import me.zhanghai.android.fastscroll.FastScrollerBuilder @AndroidEntryPoint class SelectFolderFragment : Fragment(R.layout.password_recycler_view) { + @Inject lateinit var dispatcherProvider: DispatcherProvider private val binding by viewBinding(PasswordRecyclerViewBinding::bind) private lateinit var recyclerAdapter: PasswordItemRecyclerAdapter private lateinit var listener: OnFragmentInteractionListener @@ -42,7 +45,7 @@ class SelectFolderFragment : Fragment(R.layout.password_recycler_view) { super.onViewCreated(view, savedInstanceState) binding.fab.hide() recyclerAdapter = - PasswordItemRecyclerAdapter(lifecycleScope).onItemClicked { _, item -> + PasswordItemRecyclerAdapter(lifecycleScope, dispatcherProvider).onItemClicked { _, item -> listener.onFragmentInteraction(item) } binding.passRecycler.apply { diff --git a/app/src/main/java/app/passwordstore/ui/git/base/BaseGitActivity.kt b/app/src/main/java/app/passwordstore/ui/git/base/BaseGitActivity.kt index 8ae2da0f..54a26365 100644 --- a/app/src/main/java/app/passwordstore/ui/git/base/BaseGitActivity.kt +++ b/app/src/main/java/app/passwordstore/ui/git/base/BaseGitActivity.kt @@ -9,6 +9,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.edit import app.passwordstore.R import app.passwordstore.injection.prefs.GitPreferences +import app.passwordstore.util.coroutines.DispatcherProvider import app.passwordstore.util.extensions.sharedPrefs import app.passwordstore.util.git.ErrorMessages import app.passwordstore.util.git.operation.BreakOutOfDetached @@ -27,7 +28,6 @@ import com.github.michaelbull.result.mapError import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import logcat.asLog import logcat.logcat @@ -55,6 +55,7 @@ abstract class BaseGitActivity : AppCompatActivity() { } @Inject lateinit var gitSettings: GitSettings + @Inject lateinit var dispatcherProvider: DispatcherProvider @GitPreferences @Inject lateinit var gitPrefs: SharedPreferences /** @@ -105,7 +106,7 @@ abstract class BaseGitActivity : AppCompatActivity() { gitPrefs.edit { remove(PreferenceKeys.HTTPS_PASSWORD) } sharedPrefs.edit { remove(PreferenceKeys.SSH_OPENKEYSTORE_KEYID) } logcat { error.asLog() } - withContext(Dispatchers.Main) { + withContext(dispatcherProvider.main()) { MaterialAlertDialogBuilder(this@BaseGitActivity).run { setTitle(resources.getString(R.string.jgit_error_dialog_title)) setMessage(ErrorMessages[error]) 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 0fab992f..5e2bafb9 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 @@ -30,7 +30,6 @@ import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.runCatching import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import logcat.LogPriority.ERROR @@ -242,7 +241,7 @@ class GitServerConfigActivity : BaseGitActivity() { message = getString(R.string.delete_directory_progress_text), length = Snackbar.LENGTH_INDEFINITE ) - withContext(Dispatchers.IO) { + withContext(dispatcherProvider.io()) { localDir.deleteRecursively() localDir.mkdirs() } 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 65570bcc..d6bcce11 100644 --- a/app/src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt +++ b/app/src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt @@ -33,6 +33,7 @@ import app.passwordstore.ui.dialogs.ItemCreationBottomSheet import app.passwordstore.ui.git.base.BaseGitActivity import app.passwordstore.ui.git.config.GitServerConfigActivity import app.passwordstore.ui.util.OnOffItemAnimator +import app.passwordstore.util.coroutines.DispatcherProvider import app.passwordstore.util.extensions.base64 import app.passwordstore.util.extensions.getString import app.passwordstore.util.extensions.sharedPrefs @@ -59,6 +60,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { @Inject lateinit var gitSettings: GitSettings @Inject lateinit var shortcutHandler: ShortcutHandler + @Inject lateinit var dispatcherProvider: DispatcherProvider @Inject @SettingsPreferences lateinit var prefs: SharedPreferences private lateinit var recyclerAdapter: PasswordItemRecyclerAdapter private lateinit var listener: OnFragmentInteractionListener @@ -139,7 +141,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { } recyclerAdapter = - PasswordItemRecyclerAdapter(lifecycleScope) + PasswordItemRecyclerAdapter(lifecycleScope, dispatcherProvider) .onItemClicked { _, item -> listener.onFragmentInteraction(item) } .onSelectionChanged { selection -> // In order to not interfere with drag selection, we disable the 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 4c35d6d0..80b08b77 100644 --- a/app/src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt +++ b/app/src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt @@ -55,7 +55,6 @@ import dagger.hilt.android.AndroidEntryPoint import java.io.File import java.lang.Character.UnicodeBlock import javax.inject.Inject -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import logcat.LogPriority.ERROR @@ -94,7 +93,7 @@ class PasswordStore : BaseGitActivity() { logcat { "Moving passwords to ${intentData.getStringExtra("SELECTED_FOLDER_PATH")}" } logcat { filesToMove.joinToString(", ") } - lifecycleScope.launch(Dispatchers.IO) { + lifecycleScope.launch(dispatcherProvider.io()) { for (file in filesToMove) { val source = File(file) if (!source.exists()) { @@ -107,7 +106,7 @@ class PasswordStore : BaseGitActivity() { val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) if (destinationFile.exists()) { logcat(ERROR) { "Trying to move a file that already exists." } - withContext(Dispatchers.Main) { + withContext(dispatcherProvider.main()) { MaterialAlertDialogBuilder(this@PasswordStore) .setTitle(resources.getString(R.string.password_exists_title)) .setMessage( @@ -118,13 +117,13 @@ class PasswordStore : BaseGitActivity() { ) ) .setPositiveButton(R.string.dialog_ok) { _, _ -> - launch(Dispatchers.IO) { moveFile(source, destinationFile) } + launch(dispatcherProvider.io()) { moveFile(source, destinationFile) } } .setNegativeButton(R.string.dialog_cancel, null) .show() } } else { - launch(Dispatchers.IO) { moveFile(source, destinationFile) } + launch(dispatcherProvider.io()) { moveFile(source, destinationFile) } } } when (filesToMove.size) { @@ -134,7 +133,7 @@ class PasswordStore : BaseGitActivity() { val sourceLongName = getLongName(requireNotNull(source.parent), repositoryPath, basename) val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) - withContext(Dispatchers.Main) { + withContext(dispatcherProvider.main()) { commitChange( resources.getString( R.string.git_commit_move_text, @@ -147,7 +146,7 @@ class PasswordStore : BaseGitActivity() { else -> { val repoDir = PasswordRepository.getRepositoryDirectory().absolutePath val relativePath = getRelativePath("${target.absolutePath}/", repoDir) - withContext(Dispatchers.Main) { + withContext(dispatcherProvider.main()) { commitChange( resources.getString(R.string.git_commit_move_multiple_text, relativePath), ) @@ -493,7 +492,7 @@ class PasswordStore : BaseGitActivity() { !newCategory.isInsideRepository() -> renameCategory(oldCategory, CategoryRenameError.DestinationOutsideRepo) else -> - lifecycleScope.launch(Dispatchers.IO) { + lifecycleScope.launch(dispatcherProvider.io()) { moveFile(oldCategory.file, newCategory) // associate the new category with the last category's timestamp in @@ -508,7 +507,7 @@ class PasswordStore : BaseGitActivity() { } } - withContext(Dispatchers.Main) { + withContext(dispatcherProvider.main()) { commitChange( resources.getString( R.string.git_commit_move_text, @@ -573,7 +572,7 @@ class PasswordStore : BaseGitActivity() { } if (!source.renameTo(destinationFile)) { logcat(ERROR) { "Something went wrong while moving $source to $destinationFile." } - withContext(Dispatchers.Main) { + withContext(dispatcherProvider.main()) { MaterialAlertDialogBuilder(this@PasswordStore) .setTitle(R.string.password_move_error_title) .setMessage(getString(R.string.password_move_error_message, source, destinationFile)) diff --git a/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt b/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt index 68267fff..790751f9 100644 --- a/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt +++ b/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt @@ -20,6 +20,7 @@ import app.passwordstore.injection.prefs.GitPreferences import app.passwordstore.ssh.SSHKeyAlgorithm import app.passwordstore.util.auth.BiometricAuthenticator import app.passwordstore.util.auth.BiometricAuthenticator.Result +import app.passwordstore.util.coroutines.DispatcherProvider import app.passwordstore.util.extensions.keyguardManager import app.passwordstore.util.extensions.viewBinding import app.passwordstore.util.ssh.SSHFacade @@ -30,7 +31,6 @@ import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -41,6 +41,7 @@ class SshKeyGenActivity : AppCompatActivity() { private val binding by viewBinding(ActivitySshKeygenBinding::inflate) @GitPreferences @Inject lateinit var gitPrefs: SharedPreferences @Inject lateinit var sshFacade: SSHFacade + @Inject lateinit var dispatcherProvider: DispatcherProvider override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -107,12 +108,12 @@ class SshKeyGenActivity : AppCompatActivity() { } binding.generate.text = getString(R.string.ssh_key_gen_generating_progress) val result = runCatching { - withContext(Dispatchers.IO) { + withContext(dispatcherProvider.io()) { val requireAuthentication = binding.keyRequireAuthentication.isChecked if (requireAuthentication) { val result = - withContext(Dispatchers.Main) { - suspendCoroutine { cont -> + withContext(dispatcherProvider.main()) { + suspendCoroutine { cont -> BiometricAuthenticator.authenticate( this@SshKeyGenActivity, R.string.biometric_prompt_title_ssh_keygen diff --git a/app/src/main/java/app/passwordstore/util/git/GitCommandExecutor.kt b/app/src/main/java/app/passwordstore/util/git/GitCommandExecutor.kt index 1fdcf24e..4299d968 100644 --- a/app/src/main/java/app/passwordstore/util/git/GitCommandExecutor.kt +++ b/app/src/main/java/app/passwordstore/util/git/GitCommandExecutor.kt @@ -8,6 +8,7 @@ package app.passwordstore.util.git import android.widget.Toast import androidx.fragment.app.FragmentActivity import app.passwordstore.R +import app.passwordstore.util.coroutines.DispatcherProvider import app.passwordstore.util.extensions.snackbar import app.passwordstore.util.extensions.unsafeLazy import app.passwordstore.util.git.GitException.PullException @@ -21,7 +22,6 @@ import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.android.EntryPointAccessors import dagger.hilt.components.SingletonComponent -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.eclipse.jgit.api.CommitCommand import org.eclipse.jgit.api.PullCommand @@ -44,6 +44,7 @@ class GitCommandExecutor( suspend fun execute(): Result { val gitSettings = hiltEntryPoint.gitSettings() + val dispatcherProvider = hiltEntryPoint.dispatcherProvider() val snackbar = activity.snackbar( message = activity.resources.getString(R.string.git_operation_running), @@ -55,13 +56,13 @@ class GitCommandExecutor( for (command in operation.commands) { when (command) { is StatusCommand -> { - val res = withContext(Dispatchers.IO) { command.call() } + val res = withContext(dispatcherProvider.io()) { command.call() } nbChanges = res.uncommittedChanges.size } is CommitCommand -> { // the previous status will eventually be used to avoid a commit if (nbChanges > 0) { - withContext(Dispatchers.IO) { + withContext(dispatcherProvider.io()) { val name = gitSettings.authorName.ifEmpty { "root" } val email = gitSettings.authorEmail.ifEmpty { "localhost" } val identity = PersonIdent(name, email) @@ -70,7 +71,7 @@ class GitCommandExecutor( } } is PullCommand -> { - val result = withContext(Dispatchers.IO) { command.call() } + val result = withContext(dispatcherProvider.io()) { command.call() } if (result.rebaseResult != null) { if (!result.rebaseResult.status.isSuccessful) { throw PullException.PullRebaseFailed @@ -82,7 +83,7 @@ class GitCommandExecutor( } } is PushCommand -> { - val results = withContext(Dispatchers.IO) { command.call() } + val results = withContext(dispatcherProvider.io()) { command.call() } for (result in results) { // Code imported (modified) from Gerrit PushOp, license Apache v2 for (rru in result.remoteUpdates) { @@ -102,7 +103,7 @@ class GitCommandExecutor( } } RemoteRefUpdate.Status.UP_TO_DATE -> { - withContext(Dispatchers.Main) { + withContext(dispatcherProvider.main()) { Toast.makeText( activity, activity.applicationContext.getString(R.string.git_push_up_to_date), @@ -117,7 +118,7 @@ class GitCommandExecutor( } } else -> { - withContext(Dispatchers.IO) { command.call() } + withContext(dispatcherProvider.io()) { command.call() } } } } @@ -130,5 +131,7 @@ class GitCommandExecutor( interface GitCommandExecutorEntryPoint { fun gitSettings(): GitSettings + + fun dispatcherProvider(): DispatcherProvider } } 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 396e3c7d..298ae4b1 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 @@ -17,6 +17,7 @@ import app.passwordstore.util.auth.BiometricAuthenticator.Result.Cancelled import app.passwordstore.util.auth.BiometricAuthenticator.Result.Failure import app.passwordstore.util.auth.BiometricAuthenticator.Result.Retry import app.passwordstore.util.auth.BiometricAuthenticator.Result.Success +import app.passwordstore.util.coroutines.DispatcherProvider import app.passwordstore.util.git.GitCommandExecutor import app.passwordstore.util.git.sshj.SshAuthMethod import app.passwordstore.util.git.sshj.SshjSessionFactory @@ -34,7 +35,6 @@ import dagger.hilt.android.EntryPointAccessors import dagger.hilt.components.SingletonComponent import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import logcat.LogPriority.ERROR import logcat.asLog @@ -176,8 +176,8 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { if (sshFacade.keyExists()) { if (sshFacade.needsAuthentication()) { val result = - withContext(Dispatchers.Main) { - suspendCoroutine { cont -> + withContext(hiltEntryPoint.dispatcherProvider().main()) { + suspendCoroutine { cont -> BiometricAuthenticator.authenticate( callingActivity, R.string.biometric_prompt_title_ssh_auth @@ -233,7 +233,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { open fun preExecute() = true private suspend fun postExecute() { - withContext(Dispatchers.IO) { sshSessionFactory?.close() } + withContext(hiltEntryPoint.dispatcherProvider().io()) { sshSessionFactory?.close() } } companion object { @@ -246,5 +246,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { @InstallIn(SingletonComponent::class) interface GitOperationEntryPoint { fun sshFacade(): SSHFacade + + fun dispatcherProvider(): DispatcherProvider } } 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 787b3e2d..54858530 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 @@ -57,9 +57,7 @@ abstract class InteractivePasswordFinder : PasswordFinder { final override fun reqPassword(resource: Resource<*>?): CharArray { val password = - runBlocking(Dispatchers.Main) { - suspendCoroutine { cont -> askForPassword(cont, isRetry) } - } + runBlocking(Dispatchers.Main) { suspendCoroutine { cont -> askForPassword(cont, isRetry) } } isRetry = true return password?.toCharArray() ?: throw SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER) } diff --git a/app/src/main/java/app/passwordstore/util/services/ClipboardService.kt b/app/src/main/java/app/passwordstore/util/services/ClipboardService.kt index dccbd865..47cf5926 100644 --- a/app/src/main/java/app/passwordstore/util/services/ClipboardService.kt +++ b/app/src/main/java/app/passwordstore/util/services/ClipboardService.kt @@ -17,11 +17,13 @@ import android.os.IBinder import androidx.core.app.NotificationCompat import androidx.core.content.getSystemService import app.passwordstore.R +import app.passwordstore.util.coroutines.DispatcherProvider import app.passwordstore.util.extensions.clipboard import app.passwordstore.util.extensions.sharedPrefs import app.passwordstore.util.settings.PreferenceKeys +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel import kotlinx.coroutines.delay @@ -30,9 +32,11 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import logcat.logcat +@AndroidEntryPoint class ClipboardService : Service() { - private val scope = CoroutineScope(Job() + Dispatchers.Main) + @Inject lateinit var dispatcherProvider: DispatcherProvider + private val scope = CoroutineScope(Job()) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (intent != null) { @@ -52,8 +56,8 @@ class ClipboardService : Service() { createNotification(time) scope.launch { - withContext(Dispatchers.IO) { startTimer(time) } - withContext(Dispatchers.Main) { + withContext(dispatcherProvider.io()) { startTimer(time) } + withContext(dispatcherProvider.main()) { clearClipboard() stopForeground(STOP_FOREGROUND_REMOVE) stopSelf() @@ -86,7 +90,7 @@ class ClipboardService : Service() { val clip = ClipData.newPlainText("pgp_handler_result_pm", "") clipboard.setPrimaryClip(clip) if (deepClear) { - withContext(Dispatchers.IO) { + withContext(dispatcherProvider.io()) { repeat(CLIPBOARD_CLEAR_COUNT) { val count = (it * 500).toString() clipboard.setPrimaryClip(ClipData.newPlainText(count, count)) diff --git a/app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt b/app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt index fcc62ab7..edb82cc0 100644 --- a/app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt +++ b/app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt @@ -38,7 +38,6 @@ import java.util.Locale import java.util.Stack import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -401,7 +400,9 @@ open class SearchableRepositoryAdapter( private val layoutRes: Int, private val viewHolderCreator: (view: View) -> T, private val coroutineScope: CoroutineScope, - private val viewHolderBinder: suspend T.(item: PasswordItem) -> Unit, + private val dispatcherProvider: DispatcherProvider, + private val viewHolderBinder: + suspend T.(item: PasswordItem, dispatcherProvider: DispatcherProvider) -> Unit, ) : ListAdapter(PasswordItemDiffCallback), PopupTextProvider { fun > makeSelectable( @@ -482,7 +483,9 @@ open class SearchableRepositoryAdapter( final override fun onBindViewHolder(holder: T, position: Int) { val item = getItem(position) holder.apply { - coroutineScope.launch(Dispatchers.Main.immediate) { viewHolderBinder.invoke(holder, item) } + coroutineScope.launch(dispatcherProvider.mainImmediate()) { + viewHolderBinder.invoke(holder, item, dispatcherProvider) + } selectionTracker?.let { itemView.isSelected = it.isSelected(item.stableId) } itemView.setOnClickListener { // Do not emit custom click events while the user is selecting items. diff --git a/app/src/nonFree/java/app/passwordstore/autofill/oreo/ui/AutofillSmsActivity.kt b/app/src/nonFree/java/app/passwordstore/autofill/oreo/ui/AutofillSmsActivity.kt index 7641ad95..61b42342 100644 --- a/app/src/nonFree/java/app/passwordstore/autofill/oreo/ui/AutofillSmsActivity.kt +++ b/app/src/nonFree/java/app/passwordstore/autofill/oreo/ui/AutofillSmsActivity.kt @@ -18,6 +18,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import app.passwordstore.databinding.ActivityOreoAutofillSmsBinding import app.passwordstore.util.autofill.AutofillResponseBuilder +import app.passwordstore.util.coroutines.DispatcherProvider import app.passwordstore.util.extensions.viewBinding import com.github.androidpasswordstore.autofillparser.AutofillAction import com.github.androidpasswordstore.autofillparser.Credentials @@ -29,11 +30,12 @@ import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.api.ResolvableApiException import com.google.android.gms.tasks.Task +import dagger.hilt.android.AndroidEntryPoint import java.util.concurrent.ExecutionException +import javax.inject.Inject import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import logcat.LogPriority.ERROR @@ -51,8 +53,11 @@ suspend fun Task.suspendableAwait() = } } +@AndroidEntryPoint class AutofillSmsActivity : AppCompatActivity() { + @Inject lateinit var dispatcherProvider: DispatcherProvider + companion object { private var fillOtpFromSmsRequestCode = 1 @@ -129,14 +134,16 @@ class AutofillSmsActivity : AppCompatActivity() { private suspend fun waitForSms() { val smsClient = SmsCodeRetriever.getAutofillClient(this@AutofillSmsActivity) runCatching { - withContext(Dispatchers.IO) { smsClient.startSmsCodeRetriever().suspendableAwait() } + withContext(dispatcherProvider.io()) { + smsClient.startSmsCodeRetriever().suspendableAwait() + } } .onFailure { e -> if (e is ResolvableApiException) { e.startResolutionForResult(this@AutofillSmsActivity, 1) } else { logcat(ERROR) { e.asLog() } - withContext(Dispatchers.Main) { finish() } + withContext(dispatcherProvider.main()) { finish() } } } } -- cgit v1.2.3