fix: clear violations of RawDispatcherUse

This commit is contained in:
Harsh Shandilya 2023-06-01 20:50:52 +05:30
parent 496f421f17
commit 3a4e827f1a
No known key found for this signature in database
20 changed files with 124 additions and 271 deletions

View file

@ -29,183 +29,7 @@
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt"
line="104"
column="19"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt"
line="132"
column="19"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) { finish() }"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt"
line="139"
column="17"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/git/base/BaseGitActivity.kt"
line="108"
column="19"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" private val scope = CoroutineScope(Job() + Dispatchers.Main)"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/util/services/ClipboardService.kt"
line="35"
column="46"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/util/services/ClipboardService.kt"
line="56"
column="25"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/util/git/GitCommandExecutor.kt"
line="105"
column="35"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/util/git/operation/GitOperation.kt"
line="179"
column="27"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" lifecycleScope.launch(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt"
line="358"
column="29"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt"
line="110"
column="25"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt"
line="137"
column="25"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt"
line="150"
column="25"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt"
line="511"
column="29"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/passwords/PasswordStore.kt"
line="576"
column="19"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" coroutineScope.launch(Dispatchers.Main.immediate) { viewHolderBinder.invoke(holder, item) }"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt"
line="485"
column="29"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt"
line="114"
column="25"/>
</issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" runBlocking(Dispatchers.Main) {"
errorLine1=" runBlocking(Dispatchers.Main) { suspendCoroutine { cont -> askForPassword(cont, isRetry) } }"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt"
@ -220,7 +44,7 @@
errorLine2=" ^">
<location
file="src/main/java/app/passwordstore/ui/autofill/AutofillFilterView.kt"
line="194"
line="199"
column="7"/>
</issue>
@ -275,7 +99,7 @@
errorLine2=" ^">
<location
file="src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt"
line="180"
line="182"
column="5"/>
</issue>
@ -297,7 +121,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt"
line="298"
line="297"
column="33"/>
</issue>
@ -308,7 +132,7 @@
errorLine2=" ^">
<location
file="src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt"
line="59"
line="62"
column="5"/>
</issue>

View file

@ -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<PasswordItemRecyclerAdapter.PasswordItemViewHolder>(
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<String>? = 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<Result> { cont ->
withContext(dispatcherProvider.main()) {
suspendCoroutine { cont ->
BiometricAuthenticator.authenticate(
this@SshKeyGenActivity,
R.string.biometric_prompt_title_ssh_keygen

View file

@ -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<Unit, Throwable> {
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
}
}

View file

@ -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<BiometricAuthenticator.Result> { 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
}
}

View file

@ -57,9 +57,7 @@ abstract class InteractivePasswordFinder : PasswordFinder {
final override fun reqPassword(resource: Resource<*>?): CharArray {
val password =
runBlocking(Dispatchers.Main) {
suspendCoroutine<String?> { cont -> askForPassword(cont, isRetry) }
}
runBlocking(Dispatchers.Main) { suspendCoroutine { cont -> askForPassword(cont, isRetry) } }
isRetry = true
return password?.toCharArray() ?: throw SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER)
}

View file

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

View file

@ -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<T : RecyclerView.ViewHolder>(
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<PasswordItem, T>(PasswordItemDiffCallback), PopupTextProvider {
fun <T : ItemDetailsLookup<String>> makeSelectable(
@ -482,7 +483,9 @@ open class SearchableRepositoryAdapter<T : RecyclerView.ViewHolder>(
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.

View file

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

View file

@ -14,6 +14,8 @@ public interface DispatcherProvider {
public fun main(): CoroutineDispatcher = Dispatchers.Main
public fun mainImmediate(): CoroutineDispatcher = Dispatchers.Main.immediate
public fun default(): CoroutineDispatcher = Dispatchers.Default
public fun io(): CoroutineDispatcher = Dispatchers.IO