Merge branch 'develop' into feature/fga/live_location_sharing_setup
This commit is contained in:
commit
9a984e1423
632 changed files with 4530 additions and 3107 deletions
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue