android, desktop: withLongRunningApi when needed (#3710)

This commit is contained in:
Stanislav Dmitrenko 2024-01-20 00:01:33 +07:00 committed by GitHub
parent ab9a6dcab5
commit 5d8bb24d1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 40 additions and 46 deletions

View File

@ -104,7 +104,7 @@ class SimplexService: Service() {
if (wakeLock != null || isStartingService) return if (wakeLock != null || isStartingService) return
val self = this val self = this
isStartingService = true isStartingService = true
withBGApi { withLongRunningApi(slow = 30_000, deadlock = 60_000) {
val chatController = ChatController val chatController = ChatController
waitDbMigrationEnds(chatController) waitDbMigrationEnds(chatController)
try { try {
@ -114,7 +114,7 @@ class SimplexService: Service() {
Log.w(chat.simplex.app.TAG, "SimplexService: problem with the database: $chatDbStatus") Log.w(chat.simplex.app.TAG, "SimplexService: problem with the database: $chatDbStatus")
showPassphraseNotification(chatDbStatus) showPassphraseNotification(chatDbStatus)
safeStopService() safeStopService()
return@withBGApi return@withLongRunningApi
} }
saveServiceState(self, ServiceState.STARTED) saveServiceState(self, ServiceState.STARTED)
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run { wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {

View File

@ -8,9 +8,7 @@ import androidx.core.content.ContextCompat
import chat.simplex.common.R import chat.simplex.common.R
import chat.simplex.common.platform.SoundPlayerInterface import chat.simplex.common.platform.SoundPlayerInterface
import chat.simplex.common.platform.androidAppContext import chat.simplex.common.platform.androidAppContext
import chat.simplex.common.views.helpers.withScope import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
object SoundPlayer: SoundPlayerInterface { object SoundPlayer: SoundPlayerInterface {
private var player: MediaPlayer? = null private var player: MediaPlayer? = null
@ -31,7 +29,7 @@ object SoundPlayer: SoundPlayerInterface {
val vibrator = ContextCompat.getSystemService(androidAppContext, Vibrator::class.java) val vibrator = ContextCompat.getSystemService(androidAppContext, Vibrator::class.java)
val effect = VibrationEffect.createOneShot(250, VibrationEffect.DEFAULT_AMPLITUDE) val effect = VibrationEffect.createOneShot(250, VibrationEffect.DEFAULT_AMPLITUDE)
playing = true playing = true
withScope(scope) { scope.launch {
while (playing) { while (playing) {
if (sound) player?.start() if (sound) player?.start()
vibrator?.vibrate(effect) vibrator?.vibrate(effect)

View File

@ -383,7 +383,7 @@ private fun DisabledBackgroundCallsButton() {
Modifier Modifier
.padding(bottom = 24.dp) .padding(bottom = 24.dp)
.clickable { .clickable {
withBGApi { withLongRunningApi {
show = !platform.androidAskToAllowBackgroundCalls() show = !platform.androidAskToAllowBackgroundCalls()
} }
} }

View File

@ -133,7 +133,7 @@ actual fun QRCodeScanner(
} }
} }
!cameraPermissionState.hasPermission -> { !cameraPermissionState.hasPermission -> {
Button({ withBGApi { cameraPermissionState.launchPermissionRequest() } }, modifier = modifier, colors = buttonColors) { Button({ cameraPermissionState.launchPermissionRequest() }, modifier = modifier, colors = buttonColors) {
Icon(painterResource(MR.images.ic_camera_enhance), null) Icon(painterResource(MR.images.ic_camera_enhance), null)
Spacer(Modifier.width(DEFAULT_PADDING_HALF)) Spacer(Modifier.width(DEFAULT_PADDING_HALF))
Text(stringResource(MR.strings.enable_camera_access)) Text(stringResource(MR.strings.enable_camera_access))

View File

@ -102,7 +102,7 @@ fun AppearanceScope.AppearanceLayout(
val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") } val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") }
LangSelector(state) { LangSelector(state) {
state.value = it state.value = it
withBGApi { withApi {
delay(200) delay(200)
val activity = context as? Activity val activity = context as? Activity
if (activity != null) { if (activity != null) {

View File

@ -55,7 +55,7 @@ abstract class NtfManager {
} }
fun openChatAction(userId: Long?, chatId: ChatId) { fun openChatAction(userId: Long?, chatId: ChatId) {
withBGApi { withLongRunningApi(slow = 30_000, deadlock = 60_000) {
awaitChatStartedIfNeeded(chatModel) awaitChatStartedIfNeeded(chatModel)
if (userId != null && userId != chatModel.currentUser.value?.userId && chatModel.currentUser.value != null) { if (userId != null && userId != chatModel.currentUser.value?.userId && chatModel.currentUser.value != null) {
// TODO include remote host ID in desktop notifications? // TODO include remote host ID in desktop notifications?
@ -70,7 +70,7 @@ abstract class NtfManager {
} }
fun showChatsAction(userId: Long?) { fun showChatsAction(userId: Long?) {
withBGApi { withLongRunningApi(slow = 30_000, deadlock = 60_000) {
awaitChatStartedIfNeeded(chatModel) awaitChatStartedIfNeeded(chatModel)
if (userId != null && userId != chatModel.currentUser.value?.userId && chatModel.currentUser.value != null) { if (userId != null && userId != chatModel.currentUser.value?.userId && chatModel.currentUser.value != null) {
// TODO include remote host ID in desktop notifications? // TODO include remote host ID in desktop notifications?

View File

@ -267,7 +267,7 @@ fun ComposeView(
fun loadLinkPreview(url: String, wait: Long? = null) { fun loadLinkPreview(url: String, wait: Long? = null) {
if (pendingLinkUrl.value == url) { if (pendingLinkUrl.value == url) {
composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(null)) composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(null))
withBGApi { withLongRunningApi(slow = 30_000, deadlock = 60_000) {
if (wait != null) delay(wait) if (wait != null) delay(wait)
val lp = getLinkPreview(url) val lp = getLinkPreview(url)
if (lp != null && pendingLinkUrl.value == url) { if (lp != null && pendingLinkUrl.value == url) {
@ -551,7 +551,7 @@ fun ComposeView(
} }
fun sendMessage(ttl: Int?) { fun sendMessage(ttl: Int?) {
withBGApi { withLongRunningApi(slow = 30_000, deadlock = 60_000) {
sendMessageAsync(null, false, ttl) sendMessageAsync(null, false, ttl)
} }
} }

View File

@ -54,7 +54,7 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea
}, },
inviteMembers = { inviteMembers = {
allowModifyMembers = false allowModifyMembers = false
withBGApi { withLongRunningApi(slow = 30_000, deadlock = 60_000) {
for (contactId in selectedContacts) { for (contactId in selectedContacts) {
val member = chatModel.controller.apiAddMember(rhId, groupInfo.groupId, contactId, selectedRole.value) val member = chatModel.controller.apiAddMember(rhId, groupInfo.groupId, contactId, selectedRole.value)
if (member != null) { if (member != null) {

View File

@ -94,7 +94,7 @@ fun CIFileView(
FileProtocol.LOCAL -> {} FileProtocol.LOCAL -> {}
} }
file.fileStatus is CIFileStatus.RcvComplete || (file.fileStatus is CIFileStatus.SndStored && file.fileProtocol == FileProtocol.LOCAL) -> { file.fileStatus is CIFileStatus.RcvComplete || (file.fileStatus is CIFileStatus.SndStored && file.fileProtocol == FileProtocol.LOCAL) -> {
withBGApi { withLongRunningApi(slow = 60_000, deadlock = 600_000) {
var filePath = getLoadedFilePath(file) var filePath = getLoadedFilePath(file)
if (chatModel.connectedToRemote() && filePath == null) { if (chatModel.connectedToRemote() && filePath == null) {
file.loadRemoteFile(true) file.loadRemoteFile(true)

View File

@ -41,7 +41,7 @@ fun CIVideoView(
val filePath = remember(file, CIFile.cachedRemoteFileRequests.toList()) { mutableStateOf(getLoadedFilePath(file)) } val filePath = remember(file, CIFile.cachedRemoteFileRequests.toList()) { mutableStateOf(getLoadedFilePath(file)) }
if (chatModel.connectedToRemote()) { if (chatModel.connectedToRemote()) {
LaunchedEffect(file) { LaunchedEffect(file) {
withBGApi { withLongRunningApi(slow = 60_000, deadlock = 600_000) {
if (file != null && file.loaded && getLoadedFilePath(file) == null) { if (file != null && file.loaded && getLoadedFilePath(file) == null) {
file.loadRemoteFile(false) file.loadRemoteFile(false)
filePath.value = getLoadedFilePath(file) filePath.value = getLoadedFilePath(file)

View File

@ -213,7 +213,7 @@ fun ChatItemView(
showMenu.value = false showMenu.value = false
} }
if (chatModel.connectedToRemote() && fileSource == null) { if (chatModel.connectedToRemote() && fileSource == null) {
withBGApi { withLongRunningApi(slow = 60_000, deadlock = 600_000) {
cItem.file?.loadRemoteFile(true) cItem.file?.loadRemoteFile(true)
fileSource = getLoadedFileSource(cItem.file) fileSource = getLoadedFileSource(cItem.file)
shareIfExists() shareIfExists()

View File

@ -62,7 +62,7 @@ fun DatabaseEncryptionView(m: ChatModel) {
initialRandomDBPassphrase, initialRandomDBPassphrase,
progressIndicator, progressIndicator,
onConfirmEncrypt = { onConfirmEncrypt = {
withBGApi { withLongRunningApi(slow = 30_000, deadlock = 60_000) {
encryptDatabase(currentKey, newKey, confirmNewKey, initialRandomDBPassphrase, useKeychain, storedKey, progressIndicator) encryptDatabase(currentKey, newKey, confirmNewKey, initialRandomDBPassphrase, useKeychain, storedKey, progressIndicator)
} }
} }

View File

@ -368,7 +368,7 @@ fun chatArchiveTitle(chatArchiveTime: Instant, chatLastStart: Instant): String {
} }
fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged: MutableState<Boolean>, progressIndicator: MutableState<Boolean>? = null) { fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged: MutableState<Boolean>, progressIndicator: MutableState<Boolean>? = null) {
withBGApi { withLongRunningApi(slow = 30_000, deadlock = 60_000) {
try { try {
progressIndicator?.value = true progressIndicator?.value = true
if (chatDbChanged.value) { if (chatDbChanged.value) {
@ -378,12 +378,12 @@ fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged
if (m.chatDbStatus.value !is DBMigrationResult.OK) { if (m.chatDbStatus.value !is DBMigrationResult.OK) {
/** Hide current view and show [DatabaseErrorView] */ /** Hide current view and show [DatabaseErrorView] */
ModalManager.closeAllModalsEverywhere() ModalManager.closeAllModalsEverywhere()
return@withBGApi return@withLongRunningApi
} }
val user = m.currentUser.value val user = m.currentUser.value
if (user == null) { if (user == null) {
ModalManager.closeAllModalsEverywhere() ModalManager.closeAllModalsEverywhere()
return@withBGApi return@withLongRunningApi
} else { } else {
m.controller.startChat(user) m.controller.startChat(user)
} }
@ -581,7 +581,7 @@ private fun importArchive(
progressIndicator.value = true progressIndicator.value = true
val archivePath = saveArchiveFromURI(importedArchiveURI) val archivePath = saveArchiveFromURI(importedArchiveURI)
if (archivePath != null) { if (archivePath != null) {
withBGApi { withLongRunningApi(slow = 60_000, deadlock = 180_000) {
try { try {
m.controller.apiDeleteStorage() m.controller.apiDeleteStorage()
try { try {

View File

@ -16,7 +16,7 @@ class ProcessedErrors <T: AgentErrorType>(val interval: Long) {
fun newError(error: T, offerRestart: Boolean) { fun newError(error: T, offerRestart: Boolean) {
timer.cancel() timer.cancel()
timer = withBGApi { timer = withLongRunningApi(slow = 70_000, deadlock = 130_000) {
val delayBeforeNext = (lastShownTimestamp + interval) - System.currentTimeMillis() val delayBeforeNext = (lastShownTimestamp + interval) - System.currentTimeMillis()
if ((lastShownOfferRestart || !offerRestart) && delayBeforeNext >= 0) { if ((lastShownOfferRestart || !offerRestart) && delayBeforeNext >= 0) {
delay(delayBeforeNext) delay(delayBeforeNext)

View File

@ -27,11 +27,9 @@ import kotlin.math.*
private val singleThreadDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val singleThreadDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
fun withApi(action: suspend CoroutineScope.() -> Unit): Job = withScope(GlobalScope, action) fun withApi(action: suspend CoroutineScope.() -> Unit): Job =
fun withScope(scope: CoroutineScope, action: suspend CoroutineScope.() -> Unit): Job =
Exception().let { Exception().let {
scope.launch { withContext(Dispatchers.Main, block = { wrapWithLogging(action, it) }) } CoroutineScope(Dispatchers.Main).launch(block = { wrapWithLogging(action, it) })
} }
fun withBGApi(action: suspend CoroutineScope.() -> Unit): Job = fun withBGApi(action: suspend CoroutineScope.() -> Unit): Job =

View File

@ -49,7 +49,7 @@ fun LocalAuthView(m: ChatModel, authRequest: LocalAuthRequest) {
} }
private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: (LAResult) -> Unit) { private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: (LAResult) -> Unit) {
withBGApi { withLongRunningApi(slow = 30_000, deadlock = 60_000) {
try { try {
/** Waiting until [initChatController] finishes */ /** Waiting until [initChatController] finishes */
while (m.ctrlInitInProgress.value) { while (m.ctrlInitInProgress.value) {
@ -78,7 +78,7 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: (
displayNamePref.set(null) displayNamePref.set(null)
reinitChatController() reinitChatController()
if (m.currentUser.value != null) { if (m.currentUser.value != null) {
return@withBGApi return@withLongRunningApi
} }
var profile: Profile? = null var profile: Profile? = null
if (!displayName.isNullOrEmpty()) { if (!displayName.isNullOrEmpty()) {

View File

@ -50,7 +50,7 @@ fun SetupDatabasePassphrase(m: ChatModel) {
confirmNewKey, confirmNewKey,
progressIndicator, progressIndicator,
onConfirmEncrypt = { onConfirmEncrypt = {
withBGApi { withLongRunningApi(slow = 30_000, deadlock = 60_000) {
if (m.chatRunning.value == true) { if (m.chatRunning.value == true) {
// Stop chat if it's started before doing anything // Stop chat if it's started before doing anything
stopChatAsync(m) stopChatAsync(m)

View File

@ -96,7 +96,7 @@ fun PrivacySettingsView(
val currentUser = chatModel.currentUser.value val currentUser = chatModel.currentUser.value
if (currentUser != null) { if (currentUser != null) {
fun setSendReceiptsContacts(enable: Boolean, clearOverrides: Boolean) { fun setSendReceiptsContacts(enable: Boolean, clearOverrides: Boolean) {
withBGApi { withLongRunningApi(slow = 30_000, deadlock = 60_000) {
val mrs = UserMsgReceiptSettings(enable, clearOverrides) val mrs = UserMsgReceiptSettings(enable, clearOverrides)
chatModel.controller.apiSetUserContactReceipts(currentUser, mrs) chatModel.controller.apiSetUserContactReceipts(currentUser, mrs)
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
@ -119,7 +119,7 @@ fun PrivacySettingsView(
} }
fun setSendReceiptsGroups(enable: Boolean, clearOverrides: Boolean) { fun setSendReceiptsGroups(enable: Boolean, clearOverrides: Boolean) {
withBGApi { withLongRunningApi(slow = 30_000, deadlock = 60_000) {
val mrs = UserMsgReceiptSettings(enable, clearOverrides) val mrs = UserMsgReceiptSettings(enable, clearOverrides)
chatModel.controller.apiSetUserGroupReceipts(currentUser, mrs) chatModel.controller.apiSetUserGroupReceipts(currentUser, mrs)
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)

View File

@ -211,15 +211,13 @@ actual object SoundPlayer: SoundPlayerInterface {
var playing = false var playing = false
override fun start(scope: CoroutineScope, sound: Boolean) { override fun start(scope: CoroutineScope, sound: Boolean) {
withBGApi { val tmpFile = File(tmpDir, UUID.randomUUID().toString())
val tmpFile = File(tmpDir, UUID.randomUUID().toString()) tmpFile.deleteOnExit()
tmpFile.deleteOnExit() SoundPlayer::class.java.getResource("/media/ring_once.mp3").openStream()!!.use { it.copyTo(tmpFile.outputStream()) }
SoundPlayer::class.java.getResource("/media/ring_once.mp3").openStream()!!.use { it.copyTo(tmpFile.outputStream()) } playing = true
playing = true scope.launch {
while (playing) { while (playing && sound) {
if (sound) { AudioPlayer.play(CryptoFile.plain(tmpFile.absolutePath), mutableStateOf(true), mutableStateOf(0), mutableStateOf(0), true)
AudioPlayer.play(CryptoFile.plain(tmpFile.absolutePath), mutableStateOf(true), mutableStateOf(0), mutableStateOf(0), true)
}
delay(3500) delay(3500)
} }
} }

View File

@ -42,7 +42,7 @@ actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserL
} }
var fileSource = getLoadedFileSource(cItem.file) var fileSource = getLoadedFileSource(cItem.file)
if (chatModel.connectedToRemote() && fileSource == null) { if (chatModel.connectedToRemote() && fileSource == null) {
withBGApi { withLongRunningApi(slow = 60_000, deadlock = 600_000) {
cItem.file?.loadRemoteFile(true) cItem.file?.loadRemoteFile(true)
fileSource = getLoadedFileSource(cItem.file) fileSource = getLoadedFileSource(cItem.file)
saveIfExists() saveIfExists()
@ -51,7 +51,7 @@ actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserL
}) })
} }
actual fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager) = withBGApi { actual fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager) = withLongRunningApi(slow = 60_000, deadlock = 600_000) {
var fileSource = getLoadedFileSource(cItem.file) var fileSource = getLoadedFileSource(cItem.file)
if (chatModel.connectedToRemote() && fileSource == null) { if (chatModel.connectedToRemote() && fileSource == null) {
cItem.file?.loadRemoteFile(true) cItem.file?.loadRemoteFile(true)
@ -63,10 +63,10 @@ actual fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager) = w
val tmpFile = File(tmpDir, fileSource.filePath) val tmpFile = File(tmpDir, fileSource.filePath)
tmpFile.deleteOnExit() tmpFile.deleteOnExit()
try { try {
decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs ?: return@withBGApi, tmpFile.absolutePath) decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs ?: return@withLongRunningApi, tmpFile.absolutePath)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString()) Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString())
return@withBGApi return@withLongRunningApi
} }
tmpFile.absolutePath tmpFile.absolutePath
} else { } else {

View File

@ -51,7 +51,7 @@ fun AppearanceScope.AppearanceLayout(
val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") } val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") }
LangSelector(state) { LangSelector(state) {
state.value = it state.value = it
withBGApi { withApi {
delay(200) delay(200)
if (it == "system") { if (it == "system") {
languagePref.set(null) languagePref.set(null)