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 <issue
id="RawDispatchersUse" id="RawDispatchersUse"
message="Use SlackDispatchers." message="Use SlackDispatchers."
errorLine1=" withContext(Dispatchers.Main) {" errorLine1=" runBlocking(Dispatchers.Main) { suspendCoroutine { cont -> askForPassword(cont, isRetry) } }"
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) {"
errorLine2=" ~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~">
<location <location
file="src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt" file="src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt"
@ -220,7 +44,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/java/app/passwordstore/ui/autofill/AutofillFilterView.kt" file="src/main/java/app/passwordstore/ui/autofill/AutofillFilterView.kt"
line="194" line="199"
column="7"/> column="7"/>
</issue> </issue>
@ -275,7 +99,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt" file="src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt"
line="180" line="182"
column="5"/> column="5"/>
</issue> </issue>
@ -297,7 +121,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location <location
file="src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt" file="src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt"
line="298" line="297"
column="33"/> column="33"/>
</issue> </issue>
@ -308,7 +132,7 @@
errorLine2=" ^"> errorLine2=" ^">
<location <location
file="src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt" file="src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt"
line="59" line="62"
column="5"/> column="5"/>
</issue> </issue>

View file

@ -15,17 +15,21 @@ import androidx.recyclerview.selection.Selection
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import app.passwordstore.R import app.passwordstore.R
import app.passwordstore.data.password.PasswordItem import app.passwordstore.data.password.PasswordItem
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.viewmodel.SearchableRepositoryAdapter import app.passwordstore.util.viewmodel.SearchableRepositoryAdapter
import app.passwordstore.util.viewmodel.stableId import app.passwordstore.util.viewmodel.stableId
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
open class PasswordItemRecyclerAdapter(coroutineScope: CoroutineScope) : open class PasswordItemRecyclerAdapter(
coroutineScope: CoroutineScope,
dispatcherProvider: DispatcherProvider,
) :
SearchableRepositoryAdapter<PasswordItemRecyclerAdapter.PasswordItemViewHolder>( SearchableRepositoryAdapter<PasswordItemRecyclerAdapter.PasswordItemViewHolder>(
R.layout.password_row_layout, R.layout.password_row_layout,
::PasswordItemViewHolder, ::PasswordItemViewHolder,
coroutineScope, coroutineScope,
dispatcherProvider,
PasswordItemViewHolder::bind, PasswordItemViewHolder::bind,
) { ) {
@ -52,7 +56,7 @@ open class PasswordItemRecyclerAdapter(coroutineScope: CoroutineScope) :
private val folderIndicator: AppCompatImageView = itemView.findViewById(R.id.folder_indicator) private val folderIndicator: AppCompatImageView = itemView.findViewById(R.id.folder_indicator)
var itemDetails: ItemDetailsLookup.ItemDetails<String>? = null 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 parentPath = item.fullPathToParent.replace("(^/)|(/$)".toRegex(), "")
val source = val source =
if (parentPath.isNotEmpty()) { if (parentPath.isNotEmpty()) {
@ -66,7 +70,7 @@ open class PasswordItemRecyclerAdapter(coroutineScope: CoroutineScope) :
if (item.type == PasswordItem.TYPE_CATEGORY) { if (item.type == PasswordItem.TYPE_CATEGORY) {
folderIndicator.visibility = View.VISIBLE folderIndicator.visibility = View.VISIBLE
val count = val count =
withContext(Dispatchers.IO) { withContext(dispatcherProvider.io()) {
item.file.listFiles { path -> path.isDirectory || path.extension == "gpg" }?.size ?: 0 item.file.listFiles { path -> path.isDirectory || path.extension == "gpg" }?.size ?: 0
} }
childCount.visibility = if (count > 0) View.VISIBLE else View.GONE 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.ByteArrayOutputStream
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -101,7 +100,7 @@ class AutofillDecryptActivity : BasePgpActivity() {
private fun askPassphrase(filePath: String, clientState: Bundle, action: AutofillAction) { private fun askPassphrase(filePath: String, clientState: Bundle, action: AutofillAction) {
val dialog = PasswordDialog() val dialog = PasswordDialog()
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.Main) { withContext(dispatcherProvider.main()) {
dialog.password.collectLatest { value -> dialog.password.collectLatest { value ->
if (value != null) { if (value != null) {
decrypt(File(filePath), clientState, action, value) decrypt(File(filePath), clientState, action, value)
@ -129,14 +128,14 @@ class AutofillDecryptActivity : BasePgpActivity() {
clientState, clientState,
action action
) )
withContext(Dispatchers.Main) { withContext(dispatcherProvider.main()) {
setResult( setResult(
RESULT_OK, RESULT_OK,
Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) } 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? { private suspend fun decryptCredential(file: File, password: String): Credentials? {
@ -148,7 +147,7 @@ class AutofillDecryptActivity : BasePgpActivity() {
} }
.onSuccess { encryptedInput -> .onSuccess { encryptedInput ->
runCatching { runCatching {
withContext(Dispatchers.IO) { withContext(dispatcherProvider.io()) {
val outputStream = ByteArrayOutputStream() val outputStream = ByteArrayOutputStream()
repository.decrypt( repository.decrypt(
password, password,

View file

@ -20,7 +20,6 @@ import androidx.core.text.bold
import androidx.core.text.buildSpannedString import androidx.core.text.buildSpannedString
import androidx.core.text.underline import androidx.core.text.underline
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import app.passwordstore.R import app.passwordstore.R
@ -29,6 +28,7 @@ import app.passwordstore.databinding.ActivityOreoAutofillFilterBinding
import app.passwordstore.util.autofill.AutofillMatcher import app.passwordstore.util.autofill.AutofillMatcher
import app.passwordstore.util.autofill.AutofillPreferences import app.passwordstore.util.autofill.AutofillPreferences
import app.passwordstore.util.autofill.DirectoryStructure import app.passwordstore.util.autofill.DirectoryStructure
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.viewBinding import app.passwordstore.util.extensions.viewBinding
import app.passwordstore.util.viewmodel.FilterMode import app.passwordstore.util.viewmodel.FilterMode
import app.passwordstore.util.viewmodel.ListMode import app.passwordstore.util.viewmodel.ListMode
@ -37,14 +37,17 @@ import app.passwordstore.util.viewmodel.SearchableRepositoryAdapter
import app.passwordstore.util.viewmodel.SearchableRepositoryViewModel import app.passwordstore.util.viewmodel.SearchableRepositoryViewModel
import com.github.androidpasswordstore.autofillparser.FormOrigin import com.github.androidpasswordstore.autofillparser.FormOrigin
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn import javax.inject.Inject
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import logcat.LogPriority.ERROR import logcat.LogPriority.ERROR
import logcat.logcat import logcat.logcat
@AndroidEntryPoint @AndroidEntryPoint
class AutofillFilterView : AppCompatActivity() { class AutofillFilterView : AppCompatActivity() {
@Inject lateinit var dispatcherProvider: DispatcherProvider
companion object { companion object {
private const val HEIGHT_PERCENTAGE = 0.9 private const val HEIGHT_PERCENTAGE = 0.9
@ -142,7 +145,8 @@ class AutofillFilterView : AppCompatActivity() {
R.layout.oreo_autofill_filter_row, R.layout.oreo_autofill_filter_row,
::PasswordViewHolder, ::PasswordViewHolder,
lifecycleScope, lifecycleScope,
) { item -> dispatcherProvider,
) { item, _ ->
val file = item.file.relativeTo(item.rootDir) val file = item.file.relativeTo(item.rootDir)
val pathToIdentifier = directoryStructure.getPathToIdentifierFor(file) val pathToIdentifier = directoryStructure.getPathToIdentifierFor(file)
val identifier = directoryStructure.getIdentifierFor(file) val identifier = directoryStructure.getIdentifierFor(file)
@ -191,23 +195,24 @@ class AutofillFilterView : AppCompatActivity() {
R.string.oreo_autofill_match_with, R.string.oreo_autofill_match_with,
formOrigin.getPrettyIdentifier(applicationContext) formOrigin.getPrettyIdentifier(applicationContext)
) )
model.searchResult lifecycleScope.launch { handleSearchResults() }
.flowWithLifecycle(lifecycle) }
.onEach { result -> }
val list = result.passwordItems
(rvPassword.adapter as SearchableRepositoryAdapter).submitList(list) { private suspend fun handleSearchResults() {
rvPassword.scrollToPosition(0) model.searchResult.collect { result ->
} val list = result.passwordItems
// Switch RecyclerView out for a "no results" message if the new list is empty and (binding.rvPassword.adapter as SearchableRepositoryAdapter).submitList(list) {
// the message is not yet shown (and vice versa). binding.rvPassword.scrollToPosition(0)
if ( }
(list.isEmpty() && rvPasswordSwitcher.nextView.id == rvPasswordEmpty.id) || // Switch RecyclerView out for a "no results" message if the new list is empty and
(list.isNotEmpty() && rvPasswordSwitcher.nextView.id == rvPassword.id) // the message is not yet shown (and vice versa).
) { if (
rvPasswordSwitcher.showNext() (list.isEmpty() && binding.rvPasswordSwitcher.nextView.id == binding.rvPasswordEmpty.id) ||
} (list.isNotEmpty() && binding.rvPasswordSwitcher.nextView.id == binding.rvPassword.id)
} ) {
.launchIn(lifecycleScope) binding.rvPasswordSwitcher.showNext()
}
} }
} }

View file

@ -36,8 +36,6 @@ import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import logcat.LogPriority.ERROR import logcat.LogPriority.ERROR
@ -268,7 +266,7 @@ class DecryptActivity : BasePgpActivity() {
binding.recyclerView.itemAnimator = null binding.recyclerView.itemAnimator = null
if (entry.hasTotp()) { 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.File
import java.io.IOException import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import logcat.LogPriority.ERROR import logcat.LogPriority.ERROR
@ -355,10 +354,10 @@ class PasswordCreationActivity : BasePgpActivity() {
else -> "$fullPath/$editName.gpg" else -> "$fullPath/$editName.gpg"
} }
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(dispatcherProvider.main()) {
runCatching { runCatching {
val result = val result =
withContext(Dispatchers.IO) { withContext(dispatcherProvider.io()) {
val outputStream = ByteArrayOutputStream() val outputStream = ByteArrayOutputStream()
repository.encrypt(gpgIdentifiers, content.byteInputStream(), outputStream) repository.encrypt(gpgIdentifiers, content.byteInputStream(), outputStream)
outputStream outputStream
@ -380,7 +379,7 @@ class PasswordCreationActivity : BasePgpActivity() {
return@runCatching 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 // associate the new password name with the last name's timestamp in
// history // history

View file

@ -26,9 +26,8 @@ import app.passwordstore.util.settings.PreferenceKeys.DICEWARE_SEPARATOR
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.merge 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.afterTextChanges
@AndroidEntryPoint @AndroidEntryPoint
@ -47,12 +46,13 @@ class DicewarePasswordGeneratorDialogFragment : DialogFragment() {
binding.passwordLengthText.setText(prefs.getInt(DICEWARE_LENGTH, 5).toString()) binding.passwordLengthText.setText(prefs.getInt(DICEWARE_LENGTH, 5).toString())
binding.passwordText.typeface = Typeface.MONOSPACE binding.passwordText.typeface = Typeface.MONOSPACE
merge( lifecycleScope.launch {
binding.passwordLengthText.afterTextChanges(), merge(
binding.passwordSeparatorText.afterTextChanges(), binding.passwordLengthText.afterTextChanges(),
) binding.passwordSeparatorText.afterTextChanges(),
.onEach { generatePassword(binding) } )
.launchIn(lifecycleScope) .collect { _ -> generatePassword(binding) }
}
return builder return builder
.run { .run {
setTitle(R.string.pwgen_title) 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.databinding.PasswordRecyclerViewBinding
import app.passwordstore.ui.adapters.PasswordItemRecyclerAdapter import app.passwordstore.ui.adapters.PasswordItemRecyclerAdapter
import app.passwordstore.ui.passwords.PasswordStore import app.passwordstore.ui.passwords.PasswordStore
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.viewBinding import app.passwordstore.util.extensions.viewBinding
import app.passwordstore.util.viewmodel.ListMode import app.passwordstore.util.viewmodel.ListMode
import app.passwordstore.util.viewmodel.SearchableRepositoryViewModel import app.passwordstore.util.viewmodel.SearchableRepositoryViewModel
@ -25,6 +26,7 @@ import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching import com.github.michaelbull.result.runCatching
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.io.File import java.io.File
import javax.inject.Inject
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import me.zhanghai.android.fastscroll.FastScrollerBuilder import me.zhanghai.android.fastscroll.FastScrollerBuilder
@ -32,6 +34,7 @@ import me.zhanghai.android.fastscroll.FastScrollerBuilder
@AndroidEntryPoint @AndroidEntryPoint
class SelectFolderFragment : Fragment(R.layout.password_recycler_view) { class SelectFolderFragment : Fragment(R.layout.password_recycler_view) {
@Inject lateinit var dispatcherProvider: DispatcherProvider
private val binding by viewBinding(PasswordRecyclerViewBinding::bind) private val binding by viewBinding(PasswordRecyclerViewBinding::bind)
private lateinit var recyclerAdapter: PasswordItemRecyclerAdapter private lateinit var recyclerAdapter: PasswordItemRecyclerAdapter
private lateinit var listener: OnFragmentInteractionListener private lateinit var listener: OnFragmentInteractionListener
@ -42,7 +45,7 @@ class SelectFolderFragment : Fragment(R.layout.password_recycler_view) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.fab.hide() binding.fab.hide()
recyclerAdapter = recyclerAdapter =
PasswordItemRecyclerAdapter(lifecycleScope).onItemClicked { _, item -> PasswordItemRecyclerAdapter(lifecycleScope, dispatcherProvider).onItemClicked { _, item ->
listener.onFragmentInteraction(item) listener.onFragmentInteraction(item)
} }
binding.passRecycler.apply { binding.passRecycler.apply {

View file

@ -9,6 +9,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit import androidx.core.content.edit
import app.passwordstore.R import app.passwordstore.R
import app.passwordstore.injection.prefs.GitPreferences import app.passwordstore.injection.prefs.GitPreferences
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.sharedPrefs import app.passwordstore.util.extensions.sharedPrefs
import app.passwordstore.util.git.ErrorMessages import app.passwordstore.util.git.ErrorMessages
import app.passwordstore.util.git.operation.BreakOutOfDetached 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 com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import logcat.asLog import logcat.asLog
import logcat.logcat import logcat.logcat
@ -55,6 +55,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
} }
@Inject lateinit var gitSettings: GitSettings @Inject lateinit var gitSettings: GitSettings
@Inject lateinit var dispatcherProvider: DispatcherProvider
@GitPreferences @Inject lateinit var gitPrefs: SharedPreferences @GitPreferences @Inject lateinit var gitPrefs: SharedPreferences
/** /**
@ -105,7 +106,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
gitPrefs.edit { remove(PreferenceKeys.HTTPS_PASSWORD) } gitPrefs.edit { remove(PreferenceKeys.HTTPS_PASSWORD) }
sharedPrefs.edit { remove(PreferenceKeys.SSH_OPENKEYSTORE_KEYID) } sharedPrefs.edit { remove(PreferenceKeys.SSH_OPENKEYSTORE_KEYID) }
logcat { error.asLog() } logcat { error.asLog() }
withContext(Dispatchers.Main) { withContext(dispatcherProvider.main()) {
MaterialAlertDialogBuilder(this@BaseGitActivity).run { MaterialAlertDialogBuilder(this@BaseGitActivity).run {
setTitle(resources.getString(R.string.jgit_error_dialog_title)) setTitle(resources.getString(R.string.jgit_error_dialog_title))
setMessage(ErrorMessages[error]) setMessage(ErrorMessages[error])

View file

@ -30,7 +30,6 @@ import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import logcat.LogPriority.ERROR import logcat.LogPriority.ERROR
@ -242,7 +241,7 @@ class GitServerConfigActivity : BaseGitActivity() {
message = getString(R.string.delete_directory_progress_text), message = getString(R.string.delete_directory_progress_text),
length = Snackbar.LENGTH_INDEFINITE length = Snackbar.LENGTH_INDEFINITE
) )
withContext(Dispatchers.IO) { withContext(dispatcherProvider.io()) {
localDir.deleteRecursively() localDir.deleteRecursively()
localDir.mkdirs() 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.base.BaseGitActivity
import app.passwordstore.ui.git.config.GitServerConfigActivity import app.passwordstore.ui.git.config.GitServerConfigActivity
import app.passwordstore.ui.util.OnOffItemAnimator import app.passwordstore.ui.util.OnOffItemAnimator
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.base64 import app.passwordstore.util.extensions.base64
import app.passwordstore.util.extensions.getString import app.passwordstore.util.extensions.getString
import app.passwordstore.util.extensions.sharedPrefs 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 gitSettings: GitSettings
@Inject lateinit var shortcutHandler: ShortcutHandler @Inject lateinit var shortcutHandler: ShortcutHandler
@Inject lateinit var dispatcherProvider: DispatcherProvider
@Inject @SettingsPreferences lateinit var prefs: SharedPreferences @Inject @SettingsPreferences lateinit var prefs: SharedPreferences
private lateinit var recyclerAdapter: PasswordItemRecyclerAdapter private lateinit var recyclerAdapter: PasswordItemRecyclerAdapter
private lateinit var listener: OnFragmentInteractionListener private lateinit var listener: OnFragmentInteractionListener
@ -139,7 +141,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
} }
recyclerAdapter = recyclerAdapter =
PasswordItemRecyclerAdapter(lifecycleScope) PasswordItemRecyclerAdapter(lifecycleScope, dispatcherProvider)
.onItemClicked { _, item -> listener.onFragmentInteraction(item) } .onItemClicked { _, item -> listener.onFragmentInteraction(item) }
.onSelectionChanged { selection -> .onSelectionChanged { selection ->
// In order to not interfere with drag selection, we disable the // 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.io.File
import java.lang.Character.UnicodeBlock import java.lang.Character.UnicodeBlock
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import logcat.LogPriority.ERROR import logcat.LogPriority.ERROR
@ -94,7 +93,7 @@ class PasswordStore : BaseGitActivity() {
logcat { "Moving passwords to ${intentData.getStringExtra("SELECTED_FOLDER_PATH")}" } logcat { "Moving passwords to ${intentData.getStringExtra("SELECTED_FOLDER_PATH")}" }
logcat { filesToMove.joinToString(", ") } logcat { filesToMove.joinToString(", ") }
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(dispatcherProvider.io()) {
for (file in filesToMove) { for (file in filesToMove) {
val source = File(file) val source = File(file)
if (!source.exists()) { if (!source.exists()) {
@ -107,7 +106,7 @@ class PasswordStore : BaseGitActivity() {
val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
if (destinationFile.exists()) { if (destinationFile.exists()) {
logcat(ERROR) { "Trying to move a file that already exists." } logcat(ERROR) { "Trying to move a file that already exists." }
withContext(Dispatchers.Main) { withContext(dispatcherProvider.main()) {
MaterialAlertDialogBuilder(this@PasswordStore) MaterialAlertDialogBuilder(this@PasswordStore)
.setTitle(resources.getString(R.string.password_exists_title)) .setTitle(resources.getString(R.string.password_exists_title))
.setMessage( .setMessage(
@ -118,13 +117,13 @@ class PasswordStore : BaseGitActivity() {
) )
) )
.setPositiveButton(R.string.dialog_ok) { _, _ -> .setPositiveButton(R.string.dialog_ok) { _, _ ->
launch(Dispatchers.IO) { moveFile(source, destinationFile) } launch(dispatcherProvider.io()) { moveFile(source, destinationFile) }
} }
.setNegativeButton(R.string.dialog_cancel, null) .setNegativeButton(R.string.dialog_cancel, null)
.show() .show()
} }
} else { } else {
launch(Dispatchers.IO) { moveFile(source, destinationFile) } launch(dispatcherProvider.io()) { moveFile(source, destinationFile) }
} }
} }
when (filesToMove.size) { when (filesToMove.size) {
@ -134,7 +133,7 @@ class PasswordStore : BaseGitActivity() {
val sourceLongName = val sourceLongName =
getLongName(requireNotNull(source.parent), repositoryPath, basename) getLongName(requireNotNull(source.parent), repositoryPath, basename)
val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
withContext(Dispatchers.Main) { withContext(dispatcherProvider.main()) {
commitChange( commitChange(
resources.getString( resources.getString(
R.string.git_commit_move_text, R.string.git_commit_move_text,
@ -147,7 +146,7 @@ class PasswordStore : BaseGitActivity() {
else -> { else -> {
val repoDir = PasswordRepository.getRepositoryDirectory().absolutePath val repoDir = PasswordRepository.getRepositoryDirectory().absolutePath
val relativePath = getRelativePath("${target.absolutePath}/", repoDir) val relativePath = getRelativePath("${target.absolutePath}/", repoDir)
withContext(Dispatchers.Main) { withContext(dispatcherProvider.main()) {
commitChange( commitChange(
resources.getString(R.string.git_commit_move_multiple_text, relativePath), resources.getString(R.string.git_commit_move_multiple_text, relativePath),
) )
@ -493,7 +492,7 @@ class PasswordStore : BaseGitActivity() {
!newCategory.isInsideRepository() -> !newCategory.isInsideRepository() ->
renameCategory(oldCategory, CategoryRenameError.DestinationOutsideRepo) renameCategory(oldCategory, CategoryRenameError.DestinationOutsideRepo)
else -> else ->
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(dispatcherProvider.io()) {
moveFile(oldCategory.file, newCategory) moveFile(oldCategory.file, newCategory)
// associate the new category with the last category's timestamp in // 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( commitChange(
resources.getString( resources.getString(
R.string.git_commit_move_text, R.string.git_commit_move_text,
@ -573,7 +572,7 @@ class PasswordStore : BaseGitActivity() {
} }
if (!source.renameTo(destinationFile)) { if (!source.renameTo(destinationFile)) {
logcat(ERROR) { "Something went wrong while moving $source to $destinationFile." } logcat(ERROR) { "Something went wrong while moving $source to $destinationFile." }
withContext(Dispatchers.Main) { withContext(dispatcherProvider.main()) {
MaterialAlertDialogBuilder(this@PasswordStore) MaterialAlertDialogBuilder(this@PasswordStore)
.setTitle(R.string.password_move_error_title) .setTitle(R.string.password_move_error_title)
.setMessage(getString(R.string.password_move_error_message, source, destinationFile)) .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.ssh.SSHKeyAlgorithm
import app.passwordstore.util.auth.BiometricAuthenticator import app.passwordstore.util.auth.BiometricAuthenticator
import app.passwordstore.util.auth.BiometricAuthenticator.Result import app.passwordstore.util.auth.BiometricAuthenticator.Result
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.keyguardManager import app.passwordstore.util.extensions.keyguardManager
import app.passwordstore.util.extensions.viewBinding import app.passwordstore.util.extensions.viewBinding
import app.passwordstore.util.ssh.SSHFacade import app.passwordstore.util.ssh.SSHFacade
@ -30,7 +31,6 @@ import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -41,6 +41,7 @@ class SshKeyGenActivity : AppCompatActivity() {
private val binding by viewBinding(ActivitySshKeygenBinding::inflate) private val binding by viewBinding(ActivitySshKeygenBinding::inflate)
@GitPreferences @Inject lateinit var gitPrefs: SharedPreferences @GitPreferences @Inject lateinit var gitPrefs: SharedPreferences
@Inject lateinit var sshFacade: SSHFacade @Inject lateinit var sshFacade: SSHFacade
@Inject lateinit var dispatcherProvider: DispatcherProvider
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -107,12 +108,12 @@ class SshKeyGenActivity : AppCompatActivity() {
} }
binding.generate.text = getString(R.string.ssh_key_gen_generating_progress) binding.generate.text = getString(R.string.ssh_key_gen_generating_progress)
val result = runCatching { val result = runCatching {
withContext(Dispatchers.IO) { withContext(dispatcherProvider.io()) {
val requireAuthentication = binding.keyRequireAuthentication.isChecked val requireAuthentication = binding.keyRequireAuthentication.isChecked
if (requireAuthentication) { if (requireAuthentication) {
val result = val result =
withContext(Dispatchers.Main) { withContext(dispatcherProvider.main()) {
suspendCoroutine<Result> { cont -> suspendCoroutine { cont ->
BiometricAuthenticator.authenticate( BiometricAuthenticator.authenticate(
this@SshKeyGenActivity, this@SshKeyGenActivity,
R.string.biometric_prompt_title_ssh_keygen R.string.biometric_prompt_title_ssh_keygen

View file

@ -8,6 +8,7 @@ package app.passwordstore.util.git
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import app.passwordstore.R import app.passwordstore.R
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.snackbar import app.passwordstore.util.extensions.snackbar
import app.passwordstore.util.extensions.unsafeLazy import app.passwordstore.util.extensions.unsafeLazy
import app.passwordstore.util.git.GitException.PullException import app.passwordstore.util.git.GitException.PullException
@ -21,7 +22,6 @@ import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.CommitCommand import org.eclipse.jgit.api.CommitCommand
import org.eclipse.jgit.api.PullCommand import org.eclipse.jgit.api.PullCommand
@ -44,6 +44,7 @@ class GitCommandExecutor(
suspend fun execute(): Result<Unit, Throwable> { suspend fun execute(): Result<Unit, Throwable> {
val gitSettings = hiltEntryPoint.gitSettings() val gitSettings = hiltEntryPoint.gitSettings()
val dispatcherProvider = hiltEntryPoint.dispatcherProvider()
val snackbar = val snackbar =
activity.snackbar( activity.snackbar(
message = activity.resources.getString(R.string.git_operation_running), message = activity.resources.getString(R.string.git_operation_running),
@ -55,13 +56,13 @@ class GitCommandExecutor(
for (command in operation.commands) { for (command in operation.commands) {
when (command) { when (command) {
is StatusCommand -> { is StatusCommand -> {
val res = withContext(Dispatchers.IO) { command.call() } val res = withContext(dispatcherProvider.io()) { command.call() }
nbChanges = res.uncommittedChanges.size nbChanges = res.uncommittedChanges.size
} }
is CommitCommand -> { is CommitCommand -> {
// the previous status will eventually be used to avoid a commit // the previous status will eventually be used to avoid a commit
if (nbChanges > 0) { if (nbChanges > 0) {
withContext(Dispatchers.IO) { withContext(dispatcherProvider.io()) {
val name = gitSettings.authorName.ifEmpty { "root" } val name = gitSettings.authorName.ifEmpty { "root" }
val email = gitSettings.authorEmail.ifEmpty { "localhost" } val email = gitSettings.authorEmail.ifEmpty { "localhost" }
val identity = PersonIdent(name, email) val identity = PersonIdent(name, email)
@ -70,7 +71,7 @@ class GitCommandExecutor(
} }
} }
is PullCommand -> { is PullCommand -> {
val result = withContext(Dispatchers.IO) { command.call() } val result = withContext(dispatcherProvider.io()) { command.call() }
if (result.rebaseResult != null) { if (result.rebaseResult != null) {
if (!result.rebaseResult.status.isSuccessful) { if (!result.rebaseResult.status.isSuccessful) {
throw PullException.PullRebaseFailed throw PullException.PullRebaseFailed
@ -82,7 +83,7 @@ class GitCommandExecutor(
} }
} }
is PushCommand -> { is PushCommand -> {
val results = withContext(Dispatchers.IO) { command.call() } val results = withContext(dispatcherProvider.io()) { command.call() }
for (result in results) { for (result in results) {
// Code imported (modified) from Gerrit PushOp, license Apache v2 // Code imported (modified) from Gerrit PushOp, license Apache v2
for (rru in result.remoteUpdates) { for (rru in result.remoteUpdates) {
@ -102,7 +103,7 @@ class GitCommandExecutor(
} }
} }
RemoteRefUpdate.Status.UP_TO_DATE -> { RemoteRefUpdate.Status.UP_TO_DATE -> {
withContext(Dispatchers.Main) { withContext(dispatcherProvider.main()) {
Toast.makeText( Toast.makeText(
activity, activity,
activity.applicationContext.getString(R.string.git_push_up_to_date), activity.applicationContext.getString(R.string.git_push_up_to_date),
@ -117,7 +118,7 @@ class GitCommandExecutor(
} }
} }
else -> { else -> {
withContext(Dispatchers.IO) { command.call() } withContext(dispatcherProvider.io()) { command.call() }
} }
} }
} }
@ -130,5 +131,7 @@ class GitCommandExecutor(
interface GitCommandExecutorEntryPoint { interface GitCommandExecutorEntryPoint {
fun gitSettings(): GitSettings 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.Failure
import app.passwordstore.util.auth.BiometricAuthenticator.Result.Retry import app.passwordstore.util.auth.BiometricAuthenticator.Result.Retry
import app.passwordstore.util.auth.BiometricAuthenticator.Result.Success 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.GitCommandExecutor
import app.passwordstore.util.git.sshj.SshAuthMethod import app.passwordstore.util.git.sshj.SshAuthMethod
import app.passwordstore.util.git.sshj.SshjSessionFactory import app.passwordstore.util.git.sshj.SshjSessionFactory
@ -34,7 +35,6 @@ import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import logcat.LogPriority.ERROR import logcat.LogPriority.ERROR
import logcat.asLog import logcat.asLog
@ -176,8 +176,8 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
if (sshFacade.keyExists()) { if (sshFacade.keyExists()) {
if (sshFacade.needsAuthentication()) { if (sshFacade.needsAuthentication()) {
val result = val result =
withContext(Dispatchers.Main) { withContext(hiltEntryPoint.dispatcherProvider().main()) {
suspendCoroutine<BiometricAuthenticator.Result> { cont -> suspendCoroutine { cont ->
BiometricAuthenticator.authenticate( BiometricAuthenticator.authenticate(
callingActivity, callingActivity,
R.string.biometric_prompt_title_ssh_auth R.string.biometric_prompt_title_ssh_auth
@ -233,7 +233,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
open fun preExecute() = true open fun preExecute() = true
private suspend fun postExecute() { private suspend fun postExecute() {
withContext(Dispatchers.IO) { sshSessionFactory?.close() } withContext(hiltEntryPoint.dispatcherProvider().io()) { sshSessionFactory?.close() }
} }
companion object { companion object {
@ -246,5 +246,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
interface GitOperationEntryPoint { interface GitOperationEntryPoint {
fun sshFacade(): SSHFacade fun sshFacade(): SSHFacade
fun dispatcherProvider(): DispatcherProvider
} }
} }

View file

@ -57,9 +57,7 @@ abstract class InteractivePasswordFinder : PasswordFinder {
final override fun reqPassword(resource: Resource<*>?): CharArray { final override fun reqPassword(resource: Resource<*>?): CharArray {
val password = val password =
runBlocking(Dispatchers.Main) { runBlocking(Dispatchers.Main) { suspendCoroutine { cont -> askForPassword(cont, isRetry) } }
suspendCoroutine<String?> { cont -> askForPassword(cont, isRetry) }
}
isRetry = true isRetry = true
return password?.toCharArray() ?: throw SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER) 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.app.NotificationCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import app.passwordstore.R import app.passwordstore.R
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.clipboard import app.passwordstore.util.extensions.clipboard
import app.passwordstore.util.extensions.sharedPrefs import app.passwordstore.util.extensions.sharedPrefs
import app.passwordstore.util.settings.PreferenceKeys import app.passwordstore.util.settings.PreferenceKeys
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -30,9 +32,11 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import logcat.logcat import logcat.logcat
@AndroidEntryPoint
class ClipboardService : Service() { 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 { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null) { if (intent != null) {
@ -52,8 +56,8 @@ class ClipboardService : Service() {
createNotification(time) createNotification(time)
scope.launch { scope.launch {
withContext(Dispatchers.IO) { startTimer(time) } withContext(dispatcherProvider.io()) { startTimer(time) }
withContext(Dispatchers.Main) { withContext(dispatcherProvider.main()) {
clearClipboard() clearClipboard()
stopForeground(STOP_FOREGROUND_REMOVE) stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf() stopSelf()
@ -86,7 +90,7 @@ class ClipboardService : Service() {
val clip = ClipData.newPlainText("pgp_handler_result_pm", "") val clip = ClipData.newPlainText("pgp_handler_result_pm", "")
clipboard.setPrimaryClip(clip) clipboard.setPrimaryClip(clip)
if (deepClear) { if (deepClear) {
withContext(Dispatchers.IO) { withContext(dispatcherProvider.io()) {
repeat(CLIPBOARD_CLEAR_COUNT) { repeat(CLIPBOARD_CLEAR_COUNT) {
val count = (it * 500).toString() val count = (it * 500).toString()
clipboard.setPrimaryClip(ClipData.newPlainText(count, count)) clipboard.setPrimaryClip(ClipData.newPlainText(count, count))

View file

@ -38,7 +38,6 @@ import java.util.Locale
import java.util.Stack import java.util.Stack
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -401,7 +400,9 @@ open class SearchableRepositoryAdapter<T : RecyclerView.ViewHolder>(
private val layoutRes: Int, private val layoutRes: Int,
private val viewHolderCreator: (view: View) -> T, private val viewHolderCreator: (view: View) -> T,
private val coroutineScope: CoroutineScope, 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 { ) : ListAdapter<PasswordItem, T>(PasswordItemDiffCallback), PopupTextProvider {
fun <T : ItemDetailsLookup<String>> makeSelectable( fun <T : ItemDetailsLookup<String>> makeSelectable(
@ -482,7 +483,9 @@ open class SearchableRepositoryAdapter<T : RecyclerView.ViewHolder>(
final override fun onBindViewHolder(holder: T, position: Int) { final override fun onBindViewHolder(holder: T, position: Int) {
val item = getItem(position) val item = getItem(position)
holder.apply { 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) } selectionTracker?.let { itemView.isSelected = it.isSelected(item.stableId) }
itemView.setOnClickListener { itemView.setOnClickListener {
// Do not emit custom click events while the user is selecting items. // 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 androidx.lifecycle.lifecycleScope
import app.passwordstore.databinding.ActivityOreoAutofillSmsBinding import app.passwordstore.databinding.ActivityOreoAutofillSmsBinding
import app.passwordstore.util.autofill.AutofillResponseBuilder import app.passwordstore.util.autofill.AutofillResponseBuilder
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.viewBinding import app.passwordstore.util.extensions.viewBinding
import com.github.androidpasswordstore.autofillparser.AutofillAction import com.github.androidpasswordstore.autofillparser.AutofillAction
import com.github.androidpasswordstore.autofillparser.Credentials 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.GoogleApiAvailability
import com.google.android.gms.common.api.ResolvableApiException import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.tasks.Task import com.google.android.gms.tasks.Task
import dagger.hilt.android.AndroidEntryPoint
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
import javax.inject.Inject
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import logcat.LogPriority.ERROR import logcat.LogPriority.ERROR
@ -51,8 +53,11 @@ suspend fun <T> Task<T>.suspendableAwait() =
} }
} }
@AndroidEntryPoint
class AutofillSmsActivity : AppCompatActivity() { class AutofillSmsActivity : AppCompatActivity() {
@Inject lateinit var dispatcherProvider: DispatcherProvider
companion object { companion object {
private var fillOtpFromSmsRequestCode = 1 private var fillOtpFromSmsRequestCode = 1
@ -129,14 +134,16 @@ class AutofillSmsActivity : AppCompatActivity() {
private suspend fun waitForSms() { private suspend fun waitForSms() {
val smsClient = SmsCodeRetriever.getAutofillClient(this@AutofillSmsActivity) val smsClient = SmsCodeRetriever.getAutofillClient(this@AutofillSmsActivity)
runCatching { runCatching {
withContext(Dispatchers.IO) { smsClient.startSmsCodeRetriever().suspendableAwait() } withContext(dispatcherProvider.io()) {
smsClient.startSmsCodeRetriever().suspendableAwait()
}
} }
.onFailure { e -> .onFailure { e ->
if (e is ResolvableApiException) { if (e is ResolvableApiException) {
e.startResolutionForResult(this@AutofillSmsActivity, 1) e.startResolutionForResult(this@AutofillSmsActivity, 1)
} else { } else {
logcat(ERROR) { e.asLog() } 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 main(): CoroutineDispatcher = Dispatchers.Main
public fun mainImmediate(): CoroutineDispatcher = Dispatchers.Main.immediate
public fun default(): CoroutineDispatcher = Dispatchers.Default public fun default(): CoroutineDispatcher = Dispatchers.Default
public fun io(): CoroutineDispatcher = Dispatchers.IO public fun io(): CoroutineDispatcher = Dispatchers.IO