Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@
# WrapperWorkerFactory resolves inner workers by class name from input data.
# Keep names stable so existing enqueued work remains resolvable after minification.
# See docs/minification-workmanager-compat.md
-keepnames class com.wire.kalium.logic.sync.PendingMessagesSenderWorker
-keepnames class com.wire.kalium.logic.sync.periodic.UserConfigSyncWorker
-keepnames class com.wire.kalium.logic.sync.periodic.UpdateApiVersionsWorker
-keepnames class com.wire.kalium.logic.sync.receiver.asset.AudioNormalizedLoudnessWorker
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Expand Down Expand Up @@ -349,6 +350,11 @@
android:exported="false"
android:foregroundServiceType="specialUse" />

<service
android:name=".services.PendingMessagesForegroundService"
android:exported="false"
android:foregroundServiceType="dataSync" />

<service
android:name=".services.CallService"
android:exported="false"
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/baseline-prof.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30668,7 +30668,6 @@ Lcom/wire/kalium/logic/sync/GlobalWorkScheduler;
Lcom/wire/kalium/logic/sync/GlobalWorkSchedulerImpl;
SPLcom/wire/kalium/logic/sync/GlobalWorkSchedulerImpl;-><init>(Landroid/content/Context;Lcom/wire/kalium/logic/GlobalKaliumScope;)V
SPLcom/wire/kalium/logic/sync/GlobalWorkSchedulerImpl;->schedulePeriodicApiVersionUpdate()V
Lcom/wire/kalium/logic/sync/PendingMessagesSenderWorker;
Lcom/wire/kalium/logic/sync/WorkSchedulerImplKt;
SPLcom/wire/kalium/logic/sync/WorkSchedulerImplKt;-><clinit>()V
SPLcom/wire/kalium/logic/sync/WorkSchedulerImplKt;->buildConnectedPeriodicWorkRequest$default(Lkotlin/reflect/KClass;Lcom/wire/kalium/logic/data/id/QualifiedID;ZILjava/lang/Object;)Landroidx/work/PeriodicWorkRequest;
Expand Down Expand Up @@ -40417,4 +40416,4 @@ SPLorg/slf4j/helpers/Util;-><clinit>()V
SPLorg/slf4j/helpers/Util;->safeGetBooleanSystemProperty(Ljava/lang/String;)Z
SPLorg/slf4j/helpers/Util;->safeGetSystemProperty(Ljava/lang/String;)Ljava/lang/String;
Lorg/slf4j/spi/MDCAdapter;
Lorg/slf4j/spi/SLF4JServiceProvider;
Lorg/slf4j/spi/SLF4JServiceProvider;
38 changes: 38 additions & 0 deletions app/src/main/kotlin/com/wire/android/GlobalObserversManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.wire.android.datastore.UserDataStoreProvider
import com.wire.android.di.KaliumCoreLogic
import com.wire.android.notification.NotificationChannelsManager
import com.wire.android.notification.WireNotificationManager
import com.wire.android.services.SendPendingMessagesAfterForegroundSyncUseCase
import com.wire.android.util.CurrentScreenManager
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.kalium.logic.CoreLogic
Expand All @@ -30,6 +31,8 @@ import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.auth.LogoutCallback
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import com.wire.kalium.logic.feature.user.webSocketStatus.ObservePersistentWebSocketConnectionStatusUseCase
import com.wire.kalium.network.NetworkState
import com.wire.kalium.network.NetworkStateObserver
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.awaitClose
Expand Down Expand Up @@ -61,6 +64,8 @@ class GlobalObserversManager @Inject constructor(
private val notificationChannelsManager: NotificationChannelsManager,
private val userDataStoreProvider: UserDataStoreProvider,
private val currentScreenManager: CurrentScreenManager,
private val sendPendingMessagesAfterForegroundSync: SendPendingMessagesAfterForegroundSyncUseCase,
private val networkStateObserver: NetworkStateObserver,
) {
// TODO(tests): refactor so scope/dispatcher can be injected and properly stopped
private val scope = CoroutineScope(SupervisorJob() + dispatcherProvider.io())
Expand All @@ -78,6 +83,7 @@ class GlobalObserversManager @Inject constructor(
}
scope.handleLogouts()
scope.handleDeleteEphemeralMessageEndDate()
scope.retryPendingMessagesOnAppOpen()
}

private suspend fun setUpNotifications() {
Expand Down Expand Up @@ -162,4 +168,36 @@ class GlobalObserversManager @Inject constructor(
.collect { userId -> coreLogic.getSessionScope(userId).messages.deleteEphemeralMessageEndDate() }
}
}

private fun CoroutineScope.retryPendingMessagesOnAppOpen() {
launch {
currentScreenManager.isAppVisibleFlow()
.filter { isAppVisible -> isAppVisible }
.collectLatest {
val networkState = networkStateObserver.observeNetworkState().value
if (networkState !is NetworkState.ConnectedWithInternet) {
appLogger.i("$TAG: no internet connection, skipping pending messages retry on app open")
return@collectLatest
}

when (val result = coreLogic.getGlobalScope().session.currentSession()) {
is CurrentSessionResult.Success -> {
val accountInfo = result.accountInfo
if (accountInfo.isValid()) {
sendPendingMessagesAfterForegroundSync(accountInfo.userId)
} else {
appLogger.w("$TAG: current session is invalid, skipping pending messages retry on app open")
}
}

is CurrentSessionResult.Failure ->
appLogger.w("$TAG: unable to get current valid session, skipping pending messages retry on app open: $result")
}
}
}
}

private companion object {
private const val TAG = "GlobalObserversManager"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import com.wire.android.notification.broadcastreceivers.PlayPauseAudioMessageRec
import com.wire.android.notification.broadcastreceivers.StopAudioMessageReceiver
import com.wire.android.search.SearchMetroViewModelBindings
import com.wire.android.services.CallService
import com.wire.android.services.PendingMessagesForegroundService
import com.wire.android.services.PersistentWebSocketService
import com.wire.android.services.PlayingAudioMessageService
import com.wire.android.ui.AppLockActivity
Expand Down Expand Up @@ -125,6 +126,7 @@ interface WireApplicationGraph : ViewModelGraph {
fun inject(activity: CallActivity)
fun inject(activity: OngoingCallActivity)
fun inject(service: PersistentWebSocketService)
fun inject(service: PendingMessagesForegroundService)
fun inject(service: CallService)
fun inject(service: PlayingAudioMessageService)
fun inject(receiver: StartServiceReceiver)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.core.content.ContextCompat
import com.wire.android.BuildConfig.EMM_SUPPORT_ENABLED
import com.wire.android.appLogger
import com.wire.android.emm.ManagedConfigurationsReceiver
import com.wire.android.di.ApplicationContext
import com.wire.kalium.logic.sync.PendingMessagesForegroundSync
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.SingleIn
Expand All @@ -35,35 +37,51 @@
@SingleIn(AppScope::class)
class DynamicReceiversManager @Inject constructor(
@ApplicationContext val context: Context,
private val managedConfigurationsReceiver: ManagedConfigurationsReceiver
private val managedConfigurationsReceiver: ManagedConfigurationsReceiver,
private val pendingMessagesScheduledReceiver: PendingMessagesScheduledReceiver,

Check warning on line 41 in app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt#L40-L41

Added lines #L40 - L41 were not covered by tests
) {
@Volatile
private var isRegistered = false

fun registerAll() {
if (EMM_SUPPORT_ENABLED) {
synchronized(this) {
if (!isRegistered) {
synchronized(this) {

Check warning on line 47 in app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt#L47

Added line #L47 was not covered by tests
if (!isRegistered) {
if (EMM_SUPPORT_ENABLED) {
appLogger.i("$TAG Registering Runtime ManagedConfigurations Broadcast receiver")
context.registerReceiver(managedConfigurationsReceiver, IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED))
isRegistered = true
} else {
appLogger.w("$TAG Receiver already registered, skipping")
}

appLogger.i("$TAG Registering PendingMessagesScheduledReceiver")
val pendingMessagesIntentFilter = IntentFilter(PendingMessagesForegroundSync.ACTION_SENDING_OF_PENDING_MESSAGES_SCHEDULED)
.apply { addAction(PendingMessagesForegroundSync.ACTION_SENDING_OF_PENDING_MESSAGES_CANCELLED) }
ContextCompat.registerReceiver(
context,
pendingMessagesScheduledReceiver,
pendingMessagesIntentFilter,
ContextCompat.RECEIVER_NOT_EXPORTED

Check warning on line 61 in app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt#L54-L61

Added lines #L54 - L61 were not covered by tests
)

isRegistered = true

Check warning on line 64 in app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt#L64

Added line #L64 was not covered by tests
} else {
appLogger.w("$TAG Receiver already registered, skipping")

Check warning on line 66 in app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt#L66

Added line #L66 was not covered by tests
}
}
}

fun unregisterAll() {
if (EMM_SUPPORT_ENABLED) {
synchronized(this) {
if (isRegistered) {
synchronized(this) {

Check warning on line 72 in app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt#L72

Added line #L72 was not covered by tests
if (isRegistered) {
if (EMM_SUPPORT_ENABLED) {
appLogger.i("$TAG Unregistering Runtime ManagedConfigurations Broadcast receiver")
context.unregisterReceiver(managedConfigurationsReceiver)
isRegistered = false
} else {
appLogger.w("$TAG Receiver not registered, skipping unregister")
}

appLogger.i("$TAG Unregistering PendingMessagesScheduledReceiver")
context.unregisterReceiver(pendingMessagesScheduledReceiver)

Check warning on line 80 in app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt#L79-L80

Added lines #L79 - L80 were not covered by tests

isRegistered = false

Check warning on line 82 in app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt#L82

Added line #L82 was not covered by tests
} else {
appLogger.w("$TAG Receiver not registered, skipping unregister")

Check warning on line 84 in app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/notification/broadcastreceivers/DynamicReceiversManager.kt#L84

Added line #L84 was not covered by tests
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Wire
* Copyright (C) 2026 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/

package com.wire.android.notification.broadcastreceivers

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.wire.android.appLogger
import com.wire.android.services.ServicesManager
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.sync.PendingMessagesForegroundSync
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn

@SingleIn(AppScope::class)
class PendingMessagesScheduledReceiver @Inject constructor(
private val servicesManager: ServicesManager,
) : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
PendingMessagesForegroundSync.ACTION_SENDING_OF_PENDING_MESSAGES_SCHEDULED -> {
val userId = intent.userId()
if (userId == null) {
appLogger.w("$TAG: missing user id, skipping pending messages foreground service")
return
}
servicesManager.startPendingMessagesForegroundService(userId)
}

PendingMessagesForegroundSync.ACTION_SENDING_OF_PENDING_MESSAGES_CANCELLED ->
servicesManager.stopPendingMessagesForegroundService()

else -> {
appLogger.w("$TAG: unexpected action ${intent.action}")
return
}
}
}

companion object {
private const val TAG = "PendingMessagesScheduledReceiver"
}
}

private fun Intent.userId(): UserId? {
val value = getStringExtra(PendingMessagesForegroundSync.EXTRA_USER_ID_VALUE)
val domain = getStringExtra(PendingMessagesForegroundSync.EXTRA_USER_ID_DOMAIN)
return if (value != null && domain != null) UserId(value, domain) else null
}
Loading
Loading