mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-3636 Notifications | Push display (#2389)
This commit is contained in:
parent
c5bb4faea6
commit
4a4646929d
3 changed files with 148 additions and 1 deletions
|
@ -1,7 +1,19 @@
|
|||
package com.anytypeio.anytype.device
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.util.Base64
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.app.AndroidApplication
|
||||
import com.anytypeio.anytype.core_models.DecryptedPushContent
|
||||
import com.anytypeio.anytype.domain.device.DeviceTokenStoringService
|
||||
import com.anytypeio.anytype.presentation.notifications.DecryptionPushContentService
|
||||
import com.anytypeio.anytype.ui.main.MainActivity
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import javax.inject.Inject
|
||||
|
@ -12,13 +24,21 @@ class AnytypePushService : FirebaseMessagingService() {
|
|||
@Inject
|
||||
lateinit var deviceTokenSavingService: DeviceTokenStoringService
|
||||
|
||||
@Inject
|
||||
lateinit var decryptionService: DecryptionPushContentService
|
||||
|
||||
private val notificationManager by lazy {
|
||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
}
|
||||
|
||||
init {
|
||||
Timber.d("AnytypePushService initialized")
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
(application as AndroidApplication).componentManager.pushContentComponent.get().inject(this)
|
||||
super.onCreate()
|
||||
(application as AndroidApplication).componentManager.pushContentComponent.get().inject(this)
|
||||
createNotificationChannel()
|
||||
}
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
|
@ -29,5 +49,104 @@ class AnytypePushService : FirebaseMessagingService() {
|
|||
|
||||
override fun onMessageReceived(message: RemoteMessage) {
|
||||
super.onMessageReceived(message)
|
||||
Timber.d("Received message: ${message}")
|
||||
|
||||
try {
|
||||
// Extract encrypted data and keyId from the message
|
||||
val encryptedData = message.data[PAYLOAD_KEY]?.let { Base64.decode(it, Base64.DEFAULT) }
|
||||
val keyId = message.data[KEY_ID_KEY]
|
||||
|
||||
if (encryptedData == null || keyId == null) {
|
||||
Timber.w("Missing required data in push message: encryptedData is null =${encryptedData == null}, keyId=$keyId")
|
||||
return
|
||||
}
|
||||
|
||||
// Decrypt the message
|
||||
val decryptedContent = decryptionService.decrypt(encryptedData, keyId)
|
||||
if (decryptedContent == null) {
|
||||
Timber.w("Failed to decrypt push message")
|
||||
return
|
||||
}
|
||||
|
||||
// Handle the decrypted content
|
||||
handleDecryptedContent(decryptedContent)
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e, "Error processing push message")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDecryptedContent(content: DecryptedPushContent) {
|
||||
Timber.d("Decrypted content: $content")
|
||||
when (content.type) {
|
||||
1 -> handleNewMessage(content.newMessage)
|
||||
else -> Timber.w("Unknown message type: ${content.type}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNewMessage(message: DecryptedPushContent.Message) {
|
||||
Timber.d("New message received: $message")
|
||||
|
||||
// Create an intent to open the app when notification is tapped
|
||||
//todo extra task on Navigation
|
||||
val intent = Intent(this, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra(EXTRA_CHAT_ID, message.chatId)
|
||||
putExtra(EXTRA_SPACE_ID, message.spaceName)
|
||||
}
|
||||
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
NOTIFICATION_REQUEST_CODE,
|
||||
intent,
|
||||
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
// Build the notification
|
||||
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_app_notification)
|
||||
.setContentTitle(message.senderName)
|
||||
.setContentText(message.text)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(message.text))
|
||||
.setAutoCancel(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||
.setFullScreenIntent(pendingIntent, true)
|
||||
.setLights(0xFF0000FF.toInt(), 300, 1000)
|
||||
.setVibrate(longArrayOf(0, 500, 200, 500))
|
||||
.build()
|
||||
|
||||
// Show the notification
|
||||
notificationManager.notify(System.currentTimeMillis().toInt(), notification)
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
CHANNEL_NAME,
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
description = "New messages notifications"
|
||||
enableLights(true)
|
||||
enableVibration(true)
|
||||
setShowBadge(true)
|
||||
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
|
||||
}
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CHANNEL_ID = "messages_channel"
|
||||
private const val PAYLOAD_KEY = "x-any-payload"
|
||||
private const val KEY_ID_KEY = "x-any-key-id"
|
||||
private const val CHANNEL_NAME = "Chat Messages"
|
||||
val EXTRA_CHAT_ID = "chatId"
|
||||
val EXTRA_SPACE_ID = "spaceId"
|
||||
|
||||
private const val NOTIFICATION_REQUEST_CODE = 100
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package com.anytypeio.anytype.di.feature.notifications
|
|||
import com.anytypeio.anytype.device.AnytypePushService
|
||||
import com.anytypeio.anytype.di.common.ComponentDependencies
|
||||
import com.anytypeio.anytype.domain.device.DeviceTokenStoringService
|
||||
import com.anytypeio.anytype.presentation.notifications.DecryptionPushContentService
|
||||
import dagger.Component
|
||||
import dagger.Module
|
||||
import javax.inject.Singleton
|
||||
|
@ -28,4 +29,5 @@ object PushContentModule {
|
|||
|
||||
interface PushContentDependencies : ComponentDependencies {
|
||||
fun deviceTokenSavingService(): DeviceTokenStoringService
|
||||
fun decryptionService(): DecryptionPushContentService
|
||||
}
|
|
@ -8,6 +8,7 @@ import javax.crypto.spec.GCMParameterSpec
|
|||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
@ -128,6 +129,31 @@ class DecryptionPushContentServiceImplTest {
|
|||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decrypt should successfully decrypt actual RemoteMessage data`() {
|
||||
// Given
|
||||
val actualKeyId = "626166797265696376626f79757979696a6c66636235677461336665736c6f716132656f646b707377766133326b6d6c6b76336870637366756971e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
val actualEncryptedPayload = "OllD4bCyF0VbI0VrEsz0aYFuj+X8cnsRvm1wDYJC6aCzIyBu99NhHJi3xbIX565cUvIB6tlCdFzRUDc1WJqV8/0dbaB5PZozwLwbv9Pk+Ozxgsu6AspYT8MAR67exZ2ekD3dSo3hoeqlD50bJYQQnWvTgRUns5WzOzDanwwMMXJncxERlB2BdiqC7S2LmU47dgxoMytwBaJXemw9wHiU7dPnICSDAbnNlJU6DAGTn0Rqc38GpMbDg8+u2ksa1gb7P+P8XwTn9AFRPFz4Ay/mM/5jxcignyRGm3PObxBfUCP8NDwl7jH+55Q2VUgC2SX7vVEBLb5mlNJu3DwkhJvB7iRssulypiQ8I1w+mJ+Xh3TG2RYbgjb4l48mNoecblL/hvaRh560T3OTqlWlVNh0c5wRd/eo5YH5zoXrQydk2JXO6vReEWaJQt+bPU2y6N6IUbpLlw2q7OQu9jRIF5T35R3XO8GU8CmyKmhlJK4xAvhOiKIc8X47BGfApY6hl3TSPea9dSEnb0+EB0YsC7DyRc7y3NL588+Yc0sfHLA5Mp2oWs9a"
|
||||
val actualEncryptedData = Base64.decode(actualEncryptedPayload, Base64.DEFAULT)
|
||||
|
||||
// Use the actual push key from the logs
|
||||
val actualKeyValue = "RT1gb7DyUW5tc5qCF92Jc3IlEQVOgxxBo6x2BP5T5mU="
|
||||
whenever(pushKeyProvider.getPushKey()).thenReturn(
|
||||
mapOf(actualKeyId to PushKey(id = actualKeyId, value = actualKeyValue))
|
||||
)
|
||||
|
||||
// When
|
||||
val result = decryptionService.decrypt(actualEncryptedData, actualKeyId)
|
||||
|
||||
// Then
|
||||
assertNotNull(result)
|
||||
assertEquals(1, result?.type)
|
||||
assertNotNull(result?.newMessage)
|
||||
assertEquals("Спейсдля пушей", result?.newMessage?.spaceName)
|
||||
assertEquals("Test not", result?.newMessage?.senderName)
|
||||
assertEquals("ooo", result?.newMessage?.text)
|
||||
}
|
||||
|
||||
private fun createTestContent(): DecryptedPushContent {
|
||||
return DecryptedPushContent(
|
||||
spaceId = testSpaceId,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue