Merge branch 'develop' into feature/fga/live_location_sharing_setup

This commit is contained in:
ganfra 2026-03-24 10:17:24 +01:00
commit 9a984e1423
632 changed files with 4530 additions and 3107 deletions

View file

@ -11,6 +11,11 @@
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application>
<service android:name=".push.FetchPushForegroundService"
android:foregroundServiceType="shortService"
android:exported="false"
android:enabled="true" />
<receiver
android:name=".notifications.TestNotificationReceiver"
android:exported="false" />

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.push.impl.di
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesTo
import io.element.android.libraries.push.impl.push.FetchPushForegroundService
@ContributesTo(AppScope::class)
interface PushBindings {
fun inject(fetchPushForegroundService: FetchPushForegroundService)
}

View file

@ -58,6 +58,8 @@ interface NotificationChannels {
* Get the channel for test notifications.
*/
fun getChannelIdForTest(): String
fun getSilentChannelId(): String = SILENT_NOTIFICATION_CHANNEL_ID
}
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)

View file

@ -11,11 +11,11 @@ package io.element.android.libraries.push.impl.notifications.factories
import android.app.Notification
import android.content.Context
import android.graphics.Bitmap
import android.os.Bundle
import androidx.annotation.ColorInt
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.MessagingStyle
import androidx.core.app.Person
import androidx.core.os.bundleOf
import coil3.ImageLoader
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
@ -145,7 +145,7 @@ class DefaultNotificationCreator(
sessionId = roomInfo.sessionId,
roomId = roomInfo.roomId,
eventId = eventId,
extras = bundleOf(ROOM_OPENED_FROM_NOTIFICATION to true),
extras = Bundle().apply { putBoolean(ROOM_OPENED_FROM_NOTIFICATION, true) },
)
}
val containsMissedCall = events.any { it.type == EventType.RTC_NOTIFICATION }
@ -293,7 +293,7 @@ class DefaultNotificationCreator(
sessionId = simpleNotifiableEvent.sessionId,
roomId = simpleNotifiableEvent.roomId,
eventId = null,
extras = bundleOf(ROOM_OPENED_FROM_NOTIFICATION to true),
extras = Bundle().apply { putBoolean(ROOM_OPENED_FROM_NOTIFICATION, true) },
)
)
.apply {
@ -331,9 +331,7 @@ class DefaultNotificationCreator(
.annotateForDebug(8)
)
.setExtras(
bundleOf(
FALLBACK_COUNTER_EXTRA to counter
)
Bundle().apply { putInt(FALLBACK_COUNTER_EXTRA, counter) },
)
.setNumber(counter)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)

View file

@ -31,7 +31,10 @@ import io.element.android.libraries.workmanager.api.WorkManagerScheduler
import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import timber.log.Timber
private val loggerTag = LoggerTag("PushHandler", LoggerTag.PushLoggerTag)
@ -61,7 +64,7 @@ class DefaultPushHandler(
* @param pushData the data received in the push.
* @param providerInfo the provider info.
*/
override suspend fun handle(pushData: PushData, providerInfo: String) {
override suspend fun handle(pushData: PushData, providerInfo: String): Boolean {
// Start measuring how long it takes to display a notification from when the push is received
Timber.d("Calculating push-to-notification for event ${pushData.eventId}")
val parent = analyticsService.startLongRunningTransaction(AnalyticsLongRunningTransaction.PushToNotification(pushData.eventId.value))
@ -71,11 +74,17 @@ class DefaultPushHandler(
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.tag(loggerTag.value).d("## pushData: $pushData")
}
incrementPushDataStore.incrementPushCounter()
// Update the push counter without blocking the coroutine execution, as it is not critical to be updated before handling the push
CoroutineScope(currentCoroutineContext()).launch {
incrementPushDataStore.incrementPushCounter()
}
// Diagnostic Push
if (pushData.eventId == DefaultTestPush.TEST_EVENT_ID) {
return if (pushData.eventId == DefaultTestPush.TEST_EVENT_ID) {
pushHistoryService.onDiagnosticPush(providerInfo)
diagnosticPushHandler.handlePush()
false
} else {
handleInternal(pushData, providerInfo)
}
@ -92,7 +101,7 @@ class DefaultPushHandler(
* @param pushData Object containing message data.
* @param providerInfo the provider info.
*/
private suspend fun handleInternal(pushData: PushData, providerInfo: String) {
private suspend fun handleInternal(pushData: PushData, providerInfo: String): Boolean {
try {
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.tag(loggerTag.value).d("## handleInternal() : $pushData")
@ -109,13 +118,13 @@ class DefaultPushHandler(
roomId = pushData.roomId,
reason = "Unable to get userId from client secret",
)
return
return false
}
val areNotificationsEnabled = userPushStoreFactory.getOrCreate(userId).getNotificationEnabledForDevice().first()
if (!areNotificationsEnabled) {
Timber.w("Push notification received when push notifications are disabled.")
return
return false
}
val pushRequest = PushRequest(
@ -130,13 +139,17 @@ class DefaultPushHandler(
Timber.d("Queueing notification: $pushRequest")
pushHistoryService.insertOrUpdatePushRequest(pushRequest)
Timber.d("Queueing notification finished")
if (!workManagerScheduler.hasPendingWork(userId, WorkManagerRequestType.NOTIFICATION_SYNC)) {
Timber.d("No pending worker for push notifications found")
workManagerScheduler.submit(syncPendingNotificationsRequestFactory.create(userId))
}
return true
} catch (e: Exception) {
Timber.tag(loggerTag.value).e(e, "## handleInternal() failed")
return false
}
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.push.impl.push
import android.content.Context
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.SingleIn
import io.element.android.libraries.di.annotations.ApplicationContext
import io.element.android.libraries.push.api.push.PushHandlingWakeLock
import timber.log.Timber
import java.util.concurrent.atomic.AtomicInteger
import kotlin.time.Duration
@ContributesBinding(AppScope::class)
@SingleIn(AppScope::class)
class DefaultPushHandlingWakeLock(
@ApplicationContext private val context: Context,
) : PushHandlingWakeLock {
private val count = AtomicInteger(0)
override fun lock(time: Duration) {
Timber.d("Acquiring wakelock for push handling, starting service.")
FetchPushForegroundService.startIfNeeded(context)
count.incrementAndGet()
}
override fun unlock() {
Timber.d("Releasing wakelock used for push handling.")
FetchPushForegroundService.stop(context)
if (count.decrementAndGet() <= 0) {
Timber.d("No more wakelock needed for push handling, stopping service.")
count.set(0)
} else {
Timber.d("Wakelock still needed for push handling, restarting service | count: ${count.get()}.")
FetchPushForegroundService.startIfNeeded(context)
}
}
}

View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.push.impl.push
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import androidx.core.app.NotificationCompat
import dev.zacsweers.metro.Inject
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.push.api.push.PushHandlingWakeLock
import io.element.android.libraries.push.impl.di.PushBindings
import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.minutes
private const val NOTIFICATION_ID = 1001
// This kind of foreground service can only last up to 3 minutes before onTimeout is called
private val wakelockTimeout = 3.minutes.inWholeMilliseconds
/**
* Foreground service used to ensure the device stays awake while we handle the pushes and schedule and run the work to fetch the notification content.
*/
class FetchPushForegroundService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return null
}
@Inject lateinit var notificationChannels: NotificationChannels
@Inject lateinit var pushHandlingWakeLock: PushHandlingWakeLock
@Inject @AppCoroutineScope lateinit var coroutineScope: CoroutineScope
private val wakelock: PowerManager.WakeLock by lazy {
val powerManager = getSystemService(POWER_SERVICE) as PowerManager
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "FetchPushService:WakeLock").apply {
setReferenceCounted(false)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
bindings<PushBindings>().inject(this)
wakelock.acquire(wakelockTimeout)
val notificationCompat = NotificationCompat.Builder(this, notificationChannels.getSilentChannelId())
.setSmallIcon(CommonDrawables.ic_notification)
.setContentTitle(getString(CommonStrings.common_android_fetching_notifications_title))
.setProgress(0, 0, true)
.setVibrate(longArrayOf(0))
.setSound(null)
.build()
startForeground(NOTIFICATION_ID, notificationCompat)
// The timeout is not automatic before Android 15, so we need to schedule it ourselves
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
coroutineScope.launch {
delay(wakelockTimeout)
onTimeout(startId)
}
}
return START_NOT_STICKY
}
override fun stopService(intent: Intent?): Boolean {
wakelock.release()
stopForeground(STOP_FOREGROUND_REMOVE)
return super.stopService(intent)
}
override fun onTimeout(startId: Int) {
super.onTimeout(startId)
pushHandlingWakeLock.unlock()
}
companion object {
fun startIfNeeded(context: Context) {
// Don't start the foreground service if the device is already awake
val powerManager = context.getSystemService(POWER_SERVICE) as PowerManager
if (powerManager.isInteractive) return
start(context)
}
fun start(context: Context) {
val intent = Intent(context, FetchPushForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
fun stop(context: Context) {
val intent = Intent(context, FetchPushForegroundService::class.java)
context.stopService(intent)
}
}
}

View file

@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.auth.SessionRestorationException
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.exception.ClientException
import io.element.android.libraries.matrix.api.exception.isNetworkError
import io.element.android.libraries.push.api.push.PushHandlingWakeLock
import io.element.android.libraries.push.impl.db.PushRequest
import io.element.android.libraries.push.impl.history.PushHistoryService
import io.element.android.libraries.push.impl.notifications.NotifiableEventResolver
@ -57,6 +58,7 @@ class FetchPendingNotificationsWorker(
private val resultProcessor: NotificationResultProcessor,
private val analyticsService: AnalyticsService,
private val systemClock: SystemClock,
private val pushHandlingWakeLock: PushHandlingWakeLock,
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
Timber.d("FetchNotificationsWorker started")
@ -65,6 +67,8 @@ class FetchPendingNotificationsWorker(
inputData.getString(SyncPendingNotificationsRequestBuilder.SESSION_ID)?.let(::SessionId)
}.getOrNull() ?: return Result.failure()
pushHandlingWakeLock.unlock()
// Fetch pending requests in the last 24 hours
val fetchSince = Instant.fromEpochMilliseconds(systemClock.epochMillis()).minus(1.days)
val requests = pushHistoryService.getPendingPushRequests(sessionId, fetchSince).getOrNull() ?: return Result.failure()
@ -101,9 +105,9 @@ class FetchPendingNotificationsWorker(
results
},
onFailure = {
onFailure = { throwable ->
// This is a failure at the fetch notification setup, not a failure for a single fetch notification operation
return handleSetupError(sessionId, requests, pendingAnalyticTransactions, it)
return handleSetupError(sessionId, requests, pendingAnalyticTransactions, throwable)
}
)

View file

@ -22,6 +22,7 @@
<item quantity="few">"Máte %d nové zprávy."</item>
<item quantity="other">"Máte %d nových zpráv."</item>
</plurals>
<string name="notification_incoming_audio_call">"📞 Příchozí hovor"</string>
<string name="notification_incoming_call">"📹 Příchozí hovor"</string>
<string name="notification_inline_reply_failed">"** Nepodařilo se odeslat - otevřete prosím místnost"</string>
<string name="notification_invitation_action_join">"Vstoupit"</string>

View file

@ -19,6 +19,7 @@
<item quantity="one">"Du har %d ny besked."</item>
<item quantity="other">"Du har %d nye beskeder."</item>
</plurals>
<string name="notification_incoming_audio_call">"📞 Indgående opkald"</string>
<string name="notification_incoming_call">"📹 Indgående opkald"</string>
<string name="notification_inline_reply_failed">"** Kunne ikke sende - åbn venligst rummet"</string>
<string name="notification_invitation_action_join">"Deltag"</string>
@ -42,8 +43,8 @@
<string name="notification_room_invite_body_with_sender">"%1$s inviterede dig til at deltage i rummet"</string>
<string name="notification_sender_me">"Mig"</string>
<string name="notification_sender_mention_reply">"%1$s nævnt eller besvaret"</string>
<string name="notification_space_invite_body">"Inviterede dig til at deltage i gruppen"</string>
<string name="notification_space_invite_body_with_sender">"%1$s inviterede dig til at deltage i gruppen"</string>
<string name="notification_space_invite_body">"Inviterede dig til at deltage i klyngen"</string>
<string name="notification_space_invite_body_with_sender">"%1$s inviterede dig til at deltage i klyngen"</string>
<string name="notification_test_push_notification_content">"Du ser notifikationen! Klik på mig!"</string>
<string name="notification_thread_in_room">"Tråd i %1$s"</string>
<string name="notification_ticker_text_dm">"%1$s: %2$s"</string>

View file

@ -19,6 +19,7 @@
<item quantity="one">"Έχετε %d νέο μήνυμα."</item>
<item quantity="other">"Έχετε %d νέα μηνύματα."</item>
</plurals>
<string name="notification_incoming_audio_call">"📞 Εισερχόμενη κλήση"</string>
<string name="notification_incoming_call">"📹 Εισερχόμενη κλήση"</string>
<string name="notification_inline_reply_failed">"** Αποτυχία αποστολής - παρακαλώ ανοίξτε την αίθουσα"</string>
<string name="notification_invitation_action_join">"Συμμετοχή"</string>

View file

@ -19,6 +19,7 @@
<item quantity="one">"Van %d új üzenete."</item>
<item quantity="other">"Van %d új üzenete."</item>
</plurals>
<string name="notification_incoming_audio_call">"📞 Bejövő hívás"</string>
<string name="notification_incoming_call">"📹 Bejövő hívás"</string>
<string name="notification_inline_reply_failed">"** Nem sikerült elküldeni nyissa meg a szobát"</string>
<string name="notification_invitation_action_join">"Csatlakozás"</string>

View file

@ -11,7 +11,11 @@
<plurals name="notification_compat_summary_title">
<item quantity="other">"%d 알림"</item>
</plurals>
<string name="notification_error_unified_push_unregistered_android">"통합 푸시(UnifiedPush) 알림 배포기를 등록할 수 없어 더 이상 알림을 받을 수 없습니다. 앱의 알림 설정과 푸시 배포기의 상태를 확인해 주세요."</string>
<string name="notification_fallback_content">"알림"</string>
<plurals name="notification_fallback_n_content">
<item quantity="other">"%d개의 새 메시지가 있습니다."</item>
</plurals>
<string name="notification_incoming_call">"📹 수신 전화"</string>
<string name="notification_inline_reply_failed">"** 전송 실패 - 방을 열여주세요"</string>
<string name="notification_invitation_action_join">"참가하기"</string>
@ -33,7 +37,10 @@
<string name="notification_room_invite_body_with_sender">"%1$s 가 당신을 이 방에 초대했습니다"</string>
<string name="notification_sender_me">"나"</string>
<string name="notification_sender_mention_reply">"%1$s 언급하거나 답변함"</string>
<string name="notification_space_invite_body">"귀하를 스페이스 참여에 초대했습니다"</string>
<string name="notification_space_invite_body_with_sender">"%1$s님이 귀하를 스페이스 참여하도록 초대했습니다"</string>
<string name="notification_test_push_notification_content">"알림을 보고 있습니다! 클릭해주세요!"</string>
<string name="notification_thread_in_room">"%1$s의 스레드"</string>
<string name="notification_ticker_text_dm">"%1$s: %2$s"</string>
<string name="notification_ticker_text_group">"%1$s: %2$s %3$s"</string>
<plurals name="notification_unread_notified_messages">
@ -48,10 +55,19 @@
<string name="push_distributor_background_sync_android">"백그라운드 동기화"</string>
<string name="push_distributor_firebase_android">"Google 서비스"</string>
<string name="push_no_valid_google_play_services_apk_android">"유효한 Google Play 서비스를 찾지 못했습니다. 알림이 정상적으로 동작하지 않을 수 있습니다."</string>
<string name="troubleshoot_notifications_test_blocked_users_description">"차단된 사용자 확인"</string>
<string name="troubleshoot_notifications_test_blocked_users_quick_fix">"차단된 사용자 보기"</string>
<string name="troubleshoot_notifications_test_blocked_users_result_none">"차단된 사용자는 없습니다."</string>
<plurals name="troubleshoot_notifications_test_blocked_users_result_some">
<item quantity="other">"%1$d명의 사용자를 차단했습니다. 이제 해당 사용자들의 알림을 받지 않습니다."</item>
</plurals>
<string name="troubleshoot_notifications_test_blocked_users_title">"차단한 사용자"</string>
<string name="troubleshoot_notifications_test_current_push_provider_description">"현재 제공자의 이름을 가져옵니다."</string>
<string name="troubleshoot_notifications_test_current_push_provider_failure">"푸시 제공자가 선택되지 않았습니다."</string>
<string name="troubleshoot_notifications_test_current_push_provider_failure_distributor_not_found">"현재 푸시 제공업체: %1$s, 현재 배포처: %2$s. 하지만 배포처 %3$s을(를) 찾을 수 없습니다. 애플리케이션이 삭제된 것일 수 있습니다."</string>
<string name="troubleshoot_notifications_test_current_push_provider_failure_no_distributor">"현재 푸시 제공업체는 %1$s이지만, 설정된 배포처가 없습니다."</string>
<string name="troubleshoot_notifications_test_current_push_provider_success">"현재 푸시 제공자: %1$s."</string>
<string name="troubleshoot_notifications_test_current_push_provider_success_with_distributor">"현재 푸시 제공업체: %1$s(%2$s)"</string>
<string name="troubleshoot_notifications_test_current_push_provider_title">"현재 푸시 제공자"</string>
<string name="troubleshoot_notifications_test_detect_push_provider_description">"애플리케이션이 적어도 하나의 푸시 제공자를 지원하는지 확인하십시오."</string>
<string name="troubleshoot_notifications_test_detect_push_provider_failure">"푸시 제공자 지원이 발견되지 않았습니다."</string>

View file

@ -14,6 +14,7 @@
<item quantity="few">"%d pranešimai"</item>
<item quantity="other">"%d pranešimų"</item>
</plurals>
<string name="notification_fallback_content">"Turite naujų žinučių."</string>
<string name="notification_inline_reply_failed">"** Nepavyko išsiųsti - prašome atidaryti kambarį"</string>
<plurals name="notification_invitations">
<item quantity="one">"%d kvietimas"</item>
@ -27,6 +28,7 @@
<item quantity="few">"%d naujos žinutės"</item>
<item quantity="other">"%d naujų žinučių"</item>
</plurals>
<string name="notification_reaction_body">"Reaguota su %1$s"</string>
<string name="notification_room_action_quick_reply">"Sparčiai atsakyti"</string>
<string name="notification_room_invite_body">"Pakvietė jus jungtis prie kambario"</string>
<string name="notification_sender_me">"Aš"</string>

View file

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_call">"Позвонить"</string>
<string name="notification_channel_listening_for_events">"Прослушивание событий"</string>
<string name="notification_channel_noisy">"Шумные уведомления"</string>
<string name="notification_channel_ringing_calls">"Звонки"</string>
<string name="notification_channel_silent">"Бесшумные уведомления"</string>
<string name="notification_channel_call">"Звонки"</string>
<string name="notification_channel_listening_for_events">"События"</string>
<string name="notification_channel_noisy">"Уведомления со звуком"</string>
<string name="notification_channel_ringing_calls">"Звонки со звуком"</string>
<string name="notification_channel_silent">"Уведомления без звука"</string>
<plurals name="notification_compat_summary_line_for_room">
<item quantity="one">"%1$s: %2$d сообщение"</item>
<item quantity="few">"%1$s: %2$d сообщения"</item>
@ -17,6 +17,12 @@
</plurals>
<string name="notification_error_unified_push_unregistered_android">"Не удалось зарегистрировать дистрибьютора уведомлений UnifiedPush, поэтому вы больше не будете получать уведомления. Проверьте настройки уведомлений в приложении и статус дистрибьютора push-уведомлений."</string>
<string name="notification_fallback_content">"У вас есть новые сообщения."</string>
<plurals name="notification_fallback_n_content">
<item quantity="one">"У Вас %d новое сообщение."</item>
<item quantity="few">"У Вас %d новых сообщения."</item>
<item quantity="many">"У Вас %d новых сообщений."</item>
</plurals>
<string name="notification_incoming_audio_call">"📞 Входящий вызов"</string>
<string name="notification_incoming_call">"📹 Входящий вызов"</string>
<string name="notification_inline_reply_failed">"** Не удалось отправить - пожалуйста, откройте комнату"</string>
<string name="notification_invitation_action_join">"Присоединиться"</string>
@ -26,26 +32,26 @@
<item quantity="few">"%d приглашения"</item>
<item quantity="many">"%d приглашений"</item>
</plurals>
<string name="notification_invite_body">"Пригласил вас в чат"</string>
<string name="notification_invite_body_with_sender">"%1$s пригласил вас в чат"</string>
<string name="notification_mentioned_you_body">"Упомянул вас: %1$s"</string>
<string name="notification_invite_body">"Пригласил(а) вас в чат"</string>
<string name="notification_invite_body_with_sender">"%1$s пригласил(а) вас в чат"</string>
<string name="notification_mentioned_you_body">"Упомянул(а) вас: %1$s"</string>
<string name="notification_new_messages">"Новые сообщения"</string>
<plurals name="notification_new_messages_for_room">
<item quantity="one">"%d новое сообщение"</item>
<item quantity="few">"%d новых сообщения"</item>
<item quantity="many">"%d новых сообщений"</item>
</plurals>
<string name="notification_reaction_body">"Отреагировал на %1$s"</string>
<string name="notification_reaction_body">"Отреагировал(а) на %1$s"</string>
<string name="notification_room_action_mark_as_read">"Пометить как прочитанное"</string>
<string name="notification_room_action_quick_reply">"Быстрый ответ"</string>
<string name="notification_room_invite_body">"Пригласил вас в комнату"</string>
<string name="notification_room_invite_body_with_sender">"%1$s пригласил вас присоединиться к комнате"</string>
<string name="notification_room_invite_body">"Пригласил(а) вас в комнату"</string>
<string name="notification_room_invite_body_with_sender">"%1$s пригласил(а) вас в комнату"</string>
<string name="notification_sender_me">"Я"</string>
<string name="notification_sender_mention_reply">"%1$s упомянул или ответил"</string>
<string name="notification_space_invite_body">"Пригласил вас присоединиться к пространству"</string>
<string name="notification_space_invite_body_with_sender">"%1$s пригласил вас присоединиться к пространству"</string>
<string name="notification_sender_mention_reply">"%1$s упомянул(а) или ответил(а)"</string>
<string name="notification_space_invite_body">"Пригласил(а) вас в пространство"</string>
<string name="notification_space_invite_body_with_sender">"%1$s пригласил(а) вас в пространство"</string>
<string name="notification_test_push_notification_content">"Вы просматриваете уведомление! Нажмите на меня!"</string>
<string name="notification_thread_in_room">"Ветка обсуждения в %1$s"</string>
<string name="notification_thread_in_room">"Ветка в %1$s"</string>
<string name="notification_ticker_text_dm">"%1$s: %2$s"</string>
<string name="notification_ticker_text_group">"%1$s: %2$s %3$s"</string>
<plurals name="notification_unread_notified_messages">
@ -61,7 +67,7 @@
<item quantity="few">"%d комнаты"</item>
<item quantity="many">"%d комнат"</item>
</plurals>
<string name="push_distributor_background_sync_android">"Фоновая синхронизация"</string>
<string name="push_distributor_background_sync_android">"Синхронизация в фоновом режиме"</string>
<string name="push_distributor_firebase_android">"Сервисы Google"</string>
<string name="push_no_valid_google_play_services_apk_android">"Не найдены действующие службы Google Play. Уведомления могут работать некорректно."</string>
<string name="troubleshoot_notifications_test_blocked_users_description">"Проверка заблокированных пользователей"</string>

View file

@ -41,7 +41,9 @@
<string name="notification_room_invite_body_with_sender">"%1$s запросив вас приєднатися до кімнати"</string>
<string name="notification_sender_me">"Я"</string>
<string name="notification_sender_mention_reply">"%1$s згадували або відповідали"</string>
<string name="notification_space_invite_body_with_sender">"%1$s запрошує вас приєднатися до простору"</string>
<string name="notification_test_push_notification_content">"Ви переглядаєте сповіщення! Натисніть тут!"</string>
<string name="notification_thread_in_room">"Гілка в %1$s"</string>
<string name="notification_ticker_text_dm">"%1$s: %2$s"</string>
<string name="notification_ticker_text_group">"%1$s: %2$s %3$s"</string>
<plurals name="notification_unread_notified_messages">
@ -60,10 +62,14 @@
<string name="push_distributor_background_sync_android">"Фонова синхронізація"</string>
<string name="push_distributor_firebase_android">"Сервіси Google"</string>
<string name="push_no_valid_google_play_services_apk_android">"Не знайдено дійсних сервісів Google Play. Сповіщення можуть не працювати належним чином."</string>
<string name="troubleshoot_notifications_test_blocked_users_quick_fix">"Переглянути заблокованих користувачів"</string>
<string name="troubleshoot_notifications_test_blocked_users_result_none">"Немає заблокованих користувачів"</string>
<string name="troubleshoot_notifications_test_blocked_users_title">"Заблоковані користувачі"</string>
<string name="troubleshoot_notifications_test_current_push_provider_description">"Отримує назву поточного постачальника."</string>
<string name="troubleshoot_notifications_test_current_push_provider_failure">"Постачальників push-сповіщень не вибрано."</string>
<string name="troubleshoot_notifications_test_current_push_provider_failure_no_distributor">"Поточний постачальник push-сповіщень: %1$s, але дистриб\'юторів не налаштовано."</string>
<string name="troubleshoot_notifications_test_current_push_provider_success">"Поточний постачальник: %1$s."</string>
<string name="troubleshoot_notifications_test_current_push_provider_success_with_distributor">"Поточний постачальник push-сповіщень: %1$s (%2$s)"</string>
<string name="troubleshoot_notifications_test_current_push_provider_title">"Поточний постачальник push-сповіщень"</string>
<string name="troubleshoot_notifications_test_detect_push_provider_description">"Переконайтеся, що застосунок має принаймні одного постачальника push-сповіщень."</string>
<string name="troubleshoot_notifications_test_detect_push_provider_failure">"Не знайдено постачальників push-сповіщень."</string>

View file

@ -42,6 +42,7 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import kotlin.time.Duration.Companion.milliseconds
@ -173,6 +174,10 @@ class DefaultPushHandlerTest {
workManagerScheduler = workManagerScheduler,
)
defaultPushHandler.handle(aPushData, A_PUSHER_INFO)
// Give it enough time to increment the push counter
runCurrent()
submitWorkLambda.assertions()
.isNeverCalled()
incrementPushCounterResult.assertions()

View file

@ -24,7 +24,9 @@ import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvid
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@Config(sdk = [33])
@RunWith(AndroidJUnit4::class)
class DefaultSyncPendingNotificationsRequestBuilderTest {
@Test

View file

@ -28,6 +28,7 @@ import io.element.android.libraries.push.impl.notifications.FakeNotificationResu
import io.element.android.libraries.push.impl.notifications.fixtures.aPushRequest
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
import io.element.android.libraries.push.impl.push.SyncOnNotifiableEvent
import io.element.android.libraries.push.test.push.FakePushHandlingWakeLock
import io.element.android.libraries.workmanager.api.WorkManagerRequestBuilder
import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory
import io.element.android.services.analytics.test.FakeAnalyticsService
@ -238,6 +239,7 @@ class FetchPendingNotificationWorkerTest {
pushHistoryService: FakePushHistoryService = FakePushHistoryService(),
resultProcessor: FakeNotificationResultProcessor = FakeNotificationResultProcessor(),
systemClock: FakeSystemClock = FakeSystemClock(),
pushHandlingWakeLock: FakePushHandlingWakeLock = FakePushHandlingWakeLock(),
) = FetchPendingNotificationsWorker(
params = createWorkerParams(workDataOf("session_id" to input)),
context = InstrumentationRegistry.getInstrumentation().context,
@ -248,6 +250,7 @@ class FetchPendingNotificationWorkerTest {
pushHistoryService = pushHistoryService,
resultProcessor = resultProcessor,
systemClock = systemClock,
pushHandlingWakeLock = pushHandlingWakeLock,
)
private fun TestScope.createWorkerParams(